mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
925
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5091861e6e | ||
|
|
22be6514fb | ||
|
|
7e4645ffbd | ||
|
|
4b801273c0 | ||
|
|
d6ca57d008 | ||
|
|
03555fa365 | ||
|
|
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 | ||
|
|
fd6146888b | ||
|
|
4400eedbae | ||
|
|
cbcf86e362 | ||
|
|
e69b9eb428 | ||
|
|
8cfc28f08c | ||
|
|
5473be5756 | ||
|
|
a3fca62818 | ||
|
|
d397ab9b40 | ||
|
|
e59e726d38 | ||
|
|
8272aa6fa2 | ||
|
|
43b9bf8152 | ||
|
|
7312f4bfe0 | ||
|
|
5df3280131 | ||
|
|
515696dd36 | ||
|
|
ff76f3efa0 | ||
|
|
0d19894331 | ||
|
|
1d9acf5f29 | ||
|
|
d3994b436d | ||
|
|
cc51f42430 | ||
|
|
8cf0438a98 | ||
|
|
b2ebfa9bb9 | ||
|
|
ed14acfc75 | ||
|
|
90df8efe0a | ||
|
|
f7b09c090b | ||
|
|
aa3a63c87b | ||
|
|
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 | ||
|
|
437b3bfa10 | ||
|
|
0b07305775 | ||
|
|
b5f0ebb37b | ||
|
|
3cc600d29a | ||
|
|
2cbd8bbb47 | ||
|
|
12b2acd7f7 | ||
|
|
736196b58b | ||
|
|
25772f4585 | ||
|
|
3684fee2a2 | ||
|
|
e3755679c5 | ||
|
|
aaa870e656 | ||
|
|
94274381ff | ||
|
|
3703da188e | ||
|
|
d9446fa892 | ||
|
|
450c80ca5b | ||
|
|
e1547023f6 | ||
|
|
8a4a6d83d4 | ||
|
|
6d0a76cf51 | ||
|
|
a3fa12e5b3 | ||
|
|
659bc0dab7 | ||
|
|
b524b9f934 | ||
|
|
927a5cddc4 | ||
|
|
6b51a56b2b | ||
|
|
8f481d6f2c | ||
|
|
421a9974af | ||
|
|
275a6fe078 | ||
|
|
8b4c90e603 | ||
|
|
65d1e58e34 | ||
|
|
a4acd000c4 | ||
|
|
f7e5e825f2 | ||
|
|
7dd85f9143 | ||
|
|
7da3591d2b | ||
|
|
beb119883a | ||
|
|
1da8d38daf | ||
|
|
faf13c502b | ||
|
|
43da03ef63 | ||
|
|
05fb3a5d8d | ||
|
|
5e4de3931a | ||
|
|
2b7ed2aa24 | ||
|
|
5b7d6f1ca6 | ||
|
|
5123956790 | ||
|
|
28d0348ef5 | ||
|
|
ad0b2ebb06 | ||
|
|
a3542ed7fa | ||
|
|
0aa8d402ae | ||
|
|
a4d1043684 | ||
|
|
10de0d4245 | ||
|
|
0497137d80 | ||
|
|
f05b55add7 | ||
|
|
1ced50db1f | ||
|
|
64b0cdaab7 | ||
|
|
bce5fe8e09 | ||
|
|
6116d113b8 | ||
|
|
1fb1cec1b6 | ||
|
|
fe5de129da | ||
|
|
10eae0ab87 | ||
|
|
e9c16a8fd3 | ||
|
|
bddde5f298 | ||
|
|
0466c82a42 | ||
|
|
995f844254 | ||
|
|
18a26809af | ||
|
|
42d3240d05 | ||
|
|
52c072489b | ||
|
|
602ceb55ef | ||
|
|
f09aec7651 | ||
|
|
85e2cf7d8b | ||
|
|
f818f16ebe | ||
|
|
f6d2314e11 | ||
|
|
24e108fdbf | ||
|
|
9a016d7727 | ||
|
|
d9987dd645 | ||
|
|
a0c4b7695e | ||
|
|
20162a69be | ||
|
|
86df9ca14a | ||
|
|
f1e0ef2991 | ||
|
|
476e9de2a8 | ||
|
|
a1f321d92e | ||
|
|
4e2bd9c307 | ||
|
|
e799589d11 | ||
|
|
efadf1d74b | ||
|
|
7f6482a7d2 | ||
|
|
1435e2c526 | ||
|
|
84f18341ed | ||
|
|
2ea66a0d8b | ||
|
|
be2bb984c5 | ||
|
|
b058eb31f4 | ||
|
|
1c09d93a07 | ||
|
|
31fbc64ad2 | ||
|
|
35941ddc34 | ||
|
|
2d5289c383 | ||
|
|
06068c0406 | ||
|
|
7421b29c45 | ||
|
|
af0c41dcf5 | ||
|
|
f4133609db | ||
|
|
b85ca1536a | ||
|
|
304b0dab35 | ||
|
|
fdb8e06f12 | ||
|
|
9da4a3260d | ||
|
|
8c8a56ed43 | ||
|
|
99fad4dca2 | ||
|
|
b655f24172 | ||
|
|
5a748c2783 | ||
|
|
87e93c9ff5 | ||
|
|
0ca52a1bdb | ||
|
|
5827f2ece6 | ||
|
|
a0ecffe961 | ||
|
|
17e9c313f0 | ||
|
|
62de39b858 | ||
|
|
c1cab2e41a | ||
|
|
f9ff1166b9 | ||
|
|
1ca8282944 | ||
|
|
4cd35e4d93 | ||
|
|
5e1439f64e | ||
|
|
e97dfb041a | ||
|
|
e132784532 | ||
|
|
1cef07a2d4 | ||
|
|
93e96a044d | ||
|
|
040ce6e335 | ||
|
|
a51fd49ed1 | ||
|
|
4bdcdb6f96 | ||
|
|
7914fde9f1 | ||
|
|
930c23d7e3 | ||
|
|
95dd44115d | ||
|
|
45f3a64617 | ||
|
|
548077f01e | ||
|
|
bbd24e5029 | ||
|
|
8bef181ad8 | ||
|
|
ff92fd229b | ||
|
|
5ef5ead98f | ||
|
|
63df500b6f | ||
|
|
d523c7425a | ||
|
|
728298d2f5 | ||
|
|
ac79a91aa9 | ||
|
|
668b61e620 | ||
|
|
ea28f12fb4 | ||
|
|
45298f8e23 | ||
|
|
0e87d0b483 | ||
|
|
fde30794ec | ||
|
|
51c65093b8 | ||
|
|
1f5ee53f3a | ||
|
|
3721e6b950 | ||
|
|
103e219675 | ||
|
|
da3a3b939a | ||
|
|
994a4e0fc7 | ||
|
|
36dec7c88f | ||
|
|
5eb4c6b310 | ||
|
|
152b45087e | ||
|
|
5fba2889cb | ||
|
|
9b4f8d7ac0 | ||
|
|
9056de0103 | ||
|
|
6cbf080156 | ||
|
|
89ec639f87 | ||
|
|
870fe72bf6 | ||
|
|
dda38442ad | ||
|
|
5cd737b901 | ||
|
|
cc0207cfe4 | ||
|
|
fa63983fd7 | ||
|
|
9a6d2a6d21 | ||
|
|
cf1bb1a1cc | ||
|
|
c18865423e | ||
|
|
fec779de05 | ||
|
|
330b11a64b | ||
|
|
73eb14d4bb | ||
|
|
4684688fc7 | ||
|
|
53d18a0d08 | ||
|
|
68fdf3fbb4 | ||
|
|
444d4b4e66 | ||
|
|
4a4f51622d | ||
|
|
2836f89b34 | ||
|
|
4e73559eb7 | ||
|
|
11acbc6db4 | ||
|
|
cad35c7d5c | ||
|
|
d8dbe15cb3 | ||
|
|
d2e4ed6a11 | ||
|
|
6ae113a969 | ||
|
|
2da400d5b4 | ||
|
|
e8bdb954f2 | ||
|
|
9fae4c965f | ||
|
|
83887b09b6 | ||
|
|
e6a93d4a9f | ||
|
|
4e36040f47 | ||
|
|
5727d25f32 | ||
|
|
1f7f1e7b31 | ||
|
|
14493f7ea4 | ||
|
|
fd893a8b10 | ||
|
|
beab655eba | ||
|
|
d8957a5468 | ||
|
|
2a47ccc0f9 | ||
|
|
9f65c6ce41 | ||
|
|
443ebac71c | ||
|
|
3773a5a5dc | ||
|
|
51f1aee93a | ||
|
|
0b2a9240b3 | ||
|
|
7041c1d719 | ||
|
|
75d4e931bc | ||
|
|
d949482094 | ||
|
|
28621678dd | ||
|
|
4b98579adf | ||
|
|
c95901b879 | ||
|
|
0fb2e208ab | ||
|
|
76aed33a5c | ||
|
|
c26ff1849a | ||
|
|
702a8a83a0 | ||
|
|
5be05da5ca | ||
|
|
fe5e5b8891 | ||
|
|
add56d7365 | ||
|
|
5c09d726ba | ||
|
|
db4cb5361d | ||
|
|
ad02ad3f81 | ||
|
|
a902c25363 | ||
|
|
04a6eaf5d4 | ||
|
|
f0fb7bf877 | ||
|
|
74fbbea2b3 | ||
|
|
735be3e892 | ||
|
|
49bf1198b2 | ||
|
|
9186ce3f24 | ||
|
|
bfd6cebbb0 | ||
|
|
4957f568cd | ||
|
|
64c9d8d85f | ||
|
|
7b5cb379c4 | ||
|
|
fd625fca7b | ||
|
|
e45e5a09f4 | ||
|
|
e3dc30ad93 | ||
|
|
0406d9e04d | ||
|
|
fbfde6cc77 | ||
|
|
c6f91e18c4 | ||
|
|
6db1d2e5c0 | ||
|
|
817172c460 | ||
|
|
cd445ceccf | ||
|
|
87a32f65ee | ||
|
|
698fde247f | ||
|
|
0e939ea9d7 | ||
|
|
a5ddc465e6 | ||
|
|
76a41d70b0 | ||
|
|
43777ee6d9 | ||
|
|
6ee2196fae | ||
|
|
8fa9ead279 | ||
|
|
83c0a32494 | ||
|
|
b830c08257 | ||
|
|
b747c97561 | ||
|
|
e27997dcbc | ||
|
|
6b64eb85b3 | ||
|
|
5313460c7c | ||
|
|
c94ae409e8 | ||
|
|
4844a4bf99 | ||
|
|
687698001f | ||
|
|
5ba92c7ff4 | ||
|
|
a23555783f | ||
|
|
acc0b2c184 | ||
|
|
10e4341db2 | ||
|
|
6b7fb918cc | ||
|
|
83cf47e3fd | ||
|
|
8a35e87cfd | ||
|
|
9295869279 | ||
|
|
5472bff265 | ||
|
|
27de465032 | ||
|
|
f2a59de804 | ||
|
|
4563706b76 | ||
|
|
be9bb5e34d | ||
|
|
0a582daff0 | ||
|
|
003647abd7 | ||
|
|
9100e06bbe | ||
|
|
fc83b4d986 | ||
|
|
4954655e8b | ||
|
|
319483d41d | ||
|
|
6da8c1cb12 | ||
|
|
5f6b1dda1a | ||
|
|
8df7a628c8 | ||
|
|
18ec96f124 | ||
|
|
d303d6f807 | ||
|
|
ffeb27e5a3 | ||
|
|
34075322d7 | ||
|
|
74b84ba38c | ||
|
|
07973dee2d | ||
|
|
5cfaa0807d | ||
|
|
e181954dd1 | ||
|
|
4b05a6cf8f | ||
|
|
fe4608a6c3 | ||
|
|
d598773163 | ||
|
|
79a4eac82f | ||
|
|
29bbc7299a | ||
|
|
370a823888 | ||
|
|
3fbe77a27c | ||
|
|
1a0a19a793 | ||
|
|
bd0e7456a7 | ||
|
|
6a0d2d5314 | ||
|
|
46e86ce854 | ||
|
|
721b3c1229 | ||
|
|
f63ded6159 | ||
|
|
56b9de097f | ||
|
|
5767ab8ac7 | ||
|
|
5002afde09 | ||
|
|
602e1be296 | ||
|
|
e10d51c4eb | ||
|
|
a9902c33df | ||
|
|
6d0eab2583 | ||
|
|
3d35d140d1 | ||
|
|
c13a0d1010 | ||
|
|
8ebf4fee46 | ||
|
|
9337daf7ac | ||
|
|
0829b26508 | ||
|
|
f526fc8577 | ||
|
|
63333a0cbf | ||
|
|
0048d3f911 | ||
|
|
31adbe6189 | ||
|
|
2207fff44d | ||
|
|
a1811570b3 | ||
|
|
8e274012bd | ||
|
|
8c57ff161e | ||
|
|
0d65ffbb6c | ||
|
|
75a30793ac | ||
|
|
96e5193601 | ||
|
|
48e2ee9acb | ||
|
|
5841d0a944 | ||
|
|
bf76736971 | ||
|
|
707e5d231a | ||
|
|
6abd88b8f1 | ||
|
|
1208aff569 | ||
|
|
7b5a5b8d19 | ||
|
|
20ab5b026e | ||
|
|
db46a60168 | ||
|
|
f2b96318ca | ||
|
|
1d004240e6 | ||
|
|
98ba86b876 | ||
|
|
c3881f9974 | ||
|
|
b2080fb8f9 | ||
|
|
04171dc0b8 | ||
|
|
21ba13a076 | ||
|
|
708aea2532 | ||
|
|
f947fde701 | ||
|
|
90f5f9b30b | ||
|
|
408a12322a | ||
|
|
3676af925c | ||
|
|
81e57ea30e | ||
|
|
98b4e9b063 | ||
|
|
97477686fd | ||
|
|
7d0843cc7f | ||
|
|
409cebb26e | ||
|
|
e4b51d0abb | ||
|
|
76ddaf0317 | ||
|
|
dfd6c2fbb4 | ||
|
|
399f75e8e7 | ||
|
|
7917506b58 | ||
|
|
461e07cfee | ||
|
|
706459e314 | ||
|
|
7e9d6f33c8 | ||
|
|
a1e51d27eb | ||
|
|
0b881ec58a | ||
|
|
9de9e5bdf0 | ||
|
|
de361573f2 | ||
|
|
c4a941015f | ||
|
|
379a388f61 | ||
|
|
38575d7620 | ||
|
|
fe27ae0584 | ||
|
|
8ad106632e | ||
|
|
4721523ea8 | ||
|
|
917ce627bb | ||
|
|
e7d3384660 | ||
|
|
0ccb206a1d | ||
|
|
3b2a01f470 | ||
|
|
a8f33ab964 | ||
|
|
0f67fd7618 | ||
|
|
0b810e500f | ||
|
|
ec8d48327c | ||
|
|
ce07c6ffd5 | ||
|
|
8e9fd32566 | ||
|
|
e621701cbc | ||
|
|
6679d02a35 | ||
|
|
85d7c27a38 | ||
|
|
c89bf3084a | ||
|
|
9387c480f5 | ||
|
|
2e9841c9c2 | ||
|
|
8201fea9ef | ||
|
|
bf52b1ba31 | ||
|
|
e834a95213 | ||
|
|
a9dd957a7a | ||
|
|
d2b551cd12 | ||
|
|
f99f2cc1a9 | ||
|
|
b094993f77 | ||
|
|
bc0501aaf2 | ||
|
|
a65c6136f4 | ||
|
|
78133a3c25 | ||
|
|
140cbb633d | ||
|
|
389146c625 | ||
|
|
ec927104fe | ||
|
|
dcef7ef559 | ||
|
|
63e226c761 | ||
|
|
ff518a055f | ||
|
|
00ee9c6b0e | ||
|
|
f9c5f41a1a | ||
|
|
6290a664e9 | ||
|
|
d27b578888 | ||
|
|
6c6782dd28 | ||
|
|
4660185a23 | ||
|
|
1ccf058c14 | ||
|
|
1273bcd33a | ||
|
|
add2b7b64a | ||
|
|
ac80ba270b | ||
|
|
a0d77ba1b2 | ||
|
|
f9c18a6462 | ||
|
|
63ac3bedca | ||
|
|
a286b78a0b | ||
|
|
e63f9fd6a5 | ||
|
|
d2ea6ef2ca | ||
|
|
68c354e09b | ||
|
|
1946ee86b2 | ||
|
|
5eabeeea20 | ||
|
|
1f4111d6f2 | ||
|
|
0b72dba817 | ||
|
|
fc918d8b3c | ||
|
|
c5dfc42a60 | ||
|
|
5404bfec75 | ||
|
|
93ce542d31 | ||
|
|
e6a9c68534 | ||
|
|
03f7b62ff1 | ||
|
|
38efdf18e2 | ||
|
|
4fc3e9c386 | ||
|
|
ea4a31c6c6 | ||
|
|
c9ad685e11 | ||
|
|
83d56a2f36 | ||
|
|
a1f64c6f71 | ||
|
|
94c968e941 | ||
|
|
8f79379b6e | ||
|
|
00565ea471 | ||
|
|
803f646239 | ||
|
|
8918cc75c3 | ||
|
|
f90f618bff | ||
|
|
b382a9c3e5 | ||
|
|
3fd79e3045 | ||
|
|
3e58500e13 | ||
|
|
1975ab2513 | ||
|
|
15f21daa2b | ||
|
|
48fbb59113 | ||
|
|
9b120e9db7 | ||
|
|
305e8e1ec7 | ||
|
|
93515fcc1d | ||
|
|
fd1a4542e7 | ||
|
|
7ccf2b1a1e | ||
|
|
57dd7cf952 | ||
|
|
90e681a906 | ||
|
|
d043b4740e | ||
|
|
bca876acaf | ||
|
|
6eb84c8235 | ||
|
|
61cee5892a | ||
|
|
a77558807c | ||
|
|
2fc044a7c9 | ||
|
|
f36105a7af | ||
|
|
f455bb7a58 | ||
|
|
fef4cbf3b0 | ||
|
|
32d30dfd9d | ||
|
|
b0220292a7 | ||
|
|
9ab546d743 | ||
|
|
67ab29ffeb | ||
|
|
1a41a8ea66 | ||
|
|
bdf6c660d9 | ||
|
|
b0a2ef271a | ||
|
|
538fa94c79 | ||
|
|
1a410c43d7 | ||
|
|
4d5bb30917 | ||
|
|
c35d5e348b | ||
|
|
5c7e08fdff | ||
|
|
d2922e7d5d | ||
|
|
b1c3fc0908 | ||
|
|
fd83090215 | ||
|
|
d18f64d526 | ||
|
|
2ed8247fba | ||
|
|
b2b6fb6b69 | ||
|
|
4822164e32 | ||
|
|
42542aec61 | ||
|
|
e81ae67745 | ||
|
|
1e9b364f58 | ||
|
|
6e19db130e | ||
|
|
ec0f7cf683 | ||
|
|
ced5b88b57 | ||
|
|
d58929b621 | ||
|
|
29092b277b | ||
|
|
9927e4bd8e | ||
|
|
0e0e40f991 | ||
|
|
3db776e2e5 | ||
|
|
561b482e54 | ||
|
|
d7d0ecb106 | ||
|
|
61ca4e3969 | ||
|
|
879dc6873e | ||
|
|
2263632f7d | ||
|
|
3bb68ef455 | ||
|
|
2231830826 | ||
|
|
cd6f5c64f0 | ||
|
|
d8349544d2 | ||
|
|
ec75f101bb | ||
|
|
9c9bde2ce6 | ||
|
|
07d60bb36d | ||
|
|
581fdb26dd | ||
|
|
40c2e1a1fa | ||
|
|
c849e652b3 | ||
|
|
554fdf5472 | ||
|
|
44dd0e8c32 | ||
|
|
f27172018f | ||
|
|
c2e4bc2ae3 | ||
|
|
429454ca0e | ||
|
|
4703e4ba14 | ||
|
|
56ef3b4cfa | ||
|
|
719b1885bf | ||
|
|
1d215b2840 | ||
|
|
0fbf31bd9a | ||
|
|
4d560bdff2 | ||
|
|
69c637c72d | ||
|
|
242c7f75ad | ||
|
|
f779c7aa3b | ||
|
|
cd539d972a | ||
|
|
d188bd7a2e | ||
|
|
3c4aceea48 | ||
|
|
e0d5164af2 | ||
|
|
da5a137b98 | ||
|
|
d15be21923 | ||
|
|
f5d6f6e27c | ||
|
|
8beae59454 | ||
|
|
68dc974271 | ||
|
|
236e594195 | ||
|
|
5a8be81484 | ||
|
|
7f7be46547 | ||
|
|
e1ddcd051c | ||
|
|
1c8cc6fc92 | ||
|
|
1889ccdd12 | ||
|
|
b79637eef5 | ||
|
|
b1906e5eb9 | ||
|
|
8cef822e8f | ||
|
|
6dae78a206 | ||
|
|
18ae81bbb0 | ||
|
|
e03218d8fa | ||
|
|
4f030619da | ||
|
|
e267dcf9ab | ||
|
|
9c1cbecda4 | ||
|
|
8597d48e41 | ||
|
|
971858078c | ||
|
|
e14db61e30 | ||
|
|
88bbb965f8 | ||
|
|
5510bc4f6a | ||
|
|
5ffb7c82e7 | ||
|
|
d802c57cb9 | ||
|
|
d4e32af792 | ||
|
|
32dc870eaf | ||
|
|
2c06e4185f | ||
|
|
869c35416d | ||
|
|
e6c3acb21c | ||
|
|
e7405b4393 | ||
|
|
3eabca1d7a | ||
|
|
7648978cfa | ||
|
|
ec984de510 | ||
|
|
9d6be1eef4 | ||
|
|
06a05311a4 | ||
|
|
58c248f4f2 | ||
|
|
59ee0901c9 | ||
|
|
2d3c1086ae | ||
|
|
e624040e57 | ||
|
|
496b91480b | ||
|
|
b08f3bdc52 | ||
|
|
9c646c2893 | ||
|
|
77942af4ea | ||
|
|
41b2962e64 | ||
|
|
25ae2e7314 | ||
|
|
8d378720b0 | ||
|
|
34f782d986 | ||
|
|
587a039493 | ||
|
|
93ee8f45ea | ||
|
|
8f32d01686 | ||
|
|
e1209614c5 | ||
|
|
5157da870f | ||
|
|
40aa8cb1bd | ||
|
|
6fabfe08ce | ||
|
|
4ae4531355 | ||
|
|
443f5cfb0e | ||
|
|
5a928f2c0f | ||
|
|
f5730e8eed | ||
|
|
b6afef6efc | ||
|
|
3fba7afd2e | ||
|
|
ac46ff802a | ||
|
|
8cccc79935 | ||
|
|
b03c901fa7 | ||
|
|
7b32fd0196 | ||
|
|
c80c747e48 | ||
|
|
dfde1be035 | ||
|
|
9be0ed8af0 | ||
|
|
5f8a32798d | ||
|
|
1eb7bf7619 | ||
|
|
557fef2c7b | ||
|
|
ece468d1f7 | ||
|
|
dc9f48c948 | ||
|
|
a17f9ce989 | ||
|
|
053d25c64e | ||
|
|
478a2a6e86 | ||
|
|
8292f7493d | ||
|
|
e728265d21 | ||
|
|
84b6dfa9d5 | ||
|
|
0a26608155 | ||
|
|
18694e0a28 | ||
|
|
07307c6131 | ||
|
|
0134832fb6 | ||
|
|
44b7b26adf | ||
|
|
c1506643e9 | ||
|
|
2ad95e5113 | ||
|
|
715bd40b4a | ||
|
|
8ca59f22c9 | ||
|
|
19f02a5129 | ||
|
|
d0603c4d28 | ||
|
|
187d0c0cf4 | ||
|
|
fc6bd7232e | ||
|
|
e878b0b403 | ||
|
|
f3c2502a8c | ||
|
|
18f9dfb64e | ||
|
|
31461c9c74 | ||
|
|
e9070744e8 | ||
|
|
8fa28257be | ||
|
|
63f04f893d | ||
|
|
aa5543ac0a | ||
|
|
60e51548e8 | ||
|
|
e802102c8f | ||
|
|
87f29c6889 | ||
|
|
a70d241da0 | ||
|
|
e17cdc2bd4 | ||
|
|
a4f174ea7e | ||
|
|
175fcb51a8 | ||
|
|
772358ff51 | ||
|
|
abad809a91 | ||
|
|
076e64886e | ||
|
|
88107ee7b3 | ||
|
|
db720fbb22 | ||
|
|
d92580470d | ||
|
|
fc714de11d | ||
|
|
92e8334169 | ||
|
|
8716497d1f | ||
|
|
d8486612de | ||
|
|
03be7ade3d | ||
|
|
1225aa4861 | ||
|
|
e8006dce6e | ||
|
|
894d771a7c | ||
|
|
4e928ee08b | ||
|
|
4da9873b83 | ||
|
|
082110bba8 | ||
|
|
7167fe2fb4 | ||
|
|
d1c3ee0290 | ||
|
|
6bd3821804 | ||
|
|
1d32faff4f | ||
|
|
e67016584f | ||
|
|
a4b6d9a635 | ||
|
|
1f27769cc9 | ||
|
|
0d2a4da347 | ||
|
|
ee34e2b587 | ||
|
|
52dbf14d79 | ||
|
|
70f42a0dff | ||
|
|
2ef62b309e | ||
|
|
a480d8ed73 | ||
|
|
4fea92c9cb | ||
|
|
e7072574ad | ||
|
|
d84f71e728 | ||
|
|
5f41f7fb86 | ||
|
|
05add106c1 | ||
|
|
0e3a017d50 | ||
|
|
d28cce761d | ||
|
|
abb96f650b | ||
|
|
1785a78fa1 | ||
|
|
3f06b5cd1a | ||
|
|
c5fe71684a | ||
|
|
95d5d1bdd9 | ||
|
|
d44820e00c | ||
|
|
cd6a6956d6 | ||
|
|
7e18e6f8c5 | ||
|
|
968b1c0861 | ||
|
|
2ce39768c3 | ||
|
|
69bc935517 | ||
|
|
26ff78d98a | ||
|
|
0935c60f3c | ||
|
|
54fa813bc6 | ||
|
|
f9d0cb7daf | ||
|
|
c5d5946fd2 | ||
|
|
af41c64135 | ||
|
|
63afcad23d | ||
|
|
eb76c31fdd | ||
|
|
51fd96f4bf | ||
|
|
14351c7338 | ||
|
|
01c6cc86ce | ||
|
|
c6f16cde4a | ||
|
|
b3c6e0e67f | ||
|
|
5c0c0b44c0 | ||
|
|
4f599fc75c | ||
|
|
928aa9c909 | ||
|
|
0ea8379065 | ||
|
|
0c734f8f12 | ||
|
|
229ab28427 | ||
|
|
45dc259df3 | ||
|
|
8412790436 | ||
|
|
fdca14e659 | ||
|
|
e3b1eead55 | ||
|
|
71b28eae5a | ||
|
|
9cb2ea1135 | ||
|
|
d38c921781 | ||
|
|
3c20ea47f5 | ||
|
|
32728ae4a0 | ||
|
|
ad7f83db11 | ||
|
|
c4b3956bef | ||
|
|
5fa4551400 | ||
|
|
2fb54e0846 | ||
|
|
dfbf22cf3c | ||
|
|
a5b2021d1e | ||
|
|
fb28b31906 | ||
|
|
737bfc599a | ||
|
|
819863559e | ||
|
|
f6cdcfb823 | ||
|
|
e5d818ba44 | ||
|
|
19487cf56b | ||
|
|
5741041f33 | ||
|
|
89eb5c2254 | ||
|
|
77d195f0c3 | ||
|
|
a9ed74c6a8 | ||
|
|
01db1efbc4 | ||
|
|
ffa3f741ed | ||
|
|
03c73ac0aa | ||
|
|
11fc170d72 | ||
|
|
28882a4414 | ||
|
|
467b2a9281 | ||
|
|
d993214810 | ||
|
|
2838642bae | ||
|
|
bd03ec7a50 | ||
|
|
6128a049f2 | ||
|
|
9ba6bfba62 | ||
|
|
6f73602dd7 | ||
|
|
b1e97b4539 | ||
|
|
3b2c8a4d36 | ||
|
|
043e7efb55 | ||
|
|
91cda84772 | ||
|
|
90641041ef | ||
|
|
e2f2ce3da8 | ||
|
|
374e1f8782 | ||
|
|
b0a0c1552e | ||
|
|
4020335aac | ||
|
|
75c26259a1 | ||
|
|
4e8feff46e | ||
|
|
4438e08f64 | ||
|
|
065a0fddae |
@@ -25,6 +25,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: AI Moderator
|
||||
uses: github/ai-moderator@v1
|
||||
uses: github/ai-moderator@81159c370785e295c97461ade67d7c33576e9319 # v1.1.4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Issue Labeler
|
||||
uses: github/issue-labeler@v3.4
|
||||
uses: github/issue-labeler@c1b0f9f52a63158c4adc09425e858e87b32e9685 # v3.4
|
||||
with:
|
||||
configuration-path: .github/labeler.yml
|
||||
enable-versioned-regex: false
|
||||
|
||||
+58
-64
@@ -32,7 +32,7 @@ jobs:
|
||||
actions: read
|
||||
security-events: write
|
||||
contents: read
|
||||
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@v2.3.3"
|
||||
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@c51854704019a247608d928f370c98740469d4b5" # v2.3.5
|
||||
|
||||
security:
|
||||
name: Checks / Image
|
||||
@@ -43,13 +43,13 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Build the Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
target: production
|
||||
|
||||
- name: Run Trivy vulnerability scanner on image
|
||||
uses: aquasecurity/trivy-action@0.35.0
|
||||
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
||||
with:
|
||||
image-ref: 'pr_image:${{ github.sha }}'
|
||||
format: 'sarif'
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
||||
- name: Run Trivy vulnerability scanner on source code
|
||||
uses: aquasecurity/trivy-action@0.35.0
|
||||
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
||||
with:
|
||||
scan-type: 'fs'
|
||||
scan-ref: '.'
|
||||
@@ -76,14 +76,14 @@ jobs:
|
||||
skip-setup-trivy: true
|
||||
|
||||
- name: Upload image scan results
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
if: always() && hashFiles('trivy-image-results.sarif') != ''
|
||||
with:
|
||||
sarif_file: 'trivy-image-results.sarif'
|
||||
category: 'trivy-image'
|
||||
|
||||
- name: Upload source code scan results
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
if: always() && hashFiles('trivy-fs-results.sarif') != ''
|
||||
with:
|
||||
sarif_file: 'trivy-fs-results.sarif'
|
||||
@@ -94,10 +94,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0
|
||||
with:
|
||||
php-version: '8.3'
|
||||
tools: composer:v2
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0
|
||||
with:
|
||||
php-version: '8.3'
|
||||
tools: composer:v2
|
||||
@@ -144,10 +144,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0
|
||||
with:
|
||||
php-version: '8.3'
|
||||
tools: composer:v2
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
run: composer install --prefer-dist --no-progress --ignore-platform-reqs
|
||||
|
||||
- name: Cache PHPStan result cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: .phpstan-cache
|
||||
key: phpstan-${{ github.sha }}
|
||||
@@ -172,10 +172,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0
|
||||
with:
|
||||
php-version: '8.3'
|
||||
extensions: swoole
|
||||
@@ -193,10 +193,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate matrix
|
||||
id: generate
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const allDatabases = ['MariaDB', 'PostgreSQL', 'MongoDB'];
|
||||
@@ -253,28 +253,28 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build and push Appwrite
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -297,16 +297,16 @@ jobs:
|
||||
packages: read
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -327,7 +327,7 @@ jobs:
|
||||
run: docker compose exec -T appwrite vars
|
||||
|
||||
- name: Run Unit Tests
|
||||
uses: itznotabug/php-retry@v3
|
||||
uses: itznotabug/php-retry@d6bef45a8bff490babfb613e33b00d133e4062f0 # v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 60
|
||||
@@ -350,16 +350,16 @@ jobs:
|
||||
packages: read
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -385,7 +385,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Run General Tests
|
||||
uses: itznotabug/php-retry@v3
|
||||
uses: itznotabug/php-retry@d6bef45a8bff490babfb613e33b00d133e4062f0 # v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 60
|
||||
@@ -406,7 +406,7 @@ jobs:
|
||||
|
||||
e2e_service:
|
||||
name: Tests / E2E / ${{ matrix.database }} (${{ matrix.mode }}) / ${{ matrix.service }}
|
||||
runs-on: ${{ matrix.runner || 'ubuntu-latest' }}
|
||||
runs-on: ${{ matrix.runner || format('runs-on={0}/runner=4cpu-linux-x64/volume=120g/spot=false', github.run_id) }}
|
||||
needs: [build, matrix]
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -427,6 +427,7 @@ jobs:
|
||||
FunctionsSchedule,
|
||||
GraphQL,
|
||||
Health,
|
||||
Advisor,
|
||||
Locale,
|
||||
Projects,
|
||||
Realtime,
|
||||
@@ -441,30 +442,23 @@ jobs:
|
||||
VCS,
|
||||
Messaging,
|
||||
Migrations,
|
||||
Project
|
||||
Project,
|
||||
Presences
|
||||
]
|
||||
include:
|
||||
- service: Databases
|
||||
runner: blacksmith-4vcpu-ubuntu-2404
|
||||
runner: runs-on=${{ github.run_id }}/runner=8cpu-linux-x64/volume=120g/spot=false
|
||||
paratest_processes: 3
|
||||
timeout_minutes: 30
|
||||
- service: Sites
|
||||
runner: blacksmith-4vcpu-ubuntu-2404
|
||||
- service: Functions
|
||||
runner: blacksmith-4vcpu-ubuntu-2404
|
||||
- service: Avatars
|
||||
runner: blacksmith-4vcpu-ubuntu-2404
|
||||
- service: Realtime
|
||||
runner: blacksmith-4vcpu-ubuntu-2404
|
||||
- service: TablesDB
|
||||
runner: blacksmith-4vcpu-ubuntu-2404
|
||||
runner: runs-on=${{ github.run_id }}/runner=8cpu-linux-x64/volume=120g/spot=false
|
||||
paratest_processes: 3
|
||||
timeout_minutes: 30
|
||||
- service: Migrations
|
||||
paratest_processes: 1
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set environment
|
||||
run: |
|
||||
@@ -488,13 +482,13 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -525,7 +519,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Run tests
|
||||
uses: itznotabug/php-retry@v3
|
||||
uses: itznotabug/php-retry@d6bef45a8bff490babfb613e33b00d133e4062f0 # v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 60
|
||||
@@ -573,18 +567,18 @@ jobs:
|
||||
mode: ${{ fromJSON(needs.matrix.outputs.modes) }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -607,7 +601,7 @@ jobs:
|
||||
docker compose up -d --quiet-pull --wait
|
||||
|
||||
- name: Run tests
|
||||
uses: itznotabug/php-retry@v3
|
||||
uses: itznotabug/php-retry@d6bef45a8bff490babfb613e33b00d133e4062f0 # v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 60
|
||||
@@ -640,16 +634,16 @@ jobs:
|
||||
mode: ${{ fromJSON(needs.matrix.outputs.modes) }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -679,7 +673,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Run tests
|
||||
uses: itznotabug/php-retry@v3
|
||||
uses: itznotabug/php-retry@d6bef45a8bff490babfb613e33b00d133e4062f0 # v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 60
|
||||
@@ -711,18 +705,18 @@ jobs:
|
||||
packages: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -735,7 +729,7 @@ jobs:
|
||||
docker tag ${{ env.REGISTRY_IMAGE }}:${{ github.sha }} ${{ env.IMAGE }}:after
|
||||
|
||||
- name: Setup k6
|
||||
uses: grafana/setup-k6-action@ffe7d7290dfa715e48c2ccc924d068444c94bde2
|
||||
uses: grafana/setup-k6-action@db07bd9765aac508ef18982e52ab937fe633a065 # v1.2.1
|
||||
with:
|
||||
k6-version: ${{ env.K6_VERSION }}
|
||||
|
||||
@@ -774,7 +768,7 @@ jobs:
|
||||
- name: Benchmark before
|
||||
if: steps.benchmark_before_start.outcome == 'success'
|
||||
continue-on-error: true
|
||||
uses: grafana/run-k6-action@a15e2072ede004e8d46141e33d7f7dad8ad08d9d
|
||||
uses: grafana/run-k6-action@de51a7390bdf0ac85a3bef493691bd71d4c7c158 # v1.4.0
|
||||
env:
|
||||
APPWRITE_ENDPOINT: 'http://localhost/v1'
|
||||
APPWRITE_BENCHMARK_ITERATIONS: '5'
|
||||
@@ -826,7 +820,7 @@ jobs:
|
||||
- name: Benchmark after
|
||||
id: benchmark_after
|
||||
continue-on-error: true
|
||||
uses: grafana/run-k6-action@a15e2072ede004e8d46141e33d7f7dad8ad08d9d
|
||||
uses: grafana/run-k6-action@de51a7390bdf0ac85a3bef493691bd71d4c7c158 # v1.4.0
|
||||
env:
|
||||
APPWRITE_ENDPOINT: 'http://localhost/v1'
|
||||
APPWRITE_BENCHMARK_ITERATIONS: '5'
|
||||
@@ -846,7 +840,7 @@ jobs:
|
||||
|
||||
- name: Comment on PR
|
||||
if: always()
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
BENCHMARK_BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||||
BENCHMARK_HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||
@@ -856,7 +850,7 @@ jobs:
|
||||
await comment({ github, context, core });
|
||||
|
||||
- name: Save results
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: benchmark-results
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@@ -47,14 +47,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -68,4 +68,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
|
||||
@@ -10,13 +10,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build the Docker image
|
||||
run: DOCKER_BUILDKIT=1 docker build . --target production -t appwrite_image:latest
|
||||
- name: Run Trivy vulnerability scanner on image
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
|
||||
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
||||
with:
|
||||
image-ref: 'appwrite_image:latest'
|
||||
format: 'sarif'
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
ignore-unfixed: 'false'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
- name: Upload Docker Image Scan Results
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
if: always() && hashFiles('trivy-image-results.sarif') != ''
|
||||
with:
|
||||
sarif_file: 'trivy-image-results.sarif'
|
||||
@@ -35,16 +35,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Run Trivy vulnerability scanner on filesystem
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
|
||||
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
||||
with:
|
||||
scan-type: 'fs'
|
||||
format: 'sarif'
|
||||
output: 'trivy-fs-results.sarif'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
- name: Upload Code Scan Results
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
if: always() && hashFiles('trivy-fs-results.sarif') != ''
|
||||
with:
|
||||
sarif_file: 'trivy-fs-results.sarif'
|
||||
|
||||
@@ -12,33 +12,33 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
with:
|
||||
images: appwrite/cloud
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
|
||||
- name: Build & Publish to DockerHub
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@@ -20,20 +20,20 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
with:
|
||||
images: appwrite/appwrite
|
||||
tags: |
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set SDK type
|
||||
id: set-sdk
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
docker compose exec appwrite sdks --platform=${{ steps.set-sdk.outputs.platform }} --sdk=${{ steps.set-sdk.outputs.sdk_type }} --version=latest --git=no
|
||||
sudo chown -R $USER:$USER ./app/sdks/${{ steps.set-sdk.outputs.platform }}-${{ steps.set-sdk.outputs.sdk_type }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: "This issue has been labeled as a 'question', indicating that it requires additional information from the requestor. It has been inactive for 7 days. If no further activity occurs, this issue will be closed in 14 days."
|
||||
|
||||
@@ -115,6 +115,14 @@ Common injections: `$response`, `$request`, `$dbForProject`, `$dbForPlatform`, `
|
||||
- Never hardcode credentials -- use environment variables.
|
||||
- Code changes may require container restart. No central log location -- check relevant containers.
|
||||
|
||||
## Tracing with Utopia Span
|
||||
|
||||
In handlers, only call `Span::add($key, $value)`. **Never** call `Span::init`, `Span::error`, or `Span::finish` -- lifecycle is owned by the entry-point harness (`app/http.php`, `app/worker.php`, `app/realtime.php`, `Bus::dispatch`). For selective export, filter in the sampler in `app/init/span.php`.
|
||||
|
||||
Keys are `snake_case` with dots only for child relationships: `project.id` (id of project), `storage.bucket.id`. No dot otherwise: `inbound_bytes`, not `inbound.bytes`. No camelCase, no bare top-level keys (`function.id`, not `functionId`).
|
||||
|
||||
Cross-cutting identifiers (`project.id`, `function.id`, `user.id`) live at the top level, not under a subsystem (no `realtime.project.id`). The trace sampler and downstream filters look them up by the canonical key.
|
||||
|
||||
## Patch release process
|
||||
|
||||
For bumping patch versions (e.g., `1.9.0` -> `1.9.1`), follow the checklist in `.claude/skills/patch-release-checklist/SKILL.md`. It covers the 4 files that must be updated, console image bumps, CHANGES.md updates, and common pitfalls to avoid.
|
||||
|
||||
+2
-54
@@ -2,17 +2,10 @@
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Publisher\Certificate as CertificatePublisher;
|
||||
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\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,42 +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('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForDeletes', function (Publisher $publisher) {
|
||||
return new Delete($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('logError', function (Registry $register) {
|
||||
return function (Throwable $error, string $namespace, string $action) use ($register) {
|
||||
Console::error('[Error] Timestamp: ' . date('c', time()));
|
||||
@@ -339,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
|
||||
|
||||
@@ -1523,6 +1523,13 @@ return [
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_team_confirm'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['teamInternalId', 'confirm'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -404,6 +404,13 @@ $platformCollections = [
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_teamInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['teamInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -635,6 +642,13 @@ $platformCollections = [
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_id'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -1007,7 +1021,14 @@ $platformCollections = [
|
||||
'attributes' => ['projectInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_id'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -1935,6 +1956,440 @@ $platformCollections = [
|
||||
'attributes' => [],
|
||||
'indexes' => []
|
||||
],
|
||||
|
||||
'reports' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('reports'),
|
||||
'name' => 'Reports',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('projectInternalId'),
|
||||
'type' => Database::VAR_ID,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('projectId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('appInternalId'),
|
||||
'type' => Database::VAR_ID,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('appId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('type'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 64,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('title'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('summary'),
|
||||
'type' => Database::VAR_TEXT,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
// Resource type the report is about. Plural noun, e.g. databases, sites, urls.
|
||||
'$id' => ID::custom('targetType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 64,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
// Free-form target identifier (URL for lighthouse, resource ID for db).
|
||||
// Indexed by `_key_project_target` with an explicit prefix length.
|
||||
'$id' => ID::custom('target'),
|
||||
'type' => Database::VAR_TEXT,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
// Category strings, e.g. 'performance', 'accessibility'. Native array
|
||||
// column — we never query on individual entries (MySQL JSON-array
|
||||
// indexes are weak), this is read+rewrite only.
|
||||
'$id' => ID::custom('categories'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 64,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
// Virtual attribute — insights live in the `insights` collection
|
||||
// back-referenced by `reportInternalId`. The subQuery filter joins
|
||||
// them at read time.
|
||||
'$id' => ID::custom('insights'),
|
||||
'type' => Database::VAR_TEXT,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['subQueryReportInsights'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('analyzedAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_project_app_type'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId', 'appInternalId', 'type'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_target'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId', 'appInternalId', 'targetType', 'target'],
|
||||
'lengths' => [null, null, null, 700],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'insights' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('insights'),
|
||||
'name' => 'Insights',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('projectInternalId'),
|
||||
'type' => Database::VAR_ID,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('projectId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('reportInternalId'),
|
||||
'type' => Database::VAR_ID,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('reportId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('type'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 64,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('severity'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('status'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => 'active',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('resourceType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 64,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('resourceId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('resourceInternalId'),
|
||||
'type' => Database::VAR_ID,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('parentResourceType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 64,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('parentResourceId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('parentResourceInternalId'),
|
||||
'type' => Database::VAR_ID,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('title'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('summary'),
|
||||
'type' => Database::VAR_TEXT,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('ctas'),
|
||||
'type' => Database::VAR_TEXT,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('analyzedAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('dismissedAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('dismissedBy'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_project_report'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId', 'reportInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_resource'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId', 'resourceType', 'resourceId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_parent_resource'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId', 'parentResourceType', 'parentResourceId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_type'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId', 'type'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_severity'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId', 'severity'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_status'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId', 'status'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_dismissedAt'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId', 'dismissedAt'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC, Database::ORDER_DESC],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
// Organization API keys subquery
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -623,6 +623,11 @@ return [
|
||||
'description' => 'Synchronous function execution timed out. Use asynchronous execution instead, or ensure the execution duration doesn\'t exceed 30 seconds.',
|
||||
'code' => 408,
|
||||
],
|
||||
Exception::FUNCTION_ASYNCHRONOUS_TIMEOUT => [
|
||||
'name' => Exception::FUNCTION_ASYNCHRONOUS_TIMEOUT,
|
||||
'description' => 'Asynchronous function execution timed out. Ensure the execution duration doesn\'t exceed the configured function timeout.',
|
||||
'code' => 408,
|
||||
],
|
||||
Exception::FUNCTION_TEMPLATE_NOT_FOUND => [
|
||||
'name' => Exception::FUNCTION_TEMPLATE_NOT_FOUND,
|
||||
'description' => 'Function Template with the requested ID could not be found.',
|
||||
@@ -687,6 +692,11 @@ return [
|
||||
'description' => 'Build with the requested ID failed. Please check the logs for more information.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::BUILD_TIMEOUT => [
|
||||
'name' => Exception::BUILD_TIMEOUT,
|
||||
'description' => 'Build timed out. Increase the build timeout via the `_APP_COMPUTE_BUILD_TIMEOUT` environment variable, or simplify the build to complete within the limit.',
|
||||
'code' => 408,
|
||||
],
|
||||
|
||||
/** Deployments */
|
||||
Exception::DEPLOYMENT_NOT_FOUND => [
|
||||
@@ -715,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,
|
||||
@@ -1236,6 +1258,26 @@ return [
|
||||
'description' => 'The specified database type is not supported for CSV import or export operations.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MIGRATION_SOURCE_PROJECT_ID_REQUIRED => [
|
||||
'name' => Exception::MIGRATION_SOURCE_PROJECT_ID_REQUIRED,
|
||||
'description' => 'A source projectId is required for Appwrite migrations. Provide it in the migration credentials.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MIGRATION_SOURCE_PROJECT_NOT_FOUND => [
|
||||
'name' => Exception::MIGRATION_SOURCE_PROJECT_NOT_FOUND,
|
||||
'description' => 'The source project for the provided projectId was not found. Verify the projectId and the API key has access to it.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::MIGRATION_SOURCE_TYPE_INVALID => [
|
||||
'name' => Exception::MIGRATION_SOURCE_TYPE_INVALID,
|
||||
'description' => 'The migration source type is invalid. Use one of the supported source types.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MIGRATION_DESTINATION_TYPE_INVALID => [
|
||||
'name' => Exception::MIGRATION_DESTINATION_TYPE_INVALID,
|
||||
'description' => 'The migration destination type is invalid. Use one of the supported destination types.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Realtime */
|
||||
Exception::REALTIME_MESSAGE_FORMAT_INVALID => [
|
||||
@@ -1423,4 +1465,28 @@ return [
|
||||
'description' => 'The maximum number of mock phones for this project has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Advisor */
|
||||
Exception::INSIGHT_NOT_FOUND => [
|
||||
'name' => Exception::INSIGHT_NOT_FOUND,
|
||||
'description' => 'Insight with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::INSIGHT_ALREADY_EXISTS => [
|
||||
'name' => Exception::INSIGHT_ALREADY_EXISTS,
|
||||
'description' => 'Insight with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Reports */
|
||||
Exception::REPORT_NOT_FOUND => [
|
||||
'name' => Exception::REPORT_NOT_FOUND,
|
||||
'description' => 'Report with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::REPORT_ALREADY_EXISTS => [
|
||||
'name' => Exception::REPORT_ALREADY_EXISTS,
|
||||
'description' => 'Report with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
];
|
||||
|
||||
+29
-1
@@ -426,5 +426,33 @@ return [
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a proxy rule is updated.',
|
||||
]
|
||||
]
|
||||
],
|
||||
'reports' => [
|
||||
'$model' => Response::MODEL_REPORT,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any report event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a report is created.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a report is updated.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a report is deleted.',
|
||||
],
|
||||
'insights' => [
|
||||
'$model' => Response::MODEL_INSIGHT,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any insight event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when an insight is created.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when an insight is updated.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when an insight is deleted.',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
+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',
|
||||
|
||||
+11
-3
@@ -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',
|
||||
@@ -59,8 +63,8 @@ $admins = [
|
||||
'oauth2.write',
|
||||
'mocks.read',
|
||||
'mocks.write',
|
||||
'policies.read',
|
||||
'policies.write',
|
||||
'project.policies.read',
|
||||
'project.policies.write',
|
||||
'templates.read',
|
||||
'templates.write',
|
||||
'projects.write',
|
||||
@@ -103,6 +107,10 @@ $admins = [
|
||||
'tokens.write',
|
||||
'schedules.read',
|
||||
'schedules.write',
|
||||
'insights.read',
|
||||
'insights.write',
|
||||
'reports.read',
|
||||
'reports.write',
|
||||
];
|
||||
|
||||
return [
|
||||
@@ -142,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,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -44,11 +44,23 @@ return [
|
||||
"category" => "Project",
|
||||
],
|
||||
"policies.read" => [
|
||||
"description" =>
|
||||
"Access to read project\'s policies. Replaced by \'project.policies.read\' for more granular control",
|
||||
"category" => "Project",
|
||||
'deprecated' => true,
|
||||
],
|
||||
"policies.write" => [
|
||||
"description" =>
|
||||
"Access to update project\'s policies. Replaces by \'project.policies.write\' for more granular control",
|
||||
"category" => "Project",
|
||||
'deprecated' => true,
|
||||
],
|
||||
"project.policies.read" => [
|
||||
"description" =>
|
||||
"Access to read project\'s policies",
|
||||
"category" => "Project",
|
||||
],
|
||||
"policies.write" => [
|
||||
"project.policies.write" => [
|
||||
"description" =>
|
||||
"Access to update project\'s policies",
|
||||
"category" => "Project",
|
||||
@@ -286,6 +298,16 @@ return [
|
||||
'category' => 'Messaging',
|
||||
],
|
||||
|
||||
// Proxy
|
||||
'rules.read' => [
|
||||
'description' => 'Access to read proxy rules.',
|
||||
'category' => 'Proxy',
|
||||
],
|
||||
'rules.write' => [
|
||||
'description' => 'Access to create, update, and delete proxy rules.',
|
||||
'category' => 'Proxy',
|
||||
],
|
||||
|
||||
// Other
|
||||
"webhooks.read" => [
|
||||
"description" =>
|
||||
@@ -339,12 +361,30 @@ return [
|
||||
'description' => 'Access to create, update, and delete resources under VCS service.',
|
||||
'category' => 'Other',
|
||||
],
|
||||
'rules.read' => [
|
||||
'description' => 'Access to read proxy rules.',
|
||||
'category' => 'Other',
|
||||
|
||||
// Advisor
|
||||
'insights.read' => [
|
||||
'description' => 'Access to read insights under Advisor service.',
|
||||
'category' => 'Advisor',
|
||||
],
|
||||
'rules.write' => [
|
||||
'description' => 'Access to create, update, and delete proxy rules.',
|
||||
'category' => 'Other',
|
||||
'insights.write' => [
|
||||
'description' => 'Reserved for Advisor insight ingestion outside CE.',
|
||||
'category' => 'Advisor',
|
||||
],
|
||||
'reports.read' => [
|
||||
'description' => 'Access to read reports under Advisor service.',
|
||||
'category' => 'Advisor',
|
||||
],
|
||||
'reports.write' => [
|
||||
'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',
|
||||
],
|
||||
];
|
||||
|
||||
+69
-49
@@ -8,55 +8,6 @@ return [
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'sdks' => [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '22.4.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-web',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-web'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git',
|
||||
'gitRepoName' => 'sdk-for-web',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/web/CHANGELOG.md'),
|
||||
'demos' => [
|
||||
[
|
||||
'icon' => 'react.svg',
|
||||
'name' => 'Todo App with React JS',
|
||||
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
|
||||
'source' => 'https://github.com/appwrite/todo-with-react',
|
||||
'url' => 'https://appwrite-todo-with-react.vercel.app/',
|
||||
],
|
||||
[
|
||||
'icon' => 'vue.svg',
|
||||
'name' => 'Todo App with Vue JS',
|
||||
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
|
||||
'source' => 'https://github.com/appwrite/todo-with-vue',
|
||||
'url' => 'https://appwrite-todo-with-vue.vercel.app/',
|
||||
],
|
||||
[
|
||||
'icon' => 'angular.svg',
|
||||
'name' => 'Todo App with Angular',
|
||||
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
|
||||
'source' => 'https://github.com/appwrite/todo-with-angular',
|
||||
'url' => 'https://appwrite-todo-with-angular.vercel.app/',
|
||||
],
|
||||
[
|
||||
'icon' => 'svelte.svg',
|
||||
'name' => 'Todo App with Svelte',
|
||||
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
|
||||
'source' => 'https://github.com/appwrite/todo-with-svelte',
|
||||
'url' => 'https://appwrite-todo-with-svelte.vercel.app/',
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
@@ -320,6 +271,26 @@ return [
|
||||
'repoBranch' => 'main',
|
||||
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/claude-plugin/CHANGELOG.md'),
|
||||
],
|
||||
[
|
||||
'key' => 'codex-plugin',
|
||||
'name' => 'CodexPlugin',
|
||||
'version' => '0.1.1',
|
||||
'url' => 'https://github.com/appwrite/codex-plugin.git',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'spec' => 'static',
|
||||
'family' => APP_SDK_PLATFORM_STATIC,
|
||||
'prism' => 'codex-plugin',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/static-codex-plugin'),
|
||||
'gitUrl' => 'git@github.com:appwrite/codex-plugin.git',
|
||||
'gitRepoName' => 'codex-plugin',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'repoBranch' => 'main',
|
||||
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/codex-plugin/CHANGELOG.md'),
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -330,6 +301,55 @@ return [
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'sdks' => [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '22.4.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-web',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-web'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git',
|
||||
'gitRepoName' => 'sdk-for-web',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/web/CHANGELOG.md'),
|
||||
'demos' => [
|
||||
[
|
||||
'icon' => 'react.svg',
|
||||
'name' => 'Todo App with React JS',
|
||||
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
|
||||
'source' => 'https://github.com/appwrite/todo-with-react',
|
||||
'url' => 'https://appwrite-todo-with-react.vercel.app/',
|
||||
],
|
||||
[
|
||||
'icon' => 'vue.svg',
|
||||
'name' => 'Todo App with Vue JS',
|
||||
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
|
||||
'source' => 'https://github.com/appwrite/todo-with-vue',
|
||||
'url' => 'https://appwrite-todo-with-vue.vercel.app/',
|
||||
],
|
||||
[
|
||||
'icon' => 'angular.svg',
|
||||
'name' => 'Todo App with Angular',
|
||||
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
|
||||
'source' => 'https://github.com/appwrite/todo-with-angular',
|
||||
'url' => 'https://appwrite-todo-with-angular.vercel.app/',
|
||||
],
|
||||
[
|
||||
'icon' => 'svelte.svg',
|
||||
'name' => 'Todo App with Svelte',
|
||||
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
|
||||
'source' => 'https://github.com/appwrite/todo-with-svelte',
|
||||
'url' => 'https://appwrite-todo-with-svelte.vercel.app/',
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'nodejs',
|
||||
'name' => 'Node.js',
|
||||
|
||||
+15
-1
@@ -308,5 +308,19 @@ return [
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/messaging.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
]
|
||||
],
|
||||
'advisor' => [
|
||||
'key' => 'advisor',
|
||||
'name' => 'Advisor',
|
||||
'subtitle' => 'The Advisor service surfaces actionable reports about your project resources, with CTA descriptors for one-click remediation in the console.',
|
||||
'description' => '/docs/services/advisor.md',
|
||||
'controller' => '', // Uses modules
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/server/advisor',
|
||||
'tests' => true,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/insights.png',
|
||||
'platforms' => ['server', 'console'],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -1336,6 +1336,15 @@ return [
|
||||
'category' => 'Migrations',
|
||||
'description' => '',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_MIGRATION_HOST',
|
||||
'description' => 'Internal hostname the migrations worker uses to reach this instance\'s API (for migrations and CSV/JSON imports & exports). Defaults to \'appwrite\', the API service name in the standard Docker Compose setup. Only change this for non-standard deployments.',
|
||||
'introduction' => '1.9.0',
|
||||
'default' => 'appwrite',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MIGRATIONS_FIREBASE_CLIENT_ID',
|
||||
'description' => 'Google OAuth client ID. You can find it in your GCP application settings.',
|
||||
|
||||
+179
-172
@@ -11,10 +11,13 @@ use Appwrite\Auth\Validator\PersonalData;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Bus\Events\SessionCreated;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Message\Delete as DeleteMessage;
|
||||
use Appwrite\Event\Message\Mail as MailMessage;
|
||||
use Appwrite\Event\Message\Messaging as MessagingMessage;
|
||||
use Appwrite\Event\Publisher\Delete as DeletePublisher;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Hooks\Hooks;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
@@ -332,15 +335,15 @@ Http::post('/v1/account')
|
||||
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
|
||||
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
|
||||
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
|
||||
}
|
||||
|
||||
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
|
||||
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
|
||||
}
|
||||
|
||||
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
|
||||
throw new Exception(Exception::USER_EMAIL_FREE);
|
||||
}
|
||||
|
||||
@@ -470,9 +473,9 @@ Http::delete('/v1/account')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->inject('publisherForDeletes')
|
||||
->inject('authorization')
|
||||
->action(function (Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes, Authorization $authorization) {
|
||||
->action(function (Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents, DeletePublisher $publisherForDeletes, Authorization $authorization) {
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
@@ -496,9 +499,11 @@ Http::delete('/v1/account')
|
||||
|
||||
$dbForProject->deleteDocument('users', $user->getId());
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($user);
|
||||
$publisherForDeletes->enqueue(new DeleteMessage(
|
||||
project: $project,
|
||||
type: DELETE_TYPE_DOCUMENT,
|
||||
document: $user,
|
||||
));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
@@ -580,12 +585,12 @@ Http::delete('/v1/account/sessions')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->inject('publisherForDeletes')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain) {
|
||||
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, DeletePublisher $publisherForDeletes, Store $store, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain) {
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
@@ -615,10 +620,11 @@ Http::delete('/v1/account/sessions')
|
||||
$queueForEvents
|
||||
->setPayload($response->output($session, Response::MODEL_SESSION));
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_SESSION_TARGETS)
|
||||
->setDocument($session)
|
||||
->trigger();
|
||||
$publisherForDeletes->enqueue(new DeleteMessage(
|
||||
project: $queueForEvents->getProject(),
|
||||
type: DELETE_TYPE_SESSION_TARGETS,
|
||||
document: $session,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,12 +718,12 @@ Http::delete('/v1/account/sessions/:sessionId')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->inject('publisherForDeletes')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain) {
|
||||
->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, DeletePublisher $publisherForDeletes, Store $store, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain) {
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
$sessionId = ($sessionId === 'current')
|
||||
@@ -759,10 +765,11 @@ Http::delete('/v1/account/sessions/:sessionId')
|
||||
->setParam('sessionId', $session->getId())
|
||||
->setPayload($response->output($session, Response::MODEL_SESSION));
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_SESSION_TARGETS)
|
||||
->setDocument($session)
|
||||
->trigger();
|
||||
$publisherForDeletes->enqueue(new DeleteMessage(
|
||||
project: $queueForEvents->getProject(),
|
||||
type: DELETE_TYPE_SESSION_TARGETS,
|
||||
document: $session,
|
||||
));
|
||||
|
||||
$response->noContent();
|
||||
return;
|
||||
@@ -830,11 +837,11 @@ Http::patch('/v1/account/sessions/:sessionId')
|
||||
$refreshToken = $session->getAttribute('providerRefreshToken', '');
|
||||
$oAuthProviders = Config::getParam('oAuthProviders') ?? [];
|
||||
$className = $oAuthProviders[$provider]['class'] ?? null;
|
||||
if (!empty($provider) && ($className === null || !\class_exists($className))) {
|
||||
if (!empty($refreshToken) && ($className === null || !\class_exists($className))) {
|
||||
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
|
||||
}
|
||||
|
||||
if (!empty($provider) && \class_exists($className)) {
|
||||
if ($className !== null && \class_exists($className)) {
|
||||
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
|
||||
@@ -1232,7 +1239,6 @@ Http::get('/v1/account/sessions/oauth2/:provider')
|
||||
)
|
||||
],
|
||||
contentType: ContentType::HTML,
|
||||
hide: [APP_SDK_PLATFORM_SERVER],
|
||||
))
|
||||
->label('abuse-limit', 50)
|
||||
->label('abuse-key', 'ip:{ip}')
|
||||
@@ -1676,15 +1682,15 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
$failureRedirect(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
|
||||
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
|
||||
$failureRedirect(Exception::USER_EMAIL_DISPOSABLE);
|
||||
}
|
||||
|
||||
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
|
||||
$failureRedirect(Exception::USER_EMAIL_NOT_CANONICAL);
|
||||
}
|
||||
|
||||
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
|
||||
$failureRedirect(Exception::USER_EMAIL_FREE);
|
||||
}
|
||||
|
||||
@@ -1817,15 +1823,15 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
$failureRedirect(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
|
||||
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
|
||||
$failureRedirect(Exception::USER_EMAIL_DISPOSABLE);
|
||||
}
|
||||
|
||||
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
|
||||
$failureRedirect(Exception::USER_EMAIL_NOT_CANONICAL);
|
||||
}
|
||||
|
||||
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
|
||||
$failureRedirect(Exception::USER_EMAIL_FREE);
|
||||
}
|
||||
|
||||
@@ -2113,12 +2119,12 @@ Http::post('/v1/account/tokens/magic-url')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('plan')
|
||||
->inject('proofForPassword')
|
||||
->inject('platform')
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, array $plan, ProofsPassword $proofForPassword, array $platform, Authorization $authorization) {
|
||||
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, MailPublisher $publisherForMails, array $plan, ProofsPassword $proofForPassword, array $platform, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
|
||||
}
|
||||
@@ -2175,15 +2181,15 @@ Http::post('/v1/account/tokens/magic-url')
|
||||
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
|
||||
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
|
||||
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
|
||||
}
|
||||
|
||||
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
|
||||
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
|
||||
}
|
||||
|
||||
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
|
||||
throw new Exception(Exception::USER_EMAIL_FREE);
|
||||
}
|
||||
|
||||
@@ -2304,6 +2310,7 @@ Http::post('/v1/account/tokens/magic-url')
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyToEmail = '';
|
||||
$replyToName = '';
|
||||
$smtpConfig = [];
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (!empty($smtp['senderEmail'])) {
|
||||
@@ -2321,13 +2328,6 @@ Http::post('/v1/account/tokens/magic-url')
|
||||
$replyToName = $smtp['replyToName'];
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpHost($smtp['host'] ?? '')
|
||||
->setSmtpPort($smtp['port'] ?? '')
|
||||
->setSmtpUsername($smtp['username'] ?? '')
|
||||
->setSmtpPassword($smtp['password'] ?? '')
|
||||
->setSmtpSecure($smtp['secure'] ?? '');
|
||||
|
||||
if (!empty($customTemplate)) {
|
||||
if (!empty($customTemplate['senderEmail'])) {
|
||||
$senderEmail = $customTemplate['senderEmail'];
|
||||
@@ -2348,11 +2348,17 @@ Http::post('/v1/account/tokens/magic-url')
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyToEmail($replyToEmail)
|
||||
->setSmtpReplyToName($replyToName)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
$smtpConfig = [
|
||||
'host' => $smtp['host'] ?? '',
|
||||
'port' => $smtp['port'] ?? '',
|
||||
'username' => $smtp['username'] ?? '',
|
||||
'password' => $smtp['password'] ?? '',
|
||||
'secure' => $smtp['secure'] ?? '',
|
||||
'replyToEmail' => $replyToEmail,
|
||||
'replyToName' => $replyToName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'senderName' => $senderName,
|
||||
];
|
||||
}
|
||||
|
||||
$projectName = $project->getAttribute('name');
|
||||
@@ -2374,18 +2380,17 @@ Http::post('/v1/account/tokens/magic-url')
|
||||
'team' => '',
|
||||
];
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($body)
|
||||
->appendVariables($emailVariables)
|
||||
->setRecipient($email);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
$queueForMails->setSenderName($platform['emailSenderName']);
|
||||
}
|
||||
|
||||
$queueForMails->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $email,
|
||||
subject: $subject,
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
smtp: $smtpConfig,
|
||||
variables: $emailVariables,
|
||||
customMailOptions: $project->getId() === 'console' ? ['senderName' => $platform['emailSenderName']] : [],
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$token->setAttribute('secret', $tokenSecret);
|
||||
|
||||
@@ -2436,12 +2441,12 @@ Http::post('/v1/account/tokens/email')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('plan')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForCode')
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, array $plan, ProofsPassword $proofForPassword, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Locale $locale, Event $queueForEvents, MailPublisher $publisherForMails, array $plan, ProofsPassword $proofForPassword, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
|
||||
}
|
||||
@@ -2496,15 +2501,15 @@ Http::post('/v1/account/tokens/email')
|
||||
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
|
||||
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
|
||||
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
|
||||
}
|
||||
|
||||
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
|
||||
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
|
||||
}
|
||||
|
||||
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
|
||||
throw new Exception(Exception::USER_EMAIL_FREE);
|
||||
}
|
||||
|
||||
@@ -2633,6 +2638,7 @@ Http::post('/v1/account/tokens/email')
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyToEmail = '';
|
||||
$replyToName = '';
|
||||
$smtpConfig = [];
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (!empty($smtp['senderEmail'])) {
|
||||
@@ -2650,13 +2656,6 @@ Http::post('/v1/account/tokens/email')
|
||||
$replyToName = $smtp['replyToName'];
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpHost($smtp['host'] ?? '')
|
||||
->setSmtpPort($smtp['port'] ?? '')
|
||||
->setSmtpUsername($smtp['username'] ?? '')
|
||||
->setSmtpPassword($smtp['password'] ?? '')
|
||||
->setSmtpSecure($smtp['secure'] ?? '');
|
||||
|
||||
if (!empty($customTemplate)) {
|
||||
if (!empty($customTemplate['senderEmail'])) {
|
||||
$senderEmail = $customTemplate['senderEmail'];
|
||||
@@ -2677,11 +2676,17 @@ Http::post('/v1/account/tokens/email')
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyToEmail($replyToEmail)
|
||||
->setSmtpReplyToName($replyToName)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
$smtpConfig = [
|
||||
'host' => $smtp['host'] ?? '',
|
||||
'port' => $smtp['port'] ?? '',
|
||||
'username' => $smtp['username'] ?? '',
|
||||
'password' => $smtp['password'] ?? '',
|
||||
'secure' => $smtp['secure'] ?? '',
|
||||
'replyToEmail' => $replyToEmail,
|
||||
'replyToName' => $replyToName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'senderName' => $senderName,
|
||||
];
|
||||
}
|
||||
|
||||
$projectName = $project->getAttribute('name');
|
||||
@@ -2717,20 +2722,18 @@ Http::post('/v1/account/tokens/email')
|
||||
]);
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($body)
|
||||
->setBodyTemplate($bodyTemplate)
|
||||
->appendVariables($emailVariables)
|
||||
->setRecipient($email);
|
||||
|
||||
// since this is console project, set email sender name!
|
||||
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
|
||||
$queueForMails->setSenderName($platform['emailSenderName']);
|
||||
}
|
||||
|
||||
$queueForMails->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $email,
|
||||
subject: $subject,
|
||||
bodyTemplate: $bodyTemplate,
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
smtp: $smtpConfig,
|
||||
variables: $emailVariables,
|
||||
customMailOptions: $smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE ? ['senderName' => $platform['emailSenderName']] : [],
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$token->setAttribute('secret', $tokenSecret);
|
||||
|
||||
@@ -2880,7 +2883,7 @@ Http::post('/v1/account/tokens/phone')
|
||||
->inject('platform')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('locale')
|
||||
->inject('timelimit')
|
||||
->inject('usage')
|
||||
@@ -2888,7 +2891,7 @@ Http::post('/v1/account/tokens/phone')
|
||||
->inject('store')
|
||||
->inject('proofForCode')
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, Context $usage, array $plan, Store $store, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Event $queueForEvents, MessagingPublisher $publisherForMessaging, Locale $locale, callable $timelimit, Context $usage, array $plan, Store $store, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
@@ -3021,11 +3024,13 @@ Http::post('/v1/account/tokens/phone')
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_INTERNAL)
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$phone])
|
||||
->setProviderType(MESSAGE_TYPE_SMS);
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_INTERNAL,
|
||||
project: $project,
|
||||
message: $messageDoc,
|
||||
recipients: [$phone],
|
||||
providerType: MESSAGE_TYPE_SMS,
|
||||
));
|
||||
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
try {
|
||||
@@ -3417,15 +3422,15 @@ Http::patch('/v1/account/email')
|
||||
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
|
||||
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
|
||||
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
|
||||
}
|
||||
|
||||
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
|
||||
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
|
||||
}
|
||||
|
||||
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
|
||||
throw new Exception(Exception::USER_EMAIL_FREE);
|
||||
}
|
||||
|
||||
@@ -3681,11 +3686,11 @@ Http::post('/v1/account/recovery')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('locale')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('queueForEvents')
|
||||
->inject('proofForToken')
|
||||
->inject('authorization')
|
||||
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, MailPublisher $publisherForMails, Event $queueForEvents, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
|
||||
@@ -3768,6 +3773,7 @@ Http::post('/v1/account/recovery')
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyToEmail = '';
|
||||
$replyToName = '';
|
||||
$smtpConfig = [];
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (!empty($smtp['senderEmail'])) {
|
||||
@@ -3785,13 +3791,6 @@ Http::post('/v1/account/recovery')
|
||||
$replyToName = $smtp['replyToName'];
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpHost($smtp['host'] ?? '')
|
||||
->setSmtpPort($smtp['port'] ?? '')
|
||||
->setSmtpUsername($smtp['username'] ?? '')
|
||||
->setSmtpPassword($smtp['password'] ?? '')
|
||||
->setSmtpSecure($smtp['secure'] ?? '');
|
||||
|
||||
if (!empty($customTemplate)) {
|
||||
if (!empty($customTemplate['senderEmail'])) {
|
||||
$senderEmail = $customTemplate['senderEmail'];
|
||||
@@ -3812,11 +3811,17 @@ Http::post('/v1/account/recovery')
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyToEmail($replyToEmail)
|
||||
->setSmtpReplyToName($replyToName)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
$smtpConfig = [
|
||||
'host' => $smtp['host'] ?? '',
|
||||
'port' => $smtp['port'] ?? '',
|
||||
'username' => $smtp['username'] ?? '',
|
||||
'password' => $smtp['password'] ?? '',
|
||||
'secure' => $smtp['secure'] ?? '',
|
||||
'replyToEmail' => $replyToEmail,
|
||||
'replyToName' => $replyToName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'senderName' => $senderName,
|
||||
];
|
||||
}
|
||||
|
||||
$emailVariables = [
|
||||
@@ -3829,19 +3834,18 @@ Http::post('/v1/account/recovery')
|
||||
'team' => ''
|
||||
];
|
||||
|
||||
$queueForMails
|
||||
->setRecipient($profile->getAttribute('email', ''))
|
||||
->setName($profile->getAttribute('name', ''))
|
||||
->setBody($body)
|
||||
->appendVariables($emailVariables)
|
||||
->setSubject($subject)
|
||||
->setPreview($preview);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
$queueForMails->setSenderName($platform['emailSenderName']);
|
||||
}
|
||||
|
||||
$queueForMails->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $profile->getAttribute('email', ''),
|
||||
name: $profile->getAttribute('name', ''),
|
||||
subject: $subject,
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
smtp: $smtpConfig,
|
||||
variables: $emailVariables,
|
||||
customMailOptions: $project->getId() === 'console' ? ['senderName' => $platform['emailSenderName']] : [],
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$recovery->setAttribute('secret', $secret);
|
||||
|
||||
@@ -4009,10 +4013,10 @@ Http::post('/v1/account/verifications/email')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('proofForToken')
|
||||
->inject('authorization')
|
||||
->action(function (string $url, Request $request, Response $response, Document $project, array $platform, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
->action(function (string $url, Request $request, Response $response, Document $project, array $platform, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, MailPublisher $publisherForMails, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
|
||||
@@ -4099,6 +4103,7 @@ Http::post('/v1/account/verifications/email')
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyToEmail = '';
|
||||
$replyToName = '';
|
||||
$smtpConfig = [];
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (!empty($smtp['senderEmail'])) {
|
||||
@@ -4116,13 +4121,6 @@ Http::post('/v1/account/verifications/email')
|
||||
$replyToName = $smtp['replyToName'];
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpHost($smtp['host'] ?? '')
|
||||
->setSmtpPort($smtp['port'] ?? '')
|
||||
->setSmtpUsername($smtp['username'] ?? '')
|
||||
->setSmtpPassword($smtp['password'] ?? '')
|
||||
->setSmtpSecure($smtp['secure'] ?? '');
|
||||
|
||||
if (!empty($customTemplate)) {
|
||||
if (!empty($customTemplate['senderEmail'])) {
|
||||
$senderEmail = $customTemplate['senderEmail'];
|
||||
@@ -4143,11 +4141,17 @@ Http::post('/v1/account/verifications/email')
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyToEmail($replyToEmail)
|
||||
->setSmtpReplyToName($replyToName)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
$smtpConfig = [
|
||||
'host' => $smtp['host'] ?? '',
|
||||
'port' => $smtp['port'] ?? '',
|
||||
'username' => $smtp['username'] ?? '',
|
||||
'password' => $smtp['password'] ?? '',
|
||||
'secure' => $smtp['secure'] ?? '',
|
||||
'replyToEmail' => $replyToEmail,
|
||||
'replyToName' => $replyToName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'senderName' => $senderName,
|
||||
];
|
||||
}
|
||||
|
||||
$emailVariables = [
|
||||
@@ -4174,20 +4178,19 @@ Http::post('/v1/account/verifications/email')
|
||||
]);
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($body)
|
||||
->setBodyTemplate($bodyTemplate)
|
||||
->appendVariables($emailVariables)
|
||||
->setRecipient($user->getAttribute('email'))
|
||||
->setName($user->getAttribute('name') ?? '');
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
$queueForMails->setSenderName($platform['emailSenderName']);
|
||||
}
|
||||
|
||||
$queueForMails->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $user->getAttribute('email'),
|
||||
name: $user->getAttribute('name') ?? '',
|
||||
subject: $subject,
|
||||
bodyTemplate: $bodyTemplate,
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
smtp: $smtpConfig,
|
||||
variables: $emailVariables,
|
||||
customMailOptions: $project->getId() === 'console' ? ['senderName' => $platform['emailSenderName']] : [],
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$verification->setAttribute('secret', $verificationSecret);
|
||||
|
||||
@@ -4321,7 +4324,7 @@ Http::post('/v1/account/verifications/phone')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('project')
|
||||
->inject('locale')
|
||||
->inject('timelimit')
|
||||
@@ -4329,7 +4332,7 @@ Http::post('/v1/account/verifications/phone')
|
||||
->inject('plan')
|
||||
->inject('proofForCode')
|
||||
->inject('authorization')
|
||||
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, Context $usage, array $plan, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Event $queueForEvents, MessagingPublisher $publisherForMessaging, Document $project, Locale $locale, callable $timelimit, Context $usage, array $plan, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
@@ -4398,11 +4401,13 @@ Http::post('/v1/account/verifications/phone')
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_INTERNAL)
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$user->getAttribute('phone')])
|
||||
->setProviderType(MESSAGE_TYPE_SMS);
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_INTERNAL,
|
||||
project: $project,
|
||||
message: $messageDoc,
|
||||
recipients: [$user->getAttribute('phone')],
|
||||
providerType: MESSAGE_TYPE_SMS,
|
||||
));
|
||||
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
try {
|
||||
@@ -4509,7 +4514,7 @@ Http::post('/v1/account/targets/push')
|
||||
group: 'pushTargets',
|
||||
name: 'createPushTarget',
|
||||
description: '/docs/references/account/create-push-target.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -4593,7 +4598,7 @@ Http::put('/v1/account/targets/:targetId/push')
|
||||
group: 'pushTargets',
|
||||
name: 'updatePushTarget',
|
||||
description: '/docs/references/account/update-push-target.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -4663,7 +4668,7 @@ Http::delete('/v1/account/targets/:targetId/push')
|
||||
group: 'pushTargets',
|
||||
name: 'deletePushTarget',
|
||||
description: '/docs/references/account/delete-push-target.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
@@ -4674,13 +4679,13 @@ Http::delete('/v1/account/targets/:targetId/push')
|
||||
))
|
||||
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID.', false, ['dbForProject'])
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->inject('publisherForDeletes')
|
||||
->inject('user')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
->action(function (string $targetId, Event $queueForEvents, DeletePublisher $publisherForDeletes, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
@@ -4695,9 +4700,11 @@ Http::delete('/v1/account/targets/:targetId/push')
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_TARGET)
|
||||
->setDocument($target);
|
||||
$publisherForDeletes->enqueue(new DeleteMessage(
|
||||
project: $queueForEvents->getProject(),
|
||||
type: DELETE_TYPE_TARGET,
|
||||
document: $target,
|
||||
));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Message\Delete as DeleteMessage;
|
||||
use Appwrite\Event\Message\Messaging as MessagingMessage;
|
||||
use Appwrite\Event\Publisher\Delete as DeletePublisher;
|
||||
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Status as MessageStatus;
|
||||
use Appwrite\Permission;
|
||||
@@ -2727,9 +2729,9 @@ Http::delete('/v1/messaging/topics/:topicId')
|
||||
->param('topicId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Topic ID.', false, ['dbForProject'])
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDeletes')
|
||||
->inject('publisherForDeletes')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, Event $queueForEvents, Database $dbForProject, Delete $queueForDeletes, Response $response) {
|
||||
->action(function (string $topicId, Event $queueForEvents, Database $dbForProject, DeletePublisher $publisherForDeletes, Response $response) {
|
||||
$topic = $dbForProject->getDocument('topics', $topicId);
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
@@ -2738,9 +2740,11 @@ Http::delete('/v1/messaging/topics/:topicId')
|
||||
|
||||
$dbForProject->deleteDocument('topics', $topicId);
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_TOPIC)
|
||||
->setDocument($topic);
|
||||
$publisherForDeletes->enqueue(new DeleteMessage(
|
||||
project: $queueForEvents->getProject(),
|
||||
type: DELETE_TYPE_TOPIC,
|
||||
document: $topic,
|
||||
));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('topicId', $topic->getId());
|
||||
@@ -3187,9 +3191,9 @@ Http::post('/v1/messaging/messages/email')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, string $subject, string $content, ?array $topics, ?array $users, ?array $targets, ?array $cc, ?array $bcc, ?array $attachments, bool $draft, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, string $subject, string $content, ?array $topics, ?array $users, ?array $targets, ?array $cc, ?array $bcc, ?array $attachments, bool $draft, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response) {
|
||||
$messageId = $messageId == 'unique()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
@@ -3274,9 +3278,11 @@ Http::post('/v1/messaging/messages/email')
|
||||
|
||||
switch ($status) {
|
||||
case MessageStatus::PROCESSING:
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($message->getId());
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $project,
|
||||
messageId: $message->getId(),
|
||||
));
|
||||
break;
|
||||
case MessageStatus::SCHEDULED:
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
@@ -3362,9 +3368,9 @@ Http::post('/v1/messaging/messages/sms')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, string $content, ?array $topics, ?array $users, ?array $targets, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, string $content, ?array $topics, ?array $users, ?array $targets, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response) {
|
||||
$messageId = $messageId == 'unique()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
@@ -3418,9 +3424,11 @@ Http::post('/v1/messaging/messages/sms')
|
||||
|
||||
switch ($status) {
|
||||
case MessageStatus::PROCESSING:
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($message->getId());
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $project,
|
||||
messageId: $message->getId(),
|
||||
));
|
||||
break;
|
||||
case MessageStatus::SCHEDULED:
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
@@ -3498,10 +3506,10 @@ Http::post('/v1/messaging/messages/push')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->inject('platform')
|
||||
->action(function (string $messageId, string $title, string $body, ?array $topics, ?array $users, ?array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, int $badge, bool $draft, ?string $scheduledAt, bool $contentAvailable, bool $critical, string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response, array $platform) {
|
||||
->action(function (string $messageId, string $title, string $body, ?array $topics, ?array $users, ?array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, int $badge, bool $draft, ?string $scheduledAt, bool $contentAvailable, bool $critical, string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response, array $platform) {
|
||||
$messageId = $messageId == 'unique()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
@@ -3638,9 +3646,11 @@ Http::post('/v1/messaging/messages/push')
|
||||
|
||||
switch ($status) {
|
||||
case MessageStatus::PROCESSING:
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($message->getId());
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $project,
|
||||
messageId: $message->getId(),
|
||||
));
|
||||
break;
|
||||
case MessageStatus::SCHEDULED:
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
@@ -3983,9 +3993,9 @@ Http::patch('/v1/messaging/messages/email/:messageId')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $content, ?bool $draft, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, ?array $attachments, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $content, ?bool $draft, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, ?array $attachments, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -4141,9 +4151,11 @@ Http::patch('/v1/messaging/messages/email/:messageId')
|
||||
$message = $dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
|
||||
if ($status === MessageStatus::PROCESSING) {
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($message->getId());
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $project,
|
||||
messageId: $message->getId(),
|
||||
));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
@@ -4205,9 +4217,9 @@ Http::patch('/v1/messaging/messages/sms/:messageId')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $content, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $content, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -4323,9 +4335,11 @@ Http::patch('/v1/messaging/messages/sms/:messageId')
|
||||
$message = $dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
|
||||
if ($status === MessageStatus::PROCESSING) {
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($message->getId());
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $project,
|
||||
messageId: $message->getId(),
|
||||
));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
@@ -4379,10 +4393,10 @@ Http::patch('/v1/messaging/messages/push/:messageId')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->inject('platform')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, ?bool $contentAvailable, ?bool $critical, ?string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response, array $platform) {
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, ?bool $contentAvailable, ?bool $critical, ?string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response, array $platform) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -4584,9 +4598,11 @@ Http::patch('/v1/messaging/messages/push/:messageId')
|
||||
$message = $dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
|
||||
if ($status === MessageStatus::PROCESSING) {
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($message->getId());
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $project,
|
||||
messageId: $message->getId(),
|
||||
));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
use Appwrite\Auth\Validator\MockNumber;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
@@ -27,37 +24,6 @@ Http::init()
|
||||
}
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId')
|
||||
->desc('Get project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'projects',
|
||||
name: 'get',
|
||||
description: '/docs/references/projects/get.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
// Backwards compatibility
|
||||
Http::patch('/v1/projects/:projectId/oauth2')
|
||||
->desc('Update project OAuth2')
|
||||
|
||||
@@ -11,8 +11,9 @@ use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Deletes\Identities as DeleteIdentities;
|
||||
use Appwrite\Deletes\Targets as DeleteTargets;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Message\Delete as DeleteMessage;
|
||||
use Appwrite\Event\Publisher\Delete as DeletePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Hooks\Hooks;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -131,15 +132,15 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
|
||||
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
|
||||
}
|
||||
|
||||
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
|
||||
}
|
||||
|
||||
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
|
||||
throw new Exception(Exception::USER_EMAIL_FREE);
|
||||
}
|
||||
|
||||
@@ -731,6 +732,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;
|
||||
|
||||
@@ -743,7 +751,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,
|
||||
@@ -1563,15 +1596,15 @@ Http::patch('/v1/users/:userId/email')
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
|
||||
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
|
||||
}
|
||||
|
||||
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
|
||||
}
|
||||
|
||||
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
|
||||
if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
|
||||
throw new Exception(Exception::USER_EMAIL_FREE);
|
||||
}
|
||||
|
||||
@@ -2592,8 +2625,8 @@ Http::delete('/v1/users/:userId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
|
||||
->inject('publisherForDeletes')
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Event $queueForEvents, DeletePublisher $publisherForDeletes) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -2608,9 +2641,11 @@ Http::delete('/v1/users/:userId')
|
||||
DeleteIdentities::delete($dbForProject, Query::equal('userInternalId', [$user->getSequence()]));
|
||||
DeleteTargets::delete($dbForProject, Query::equal('userInternalId', [$user->getSequence()]));
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($clone);
|
||||
$publisherForDeletes->enqueue(new DeleteMessage(
|
||||
project: $queueForEvents->getProject(),
|
||||
type: DELETE_TYPE_DOCUMENT,
|
||||
document: $clone,
|
||||
));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
@@ -2643,10 +2678,10 @@ Http::delete('/v1/users/:userId/targets/:targetId')
|
||||
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
|
||||
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID.', false, ['dbForProject'])
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->inject('publisherForDeletes')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, string $targetId, Event $queueForEvents, Delete $queueForDeletes, Response $response, Database $dbForProject) {
|
||||
->action(function (string $userId, string $targetId, Event $queueForEvents, DeletePublisher $publisherForDeletes, Response $response, Database $dbForProject) {
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
@@ -2666,9 +2701,11 @@ Http::delete('/v1/users/:userId/targets/:targetId')
|
||||
$dbForProject->deleteDocument('targets', $target->getId());
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_TARGET)
|
||||
->setDocument($target);
|
||||
$publisherForDeletes->enqueue(new DeleteMessage(
|
||||
project: $queueForEvents->getProject(),
|
||||
type: DELETE_TYPE_TARGET,
|
||||
document: $target,
|
||||
));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
|
||||
+74
-75
@@ -7,9 +7,10 @@ use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Bus\Events\ExecutionCompleted;
|
||||
use Appwrite\Bus\Events\RequestCompleted;
|
||||
use Appwrite\Event\Delete as DeleteEvent;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Message\Delete as DeleteMessage;
|
||||
use Appwrite\Event\Publisher\Certificate;
|
||||
use Appwrite\Event\Publisher\Delete as DeletePublisher;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Network\Cors;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
@@ -28,6 +29,8 @@ use Appwrite\Utopia\Request\Filters\V21 as RequestV21;
|
||||
use Appwrite\Utopia\Request\Filters\V22 as RequestV22;
|
||||
use Appwrite\Utopia\Request\Filters\V23 as RequestV23;
|
||||
use Appwrite\Utopia\Request\Filters\V24 as RequestV24;
|
||||
use Appwrite\Utopia\Request\Filters\V25 as RequestV25;
|
||||
use Appwrite\Utopia\Request\Filters\V26 as RequestV26;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filters\V16 as ResponseV16;
|
||||
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17;
|
||||
@@ -38,7 +41,10 @@ use Appwrite\Utopia\Response\Filters\V21 as ResponseV21;
|
||||
use Appwrite\Utopia\Response\Filters\V22 as ResponseV22;
|
||||
use Appwrite\Utopia\Response\Filters\V23 as ResponseV23;
|
||||
use Appwrite\Utopia\Response\Filters\V24 as ResponseV24;
|
||||
use Appwrite\Utopia\Response\Filters\V25 as ResponseV25;
|
||||
use Appwrite\Utopia\Response\Filters\V26 as ResponseV26;
|
||||
use Appwrite\Utopia\View;
|
||||
use Executor\Exception\Timeout as ExecutorTimeout;
|
||||
use Executor\Executor;
|
||||
use MaxMind\Db\Reader;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
@@ -69,7 +75,7 @@ use Utopia\Validator\Text;
|
||||
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
|
||||
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeletePublisher $publisherForDeletes, int $executionsRetentionCount)
|
||||
{
|
||||
$host = $request->getHostname();
|
||||
if (!empty($previewHostname)) {
|
||||
@@ -178,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', '');
|
||||
@@ -579,26 +561,30 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
'site' => '',
|
||||
};
|
||||
|
||||
$executionResponse = $executor->createExecution(
|
||||
projectId: $project->getId(),
|
||||
deploymentId: $deployment->getId(),
|
||||
body: \strlen($body) > 0 ? $body : null,
|
||||
variables: $vars,
|
||||
timeout: $resource->getAttribute('timeout', 30),
|
||||
image: $runtime['image'],
|
||||
source: $source,
|
||||
entrypoint: $entrypoint,
|
||||
version: $version,
|
||||
path: $path,
|
||||
method: $method,
|
||||
headers: $headers,
|
||||
runtimeEntrypoint: $runtimeEntrypoint,
|
||||
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
|
||||
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
|
||||
logging: $resource->getAttribute('logging', true),
|
||||
requestTimeout: 30,
|
||||
responseFormat: Executor::RESPONSE_FORMAT_ARRAY_HEADERS
|
||||
);
|
||||
try {
|
||||
$executionResponse = $executor->createExecution(
|
||||
projectId: $project->getId(),
|
||||
deploymentId: $deployment->getId(),
|
||||
body: \strlen($body) > 0 ? $body : null,
|
||||
variables: $vars,
|
||||
timeout: $resource->getAttribute('timeout', 30),
|
||||
image: $runtime['image'],
|
||||
source: $source,
|
||||
entrypoint: $entrypoint,
|
||||
version: $version,
|
||||
path: $path,
|
||||
method: $method,
|
||||
headers: $headers,
|
||||
runtimeEntrypoint: $runtimeEntrypoint,
|
||||
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
|
||||
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
|
||||
logging: $resource->getAttribute('logging', true),
|
||||
requestTimeout: 30,
|
||||
responseFormat: Executor::RESPONSE_FORMAT_ARRAY_HEADERS
|
||||
);
|
||||
} catch (ExecutorTimeout $th) {
|
||||
throw new AppwriteException(AppwriteException::FUNCTION_SYNCHRONOUS_TIMEOUT, previous: $th);
|
||||
}
|
||||
|
||||
$headerOverrides = [];
|
||||
|
||||
@@ -781,12 +767,12 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
? RESOURCE_TYPE_FUNCTIONS
|
||||
: RESOURCE_TYPE_SITES;
|
||||
|
||||
$queueForDeletes
|
||||
->setProject($project)
|
||||
->setResourceType($resourceType)
|
||||
->setResource($resource->getSequence())
|
||||
->setType(DELETE_TYPE_EXECUTIONS_LIMIT)
|
||||
->trigger();
|
||||
$publisherForDeletes->enqueue(new DeleteMessage(
|
||||
project: $project,
|
||||
type: DELETE_TYPE_EXECUTIONS_LIMIT,
|
||||
resource: (string) $resource->getSequence(),
|
||||
resourceType: $resourceType,
|
||||
));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -847,9 +833,10 @@ Http::init()
|
||||
->inject('apiKey')
|
||||
->inject('cors')
|
||||
->inject('authorization')
|
||||
->inject('queueForDeletes')
|
||||
->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, DeleteEvent $queueForDeletes, 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
|
||||
*/
|
||||
@@ -857,15 +844,15 @@ Http::init()
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
// 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, $queueForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $publisherForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->match($request)?->route->label('router', true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Request format
|
||||
*/
|
||||
$route = $utopia->getRoute();
|
||||
$route = $utopia->match($request)?->route;
|
||||
$request->setRoute($route);
|
||||
|
||||
if ($route === null) {
|
||||
@@ -890,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());
|
||||
@@ -904,6 +891,12 @@ Http::init()
|
||||
if (version_compare($requestFormat, '1.9.3', '<')) {
|
||||
$request->addFilter(new RequestV24());
|
||||
}
|
||||
if (version_compare($requestFormat, '1.9.4', '<')) {
|
||||
$request->addFilter(new RequestV25());
|
||||
}
|
||||
if (version_compare($requestFormat, '1.9.5', '<')) {
|
||||
$request->addFilter(new RequestV26());
|
||||
}
|
||||
}
|
||||
|
||||
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
|
||||
@@ -928,6 +921,12 @@ Http::init()
|
||||
*/
|
||||
$responseFormat = $request->getHeader('x-appwrite-response-format', System::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($responseFormat) {
|
||||
if (version_compare($responseFormat, '1.9.5', '<')) {
|
||||
$response->addFilter(new ResponseV26());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.9.4', '<')) {
|
||||
$response->addFilter(new ResponseV25());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.9.3', '<')) {
|
||||
$response->addFilter(new ResponseV24());
|
||||
}
|
||||
@@ -1146,17 +1145,17 @@ Http::options()
|
||||
->inject('apiKey')
|
||||
->inject('cors')
|
||||
->inject('authorization')
|
||||
->inject('queueForDeletes')
|
||||
->inject('publisherForDeletes')
|
||||
->inject('executionsRetentionCount')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeletePublisher $publisherForDeletes, int $executionsRetentionCount) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
// 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, $queueForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $publisherForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->match($request)?->route->label('router', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1191,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();
|
||||
@@ -1261,7 +1260,7 @@ Http::error()
|
||||
if (!$publish && $project->getId() !== 'console') {
|
||||
$errorUser = new DBUser();
|
||||
try {
|
||||
$resolvedUser = $utopia->getResource('user');
|
||||
$resolvedUser = $utopia->context()->get('user');
|
||||
if ($resolvedUser instanceof DBUser) {
|
||||
$errorUser = $resolvedUser;
|
||||
}
|
||||
@@ -1280,7 +1279,7 @@ Http::error()
|
||||
if ($logger && $publish) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $utopia->getResource('user');
|
||||
$user = $utopia->context()->get('user');
|
||||
} catch (\Throwable) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
@@ -1481,7 +1480,7 @@ Http::error()
|
||||
// the cors resource (which depends on rule -> DB) would cascade.
|
||||
// Uses override:true to avoid duplicate headers if init() already set them.
|
||||
try {
|
||||
$cors = $utopia->getResource('cors');
|
||||
$cors = $utopia->context()->get('cors');
|
||||
foreach ($cors->headers($request->getOrigin()) as $name => $value) {
|
||||
$response
|
||||
->removeHeader($name)
|
||||
@@ -1548,16 +1547,16 @@ Http::get('/robots.txt')
|
||||
->inject('previewHostname')
|
||||
->inject('apiKey')
|
||||
->inject('authorization')
|
||||
->inject('queueForDeletes')
|
||||
->inject('publisherForDeletes')
|
||||
->inject('executionsRetentionCount')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeletePublisher $publisherForDeletes, int $executionsRetentionCount) {
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $publisherForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->match($request)?->route->label('router', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1582,16 +1581,16 @@ Http::get('/humans.txt')
|
||||
->inject('previewHostname')
|
||||
->inject('apiKey')
|
||||
->inject('authorization')
|
||||
->inject('queueForDeletes')
|
||||
->inject('publisherForDeletes')
|
||||
->inject('executionsRetentionCount')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeletePublisher $publisherForDeletes, int $executionsRetentionCount) {
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $publisherForDeletes, $executionsRetentionCount)) {
|
||||
$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) : [];
|
||||
|
||||
|
||||
+131
-149
@@ -3,23 +3,21 @@
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Bus\Events\RequestCompleted;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Context\Audit as AuditContext;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Message\Audit as AuditMessage;
|
||||
use Appwrite\Event\Message\Func as FunctionMessage;
|
||||
use Appwrite\Event\Message\Usage as UsageMessage;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Publisher\Audit;
|
||||
use Appwrite\Event\Publisher\Func as FunctionPublisher;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Functions\EventProcessor;
|
||||
use Appwrite\Platform\Modules\Storage\Config\CacheControl;
|
||||
use Appwrite\Platform\Modules\Storage\Config\StorageCacheControl;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\Usage\Context;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
@@ -39,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;
|
||||
@@ -87,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')
|
||||
@@ -100,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.
|
||||
@@ -180,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()) {
|
||||
@@ -191,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(),
|
||||
@@ -263,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;
|
||||
}
|
||||
@@ -430,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);
|
||||
}
|
||||
@@ -440,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);
|
||||
}
|
||||
@@ -479,111 +474,110 @@ Http::init()
|
||||
|
||||
Http::init()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('route')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('timelimit')
|
||||
->inject('devKey')
|
||||
->inject('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->isKey($roles)
|
||||
&& ! $user->isPrivileged($roles)
|
||||
&& $devKey->isEmpty();
|
||||
|
||||
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
|
||||
$abuseKeyLabel = (! is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
|
||||
$closestLimit = null;
|
||||
|
||||
foreach ($abuseKeyLabel as $abuseKey) {
|
||||
$isRateLimited = false;
|
||||
|
||||
try {
|
||||
$start = $request->getContentRangeStart();
|
||||
$end = $request->getContentRangeEnd();
|
||||
$timeLimit = $timelimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600));
|
||||
$timeLimit
|
||||
->setParam('{projectId}', $project->getId())
|
||||
->setParam('{userId}', $user->getId())
|
||||
->setParam('{userAgent}', $request->getUserAgent(''))
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getHostname() . $route->getPath())
|
||||
->setParam('{method}', $request->getMethod())
|
||||
->setParam('{chunkId}', (int) ($start / ($end + 1 - $start)));
|
||||
|
||||
foreach ($request->getParams() as $key => $value) {
|
||||
if (! empty($value)) {
|
||||
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
|
||||
}
|
||||
}
|
||||
|
||||
$abuse = new Abuse($timeLimit);
|
||||
$remaining = $timeLimit->remaining();
|
||||
$limit = $timeLimit->limit();
|
||||
$time = $timeLimit->time() + $route->getLabel('abuse-time', 3600);
|
||||
|
||||
if ($limit && ($remaining < $closestLimit || is_null($closestLimit))) {
|
||||
$closestLimit = $remaining;
|
||||
$response
|
||||
->addHeader('X-RateLimit-Limit', $limit)
|
||||
->addHeader('X-RateLimit-Remaining', $remaining)
|
||||
->addHeader('X-RateLimit-Reset', $time);
|
||||
}
|
||||
|
||||
if ($shouldCheckAbuse) {
|
||||
$isRateLimited = $abuse->check();
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
\error_log((string) $th);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($isRateLimited) {
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Http::init()
|
||||
->groups(['api'])
|
||||
->inject('route')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('auditContext')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForBuilds')
|
||||
->inject('usage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForFunctions')
|
||||
->inject('dbForProject')
|
||||
->inject('timelimit')
|
||||
->inject('resourceToken')
|
||||
->inject('mode')
|
||||
->inject('apiKey')
|
||||
->inject('plan')
|
||||
->inject('devKey')
|
||||
->inject('telemetry')
|
||||
->inject('platform')
|
||||
->inject('authorization')
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Messaging $queueForMessaging, AuditContext $auditContext, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Context $usage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) {
|
||||
->inject('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,
|
||||
default => '',
|
||||
};
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
*/
|
||||
|
||||
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
|
||||
$timeLimitArray = [];
|
||||
|
||||
$abuseKeyLabel = (! is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
|
||||
|
||||
foreach ($abuseKeyLabel as $abuseKey) {
|
||||
$start = $request->getContentRangeStart();
|
||||
$end = $request->getContentRangeEnd();
|
||||
$timeLimit = $timelimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600));
|
||||
$timeLimit
|
||||
->setParam('{projectId}', $project->getId())
|
||||
->setParam('{userId}', $user->getId())
|
||||
->setParam('{userAgent}', $request->getUserAgent(''))
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getHostname() . $route->getPath())
|
||||
->setParam('{method}', $request->getMethod())
|
||||
->setParam('{chunkId}', (int) ($start / ($end + 1 - $start)));
|
||||
$timeLimitArray[] = $timeLimit;
|
||||
}
|
||||
|
||||
$closestLimit = null;
|
||||
|
||||
$roles = $authorization->getRoles();
|
||||
$isPrivilegedUser = $user->isPrivileged($roles);
|
||||
$isAppUser = $user->isApp($roles);
|
||||
|
||||
foreach ($timeLimitArray as $timeLimit) {
|
||||
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
|
||||
if (! empty($value)) {
|
||||
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
|
||||
}
|
||||
}
|
||||
|
||||
$abuse = new Abuse($timeLimit);
|
||||
$remaining = $timeLimit->remaining();
|
||||
|
||||
$limit = $timeLimit->limit();
|
||||
$time = $timeLimit->time() + $route->getLabel('abuse-time', 3600);
|
||||
|
||||
if ($limit && ($remaining < $closestLimit || is_null($closestLimit))) {
|
||||
$closestLimit = $remaining;
|
||||
$response
|
||||
->addHeader('X-RateLimit-Limit', $limit)
|
||||
->addHeader('X-RateLimit-Remaining', $remaining)
|
||||
->addHeader('X-RateLimit-Reset', $time);
|
||||
}
|
||||
|
||||
$enabled = System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled';
|
||||
|
||||
if (
|
||||
$enabled // Abuse is enabled
|
||||
&& ! $isAppUser // User is not API key
|
||||
&& ! $isPrivilegedUser // User is not an admin
|
||||
&& $devKey->isEmpty() // request doesn't not contain development key
|
||||
&& $abuse->check() // Route is rate-limited
|
||||
) {
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: (@loks0n)
|
||||
* Avoid mutating the message across file boundaries - it's difficult to reason about at scale.
|
||||
@@ -608,30 +602,18 @@ 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 */
|
||||
$queueForDeletes->setProject($project);
|
||||
$queueForDatabase->setProject($project);
|
||||
$queueForMessaging->setProject($project);
|
||||
$queueForFunctions->setProject($project);
|
||||
$queueForBuilds->setProject($project);
|
||||
$queueForMails->setProject($project);
|
||||
|
||||
/* Auto-set platforms */
|
||||
$queueForFunctions->setPlatform($platform);
|
||||
$queueForBuilds->setPlatform($platform);
|
||||
$queueForMails->setPlatform($platform);
|
||||
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
$storageCacheOperationsCounter = $telemetry->createCounter('storage.cache.operations.load');
|
||||
if ($useCache) {
|
||||
$route = $utopia->match($request);
|
||||
$roles = $authorization->getRoles();
|
||||
$isAppUser = $user->isKey($roles);
|
||||
$isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview';
|
||||
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && ! $user->isPrivileged($authorization->getRoles());
|
||||
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && ! $user->isPrivileged($roles);
|
||||
|
||||
$key = $request->cacheIdentifier();
|
||||
Span::add('storage.cache.key', $key);
|
||||
@@ -643,6 +625,7 @@ Http::init()
|
||||
$data = $cache->load($key, $timestamp);
|
||||
|
||||
if (! empty($data) && ! $cacheLog->isEmpty()) {
|
||||
$cacheControl = \sprintf('private, max-age=%d', $timestamp);
|
||||
$parts = explode('/', $cacheLog->getAttribute('resourceType', ''));
|
||||
$type = $parts[0];
|
||||
|
||||
@@ -651,7 +634,7 @@ Http::init()
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isToken = ! $resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
|
||||
$isPrivilegedUser = $user->isPrivileged($authorization->getRoles());
|
||||
$isPrivilegedUser = $user->isPrivileged($roles);
|
||||
|
||||
if ($bucket->isEmpty() || (! $bucket->getAttribute('enabled') && ! $isAppUser && ! $isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
@@ -695,6 +678,21 @@ Http::init()
|
||||
])));
|
||||
}
|
||||
}
|
||||
|
||||
if ($isImageTransformation) {
|
||||
$cacheControl = $cacheControlForStorage(new StorageCacheControl(
|
||||
source: CacheControl::SOURCE_CACHE,
|
||||
user: $user,
|
||||
maxAge: $timestamp,
|
||||
project: $project,
|
||||
bucket: $bucket,
|
||||
file: $file,
|
||||
resourceToken: $resourceToken,
|
||||
fileSecurity: $fileSecurity,
|
||||
cacheLog: $cacheLog,
|
||||
route: $route,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
|
||||
@@ -707,7 +705,7 @@ Http::init()
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', sprintf('private, max-age=%d', $timestamp))
|
||||
->addHeader('Cache-Control', $cacheControl)
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->setContentType($cacheLog->getAttribute('mimeType'));
|
||||
$storageCacheOperationsCounter->add(1, ['result' => 'hit']);
|
||||
@@ -749,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) {
|
||||
@@ -788,7 +785,7 @@ Http::shutdown()
|
||||
|
||||
Http::shutdown()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('route')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
@@ -798,11 +795,7 @@ Http::shutdown()
|
||||
->inject('publisherForAudits')
|
||||
->inject('usage')
|
||||
->inject('publisherForUsage')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForBuilds')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForFunctions')
|
||||
->inject('publisherForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForRealtime')
|
||||
->inject('dbForProject')
|
||||
@@ -812,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, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor, Bus $bus, ?Key $apiKey, string $mode) use ($parseLabel) {
|
||||
->action(function (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();
|
||||
|
||||
@@ -841,9 +834,15 @@ Http::shutdown()
|
||||
if (! empty($functionsEvents)) {
|
||||
foreach ($generatedEvents as $event) {
|
||||
if (isset($functionsEvents[$event])) {
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
$publisherForFunctions->enqueue(FunctionMessage::fromEvent(
|
||||
event: $queueForEvents->getEvent(),
|
||||
params: $queueForEvents->getParams(),
|
||||
project: $queueForEvents->getProject(),
|
||||
user: $queueForEvents->getUser(),
|
||||
userId: $queueForEvents->getUserId(),
|
||||
payload: $queueForEvents->getPayload(),
|
||||
platform: $queueForEvents->getPlatform(),
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -862,7 +861,6 @@ Http::shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
$requestParams = $route->getParamsValues();
|
||||
|
||||
/**
|
||||
@@ -915,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()) {
|
||||
@@ -930,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',
|
||||
@@ -953,22 +951,6 @@ Http::shutdown()
|
||||
$publisherForAudits->enqueue(AuditMessage::fromContext($auditContext));
|
||||
}
|
||||
|
||||
if (! empty($queueForDeletes->getType())) {
|
||||
$queueForDeletes->trigger();
|
||||
}
|
||||
|
||||
if (! empty($queueForDatabase->getType())) {
|
||||
$queueForDatabase->trigger();
|
||||
}
|
||||
|
||||
if (! empty($queueForBuilds->getType())) {
|
||||
$queueForBuilds->trigger();
|
||||
}
|
||||
|
||||
if (! empty($queueForMessaging->getType())) {
|
||||
$queueForMessaging->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;
|
||||
|
||||
+33
-41
@@ -3,7 +3,7 @@
|
||||
require_once __DIR__ . '/init.php';
|
||||
require_once __DIR__ . '/init/span.php';
|
||||
|
||||
$registerRequestResources = require __DIR__ . '/init/resources/request.php';
|
||||
$setRequestContext = require __DIR__ . '/init/resources/request.php';
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
@@ -26,6 +26,7 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\Http\Adapter\Swoole\Server;
|
||||
use Utopia\Http\Files;
|
||||
use Utopia\Http\Http;
|
||||
@@ -57,7 +58,7 @@ $container->set('pools', function ($register) {
|
||||
$payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
|
||||
$totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
|
||||
$swooleAdapter = new Server(
|
||||
$swoole = new Server(
|
||||
host: "0.0.0.0",
|
||||
port: System::getEnv('PORT', 80),
|
||||
settings: [
|
||||
@@ -69,10 +70,10 @@ $swooleAdapter = new Server(
|
||||
Constant::OPTION_OUTPUT_BUFFER_SIZE => $payloadSize,
|
||||
Constant::OPTION_TASK_WORKER_NUM => 1, // required for the task to fetch domains background
|
||||
],
|
||||
container: $container,
|
||||
resources: $container,
|
||||
);
|
||||
|
||||
$http = $swooleAdapter->getServer();
|
||||
$http = $swoole->getServer();
|
||||
|
||||
/**
|
||||
* Assigns HTTP requests to worker threads by analyzing its payload/content.
|
||||
@@ -190,13 +191,11 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server) {
|
||||
Console::success('Reload completed...');
|
||||
});
|
||||
|
||||
$container->set('bus', function ($register) use ($swooleAdapter) {
|
||||
return $register->get('bus')->setResolver(fn (string $name) => $swooleAdapter->getContainer()->get($name));
|
||||
}, ['register']);
|
||||
$container->set('bus', fn ($register) => $register->get('bus')->setResolver(fn (string $name) => $swoole->context()->get($name)), ['register']);
|
||||
|
||||
include __DIR__ . '/controllers/general.php';
|
||||
|
||||
function createDatabase(Http $app, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void
|
||||
function createDatabase(Container $resources, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void
|
||||
{
|
||||
$max = 15;
|
||||
$sleep = 2;
|
||||
@@ -205,7 +204,7 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
|
||||
while (true) {
|
||||
try {
|
||||
$attempts++;
|
||||
$resource = $app->getResource($resourceKey);
|
||||
$resource = $resources->get($resourceKey);
|
||||
/* @var $database Database */
|
||||
$database = is_callable($resource) ? $resource() : $resource;
|
||||
break; // exit loop on success
|
||||
@@ -288,23 +287,21 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
|
||||
Span::current()?->finish();
|
||||
}
|
||||
|
||||
$http->on(Constant::EVENT_START, function ($http) use ($payloadSize, $totalWorkers, $swooleAdapter) {
|
||||
$app = new Http($swooleAdapter, 'UTC');
|
||||
|
||||
$http->on(Constant::EVENT_START, function ($http) use ($payloadSize, $totalWorkers, $container) {
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
$pools = $app->getResource('pools');
|
||||
$pools = $container->get('pools');
|
||||
|
||||
go(function () use ($app, $pools) {
|
||||
go(function () use ($container, $pools) {
|
||||
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
|
||||
// create logs database first, `getLogsDB` is a callable.
|
||||
createDatabase($app, 'getLogsDB', 'logs', $collections['logs'], $pools);
|
||||
createDatabase($container, 'getLogsDB', 'logs', $collections['logs'], $pools);
|
||||
|
||||
// create appwrite database, `dbForPlatform` is a direct access call.
|
||||
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $app) {
|
||||
$authorization = $app->getResource('authorization');
|
||||
createDatabase($container, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $container) {
|
||||
$authorization = $container->get('authorization');
|
||||
|
||||
if ($dbForPlatform->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
|
||||
$adapter = new AdapterDatabase($dbForPlatform);
|
||||
@@ -416,7 +413,7 @@ $http->on(Constant::EVENT_START, function ($http) use ($payloadSize, $totalWorke
|
||||
$documentsSharedTables = \explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES', ''));
|
||||
$vectorSharedTables = \explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES', ''));
|
||||
|
||||
$cache = $app->getResource('cache');
|
||||
$cache = $container->get('cache');
|
||||
|
||||
// All shared tables pools that need project metadata collections
|
||||
$allSharedTables = \array_values(\array_unique(\array_filter([
|
||||
@@ -502,7 +499,7 @@ $http->on(Constant::EVENT_START, function ($http) use ($payloadSize, $totalWorke
|
||||
});
|
||||
});
|
||||
|
||||
$swooleAdapter->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files, $swooleAdapter, $registerRequestResources) {
|
||||
$swoole->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files, $swoole, $setRequestContext) {
|
||||
Span::init('http.request');
|
||||
|
||||
$request = new Request($utopiaRequest->getSwooleRequest());
|
||||
@@ -522,21 +519,18 @@ $swooleAdapter->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files
|
||||
return;
|
||||
}
|
||||
|
||||
$requestContainer = $swooleAdapter->getContainer();
|
||||
$requestContainer->set('container', fn () => $requestContainer);
|
||||
$requestContainer->set('request', fn () => $request);
|
||||
$requestContainer->set('response', fn () => $response);
|
||||
$app = new Http($swoole, 'UTC');
|
||||
$app->context()->set('request', fn () => $request);
|
||||
$app->context()->set('response', fn () => $response);
|
||||
$app->context()->set('utopia', fn () => $app);
|
||||
|
||||
$app = new Http($swooleAdapter, 'UTC');
|
||||
$requestContainer->set('utopia', fn () => $app);
|
||||
|
||||
$registerRequestResources($requestContainer);
|
||||
$setRequestContext($app->context());
|
||||
|
||||
$app->setCompression(System::getEnv('_APP_COMPRESSION_ENABLED', 'enabled') === 'enabled');
|
||||
$app->setCompressionMinSize(intval(System::getEnv('_APP_COMPRESSION_MIN_SIZE_BYTES', '1024'))); // 1KB
|
||||
|
||||
try {
|
||||
$authorization = $app->getResource('authorization');
|
||||
$authorization = $app->context()->get('authorization');
|
||||
|
||||
$request->setAuthorization($authorization);
|
||||
$response->setAuthorization($authorization);
|
||||
@@ -545,25 +539,25 @@ $swooleAdapter->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files
|
||||
|
||||
$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);
|
||||
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$logger = $app->getResource("logger");
|
||||
$logger = $app->context()->get("logger");
|
||||
if ($logger) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $app->getResource('user');
|
||||
$user = $app->context()->get('user');
|
||||
} catch (\Throwable $_th) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
$route = $app->getRoute();
|
||||
$route = $app->match($request)?->route;
|
||||
|
||||
$log = $app->getResource("log");
|
||||
$log = $app->context()->get("log");
|
||||
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
@@ -642,18 +636,16 @@ $swooleAdapter->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files
|
||||
});
|
||||
|
||||
// Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory
|
||||
$http->on(Constant::EVENT_TASK, function () use ($swooleAdapter) {
|
||||
$http->on(Constant::EVENT_TASK, function () use ($container) {
|
||||
$lastSyncUpdate = null;
|
||||
|
||||
$app = new Http($swooleAdapter, 'UTC');
|
||||
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
$dbForPlatform = $app->getResource('dbForPlatform');
|
||||
$dbForPlatform = $container->get('dbForPlatform');
|
||||
|
||||
/** @var \Swoole\Table $riskyDomains */
|
||||
$riskyDomains = $app->getResource('riskyDomains');
|
||||
$riskyDomains = $container->get('riskyDomains');
|
||||
|
||||
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $riskyDomains, &$lastSyncUpdate, $app) {
|
||||
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $riskyDomains, &$lastSyncUpdate, $container) {
|
||||
try {
|
||||
$time = DateTime::now();
|
||||
$limit = 1000;
|
||||
@@ -670,7 +662,7 @@ $http->on(Constant::EVENT_TASK, function () use ($swooleAdapter) {
|
||||
}
|
||||
$results = [];
|
||||
try {
|
||||
$authorization = $app->getResource('authorization');
|
||||
$authorization = $container->get('authorization');
|
||||
$results = $authorization->skip(fn () => $dbForPlatform->find('rules', $queries));
|
||||
} catch (Throwable $th) {
|
||||
Console::error('rules ' . $th->getMessage());
|
||||
@@ -720,4 +712,4 @@ $http->on(Constant::EVENT_TASK, function () use ($swooleAdapter) {
|
||||
});
|
||||
});
|
||||
|
||||
$swooleAdapter->start();
|
||||
$swoole->start();
|
||||
|
||||
+72
-10
@@ -1,5 +1,11 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Platform\Modules\Advisor\Enums\InsightCTAMethod;
|
||||
use Appwrite\Platform\Modules\Advisor\Enums\InsightCTAService;
|
||||
use Appwrite\Platform\Modules\Advisor\Enums\InsightSeverity;
|
||||
use Appwrite\Platform\Modules\Advisor\Enums\InsightStatus;
|
||||
use Appwrite\Platform\Modules\Advisor\Enums\InsightType;
|
||||
use Appwrite\Platform\Modules\Advisor\Enums\ReportType;
|
||||
use Appwrite\Platform\Modules\Compute\Specification;
|
||||
use Utopia\System\System;
|
||||
|
||||
@@ -44,14 +50,15 @@ const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_RESOURCE_TOKEN_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 4324;
|
||||
const APP_VERSION_STABLE = '1.9.3';
|
||||
const APP_CACHE_BUSTER = 4326;
|
||||
const APP_VERSION_STABLE = '1.9.5';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
const APP_DATABASE_ATTRIBUTE_DATETIME = 'datetime';
|
||||
const APP_DATABASE_ATTRIBUTE_URL = 'url';
|
||||
const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange';
|
||||
const APP_DATABASE_ATTRIBUTE_BIGINT_RANGE = 'bigintRange';
|
||||
const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange';
|
||||
const APP_DATABASE_ATTRIBUTE_POINT = 'point';
|
||||
const APP_DATABASE_ATTRIBUTE_LINE = 'line';
|
||||
@@ -152,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
|
||||
@@ -193,7 +200,7 @@ const BUILD_TYPE_RETRY = 'retry';
|
||||
const DELETE_TYPE_DATABASES = 'databases';
|
||||
const DELETE_TYPE_DOCUMENT = 'document';
|
||||
const DELETE_TYPE_COLLECTIONS = 'collections';
|
||||
const DELETE_TYPE_TRANSACTION = 'transaction';
|
||||
const DELETE_TYPE_TRANSACTIONS = 'transactions';
|
||||
const DELETE_TYPE_EXPIRED_TRANSACTIONS = 'expired_transactions';
|
||||
const DELETE_TYPE_PROJECTS = 'projects';
|
||||
const DELETE_TYPE_SITES = 'sites';
|
||||
@@ -221,6 +228,7 @@ const DELETE_TYPE_EXPIRED_TARGETS = 'invalid_targets';
|
||||
const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
|
||||
const DELETE_TYPE_CSV_EXPORTS = 'csv_exports';
|
||||
const DELETE_TYPE_MAINTENANCE = 'maintenance';
|
||||
const DELETE_TYPE_REPORT = 'report';
|
||||
|
||||
// Rule statuses
|
||||
const RULE_STATUS_CREATED = 'created'; // This is also the status when domain DNS verification fails.
|
||||
@@ -386,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';
|
||||
@@ -423,6 +432,55 @@ const RESOURCE_TYPE_MESSAGES = 'messages';
|
||||
const RESOURCE_TYPE_EXECUTIONS = 'executions';
|
||||
const RESOURCE_TYPE_VCS = 'vcs';
|
||||
const RESOURCE_TYPE_EMBEDDINGS_TEXT = 'embeddingsText';
|
||||
const RESOURCE_TYPE_INSIGHTS = 'insights';
|
||||
const RESOURCE_TYPE_REPORTS = 'reports';
|
||||
|
||||
// Insight types — engine-specific so the CTA action can reference the right public API.
|
||||
const ADVISOR_INSIGHT_TYPES = [
|
||||
InsightType::DATABASE_INDEX->value, // legacy databases.createIndex
|
||||
InsightType::TABLES_DB_INDEX->value, // tablesDB.createIndex
|
||||
InsightType::DOCUMENTS_DB_INDEX->value, // documentsDB.createIndex
|
||||
InsightType::VECTORS_DB_INDEX->value, // vectorsDB.createIndex
|
||||
InsightType::DATABASE_PERFORMANCE->value,
|
||||
InsightType::SITE_PERFORMANCE->value,
|
||||
InsightType::SITE_ACCESSIBILITY->value,
|
||||
InsightType::SITE_SEO->value,
|
||||
InsightType::FUNCTION_PERFORMANCE->value,
|
||||
];
|
||||
|
||||
// Public API services (SDK namespaces) that an insight CTA's `service` can reference.
|
||||
// Analyzers must pick the one matching the engine the resource lives in.
|
||||
const ADVISOR_CTA_SERVICES = [
|
||||
InsightCTAService::DATABASES->value, // legacy
|
||||
InsightCTAService::TABLES_DB->value,
|
||||
InsightCTAService::DOCUMENTS_DB->value,
|
||||
InsightCTAService::VECTORS_DB->value,
|
||||
];
|
||||
|
||||
// Public API method names that an insight CTA's `method` can reference for index suggestions.
|
||||
const ADVISOR_CTA_METHODS = [
|
||||
InsightCTAMethod::CREATE_INDEX->value,
|
||||
];
|
||||
|
||||
// Insight severities
|
||||
const ADVISOR_SEVERITIES = [
|
||||
InsightSeverity::INFO->value,
|
||||
InsightSeverity::WARNING->value,
|
||||
InsightSeverity::CRITICAL->value,
|
||||
];
|
||||
|
||||
// Insight statuses
|
||||
const ADVISOR_STATUSES = [
|
||||
InsightStatus::ACTIVE->value,
|
||||
InsightStatus::DISMISSED->value,
|
||||
];
|
||||
|
||||
// Report types
|
||||
const ADVISOR_REPORT_TYPES = [
|
||||
ReportType::LIGHTHOUSE->value,
|
||||
ReportType::AUDIT->value,
|
||||
ReportType::DATABASE_ANALYZER->value,
|
||||
];
|
||||
|
||||
// Resource types for Tokens
|
||||
const TOKENS_RESOURCE_TYPE_FILES = 'files';
|
||||
@@ -457,3 +515,7 @@ const CSV_ALLOWED_DATABASE_TYPES = [
|
||||
DATABASE_TYPE_TABLESDB,
|
||||
DATABASE_TYPE_VECTORSDB
|
||||
];
|
||||
|
||||
const VCS_DEPLOYMENT_SKIP_PATTERNS = [
|
||||
'[skip ci]',
|
||||
];
|
||||
|
||||
@@ -475,3 +475,17 @@ Database::addFilter(
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryReportInsights',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database->getAuthorization()->skip(fn () => $database->find('insights', [
|
||||
Query::equal('projectInternalId', [$document->getAttribute('projectInternalId')]),
|
||||
Query::equal('reportInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -36,6 +36,13 @@ Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) {
|
||||
return new Range($min, $max, Range::TYPE_INTEGER);
|
||||
}, Database::VAR_INTEGER);
|
||||
|
||||
// BigInt uses a dedicated bigintRange format name to avoid clobbering `intRange`.
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_BIGINT_RANGE, function ($attribute) {
|
||||
$min = $attribute['formatOptions']['min'] ?? -INF;
|
||||
$max = $attribute['formatOptions']['max'] ?? INF;
|
||||
return new Range($min, $max, Range::TYPE_INTEGER);
|
||||
}, Database::VAR_BIGINT);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) {
|
||||
$min = $attribute['formatOptions']['min'] ?? -INF;
|
||||
$max = $attribute['formatOptions']['max'] ?? INF;
|
||||
|
||||
+24
-1
@@ -11,6 +11,7 @@ use Appwrite\Utopia\Response\Model\AlgoScryptModified;
|
||||
use Appwrite\Utopia\Response\Model\AlgoSha;
|
||||
use Appwrite\Utopia\Response\Model\Any;
|
||||
use Appwrite\Utopia\Response\Model\Attribute;
|
||||
use Appwrite\Utopia\Response\Model\AttributeBigInt;
|
||||
use Appwrite\Utopia\Response\Model\AttributeBoolean;
|
||||
use Appwrite\Utopia\Response\Model\AttributeDatetime;
|
||||
use Appwrite\Utopia\Response\Model\AttributeEmail;
|
||||
@@ -37,6 +38,7 @@ use Appwrite\Utopia\Response\Model\Branch;
|
||||
use Appwrite\Utopia\Response\Model\Bucket;
|
||||
use Appwrite\Utopia\Response\Model\Collection;
|
||||
use Appwrite\Utopia\Response\Model\Column;
|
||||
use Appwrite\Utopia\Response\Model\ColumnBigInt;
|
||||
use Appwrite\Utopia\Response\Model\ColumnBoolean;
|
||||
use Appwrite\Utopia\Response\Model\ColumnDatetime;
|
||||
use Appwrite\Utopia\Response\Model\ColumnEmail;
|
||||
@@ -90,6 +92,8 @@ use Appwrite\Utopia\Response\Model\HealthTime;
|
||||
use Appwrite\Utopia\Response\Model\HealthVersion;
|
||||
use Appwrite\Utopia\Response\Model\Identity;
|
||||
use Appwrite\Utopia\Response\Model\Index;
|
||||
use Appwrite\Utopia\Response\Model\Insight;
|
||||
use Appwrite\Utopia\Response\Model\InsightCTA;
|
||||
use Appwrite\Utopia\Response\Model\Installation;
|
||||
use Appwrite\Utopia\Response\Model\JWT;
|
||||
use Appwrite\Utopia\Response\Model\Key;
|
||||
@@ -170,13 +174,18 @@ 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;
|
||||
use Appwrite\Utopia\Response\Model\ProjectService;
|
||||
use Appwrite\Utopia\Response\Model\Provider;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepository;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepositoryFramework;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepositoryFrameworkList;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepositoryRuntime;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepositoryRuntimeList;
|
||||
use Appwrite\Utopia\Response\Model\Report;
|
||||
use Appwrite\Utopia\Response\Model\ResourceToken;
|
||||
use Appwrite\Utopia\Response\Model\Row;
|
||||
use Appwrite\Utopia\Response\Model\Rule;
|
||||
@@ -206,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;
|
||||
@@ -229,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));
|
||||
@@ -255,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));
|
||||
@@ -286,6 +297,8 @@ Response::setModel(new BaseList('Specifications List', Response::MODEL_SPECIFICA
|
||||
Response::setModel(new BaseList('VCS Content List', Response::MODEL_VCS_CONTENT_LIST, 'contents', Response::MODEL_VCS_CONTENT));
|
||||
Response::setModel(new BaseList('VectorsDB Collections List', Response::MODEL_VECTORSDB_COLLECTION_LIST, 'collections', Response::MODEL_VECTORSDB_COLLECTION));
|
||||
Response::setModel(new BaseList('Embedding list', Response::MODEL_EMBEDDING_LIST, 'embeddings', Response::MODEL_EMBEDDING));
|
||||
Response::setModel(new BaseList('Insights List', Response::MODEL_INSIGHT_LIST, 'insights', Response::MODEL_INSIGHT));
|
||||
Response::setModel(new BaseList('Reports List', Response::MODEL_REPORT_LIST, 'reports', Response::MODEL_REPORT));
|
||||
|
||||
// Entities
|
||||
Response::setModel(new Database());
|
||||
@@ -297,6 +310,7 @@ Response::setModel(new Attribute());
|
||||
Response::setModel(new AttributeList());
|
||||
Response::setModel(new AttributeString());
|
||||
Response::setModel(new AttributeInteger());
|
||||
Response::setModel(new AttributeBigInt());
|
||||
Response::setModel(new AttributeFloat());
|
||||
Response::setModel(new AttributeBoolean());
|
||||
Response::setModel(new AttributeEmail());
|
||||
@@ -330,6 +344,7 @@ Response::setModel(new Column());
|
||||
Response::setModel(new ColumnList());
|
||||
Response::setModel(new ColumnString());
|
||||
Response::setModel(new ColumnInteger());
|
||||
Response::setModel(new ColumnBigInt());
|
||||
Response::setModel(new ColumnFloat());
|
||||
Response::setModel(new ColumnBoolean());
|
||||
Response::setModel(new ColumnEmail());
|
||||
@@ -349,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());
|
||||
@@ -393,6 +409,9 @@ Response::setModel(new FrameworkAdapter());
|
||||
Response::setModel(new Deployment());
|
||||
Response::setModel(new Execution());
|
||||
Response::setModel(new Project());
|
||||
Response::setModel(new ProjectAuthMethod());
|
||||
Response::setModel(new ProjectService());
|
||||
Response::setModel(new ProjectProtocol());
|
||||
Response::setModel(new Webhook());
|
||||
Response::setModel(new Key());
|
||||
Response::setModel(new EphemeralKey());
|
||||
@@ -474,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());
|
||||
@@ -505,6 +525,9 @@ Response::setModel(new Target());
|
||||
Response::setModel(new Migration());
|
||||
Response::setModel(new MigrationReport());
|
||||
Response::setModel(new MigrationFirebaseProject());
|
||||
Response::setModel(new Insight());
|
||||
Response::setModel(new InsightCTA());
|
||||
Response::setModel(new Report());
|
||||
|
||||
// Tests (keep last)
|
||||
Response::setModel(new Mock());
|
||||
|
||||
+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.");
|
||||
}
|
||||
|
||||
+109
-61
@@ -2,12 +2,19 @@
|
||||
|
||||
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;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
|
||||
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
|
||||
use Appwrite\Event\Publisher\Screenshot as ScreenshotPublisher;
|
||||
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Platform\Modules\Storage\Config\StorageCacheControl;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Executor\Executor;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
@@ -22,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;
|
||||
@@ -45,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']);
|
||||
@@ -59,59 +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('publisherForMigrations', fn (Publisher $publisher) => new MigrationPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForStatsResources', fn (Publisher $publisher) => new StatsResourcesPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
|
||||
/**
|
||||
* Platform configuration
|
||||
@@ -120,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();
|
||||
}, []);
|
||||
@@ -182,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 = [];
|
||||
@@ -198,6 +234,10 @@ $container->set('cache', function (Group $pools, Telemetry $telemetry) {
|
||||
return $cache;
|
||||
}, ['pools', 'telemetry']);
|
||||
|
||||
$container->set('cacheControlForStorage', fn () => function (StorageCacheControl $config): string {
|
||||
return \sprintf('private, max-age=%d', $config->maxAge);
|
||||
});
|
||||
|
||||
$container->set('redis', function () {
|
||||
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
|
||||
$port = System::getEnv('_APP_REDIS_PORT', 6379);
|
||||
@@ -213,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);
|
||||
@@ -370,5 +420,3 @@ $container->set(
|
||||
'isResourceBlocked',
|
||||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
|
||||
);
|
||||
|
||||
$container->set('executor', fn () => new Executor());
|
||||
|
||||
+93
-135
@@ -4,14 +4,10 @@ use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Databases\TransactionState;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Context\Audit as AuditContext;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Message\Func as FunctionMessage;
|
||||
use Appwrite\Event\Publisher\Func as FunctionPublisher;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception;
|
||||
@@ -51,6 +47,7 @@ use Utopia\Locale\Locale;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
@@ -62,26 +59,18 @@ use Utopia\Validator\WhiteList;
|
||||
* These resources depend (directly or transitively) on request/response
|
||||
* and must be fresh for each HTTP request.
|
||||
*/
|
||||
return function (Container $container): void {
|
||||
$container->set('utopia:graphql', function ($utopia) {
|
||||
return $utopia;
|
||||
}, ['utopia']);
|
||||
return function (Container $context): void {
|
||||
$context->set('utopia:graphql', fn ($utopia) => $utopia, ['utopia']);
|
||||
|
||||
$container->set('log', fn () => new Log(), []);
|
||||
$context->set('log', fn () => new Log(), []);
|
||||
|
||||
$container->set('logger', function ($register) {
|
||||
return $register->get('logger');
|
||||
}, ['register']);
|
||||
$context->set('logger', fn ($register) => $register->get('logger'), ['register']);
|
||||
|
||||
$container->set('authorization', function () {
|
||||
return new Authorization();
|
||||
}, []);
|
||||
$context->set('authorization', fn () => new Authorization(), []);
|
||||
|
||||
$container->set('store', function (): Store {
|
||||
return new Store();
|
||||
}, []);
|
||||
$context->set('store', fn (): Store => new Store(), []);
|
||||
|
||||
$container->set('proofForPassword', function (): Password {
|
||||
$context->set('proofForPassword', function (): Password {
|
||||
$hash = new Argon2();
|
||||
$hash
|
||||
->setMemoryCost(7168)
|
||||
@@ -95,21 +84,21 @@ return function (Container $container): void {
|
||||
return $password;
|
||||
});
|
||||
|
||||
$container->set('proofForToken', function (): Token {
|
||||
$context->set('proofForToken', function (): Token {
|
||||
$token = new Token();
|
||||
$token->setHash(new Sha());
|
||||
|
||||
return $token;
|
||||
});
|
||||
|
||||
$container->set('proofForCode', function (): Code {
|
||||
$context->set('proofForCode', function (): Code {
|
||||
$code = new Code();
|
||||
$code->setHash(new Sha());
|
||||
|
||||
return $code;
|
||||
});
|
||||
|
||||
$container->set('locale', function () {
|
||||
$context->set('locale', function () {
|
||||
$locale = new Locale(System::getEnv('_APP_LOCALE', 'en'));
|
||||
$locale->setFallback(System::getEnv('_APP_LOCALE', 'en'));
|
||||
|
||||
@@ -117,41 +106,17 @@ return function (Container $container): void {
|
||||
});
|
||||
|
||||
// Per-request queue resources (stateful, accumulate event data during request)
|
||||
$container->set('queueForMessaging', function (Publisher $publisher) {
|
||||
return new Messaging($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForMails', function (Publisher $publisher) {
|
||||
return new Mail($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForBuilds', function (Publisher $publisher) {
|
||||
return new Build($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForDatabase', function (Publisher $publisher) {
|
||||
return new EventDatabase($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForDeletes', function (Publisher $publisher) {
|
||||
return new Delete($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForEvents', function (Publisher $publisher) {
|
||||
return new Event($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForWebhooks', function (Publisher $publisher) {
|
||||
return new Webhook($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForRealtime', function () {
|
||||
return new Realtime();
|
||||
}, []);
|
||||
$container->set('usage', function () {
|
||||
return new UsageContext();
|
||||
}, []);
|
||||
$container->set('auditContext', fn () => new AuditContext(), []);
|
||||
$container->set('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('eventProcessor', function () {
|
||||
return new EventProcessor();
|
||||
}, []);
|
||||
$container->set('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$context->set('queueForEvents', fn (Publisher $publisher) => new Event($publisher), ['publisher']);
|
||||
$context->set('queueForWebhooks', fn (Publisher $publisher) => new Webhook($publisher), ['publisher']);
|
||||
$context->set('queueForRealtime', fn () => new Realtime(), []);
|
||||
$context->set('usage', fn () => new UsageContext(), []);
|
||||
$context->set('auditContext', fn () => new AuditContext(), []);
|
||||
$context->set('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']);
|
||||
$context->set('eventProcessor', fn () => new EventProcessor(), []);
|
||||
$context->set('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$adapter = new DatabasePool($pools->get('console'));
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
@@ -169,7 +134,7 @@ return function (Container $container): void {
|
||||
return $database;
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
$container->set('getProjectDB', function (Group $pools, Database $dbForPlatform, Cache $cache, Authorization $authorization) {
|
||||
$context->set('getProjectDB', function (Group $pools, Database $dbForPlatform, Cache $cache, Authorization $authorization) {
|
||||
$adapters = [];
|
||||
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$adapters) {
|
||||
@@ -226,7 +191,7 @@ return function (Container $container): void {
|
||||
};
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
$container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$context->set('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$adapter = null;
|
||||
|
||||
return function (?Document $project = null) use ($pools, $cache, $authorization, &$adapter) {
|
||||
@@ -258,7 +223,7 @@ return function (Container $container): void {
|
||||
/**
|
||||
* List of allowed request hostnames for the request.
|
||||
*/
|
||||
$container->set('allowedHostnames', function (array $platform, Document $project, Document $rule, Document $devKey, Request $request) {
|
||||
$context->set('allowedHostnames', function (array $platform, Document $project, Document $rule, Document $devKey, Request $request) {
|
||||
$allowed = [...($platform['hostnames'] ?? [])];
|
||||
|
||||
/* Add platform configured hostnames */
|
||||
@@ -302,7 +267,7 @@ return function (Container $container): void {
|
||||
/**
|
||||
* List of allowed request schemes for the request.
|
||||
*/
|
||||
$container->set('allowedSchemes', function (array $platform, Document $project) {
|
||||
$context->set('allowedSchemes', function (array $platform, Document $project) {
|
||||
$allowed = [...($platform['schemas'] ?? [])];
|
||||
|
||||
if (! $project->isEmpty() && $project->getId() !== 'console') {
|
||||
@@ -322,7 +287,7 @@ return function (Container $container): void {
|
||||
/**
|
||||
* Whether the request origin is verified against the request hostname.
|
||||
*/
|
||||
$container->set('domainVerification', function (Request $request) {
|
||||
$context->set('domainVerification', function (Request $request) {
|
||||
$origin = \parse_url($request->getOrigin($request->getReferer('')), PHP_URL_HOST);
|
||||
$selfDomain = new Domain($request->getHostname());
|
||||
$endDomain = new Domain((string) $origin);
|
||||
@@ -334,7 +299,7 @@ return function (Container $container): void {
|
||||
/**
|
||||
* Cookie domain for the current request.
|
||||
*/
|
||||
$container->set('cookieDomain', function (Request $request, Document $project) {
|
||||
$context->set('cookieDomain', function (Request $request, Document $project) {
|
||||
$localHosts = ['localhost', 'localhost:' . $request->getPort()];
|
||||
|
||||
$migrationHost = System::getEnv('_APP_MIGRATION_HOST');
|
||||
@@ -368,7 +333,7 @@ return function (Container $container): void {
|
||||
/**
|
||||
* Rule associated with a request origin.
|
||||
*/
|
||||
$container->set('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) {
|
||||
$context->set('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) {
|
||||
$domain = \parse_url($request->getOrigin(), PHP_URL_HOST);
|
||||
|
||||
if (empty($domain)) {
|
||||
@@ -418,7 +383,7 @@ return function (Container $container): void {
|
||||
/**
|
||||
* CORS service
|
||||
*/
|
||||
$container->set('cors', function (array $allowedHostnames) {
|
||||
$context->set('cors', function (array $allowedHostnames) {
|
||||
$corsConfig = Config::getParam('cors');
|
||||
|
||||
return new Cors(
|
||||
@@ -430,23 +395,23 @@ return function (Container $container): void {
|
||||
);
|
||||
}, ['allowedHostnames']);
|
||||
|
||||
$container->set('originValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) {
|
||||
if (! $devKey->isEmpty()) {
|
||||
return new URL();
|
||||
}
|
||||
$context->set(
|
||||
'originValidator',
|
||||
fn (Document $devKey, array $allowedHostnames, array $allowedSchemes) => $devKey->isEmpty()
|
||||
? new Origin($allowedHostnames, $allowedSchemes)
|
||||
: new URL(),
|
||||
['devKey', 'allowedHostnames', 'allowedSchemes']
|
||||
);
|
||||
|
||||
return new Origin($allowedHostnames, $allowedSchemes);
|
||||
}, ['devKey', 'allowedHostnames', 'allowedSchemes']);
|
||||
$context->set(
|
||||
'redirectValidator',
|
||||
fn (Document $devKey, array $allowedHostnames, array $allowedSchemes) => $devKey->isEmpty()
|
||||
? new Redirect($allowedHostnames, $allowedSchemes)
|
||||
: new URL(),
|
||||
['devKey', 'allowedHostnames', 'allowedSchemes']
|
||||
);
|
||||
|
||||
$container->set('redirectValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) {
|
||||
if (! $devKey->isEmpty()) {
|
||||
return new URL();
|
||||
}
|
||||
|
||||
return new Redirect($allowedHostnames, $allowedSchemes);
|
||||
}, ['devKey', 'allowedHostnames', 'allowedSchemes']);
|
||||
|
||||
$container->set('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken, $authorization) {
|
||||
$context->set('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken, $authorization) {
|
||||
/**
|
||||
* Handles user authentication and session validation.
|
||||
*
|
||||
@@ -617,7 +582,7 @@ return function (Container $container): void {
|
||||
return $user;
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']);
|
||||
|
||||
$container->set('project', function ($dbForPlatform, $request, $console, $authorization, Http $utopia) {
|
||||
$context->set('project', function ($dbForPlatform, $request, $console, $authorization, Http $utopia) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
/** @var Utopia\Database\Document $console */
|
||||
@@ -631,7 +596,7 @@ return function (Container $container): 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);
|
||||
@@ -650,7 +615,7 @@ return function (Container $container): void {
|
||||
return $project;
|
||||
}, ['dbForPlatform', 'request', 'console', 'authorization', 'utopia']);
|
||||
|
||||
$container->set('session', function (User $user, Store $store, Token $proofForToken) {
|
||||
$context->set('session', function (User $user, Store $store, Token $proofForToken) {
|
||||
if ($user->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -671,7 +636,7 @@ return function (Container $container): void {
|
||||
return;
|
||||
}, ['user', 'store', 'proofForToken']);
|
||||
|
||||
$container->set('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Response $response, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, UsageContext $usage, Authorization $authorization, Request $request) {
|
||||
$context->set('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Response $response, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, FunctionPublisher $publisherForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, UsageContext $usage, Authorization $authorization, Request $request) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -727,7 +692,7 @@ return function (Container $container): void {
|
||||
* Accounts can be created in many ways beyond `createAccount`
|
||||
* (anonymous, OAuth, phone, etc.), and those flows are probably not covered in event tests; so we handle this here.
|
||||
*/
|
||||
$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) {
|
||||
$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, FunctionPublisher $publisherForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) {
|
||||
// Only trigger events for user creation with the database listener.
|
||||
if ($document->getCollection() !== 'users') {
|
||||
return;
|
||||
@@ -739,9 +704,15 @@ return function (Container $container): void {
|
||||
->setPayload($response->output($document, Response::MODEL_USER));
|
||||
|
||||
// Trigger functions, webhooks, and realtime events
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
$publisherForFunctions->enqueue(FunctionMessage::fromEvent(
|
||||
event: $queueForEvents->getEvent(),
|
||||
params: $queueForEvents->getParams(),
|
||||
project: $queueForEvents->getProject(),
|
||||
user: $queueForEvents->getUser(),
|
||||
userId: $queueForEvents->getUserId(),
|
||||
payload: $queueForEvents->getPayload(),
|
||||
platform: $queueForEvents->getPlatform(),
|
||||
));
|
||||
|
||||
/** Trigger webhooks events only if a project has them enabled */
|
||||
if (! empty($project->getAttribute('webhooks'))) {
|
||||
@@ -921,7 +892,6 @@ return function (Container $container): void {
|
||||
// Clone the queues, to prevent events triggered by the database listener
|
||||
// from overwriting the events that are supposed to be triggered in the shutdown hook.
|
||||
$queueForEventsClone = new Event($publisher);
|
||||
$queueForFunctions = new Func($publisherFunctions);
|
||||
$queueForWebhooks = new Webhook($publisherWebhooks);
|
||||
$queueForRealtime = new Realtime();
|
||||
|
||||
@@ -936,7 +906,7 @@ return function (Container $container): void {
|
||||
$document,
|
||||
$response,
|
||||
$queueForEventsClone->from($queueForEvents),
|
||||
$queueForFunctions->from($queueForEvents),
|
||||
$publisherForFunctions,
|
||||
$queueForWebhooks->from($queueForEvents),
|
||||
$queueForRealtime->from($queueForEvents)
|
||||
))
|
||||
@@ -945,9 +915,9 @@ return function (Container $container): void {
|
||||
->on(Database::EVENT_DOCUMENT_DELETE, 'purge-function-events-cache', fn ($event, $document) => $functionsEventsCacheListener($event, $document, $project, $database));
|
||||
|
||||
return $database;
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'project', 'response', 'publisher', 'publisherFunctions', 'publisherWebhooks', 'queueForEvents', 'queueForFunctions', 'queueForWebhooks', 'queueForRealtime', 'usage', 'authorization', 'request']);
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'project', 'response', 'publisher', 'publisherFunctions', 'publisherWebhooks', 'queueForEvents', 'publisherForFunctions', 'queueForWebhooks', 'queueForRealtime', 'usage', 'authorization', 'request']);
|
||||
|
||||
$container->set('schema', function ($utopia, $dbForProject, $authorization) {
|
||||
$context->set('schema', function ($utopia, $dbForProject, $authorization) {
|
||||
|
||||
$complexity = function (int $complexity, array $args) {
|
||||
$queries = Query::parseQueries($args['queries'] ?? []);
|
||||
@@ -1034,13 +1004,9 @@ return function (Container $container): void {
|
||||
);
|
||||
}, ['utopia', 'dbForProject', 'authorization']);
|
||||
|
||||
$container->set('audit', function ($dbForProject) {
|
||||
$adapter = new AdapterDatabase($dbForProject);
|
||||
$context->set('audit', fn ($dbForProject) => new Audit(new AdapterDatabase($dbForProject)), ['dbForProject']);
|
||||
|
||||
return new Audit($adapter);
|
||||
}, ['dbForProject']);
|
||||
|
||||
$container->set('mode', function ($request, Document $project) {
|
||||
$context->set('mode', function ($request, Document $project) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
|
||||
/**
|
||||
@@ -1058,7 +1024,7 @@ return function (Container $container): void {
|
||||
return $mode;
|
||||
}, ['request', 'project']);
|
||||
|
||||
$container->set('requestTimestamp', function ($request) {
|
||||
$context->set('requestTimestamp', function ($request) {
|
||||
// TODO: Move this to the Request class itself
|
||||
$timestampHeader = $request->getHeader('x-appwrite-timestamp');
|
||||
$requestTimestamp = null;
|
||||
@@ -1073,7 +1039,7 @@ return function (Container $container): void {
|
||||
return $requestTimestamp;
|
||||
}, ['request']);
|
||||
|
||||
$container->set('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) {
|
||||
$context->set('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) {
|
||||
$devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', ''));
|
||||
|
||||
// Check if given key match project's development keys
|
||||
@@ -1122,12 +1088,12 @@ return function (Container $container): void {
|
||||
return $key;
|
||||
}, ['request', 'project', 'servers', 'dbForPlatform', 'authorization']);
|
||||
|
||||
$container->set('team', function (Document $project, Database $dbForPlatform, Http $utopia, Request $request, Authorization $authorization) {
|
||||
$context->set('team', function (Document $project, Database $dbForPlatform, Http $utopia, Request $request, Authorization $authorization) {
|
||||
$teamInternalId = '';
|
||||
if ($project->getId() !== 'console') {
|
||||
$teamInternalId = $project->getAttribute('teamInternalId', '');
|
||||
} 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')) {
|
||||
@@ -1165,7 +1131,7 @@ return function (Container $container): void {
|
||||
return $team;
|
||||
}, ['project', 'dbForPlatform', 'utopia', 'request', 'authorization']);
|
||||
|
||||
$container->set('previewHostname', function (Request $request, ?Key $apiKey) {
|
||||
$context->set('previewHostname', function (Request $request, ?Key $apiKey) {
|
||||
$allowed = false;
|
||||
|
||||
if (Http::isDevelopment()) {
|
||||
@@ -1184,7 +1150,7 @@ return function (Container $container): void {
|
||||
return '';
|
||||
}, ['request', 'apiKey']);
|
||||
|
||||
$container->set('apiKey', function (Request $request, Document $project, Document $team, Document $user): ?Key {
|
||||
$context->set('apiKey', function (Request $request, Document $project, Document $team, Document $user): ?Key {
|
||||
$key = $request->getHeader('x-appwrite-key');
|
||||
|
||||
if (empty($key)) {
|
||||
@@ -1218,7 +1184,7 @@ return function (Container $container): void {
|
||||
return $key;
|
||||
}, ['request', 'project', 'team', 'user']);
|
||||
|
||||
$container->set('resourceToken', function ($project, $dbForProject, $request, Authorization $authorization) {
|
||||
$context->set('resourceToken', function ($project, $dbForProject, $request, Authorization $authorization) {
|
||||
$tokenJWT = $request->getParam('token');
|
||||
|
||||
if (! empty($tokenJWT) && ! $project->isEmpty()) { // JWT authentication
|
||||
@@ -1285,10 +1251,10 @@ return function (Container $container): void {
|
||||
return new Document([]);
|
||||
}, ['project', 'dbForProject', 'request', 'authorization']);
|
||||
|
||||
$container->set('getDatabasesDB', function (Group $pools, Cache $cache, Document $project, Request $request, UsageContext $usage, Authorization $authorization) {
|
||||
$context->set('getDatabasesDB', function (Group $pools, Cache $cache, Document $project, Request $request, UsageContext $usage, Authorization $authorization) {
|
||||
|
||||
return function (Document $database) use ($pools, $cache, $project, $request, $usage, $authorization): Database {
|
||||
$databaseDSN = $database->getAttribute('database', $project->getAttribute('database', ''));
|
||||
$databaseDSN = $database->getAttribute('database') ?: $project->getAttribute('database', '');
|
||||
$databaseType = $database->getAttribute('type', '');
|
||||
|
||||
try {
|
||||
@@ -1447,35 +1413,27 @@ return function (Container $container): void {
|
||||
|
||||
}, ['pools', 'cache', 'project', 'request', 'usage', 'authorization']);
|
||||
|
||||
$container->set('transactionState', function (Database $dbForProject, Authorization $authorization, callable $getDatabasesDB) {
|
||||
return new TransactionState($dbForProject, $authorization, $getDatabasesDB);
|
||||
}, ['dbForProject', 'authorization', 'getDatabasesDB']);
|
||||
$context->set(
|
||||
'transactionState',
|
||||
fn (Database $dbForProject, Authorization $authorization, callable $getDatabasesDB) => new TransactionState($dbForProject, $authorization, $getDatabasesDB),
|
||||
['dbForProject', 'authorization', 'getDatabasesDB']
|
||||
);
|
||||
|
||||
$container->set('executionsRetentionCount', function (Document $project, array $plan) {
|
||||
if ($project->getId() === 'console' || empty($plan)) {
|
||||
return 0;
|
||||
}
|
||||
$context->set(
|
||||
'executionsRetentionCount',
|
||||
fn (Document $project, array $plan) => ($project->getId() === 'console' || empty($plan))
|
||||
? 0
|
||||
: (int) ($plan['executionsRetentionCount'] ?? 100),
|
||||
['project', 'plan']
|
||||
);
|
||||
|
||||
return (int) ($plan['executionsRetentionCount'] ?? 100);
|
||||
}, ['project', 'plan']);
|
||||
$context->set('deviceForFiles', fn ($project, Telemetry $telemetry) => new Device\Telemetry($telemetry, getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId())), ['project', 'telemetry']);
|
||||
$context->set('deviceForSites', fn ($project, Telemetry $telemetry) => new Device\Telemetry($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId())), ['project', 'telemetry']);
|
||||
$context->set('deviceForMigrations', fn ($project, Telemetry $telemetry) => new Device\Telemetry($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId())), ['project', 'telemetry']);
|
||||
$context->set('deviceForFunctions', fn ($project, Telemetry $telemetry) => new Device\Telemetry($telemetry, getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId())), ['project', 'telemetry']);
|
||||
$context->set('deviceForBuilds', fn ($project, Telemetry $telemetry) => new Device\Telemetry($telemetry, getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId())), ['project', 'telemetry']);
|
||||
|
||||
$container->set('deviceForFiles', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
$container->set('deviceForSites', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
$container->set('deviceForMigrations', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
$container->set('deviceForFunctions', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
$container->set('deviceForBuilds', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
$container->set('embeddingAgent', function ($register) {
|
||||
$context->set('embeddingAgent', function ($register) {
|
||||
$adapter = new Ollama();
|
||||
$adapter->setEndpoint(System::getEnv('_APP_EMBEDDING_ENDPOINT', 'http://ollama:11434/api/embed'));
|
||||
$adapter->setTimeout((int) System::getEnv('_APP_EMBEDDING_TIMEOUT', '30000'));
|
||||
|
||||
+20
-1
@@ -3,11 +3,30 @@
|
||||
use Utopia\Span\Exporter;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\Span\Storage;
|
||||
use Utopia\System\System;
|
||||
|
||||
Span::setStorage(new Storage\Coroutine());
|
||||
Span::addExporter(new Exporter\Pretty(), function (Span $span): bool {
|
||||
|
||||
// Resolve trace filters once at boot to avoid repeated env lookups per span.
|
||||
$traceProjectId = System::getEnv('_APP_TRACE_PROJECT_ID', '');
|
||||
$traceFunctionId = System::getEnv('_APP_TRACE_FUNCTION_ID', '');
|
||||
$traceEnabled = $traceProjectId !== '' || $traceFunctionId !== '';
|
||||
|
||||
Span::addExporter(new Exporter\Pretty(), function (Span $span) use ($traceEnabled, $traceProjectId, $traceFunctionId): bool {
|
||||
if (\str_starts_with($span->getAction(), 'listener.')) {
|
||||
return $span->getError() !== null;
|
||||
}
|
||||
|
||||
// Selective tracing: when _APP_TRACE_PROJECT_ID / _APP_TRACE_FUNCTION_ID are set,
|
||||
// only export spans tagged with matching project.id / function.id.
|
||||
if ($traceEnabled) {
|
||||
if ($traceProjectId !== '' && $span->get('project.id') !== $traceProjectId) {
|
||||
return false;
|
||||
}
|
||||
if ($traceFunctionId !== '' && $span->get('function.id') !== $traceFunctionId) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Usage\Context;
|
||||
@@ -330,26 +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('queueForMessaging', function (Publisher $publisher) {
|
||||
return new Messaging($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForMails', function (Publisher $publisher) {
|
||||
return new Mail($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForBuilds', function (Publisher $publisher) {
|
||||
return new Build($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForDeletes', function (Publisher $publisher) {
|
||||
return new Delete($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForEvents', function (Publisher $publisher) {
|
||||
return new Event($publisher);
|
||||
}, ['publisher']);
|
||||
@@ -358,10 +332,6 @@ return function (Container $container): void {
|
||||
return new Webhook($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForRealtime', function () {
|
||||
return new Realtime();
|
||||
}, []);
|
||||
|
||||
+320
-336
@@ -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)) {
|
||||
@@ -728,8 +831,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$success = false;
|
||||
|
||||
Span::init('realtime.open');
|
||||
Span::add('realtime.connectionId', $connection);
|
||||
Span::add('realtime.inboundBytes', $rawSize);
|
||||
Span::add('realtime.connection.id', $connection);
|
||||
Span::add('realtime.inbound_bytes', $rawSize);
|
||||
if (!empty($request->getOrigin())) {
|
||||
Span::add('realtime.origin', $request->getOrigin());
|
||||
}
|
||||
@@ -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,56 +1046,79 @@ $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.responseCode', $responseCode);
|
||||
Span::add('realtime.subscriptionMode', $subscriptionMode);
|
||||
Span::add('realtime.channelCount', $channelCount);
|
||||
Span::add('realtime.subscriptionCount', $subscriptionCount);
|
||||
Span::add('realtime.outboundBytes', $outboundBytes);
|
||||
Span::add('realtime.response_code', $responseCode);
|
||||
Span::add('realtime.subscription_mode', $subscriptionMode);
|
||||
Span::add('realtime.channel_count', $channelCount);
|
||||
Span::add('realtime.subscription_count', $subscriptionCount);
|
||||
Span::add('realtime.outbound_bytes', $outboundBytes);
|
||||
if (!empty($project?->getId())) {
|
||||
Span::add('realtime.projectId', $project->getId());
|
||||
Span::add('project.id', $project->getId());
|
||||
}
|
||||
if (!empty($logUser?->getId())) {
|
||||
Span::add('realtime.userId', $logUser->getId());
|
||||
Span::add('user.id', $logUser->getId());
|
||||
}
|
||||
Span::current()?->finish();
|
||||
}
|
||||
});
|
||||
|
||||
$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;
|
||||
|
||||
Span::init('realtime.message');
|
||||
Span::add('realtime.connectionId', $connection);
|
||||
Span::add('realtime.inboundBytes', $rawSize);
|
||||
Span::add('realtime.containerId', $containerId);
|
||||
Span::add('realtime.connection.id', $connection);
|
||||
Span::add('realtime.inbound_bytes', $rawSize);
|
||||
Span::add('realtime.container.id', $containerId);
|
||||
|
||||
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,30 +1244,25 @@ $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.responseCode', $responseCode);
|
||||
Span::add('realtime.subscriptionDelta', $subscriptionDelta);
|
||||
Span::add('realtime.subscriptionsRequested', $subscriptionsRequested);
|
||||
Span::add('realtime.subscriptionsRemoved', $subscriptionsRemoved);
|
||||
Span::add('realtime.subscribe.subscriptionsCount', $subscriptionsRequested);
|
||||
Span::add('realtime.outboundBytes', $outboundBytes);
|
||||
Span::add('realtime.projectId', $project?->getId() ?? $projectId);
|
||||
Span::add('realtime.userId', $realtime->connections[$connection]['userId'] ?? null);
|
||||
Span::add('realtime.messageType', $messageType);
|
||||
Span::add('realtime.response_code', $responseCode);
|
||||
Span::add('realtime.outbound_bytes', $outboundBytes);
|
||||
Span::add('project.id', $project?->getId() ?? $projectId);
|
||||
Span::add('user.id', $realtime->connections[$connection]['userId'] ?? null);
|
||||
Span::add('realtime.message_type', $messageType);
|
||||
Span::current()?->finish();
|
||||
}
|
||||
});
|
||||
|
||||
$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;
|
||||
$success = false;
|
||||
|
||||
Span::init('realtime.close');
|
||||
Span::add('realtime.connectionId', $connection);
|
||||
Span::add('realtime.connection.id', $connection);
|
||||
|
||||
if (array_key_exists($connection, $realtime->connections)) {
|
||||
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
|
||||
@@ -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,
|
||||
@@ -1411,12 +1395,12 @@ $server->onClose(function (int $connection) use ($realtime, $stats, $register) {
|
||||
|
||||
Span::add('realtime.success', $success);
|
||||
if (!empty($projectId)) {
|
||||
Span::add('realtime.projectId', $projectId);
|
||||
Span::add('project.id', $projectId);
|
||||
}
|
||||
if (!empty($userId)) {
|
||||
Span::add('realtime.userId', $userId);
|
||||
Span::add('user.id', $userId);
|
||||
}
|
||||
Span::add('realtime.subscriptionsBeforeClose', $subscriptionsBeforeClose);
|
||||
Span::add('realtime.subscriptions_before_close', $subscriptionsBeforeClose);
|
||||
Span::current()?->finish();
|
||||
}
|
||||
|
||||
|
||||
@@ -881,6 +881,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
- _APP_DOMAIN
|
||||
- _APP_CONSOLE_DOMAIN
|
||||
- _APP_DOMAIN_FUNCTIONS
|
||||
- _APP_DOMAIN_SITES
|
||||
- _APP_MIGRATION_HOST
|
||||
- _APP_CONSOLE_SCHEMA
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
@@ -909,6 +916,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
- _APP_DOMAIN
|
||||
- _APP_CONSOLE_DOMAIN
|
||||
- _APP_DOMAIN_FUNCTIONS
|
||||
- _APP_DOMAIN_SITES
|
||||
- _APP_MIGRATION_HOST
|
||||
- _APP_CONSOLE_SCHEMA
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
|
||||
+9
-1
@@ -16,6 +16,7 @@ use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Adapter\Swoole;
|
||||
use Utopia\Queue\Broker\Pool as BrokerPool;
|
||||
use Utopia\Queue\Server;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\System\System;
|
||||
|
||||
Runtime::enableCoroutine();
|
||||
@@ -91,8 +92,13 @@ $adapter = new Swoole(
|
||||
$worker = new Server($adapter, $container);
|
||||
|
||||
try {
|
||||
$worker->init()->action(function () use ($worker, $registerWorkerMessageResources) {
|
||||
$worker->init()->action(function () use ($worker, $registerWorkerMessageResources, $queueName) {
|
||||
$registerWorkerMessageResources($worker->getContainer());
|
||||
Span::init("worker.{$queueName}");
|
||||
});
|
||||
|
||||
$worker->shutdown()->action(function () {
|
||||
Span::current()?->finish();
|
||||
});
|
||||
|
||||
$container->set('bus', function ($register) use ($worker) {
|
||||
@@ -120,6 +126,8 @@ $worker
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log, Document $project, Authorization $authorization) use ($queueName) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
Span::error($error);
|
||||
|
||||
if ($logger) {
|
||||
$log->setNamespace('appwrite-worker');
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
|
||||
+18
-17
@@ -51,41 +51,42 @@
|
||||
"ext-sockets": "*",
|
||||
"appwrite/php-runtimes": "0.20.*",
|
||||
"appwrite/php-clamav": "2.0.*",
|
||||
"utopia-php/abuse": "1.2.*",
|
||||
"utopia-php/abuse": "1.3.*",
|
||||
"utopia-php/agents": "1.2.*",
|
||||
"utopia-php/analytics": "0.15.*",
|
||||
"utopia-php/audit": "2.2.*",
|
||||
"utopia-php/audit": "^2.4",
|
||||
"utopia-php/auth": "0.5.*",
|
||||
"utopia-php/cache": "1.0.*",
|
||||
"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/console": "0.2.*",
|
||||
"utopia-php/database": "dev-chore-bump-console as 5.9.0",
|
||||
"utopia-php/detector": "0.2.*",
|
||||
"utopia-php/domains": "1.*",
|
||||
"utopia-php/emails": "0.6.*",
|
||||
"utopia-php/dns": "1.6.*",
|
||||
"utopia-php/domains": "^2.1",
|
||||
"utopia-php/emails": "0.7.*",
|
||||
"utopia-php/dns": "1.7.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/http": "0.34.*",
|
||||
"utopia-php/fetch": "0.5.*",
|
||||
"utopia-php/http": "2.0.0-rc2",
|
||||
"utopia-php/fetch": "^1.1",
|
||||
"utopia-php/validators": "0.2.*",
|
||||
"utopia-php/image": "0.8.*",
|
||||
"utopia-php/locale": "0.8.*",
|
||||
"utopia-php/logger": "0.6.*",
|
||||
"utopia-php/lock": "0.2.*",
|
||||
"utopia-php/logger": "0.8.*",
|
||||
"utopia-php/messaging": "0.22.*",
|
||||
"utopia-php/migration": "1.9.*",
|
||||
"utopia-php/platform": "0.13.*",
|
||||
"utopia-php/migration": "1.*",
|
||||
"utopia-php/platform": "1.0.0-rc2",
|
||||
"utopia-php/pools": "1.*",
|
||||
"utopia-php/span": "1.1.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/queue": "0.17.*",
|
||||
"utopia-php/servers": "0.3.*",
|
||||
"utopia-php/queue": "0.18.*",
|
||||
"utopia-php/servers": "0.4.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "2.*",
|
||||
"utopia-php/system": "0.10.*",
|
||||
"utopia-php/telemetry": "0.2.*",
|
||||
"utopia-php/vcs": "3.*",
|
||||
"utopia-php/vcs": "4.*",
|
||||
"utopia-php/websocket": "1.0.*",
|
||||
"matomo/device-detector": "6.4.*",
|
||||
"dragonmantank/cron-expression": "3.4.*",
|
||||
@@ -93,7 +94,7 @@
|
||||
"chillerlan/php-qrcode": "4.3.*",
|
||||
"adhocore/jwt": "1.1.*",
|
||||
"spomky-labs/otphp": "11.*",
|
||||
"webonyx/graphql-php": "15.31.*",
|
||||
"webonyx/graphql-php": "15.32.*",
|
||||
"league/csv": "9.14.*",
|
||||
"enshrined/svg-sanitize": "0.22.*"
|
||||
},
|
||||
|
||||
Generated
+496
-380
File diff suppressed because it is too large
Load Diff
+16
-2
@@ -255,7 +255,7 @@ services:
|
||||
appwrite-console:
|
||||
<<: *x-logging
|
||||
container_name: appwrite-console
|
||||
image: appwrite/console:7.8.45
|
||||
image: appwrite/console:8
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -1114,6 +1114,13 @@ services:
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_POOL_ADAPTER
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
- _APP_DOMAIN
|
||||
- _APP_CONSOLE_DOMAIN
|
||||
- _APP_DOMAIN_FUNCTIONS
|
||||
- _APP_DOMAIN_SITES
|
||||
- _APP_MIGRATION_HOST
|
||||
- _APP_CONSOLE_SCHEMA
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
@@ -1145,6 +1152,13 @@ services:
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_POOL_ADAPTER
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
- _APP_DOMAIN
|
||||
- _APP_CONSOLE_DOMAIN
|
||||
- _APP_DOMAIN_FUNCTIONS
|
||||
- _APP_DOMAIN_SITES
|
||||
- _APP_MIGRATION_HOST
|
||||
- _APP_CONSOLE_SCHEMA
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
@@ -1478,4 +1492,4 @@ volumes:
|
||||
appwrite-sites:
|
||||
appwrite-builds:
|
||||
appwrite-config:
|
||||
appwrite-models:
|
||||
appwrite-models:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Delete an analyzer report by its unique ID. Nested insights and CTA metadata are removed asynchronously by the deletes worker.
|
||||
@@ -0,0 +1 @@
|
||||
Get an insight by its unique ID, scoped to its parent report.
|
||||
@@ -0,0 +1 @@
|
||||
Get an analyzer report by its unique ID. The response includes the report's metadata and the nested insights it produced.
|
||||
@@ -0,0 +1 @@
|
||||
List the insights produced under a single analyzer report. You can use the query params to filter your results further.
|
||||
@@ -0,0 +1 @@
|
||||
Get a list of all the project's analyzer reports. You can use the query params to filter your results.
|
||||
@@ -0,0 +1 @@
|
||||
Create a bigint attribute. Optionally, minimum and maximum values can be provided.
|
||||
@@ -0,0 +1 @@
|
||||
Update a bigint attribute. Changing the `default` value will not update already existing documents.
|
||||
@@ -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 @@
|
||||
Create a bigint column. Optionally, minimum and maximum values can be provided.
|
||||
@@ -0,0 +1 @@
|
||||
Update a bigint column. Changing the `default` value will not update already existing rows.
|
||||
@@ -1 +1 @@
|
||||
Get a list of all branches from a GitHub repository in your installation. This endpoint returns the names of all branches in the repository and their total count. The GitHub installation must be properly configured and have access to the requested repository for this endpoint to work.
|
||||
Get a list of branches from a GitHub repository in your installation. This endpoint supports filtering by a search term and pagination using query strings such as `Query.limit()`, `Query.offset()`, `Query.cursorAfter()`, and `Query.cursorBefore()`. It returns branch names along with the total number of matches. The GitHub installation must be properly configured and have access to the requested repository for this endpoint to work.
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
The Advisor service provides read access to analyzer reports and their nested insights for a project.
|
||||
|
||||
Use the reports endpoints to list and fetch analyzer runs, then use the insights endpoints to inspect individual findings attached to a report.
|
||||
@@ -38,6 +38,7 @@
|
||||
<directory>./tests/e2e/Services/Messaging</directory>
|
||||
<directory>./tests/e2e/Services/Migrations</directory>
|
||||
<directory>./tests/e2e/Services/Project</directory>
|
||||
<directory>./tests/e2e/Services/Advisor</directory>
|
||||
<file>./tests/e2e/Services/Functions/FunctionsBase.php</file>
|
||||
<file>./tests/e2e/Services/Functions/FunctionsCustomServerTest.php</file>
|
||||
<file>./tests/e2e/Services/Functions/FunctionsCustomClientTest.php</file>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Advisor\Validator;
|
||||
|
||||
use Utopia\Validator;
|
||||
|
||||
class CTAs extends Validator
|
||||
{
|
||||
public const MAX_COUNT_DEFAULT = 16;
|
||||
|
||||
protected string $message = 'Value must be an array of CTA descriptors. Each entry must define `label`, `service`, `method`, and an optional `params` object.';
|
||||
protected array $allowedServices;
|
||||
protected array $allowedMethods;
|
||||
|
||||
public function __construct(
|
||||
protected int $maxCount = self::MAX_COUNT_DEFAULT,
|
||||
?array $allowedServices = null,
|
||||
?array $allowedMethods = null,
|
||||
) {
|
||||
$this->allowedServices = $allowedServices ?? ADVISOR_CTA_SERVICES;
|
||||
$this->allowedMethods = $allowedMethods ?? ADVISOR_CTA_METHODS;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function isArray(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_ARRAY;
|
||||
}
|
||||
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!\is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\count($value) > $this->maxCount) {
|
||||
$this->message = "A maximum of {$this->maxCount} CTAs are allowed per insight.";
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($value as $entry) {
|
||||
if (!\is_array($entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$maxLengths = ['label' => 256, 'service' => 64, 'method' => 64];
|
||||
foreach ($maxLengths as $required => $maxLength) {
|
||||
if (!isset($entry[$required]) || !\is_string($entry[$required]) || $entry[$required] === '') {
|
||||
return false;
|
||||
}
|
||||
if (\strlen($entry[$required]) > $maxLength) {
|
||||
$this->message = "CTA `{$required}` must not exceed {$maxLength} characters.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->allowedServices) && !\in_array($entry['service'], $this->allowedServices, true)) {
|
||||
$this->message = "CTA `service` must be one of: " . \implode(', ', $this->allowedServices) . '.';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($this->allowedMethods) && !\in_array($entry['method'], $this->allowedMethods, true)) {
|
||||
$this->message = "CTA `method` must be one of: " . \implode(', ', $this->allowedMethods) . '.';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($entry['params']) && !\is_array($entry['params']) && !\is_object($entry['params'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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', []);
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class Google extends OAuth2
|
||||
'state' => \json_encode($this->state),
|
||||
'response_type' => 'code',
|
||||
'access_type' => 'offline',
|
||||
'prompt' => 'consent'
|
||||
'prompt' => $this->getPrompt()
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ class Google extends OAuth2
|
||||
'https://oauth2.googleapis.com/token?' . \http_build_query([
|
||||
'code' => $code,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'client_secret' => $this->getClientSecret(),
|
||||
'redirect_uri' => $this->callback,
|
||||
'scope' => null,
|
||||
'grant_type' => 'authorization_code'
|
||||
@@ -95,7 +95,7 @@ class Google extends OAuth2
|
||||
'https://oauth2.googleapis.com/token?' . \http_build_query([
|
||||
'refresh_token' => $refreshToken,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'client_secret' => $this->getClientSecret(),
|
||||
'grant_type' => 'refresh_token'
|
||||
])
|
||||
), true);
|
||||
@@ -177,4 +177,54 @@ class Google extends OAuth2
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the Client Secret from the JSON stored in appSecret
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getClientSecret(): string
|
||||
{
|
||||
$secret = $this->getAppSecret();
|
||||
|
||||
return $secret['clientSecret'] ?? $this->appSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the prompt values from the JSON stored in appSecret
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getPrompt(): string
|
||||
{
|
||||
$secret = $this->getAppSecret();
|
||||
$prompt = $secret['prompt'] ?? [];
|
||||
|
||||
if (empty($prompt)) {
|
||||
$prompt = ['consent'];
|
||||
}
|
||||
|
||||
return \implode(' ', $prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON stored in appSecret.
|
||||
* Falls back to treating the raw string as the client secret for backwards compatibility.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAppSecret(): array
|
||||
{
|
||||
try {
|
||||
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (\Throwable $th) {
|
||||
return ['clientSecret' => $this->appSecret];
|
||||
}
|
||||
|
||||
if (!\is_array($secret)) {
|
||||
return ['clientSecret' => $this->appSecret];
|
||||
}
|
||||
|
||||
return $secret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use Appwrite\Event\Publisher\Execution as ExecutionPublisher;
|
||||
use Utopia\Bus\Listener;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Log extends Listener
|
||||
{
|
||||
@@ -34,20 +33,13 @@ class Log extends Listener
|
||||
{
|
||||
$project = new Document($event->project);
|
||||
$execution = new Document($event->execution);
|
||||
|
||||
if ($execution->getAttribute('resourceType', '') === 'functions') {
|
||||
$traceProjectId = System::getEnv('_APP_TRACE_PROJECT_ID', '');
|
||||
$traceFunctionId = System::getEnv('_APP_TRACE_FUNCTION_ID', '');
|
||||
$resourceId = $execution->getAttribute('resourceId', '');
|
||||
if ($traceProjectId !== '' && $traceFunctionId !== '' && $project->getId() === $traceProjectId && $resourceId === $traceFunctionId) {
|
||||
Span::init('execution.trace.v1_executions_enqueue');
|
||||
Span::add('datetime', gmdate('c'));
|
||||
Span::add('projectId', $project->getId());
|
||||
Span::add('functionId', $resourceId);
|
||||
Span::add('executionId', $execution->getId());
|
||||
Span::add('deploymentId', $execution->getAttribute('deploymentId', ''));
|
||||
Span::add('status', $execution->getAttribute('status', ''));
|
||||
Span::current()?->finish();
|
||||
}
|
||||
Span::add('project.id', $project->getId());
|
||||
Span::add('function.id', $execution->getAttribute('resourceId', ''));
|
||||
Span::add('execution.id', $execution->getId());
|
||||
Span::add('deployment.id', $execution->getAttribute('deploymentId', ''));
|
||||
Span::add('execution.status', $execution->getAttribute('status', ''));
|
||||
}
|
||||
|
||||
$publisherForExecutions->enqueue(new ExecutionMessage(
|
||||
|
||||
@@ -4,14 +4,14 @@ namespace Appwrite\Bus\Listeners;
|
||||
|
||||
use Appwrite\Auth\MFA\Type;
|
||||
use Appwrite\Bus\Events\SessionCreated;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Message\Mail as MailMessage;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
use Appwrite\Template\Template;
|
||||
use Utopia\Bus\Listener;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Storage\Validator\FileName;
|
||||
use Utopia\System\System;
|
||||
|
||||
@@ -31,14 +31,14 @@ class Mails extends Listener
|
||||
{
|
||||
$this
|
||||
->desc('Sends session alert emails')
|
||||
->inject('publisher')
|
||||
->inject('publisherForMails')
|
||||
->inject('locale')
|
||||
->inject('platform')
|
||||
->inject('dbForProject')
|
||||
->callback($this->handle(...));
|
||||
}
|
||||
|
||||
public function handle(SessionCreated $event, Publisher $publisher, Locale $locale, array $platform, Database $dbForProject): void
|
||||
public function handle(SessionCreated $event, MailPublisher $publisherForMails, Locale $locale, array $platform, Database $dbForProject): void
|
||||
{
|
||||
$project = new Document($event->project);
|
||||
|
||||
@@ -124,34 +124,32 @@ class Mails extends Listener
|
||||
];
|
||||
}
|
||||
|
||||
$queueForMails = new Mail($publisher);
|
||||
|
||||
$smtpConfig = [];
|
||||
if ($smtp['enabled'] ?? false) {
|
||||
$queueForMails
|
||||
->setSmtpHost($smtp['host'] ?? '')
|
||||
->setSmtpPort($smtp['port'] ?? '')
|
||||
->setSmtpUsername($smtp['username'] ?? '')
|
||||
->setSmtpPassword($smtp['password'] ?? '')
|
||||
->setSmtpSecure($smtp['secure'] ?? '')
|
||||
->setSmtpReplyToEmail($customTemplate['replyToEmail'] ?? $customTemplate['replyTo'] ?? $smtp['replyToEmail'] ?? $smtp['replyTo'] ?? '') // Includes backwards compatibility
|
||||
->setSmtpReplyToName($customTemplate['replyToName'] ?? $smtp['replyToName'] ?? '')
|
||||
->setSmtpSenderEmail($customTemplate['senderEmail'] ?? $smtp['senderEmail'] ?? System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM))
|
||||
->setSmtpSenderName($customTemplate['senderName'] ?? $smtp['senderName'] ?? System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
|
||||
$smtpConfig = [
|
||||
'host' => $smtp['host'] ?? '',
|
||||
'port' => $smtp['port'] ?? '',
|
||||
'username' => $smtp['username'] ?? '',
|
||||
'password' => $smtp['password'] ?? '',
|
||||
'secure' => $smtp['secure'] ?? '',
|
||||
'replyToEmail' => $customTemplate['replyToEmail'] ?? $customTemplate['replyTo'] ?? $smtp['replyToEmail'] ?? $smtp['replyTo'] ?? '', // Includes backwards compatibility
|
||||
'replyToName' => $customTemplate['replyToName'] ?? $smtp['replyToName'] ?? '',
|
||||
'senderEmail' => $customTemplate['senderEmail'] ?? $smtp['senderEmail'] ?? System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM),
|
||||
'senderName' => $customTemplate['senderName'] ?? $smtp['senderName'] ?? System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'),
|
||||
];
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setProject($project)
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($body)
|
||||
->setBodyTemplate(__DIR__ . '/../../../../app/config/locale/templates/' . $smtpBaseTemplate . '.tpl')
|
||||
->appendVariables($emailVariables)
|
||||
->setRecipient($event->user['email']);
|
||||
|
||||
if ($isBranded) {
|
||||
$queueForMails->setSenderName($platform['emailSenderName']);
|
||||
}
|
||||
|
||||
$queueForMails->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $event->user['email'],
|
||||
subject: $subject,
|
||||
bodyTemplate: __DIR__ . '/../../../../app/config/locale/templates/' . $smtpBaseTemplate . '.tpl',
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
smtp: $smtpConfig,
|
||||
variables: $emailVariables,
|
||||
customMailOptions: $isBranded ? ['senderName' => $platform['emailSenderName']] : [],
|
||||
platform: $platform,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Appwrite\Certificates;
|
||||
|
||||
use Appwrite\Certificates\Exception\CertificateStatus as CertificateStatusException;
|
||||
use Exception;
|
||||
use Utopia\Command;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Http\Http;
|
||||
@@ -24,17 +25,22 @@ class LetsEncrypt implements Adapter
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
|
||||
$staging = (Http::isProduction()) ? '' : ' --dry-run';
|
||||
$exit = Console::execute(
|
||||
"certbot certonly -v --webroot --noninteractive --agree-tos{$staging}"
|
||||
. " --email " . $this->email
|
||||
. " --cert-name " . $certName
|
||||
. " -w " . APP_STORAGE_CERTIFICATES
|
||||
. " -d {$domain}",
|
||||
'',
|
||||
$stdout,
|
||||
$stderr
|
||||
);
|
||||
$issueCommand = (new Command('certbot'))
|
||||
->argument('certonly')
|
||||
->flag('-v')
|
||||
->flag('--webroot')
|
||||
->flag('--noninteractive')
|
||||
->flag('--agree-tos')
|
||||
->option('--email', $this->email)
|
||||
->option('--cert-name', $certName)
|
||||
->option('-w', APP_STORAGE_CERTIFICATES)
|
||||
->option('-d', $domain);
|
||||
|
||||
if (! Http::isProduction()) {
|
||||
$issueCommand->flag('--dry-run');
|
||||
}
|
||||
|
||||
$exit = Console::execute($issueCommand, '', $stdout, $stderr);
|
||||
|
||||
// Unexpected error, usually 5XX, API limits, ...
|
||||
if ($exit !== 0) {
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Build extends Event
|
||||
{
|
||||
protected string $type = '';
|
||||
protected ?Document $resource = null;
|
||||
protected ?Document $deployment = null;
|
||||
protected ?Document $template = null;
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_BUILDS_CLASS_NAME', Event::BUILDS_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets template for the build event.
|
||||
*
|
||||
* @param Document $template
|
||||
* @return self
|
||||
*/
|
||||
public function setTemplate(Document $template): self
|
||||
{
|
||||
$this->template = $template;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets resource document for the build event.
|
||||
*
|
||||
* @param Document $resource
|
||||
* @return self
|
||||
*/
|
||||
public function setResource(Document $resource): self
|
||||
{
|
||||
$this->resource = $resource;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set resource document for the build event.
|
||||
*
|
||||
* @return null|Document
|
||||
*/
|
||||
public function getResource(): ?Document
|
||||
{
|
||||
return $this->resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets deployment for the build event.
|
||||
*
|
||||
* @param Document $deployment
|
||||
* @return self
|
||||
*/
|
||||
public function setDeployment(Document $deployment): self
|
||||
{
|
||||
$this->deployment = $deployment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set deployment for the build event.
|
||||
*
|
||||
* @return null|Document
|
||||
*/
|
||||
public function getDeployment(): ?Document
|
||||
{
|
||||
return $this->deployment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets type for the build event.
|
||||
*
|
||||
* @param string $type Can be `BUILD_TYPE_DEPLOYMENT` or `BUILD_TYPE_RETRY`.
|
||||
* @return self
|
||||
*/
|
||||
public function setType(string $type): self
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set type for the function event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare payload for queue.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function preparePayload(): array
|
||||
{
|
||||
$platform = $this->platform;
|
||||
if (empty($platform)) {
|
||||
$platform = Config::getParam('platform', []);
|
||||
}
|
||||
|
||||
return [
|
||||
'project' => $this->project,
|
||||
'resource' => $this->resource,
|
||||
'deployment' => $this->deployment,
|
||||
'type' => $this->type,
|
||||
'template' => $this->template,
|
||||
'platform' => $platform,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets event.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function reset(): self
|
||||
{
|
||||
$this->type = '';
|
||||
$this->resource = null;
|
||||
$this->deployment = null;
|
||||
$this->template = null;
|
||||
$this->platform = [];
|
||||
parent::reset();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,576 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Mail extends Event
|
||||
{
|
||||
protected string $recipient = '';
|
||||
protected string $name = '';
|
||||
protected string $subject = '';
|
||||
protected string $body = '';
|
||||
protected string $preview = '';
|
||||
protected array $smtp = [];
|
||||
protected array $variables = [];
|
||||
protected string $bodyTemplate = '';
|
||||
protected array $attachment = [];
|
||||
|
||||
protected array $customMailOptions = [];
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_MAILS_CLASS_NAME', Event::MAILS_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets subject for the mail event.
|
||||
*
|
||||
* @param string $subject
|
||||
* @return self
|
||||
*/
|
||||
public function setSubject(string $subject): self
|
||||
{
|
||||
$this->subject = $subject;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns subject for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSubject(): string
|
||||
{
|
||||
return $this->subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets recipient for the mail event.
|
||||
*
|
||||
* @param string $recipient
|
||||
* @return self
|
||||
*/
|
||||
public function setRecipient(string $recipient): self
|
||||
{
|
||||
$this->recipient = $recipient;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set recipient for mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRecipient(): string
|
||||
{
|
||||
return $this->recipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets body for the mail event.
|
||||
*
|
||||
* @param string $body
|
||||
* @return self
|
||||
*/
|
||||
public function setBody(string $body): self
|
||||
{
|
||||
$this->body = $body;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns body for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBody(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets preview for the mail event.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPreview(string $preview): self
|
||||
{
|
||||
$this->preview = $preview;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns preview for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPreview(): string
|
||||
{
|
||||
return $this->preview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets name for the mail event.
|
||||
*
|
||||
* @param string $name
|
||||
* @return self
|
||||
*/
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set name for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bodyTemplate for the mail event.
|
||||
*
|
||||
* @param string $bodyTemplate
|
||||
* @return self
|
||||
*/
|
||||
public function setBodyTemplate(string $bodyTemplate): self
|
||||
{
|
||||
$this->bodyTemplate = $bodyTemplate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns subject for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBodyTemplate(): string
|
||||
{
|
||||
return $this->bodyTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP Host
|
||||
*
|
||||
* @param string $host
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpHost(string $host): self
|
||||
{
|
||||
$this->smtp['host'] = $host;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP port
|
||||
*
|
||||
* @param int $port
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpPort(int $port): self
|
||||
{
|
||||
$this->smtp['port'] = $port;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP username
|
||||
*
|
||||
* @param string $username
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpUsername(string $username): self
|
||||
{
|
||||
$this->smtp['username'] = $username;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP password
|
||||
*
|
||||
* @param string $password
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpPassword(string $password): self
|
||||
{
|
||||
$this->smtp['password'] = $password;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP secure
|
||||
*
|
||||
* @param string $secure
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpSecure(string $secure): self
|
||||
{
|
||||
$this->smtp['secure'] = $secure;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP sender email
|
||||
*
|
||||
* @param string $senderEmail
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpSenderEmail(string $senderEmail): self
|
||||
{
|
||||
$this->smtp['senderEmail'] = $senderEmail;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP sender name
|
||||
*
|
||||
* @param string $senderName
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpSenderName(string $senderName): self
|
||||
{
|
||||
$this->smtp['senderName'] = $senderName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP reply-to email
|
||||
*
|
||||
* @param string $email
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpReplyToEmail(string $email): self
|
||||
{
|
||||
$this->smtp['replyToEmail'] = $email;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP reply-to name
|
||||
*
|
||||
* @param string $name
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpReplyToName(string $name): self
|
||||
{
|
||||
$this->smtp['replyToName'] = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpHost(): string
|
||||
{
|
||||
return $this->smtp['host'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP port
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getSmtpPort(): int
|
||||
{
|
||||
return $this->smtp['port'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP username
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpUsername(): string
|
||||
{
|
||||
return $this->smtp['username'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP password
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpPassword(): string
|
||||
{
|
||||
return $this->smtp['password'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP secure
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpSecure(): string
|
||||
{
|
||||
return $this->smtp['secure'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP sender email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpSenderEmail(): string
|
||||
{
|
||||
return $this->smtp['senderEmail'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP sender name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpSenderName(): string
|
||||
{
|
||||
return $this->smtp['senderName'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP reply-to email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpReplyToEmail(): string
|
||||
{
|
||||
return $this->smtp['replyToEmail'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP reply-to name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpReplyToName(): string
|
||||
{
|
||||
return $this->smtp['replyToName'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Email Variables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getVariables(): array
|
||||
{
|
||||
return $this->variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Email Variables
|
||||
*
|
||||
* @param array $variables
|
||||
* @return self
|
||||
*/
|
||||
public function setVariables(array $variables): self
|
||||
{
|
||||
$this->variables = $variables;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append variables to the email event.
|
||||
*
|
||||
* @param array $variables
|
||||
* @return self
|
||||
*/
|
||||
public function appendVariables(array $variables): self
|
||||
{
|
||||
$this->variables = \array_merge($this->variables, $variables);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attachment
|
||||
* @param string $content
|
||||
* @param string $filename
|
||||
* @param string $encoding
|
||||
* @param string $type
|
||||
* @return self
|
||||
*/
|
||||
public function setAttachment(string $content, string $filename, string $encoding = 'base64', string $type = 'plain/text')
|
||||
{
|
||||
$this->attachment = [
|
||||
'content' => base64_encode($content),
|
||||
'filename' => $filename,
|
||||
'encoding' => $encoding,
|
||||
'type' => $type,
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAttachment(): array
|
||||
{
|
||||
return $this->attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset attachment
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function resetAttachment(): self
|
||||
{
|
||||
$this->attachment = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sender email
|
||||
*
|
||||
* @param string $email
|
||||
* @return self
|
||||
*/
|
||||
public function setSenderEmail(string $email): self
|
||||
{
|
||||
$this->customMailOptions['senderEmail'] = $email;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sender email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSenderEmail(): string
|
||||
{
|
||||
return $this->customMailOptions['senderEmail'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sender name
|
||||
*
|
||||
* @param string $name
|
||||
* @return self
|
||||
*/
|
||||
public function setSenderName(string $name): self
|
||||
{
|
||||
$this->customMailOptions['senderName'] = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sender name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSenderName(): string
|
||||
{
|
||||
return $this->customMailOptions['senderName'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set reply-to email
|
||||
*
|
||||
* @param string $email
|
||||
* @return self
|
||||
*/
|
||||
public function setReplyToEmail(string $email): self
|
||||
{
|
||||
$this->customMailOptions['replyToEmail'] = $email;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reply-to email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReplyToEmail(): string
|
||||
{
|
||||
return $this->customMailOptions['replyToEmail'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set reply-to name
|
||||
*
|
||||
* @param string $name
|
||||
* @return self
|
||||
*/
|
||||
public function setReplyToName(string $name): self
|
||||
{
|
||||
$this->customMailOptions['replyToName'] = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reply-to name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReplyToName(): string
|
||||
{
|
||||
return $this->customMailOptions['replyToName'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function reset(): self
|
||||
{
|
||||
$this->project = null;
|
||||
$this->recipient = '';
|
||||
$this->name = '';
|
||||
$this->subject = '';
|
||||
$this->body = '';
|
||||
$this->variables = [];
|
||||
$this->bodyTemplate = '';
|
||||
$this->attachment = [];
|
||||
$this->customMailOptions = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the payload for the event
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function preparePayload(): array
|
||||
{
|
||||
$platform = $this->platform;
|
||||
if (empty($platform)) {
|
||||
$platform = Config::getParam('platform', []);
|
||||
}
|
||||
|
||||
return [
|
||||
'project' => $this->project,
|
||||
'recipient' => $this->recipient,
|
||||
'name' => $this->name,
|
||||
'subject' => $this->subject,
|
||||
'bodyTemplate' => $this->bodyTemplate,
|
||||
'body' => $this->body,
|
||||
'preview' => $this->preview,
|
||||
'smtp' => $this->smtp,
|
||||
'variables' => $this->variables,
|
||||
'attachment' => $this->attachment,
|
||||
'customMailOptions' => $this->customMailOptions,
|
||||
'events' => Event::generateEvents($this->getEvent(), $this->getParams()),
|
||||
'platform' => $platform,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Build extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly Document $project,
|
||||
public readonly Document $resource,
|
||||
public readonly Document $deployment,
|
||||
public readonly string $type,
|
||||
public readonly ?Document $template = null,
|
||||
public readonly array $platform = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$platform = !empty($this->platform) ? $this->platform : Config::getParam('platform', []);
|
||||
|
||||
return [
|
||||
'project' => $this->project->getArrayCopy(),
|
||||
'resource' => $this->resource->getArrayCopy(),
|
||||
'deployment' => $this->deployment->getArrayCopy(),
|
||||
'type' => $this->type,
|
||||
'template' => $this->template?->getArrayCopy(),
|
||||
'platform' => $platform,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
project: new Document($data['project'] ?? []),
|
||||
resource: new Document($data['resource'] ?? []),
|
||||
deployment: new Document($data['deployment'] ?? []),
|
||||
type: $data['type'] ?? '',
|
||||
template: !empty($data['template']) ? new Document($data['template']) : null,
|
||||
platform: $data['platform'] ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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'] ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Delete extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ?Document $project = null,
|
||||
public readonly string $type = '',
|
||||
public readonly ?Document $document = null,
|
||||
public readonly ?string $resource = null,
|
||||
public readonly ?string $resourceType = null,
|
||||
public readonly ?string $datetime = null,
|
||||
public readonly ?string $hourlyUsageRetentionDatetime = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'project' => $this->project?->getArrayCopy(),
|
||||
'type' => $this->type,
|
||||
'document' => $this->document?->getArrayCopy(),
|
||||
'resource' => $this->resource,
|
||||
'resourceType' => $this->resourceType,
|
||||
'datetime' => $this->datetime,
|
||||
'hourlyUsageRetentionDatetime' => $this->hourlyUsageRetentionDatetime,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
project: !empty($data['project']) ? new Document($data['project']) : null,
|
||||
type: $data['type'] ?? '',
|
||||
document: !empty($data['document']) ? new Document($data['document']) : null,
|
||||
resource: $data['resource'] ?? null,
|
||||
resourceType: $data['resourceType'] ?? null,
|
||||
datetime: $data['datetime'] ?? null,
|
||||
hourlyUsageRetentionDatetime: $data['hourlyUsageRetentionDatetime'] ?? null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Func extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ?Document $project = null,
|
||||
public readonly ?Document $user = null,
|
||||
public readonly ?string $userId = null,
|
||||
public readonly ?Document $function = null,
|
||||
public readonly ?string $functionId = null,
|
||||
public readonly ?Document $execution = null,
|
||||
public readonly string $type = '',
|
||||
public readonly string $jwt = '',
|
||||
public readonly array $payload = [],
|
||||
public readonly array $events = [],
|
||||
public readonly string $body = '',
|
||||
public readonly string $path = '',
|
||||
public readonly array $headers = [],
|
||||
public readonly string $method = '',
|
||||
public readonly array $platform = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromEvent(
|
||||
string $event,
|
||||
array $params,
|
||||
?Document $project = null,
|
||||
?Document $user = null,
|
||||
?string $userId = null,
|
||||
array $payload = [],
|
||||
array $platform = [],
|
||||
): static {
|
||||
return new self(
|
||||
project: $project,
|
||||
user: $user,
|
||||
userId: $userId,
|
||||
payload: $payload,
|
||||
events: $event !== '' ? Event::generateEvents($event, $params) : [],
|
||||
platform: $platform,
|
||||
);
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$platform = !empty($this->platform) ? $this->platform : Config::getParam('platform', []);
|
||||
|
||||
return [
|
||||
'project' => $this->project?->getArrayCopy(),
|
||||
'user' => $this->user?->getArrayCopy(),
|
||||
'userId' => $this->userId,
|
||||
'function' => $this->function?->getArrayCopy(),
|
||||
'functionId' => $this->functionId,
|
||||
'execution' => $this->execution?->getArrayCopy(),
|
||||
'type' => $this->type,
|
||||
'jwt' => $this->jwt,
|
||||
'payload' => $this->payload,
|
||||
'events' => $this->events,
|
||||
'body' => $this->body,
|
||||
'path' => $this->path,
|
||||
'headers' => $this->headers,
|
||||
'method' => $this->method,
|
||||
'platform' => $platform,
|
||||
];
|
||||
}
|
||||
|
||||
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,
|
||||
userId: $data['userId'] ?? null,
|
||||
function: !empty($data['function']) ? new Document($data['function']) : null,
|
||||
functionId: $data['functionId'] ?? null,
|
||||
execution: !empty($data['execution']) ? new Document($data['execution']) : null,
|
||||
type: $data['type'] ?? '',
|
||||
jwt: $data['jwt'] ?? '',
|
||||
payload: $data['payload'] ?? [],
|
||||
events: $data['events'] ?? [],
|
||||
body: $data['body'] ?? '',
|
||||
path: $data['path'] ?? '',
|
||||
headers: $data['headers'] ?? [],
|
||||
method: $data['method'] ?? '',
|
||||
platform: $data['platform'] ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Mail extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ?Document $project = null,
|
||||
public readonly string $recipient = '',
|
||||
public readonly string $name = '',
|
||||
public readonly string $subject = '',
|
||||
public readonly string $bodyTemplate = '',
|
||||
public readonly string $body = '',
|
||||
public readonly string $preview = '',
|
||||
public readonly array $smtp = [],
|
||||
public readonly array $variables = [],
|
||||
public readonly array $attachment = [],
|
||||
public readonly array $customMailOptions = [],
|
||||
public readonly array $events = [],
|
||||
public readonly array $platform = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$platform = !empty($this->platform) ? $this->platform : Config::getParam('platform', []);
|
||||
|
||||
return [
|
||||
'project' => $this->project?->getArrayCopy(),
|
||||
'recipient' => $this->recipient,
|
||||
'name' => $this->name,
|
||||
'subject' => $this->subject,
|
||||
'bodyTemplate' => $this->bodyTemplate,
|
||||
'body' => $this->body,
|
||||
'preview' => $this->preview,
|
||||
'smtp' => $this->smtp,
|
||||
'variables' => $this->variables,
|
||||
'attachment' => $this->attachment,
|
||||
'customMailOptions' => $this->customMailOptions,
|
||||
'events' => $this->events,
|
||||
'platform' => $platform,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
project: !empty($data['project']) ? new Document($data['project']) : null,
|
||||
recipient: $data['recipient'] ?? '',
|
||||
name: $data['name'] ?? '',
|
||||
subject: $data['subject'] ?? '',
|
||||
bodyTemplate: $data['bodyTemplate'] ?? '',
|
||||
body: $data['body'] ?? '',
|
||||
preview: $data['preview'] ?? '',
|
||||
smtp: $data['smtp'] ?? [],
|
||||
variables: $data['variables'] ?? [],
|
||||
attachment: $data['attachment'] ?? [],
|
||||
customMailOptions: $data['customMailOptions'] ?? [],
|
||||
events: $data['events'] ?? [],
|
||||
platform: $data['platform'] ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Messaging extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $type,
|
||||
public readonly Document $project,
|
||||
public readonly ?Document $user = null,
|
||||
public readonly ?string $messageId = null,
|
||||
public readonly ?Document $message = null,
|
||||
public readonly ?array $recipients = null,
|
||||
public readonly ?string $providerType = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->type,
|
||||
'project' => $this->project->getArrayCopy(),
|
||||
'user' => $this->user?->getArrayCopy(),
|
||||
'messageId' => $this->messageId,
|
||||
'message' => $this->message?->getArrayCopy(),
|
||||
'recipients' => $this->recipients,
|
||||
'providerType' => $this->providerType,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
type: $data['type'] ?? '',
|
||||
project: new Document($data['project'] ?? []),
|
||||
user: !empty($data['user']) ? new Document($data['user']) : null,
|
||||
messageId: $data['messageId'] ?? null,
|
||||
message: !empty($data['message']) ? new Document($data['message']) : null,
|
||||
recipients: $data['recipients'] ?? null,
|
||||
providerType: $data['providerType'] ?? null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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'] ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Messaging extends Event
|
||||
{
|
||||
protected string $type = '';
|
||||
protected ?string $messageId = null;
|
||||
protected ?Document $message = null;
|
||||
protected ?array $recipients = null;
|
||||
protected ?string $scheduledAt = null;
|
||||
protected ?string $providerType = null;
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_MESSAGING_CLASS_NAME', Event::MESSAGING_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets type for the build event.
|
||||
*
|
||||
* @param string $type Can be `MESSAGE_SEND_TYPE_INTERNAL` or `MESSAGE_SEND_TYPE_EXTERNAL`.
|
||||
* @return self
|
||||
*/
|
||||
public function setType(string $type): self
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set type for the function event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets recipient for the messaging event.
|
||||
*
|
||||
* @param string[] $recipients
|
||||
* @return self
|
||||
*/
|
||||
public function setRecipients(array $recipients): self
|
||||
{
|
||||
$this->recipients = $recipients;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set recipient for messaging event.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getRecipient(): array
|
||||
{
|
||||
return $this->recipients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets message document for the messaging event.
|
||||
*
|
||||
* @param Document $message
|
||||
* @return self
|
||||
*/
|
||||
public function setMessage(Document $message): self
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns message document for the messaging event.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function getMessage(): Document
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets message ID for the messaging event.
|
||||
*
|
||||
* @param string $messageId
|
||||
* @return self
|
||||
*/
|
||||
public function setMessageId(string $messageId): self
|
||||
{
|
||||
$this->messageId = $messageId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set message ID for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageId(): string
|
||||
{
|
||||
return $this->messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets provider type for the messaging event.
|
||||
*
|
||||
* @param string $providerType
|
||||
* @return self
|
||||
*/
|
||||
public function setProviderType(string $providerType): self
|
||||
{
|
||||
$this->providerType = $providerType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set provider type for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProviderType(): string
|
||||
{
|
||||
return $this->providerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Scheduled delivery time for the messaging event.
|
||||
*
|
||||
* @param string $scheduledAt
|
||||
* @return self
|
||||
*/
|
||||
public function setScheduledAt(string $scheduledAt): self
|
||||
{
|
||||
$this->scheduledAt = $scheduledAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set Delivery Time for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getScheduledAt(): string
|
||||
{
|
||||
return $this->scheduledAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the payload for the event
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function preparePayload(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->type,
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'messageId' => $this->messageId,
|
||||
'message' => $this->message,
|
||||
'recipients' => $this->recipients,
|
||||
'providerType' => $this->providerType,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Publisher;
|
||||
|
||||
use Appwrite\Event\Message\Build as BuildMessage;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
|
||||
readonly class Build extends Base
|
||||
{
|
||||
public function __construct(
|
||||
Publisher $publisher,
|
||||
protected Queue $queue
|
||||
) {
|
||||
parent::__construct($publisher);
|
||||
}
|
||||
|
||||
public function enqueue(BuildMessage $message, ?Queue $queue = null): string|bool
|
||||
{
|
||||
return $this->publish($queue ?? $this->queue, $message);
|
||||
}
|
||||
|
||||
public function getSize(bool $failed = false, ?Queue $queue = null): int
|
||||
{
|
||||
return $this->getQueueSize($queue ?? $this->queue, $failed);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user