mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
1145
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a369d49b2c | ||
|
|
22691bee66 | ||
|
|
51f036491a | ||
|
|
4df0934023 | ||
|
|
5e974713b7 | ||
|
|
e2071bd5dd | ||
|
|
0621a32aa6 | ||
|
|
e1cbbe3943 | ||
|
|
226e36db95 | ||
|
|
faedf49488 | ||
|
|
b432918385 | ||
|
|
a87263a571 | ||
|
|
dafa97879c | ||
|
|
a263afeff1 | ||
|
|
29915ddd3b | ||
|
|
c0f5fa90cb | ||
|
|
e666dc9504 | ||
|
|
3dc69ba62a | ||
|
|
7bf5f2d360 | ||
|
|
6df5556473 | ||
|
|
96e85c0bab | ||
|
|
40ab50ec9d | ||
|
|
9b762dde40 | ||
|
|
615aff0714 | ||
|
|
994850c5a6 | ||
|
|
525b929e54 | ||
|
|
074ffad826 | ||
|
|
36c87d109a | ||
|
|
e09dd98f36 | ||
|
|
96e1221a5f | ||
|
|
e13f4c8545 | ||
|
|
353b7f2a49 | ||
|
|
f7ab8e46d3 | ||
|
|
bec6725900 | ||
|
|
494ce5fc6a | ||
|
|
bae7b92dd9 | ||
|
|
801707c407 | ||
|
|
94581adfcb | ||
|
|
fd323feae8 | ||
|
|
3a0dc60a4c | ||
|
|
bed46a6bef | ||
|
|
ba5273872b | ||
|
|
f1639e0b67 | ||
|
|
e8fe9222b2 | ||
|
|
a520a67da8 | ||
|
|
9c03c5fab3 | ||
|
|
d864a61f93 | ||
|
|
fb1cbdca6c | ||
|
|
ab35806372 | ||
|
|
9612ed338f | ||
|
|
1219391c76 | ||
|
|
da7f5b7336 | ||
|
|
ec418e60b7 | ||
|
|
455004a536 | ||
|
|
2ad0135890 | ||
|
|
6414c0ebda | ||
|
|
801befeacd | ||
|
|
941d40e704 | ||
|
|
093bc7bf3d | ||
|
|
90f10ce685 | ||
|
|
45e74d7b3d | ||
|
|
4090ce853e | ||
|
|
af5c0da1d9 | ||
|
|
b5bb4c2e07 | ||
|
|
c492cfe9ab | ||
|
|
a866f0a69f | ||
|
|
3b2c05eb8d | ||
|
|
e28ba64dbf | ||
|
|
747b400616 | ||
|
|
cb58da7279 | ||
|
|
106458a79d | ||
|
|
d40b1707ba | ||
|
|
8600d0e64f | ||
|
|
2bc8d80273 | ||
|
|
1d94e60a37 | ||
|
|
4f7337759f | ||
|
|
657f16031b | ||
|
|
fed8354c52 | ||
|
|
cbfef909ee | ||
|
|
107fadfe37 | ||
|
|
e21614088f | ||
|
|
1c6880a78e | ||
|
|
18125156d2 | ||
|
|
765d334674 | ||
|
|
a46fa50c51 | ||
|
|
cb81939604 | ||
|
|
3e31094e1e | ||
|
|
074470c2c2 | ||
|
|
f8f2f9ca25 | ||
|
|
aff180c517 | ||
|
|
5d1b63fd35 | ||
|
|
aa30db2136 | ||
|
|
64f8b9195b | ||
|
|
12b7f94c1f | ||
|
|
4aaa3bfd9f | ||
|
|
6dd928b73c | ||
|
|
e490f375ee | ||
|
|
96ea3391d3 | ||
|
|
7f824785d5 | ||
|
|
8c191fca92 | ||
|
|
85ea4c8904 | ||
|
|
7e385fe87f | ||
|
|
d06759e33e | ||
|
|
ecf0a1ec67 | ||
|
|
ead2afb9d4 | ||
|
|
26b0447657 | ||
|
|
f7f6b5187c | ||
|
|
c3fbb83ed6 | ||
|
|
a860975b0e | ||
|
|
ce99d80c2f | ||
|
|
a30f12cd66 | ||
|
|
0003443c77 | ||
|
|
227d61e194 | ||
|
|
4cf6f4e066 | ||
|
|
b1409e5e8e | ||
|
|
a744cb0843 | ||
|
|
186ed5b61f | ||
|
|
522c9d3272 | ||
|
|
720ceabbbc | ||
|
|
5ff31e92d7 | ||
|
|
4ef43aacd9 | ||
|
|
ce49b8ecb8 | ||
|
|
1db42c6fc7 | ||
|
|
9bd2966222 | ||
|
|
579f07f8bb | ||
|
|
febb7dc10a | ||
|
|
4b02bf1aa8 | ||
|
|
d45893d123 | ||
|
|
ffd568aecd | ||
|
|
d811d1c534 | ||
|
|
c2b87cff66 | ||
|
|
ca03176ad6 | ||
|
|
946a1c8f34 | ||
|
|
91c1572318 | ||
|
|
05a3417297 | ||
|
|
5e495aa2da | ||
|
|
cdd2be175f | ||
|
|
2f5e5d384f | ||
|
|
2c790ecd84 | ||
|
|
5733972218 | ||
|
|
2416eef4a7 | ||
|
|
49195e5d6f | ||
|
|
1eaf5d5ffa | ||
|
|
17fc91aa19 | ||
|
|
94d733ee63 | ||
|
|
17be0eb634 | ||
|
|
a419501ea7 | ||
|
|
b89f474cb5 | ||
|
|
345d8f2db5 | ||
|
|
9bafa90ac8 | ||
|
|
736b72164e | ||
|
|
f82662e84e | ||
|
|
5537fe210e | ||
|
|
4c08cb36a8 | ||
|
|
1ee3f993d0 | ||
|
|
952f209a44 | ||
|
|
7d193d9c36 | ||
|
|
c04353bd38 | ||
|
|
69cfde440a | ||
|
|
739b36b0c9 | ||
|
|
8b4f4d2fea | ||
|
|
d2eb3f6684 | ||
|
|
31b5a1cb36 | ||
|
|
727c29ec0c | ||
|
|
3da3b6a324 | ||
|
|
20e209038d | ||
|
|
c0cbf991ab | ||
|
|
91f3bf0244 | ||
|
|
fab0c7ce7c | ||
|
|
413a91bd0a | ||
|
|
23c3496a63 | ||
|
|
0528c0d693 | ||
|
|
4319612358 | ||
|
|
b7df5cfcc1 | ||
|
|
a10fbc64c7 | ||
|
|
f1a2e869a2 | ||
|
|
def3ce056f | ||
|
|
ae720c3462 | ||
|
|
e14a026415 | ||
|
|
52cb8a9c56 | ||
|
|
787e7aed43 | ||
|
|
d18d4d8263 | ||
|
|
b4e98bd950 | ||
|
|
92d2310a53 | ||
|
|
29b6adf407 | ||
|
|
10e971aded | ||
|
|
ca464f8a71 | ||
|
|
6c4394947f | ||
|
|
2a15c91768 | ||
|
|
f15e9eb356 | ||
|
|
cbe7ce7269 | ||
|
|
f2cfa4465f | ||
|
|
f648d82c8c | ||
|
|
8117ab9542 | ||
|
|
255098ed9c | ||
|
|
914be48e25 | ||
|
|
47e406baa0 | ||
|
|
3c2f0cc982 | ||
|
|
a967ee9b1b | ||
|
|
6e576b5140 | ||
|
|
ab689af94b | ||
|
|
e7b4f03227 | ||
|
|
630d3376a6 | ||
|
|
a25977c6b8 | ||
|
|
19bfda541f | ||
|
|
2ac265baea | ||
|
|
a52f7f2485 | ||
|
|
6876828750 | ||
|
|
02c92d95b7 | ||
|
|
eb97a0e9b2 | ||
|
|
ae6a81bd12 | ||
|
|
6f7da6d622 | ||
|
|
e79dc39d5d | ||
|
|
4d062b818d | ||
|
|
a1dad882e4 | ||
|
|
abb546d099 | ||
|
|
dfdbcf015c | ||
|
|
29ab9ef9d2 | ||
|
|
52174816b2 | ||
|
|
9ee318de3c | ||
|
|
2e5b232e2b | ||
|
|
53b8886729 | ||
|
|
e9e51d5334 | ||
|
|
c1161ff27c | ||
|
|
1854d01e6e | ||
|
|
f530ec3db5 | ||
|
|
3502cad4a5 | ||
|
|
05e1bb242f | ||
|
|
e760a1386b | ||
|
|
644dcb8fb1 | ||
|
|
40dede6503 | ||
|
|
d19eec9849 | ||
|
|
88044eaa71 | ||
|
|
6011a7c66d | ||
|
|
771dd92a14 | ||
|
|
e1f8d0bbbc | ||
|
|
7da2bdf4da | ||
|
|
d89539b56a | ||
|
|
23f08ef144 | ||
|
|
ce1bb3fd29 | ||
|
|
fea4994ef0 | ||
|
|
4807dcf529 | ||
|
|
817475a251 | ||
|
|
150c9033cc | ||
|
|
8938dcf8f4 | ||
|
|
edbec1ef41 | ||
|
|
edceb3e0db | ||
|
|
d3974776be | ||
|
|
1110fdb719 | ||
|
|
252dc6b993 | ||
|
|
a987195f6f | ||
|
|
2144f27059 | ||
|
|
67e43cc1a5 | ||
|
|
d4b0ea64ad | ||
|
|
cb0f2299fb | ||
|
|
a38b6cc78d | ||
|
|
9d7bc04089 | ||
|
|
5c61f7232a | ||
|
|
01cc9bcd1f | ||
|
|
6e6081f693 | ||
|
|
ddb72c7546 | ||
|
|
1b82d32436 | ||
|
|
e8d40a48dc | ||
|
|
6ccc8b9261 | ||
|
|
0d79ab0fd3 | ||
|
|
c9aac86a6b | ||
|
|
8c626ea2ca | ||
|
|
9d953f898d | ||
|
|
c466ffcf7e | ||
|
|
366e578cf8 | ||
|
|
63ceea7aca | ||
|
|
1df09c652d | ||
|
|
6b9f48aa76 | ||
|
|
c8c5fbaca2 | ||
|
|
1131e4ed18 | ||
|
|
a2d920195d | ||
|
|
5753c7694d | ||
|
|
0d057390ab | ||
|
|
e82345be40 | ||
|
|
077068e9fc | ||
|
|
64392c1520 | ||
|
|
7973824321 | ||
|
|
b1ca73a041 | ||
|
|
845aa5c363 | ||
|
|
23dae85d2f | ||
|
|
7bf02811f2 | ||
|
|
23c1f8fd5a | ||
|
|
b9cb81496d | ||
|
|
e22e8d6a5f | ||
|
|
24385355a0 | ||
|
|
44b52b5a00 | ||
|
|
6c29dd055c | ||
|
|
14a96a2b56 | ||
|
|
273364114a | ||
|
|
72db0c721b | ||
|
|
cbe13d71f1 | ||
|
|
95ce53b66e | ||
|
|
b785dea7ac | ||
|
|
f35aad0816 | ||
|
|
69e6c0afc0 | ||
|
|
2f3fa9e0d3 | ||
|
|
71670f8169 | ||
|
|
a4a589c535 | ||
|
|
cbe2d2383d | ||
|
|
aef7b8df38 | ||
|
|
125394166a | ||
|
|
68cb03d22c | ||
|
|
7f3ea98924 | ||
|
|
cb66e50612 | ||
|
|
87db62b018 | ||
|
|
5fe5445283 | ||
|
|
303fca0fd5 | ||
|
|
99dc31062d | ||
|
|
d182c85330 | ||
|
|
d792d3bbea | ||
|
|
00d091513d | ||
|
|
63e6a51af1 | ||
|
|
1ee2539ce0 | ||
|
|
cb32dc40ec | ||
|
|
f66e0c2ff5 | ||
|
|
aed9816d1e | ||
|
|
6514a992a1 | ||
|
|
1167654c6a | ||
|
|
28c5df074d | ||
|
|
785efa3355 | ||
|
|
329b7e1856 | ||
|
|
752731050e | ||
|
|
368113ade5 | ||
|
|
f1d1c5a737 | ||
|
|
5815008994 | ||
|
|
ac83d417aa | ||
|
|
9993c15fb8 | ||
|
|
11aecdf33f | ||
|
|
a088e85a24 | ||
|
|
1fd687b6b7 | ||
|
|
aa26823549 | ||
|
|
109e16362d | ||
|
|
294518d014 | ||
|
|
046a7cd90e | ||
|
|
fea210c9ed | ||
|
|
760f065711 | ||
|
|
c42fbc49f5 | ||
|
|
0dd2f29a7e | ||
|
|
e20851e7a8 | ||
|
|
67e2104e98 | ||
|
|
a2ff833efe | ||
|
|
e894eca201 | ||
|
|
7573e0fee9 | ||
|
|
46be2d0e49 | ||
|
|
700ed9f882 | ||
|
|
440be52405 | ||
|
|
7643196d97 | ||
|
|
7e994f8db5 | ||
|
|
1b7cc0b5f9 | ||
|
|
60a0b663b9 | ||
|
|
e6496ec453 | ||
|
|
07204d748d | ||
|
|
b317f85fb6 | ||
|
|
8e98c08a23 | ||
|
|
6ebc36d0f3 | ||
|
|
e5ed7c835a | ||
|
|
cbb5963b29 | ||
|
|
f376db7b26 | ||
|
|
bfd34b3b15 | ||
|
|
a66551863a | ||
|
|
33abd7f0ed | ||
|
|
633e290bb4 | ||
|
|
9b14a8f08d | ||
|
|
ddbddafbf9 | ||
|
|
86a4bfa74e | ||
|
|
8124b07860 | ||
|
|
a7898b3d5b | ||
|
|
5d24b51421 | ||
|
|
d095f25a94 | ||
|
|
069e4c6001 | ||
|
|
0203323b4a | ||
|
|
94e29cff53 | ||
|
|
72def3b2fb | ||
|
|
d015a75e81 | ||
|
|
1306c85eb5 | ||
|
|
c3ea66b37a | ||
|
|
72ce068714 | ||
|
|
ea76213dd2 | ||
|
|
12b79363d1 | ||
|
|
0723101397 | ||
|
|
d56a3c1534 | ||
|
|
b86d7ac3fa | ||
|
|
b1fab79dc4 | ||
|
|
2f066a6ba8 | ||
|
|
e8ca0610ee | ||
|
|
15caa27977 | ||
|
|
5f22022527 | ||
|
|
b9c7c172ad | ||
|
|
c2bf1e8040 | ||
|
|
cda03f63ab | ||
|
|
fc04e17a69 | ||
|
|
f5a61fb4d6 | ||
|
|
429f73eb85 | ||
|
|
da871635d9 | ||
|
|
ccaea5d010 | ||
|
|
b5e9c1786a | ||
|
|
79e150b7b2 | ||
|
|
beee5e721e | ||
|
|
0ef4bf21cc | ||
|
|
b2486fcb6c | ||
|
|
a7b729b9a7 | ||
|
|
c67b77bca0 | ||
|
|
a19705a46f | ||
|
|
5d5a14bd77 | ||
|
|
1308706023 | ||
|
|
ce91b1a03d | ||
|
|
fbf390c710 | ||
|
|
991f5ff9fd | ||
|
|
e8d8373922 | ||
|
|
0fee1f0ffd | ||
|
|
eb07e99225 | ||
|
|
325968c52a | ||
|
|
39e66711c8 | ||
|
|
42a1c8da38 | ||
|
|
206bd63620 | ||
|
|
75696a21bf | ||
|
|
48866acdb7 | ||
|
|
b1171c661e | ||
|
|
a0e4e89621 | ||
|
|
728ed57df0 | ||
|
|
629479d275 | ||
|
|
5c922f89fe | ||
|
|
6e292349b9 | ||
|
|
7b940e3a17 | ||
|
|
7ab3debb10 | ||
|
|
c083e1ce74 | ||
|
|
61e886eed4 | ||
|
|
71ac9c7264 | ||
|
|
f78d5523ce | ||
|
|
955e1bcbe9 | ||
|
|
09a337aa1b | ||
|
|
2cfb5ecfd9 | ||
|
|
38687bc24e | ||
|
|
77b2a57968 | ||
|
|
223bb7f08a | ||
|
|
b73abf69d0 | ||
|
|
e76ed98590 | ||
|
|
7b10fe1371 | ||
|
|
e4abc0e0da | ||
|
|
6478499f1c | ||
|
|
3bbfbdb063 | ||
|
|
bc6ecbd22c | ||
|
|
4d2f631393 | ||
|
|
479a583ff5 | ||
|
|
08cd610823 | ||
|
|
6cd19bf330 | ||
|
|
d6d8729983 | ||
|
|
6c866be9f3 | ||
|
|
0ec515779e | ||
|
|
91240e0ca7 | ||
|
|
e3ae7daab4 | ||
|
|
f482e6de68 | ||
|
|
c12cad80bb | ||
|
|
2a17429226 | ||
|
|
e2627d784b | ||
|
|
5c915ef92f | ||
|
|
5697d983d0 | ||
|
|
a5fb10c61d | ||
|
|
5469b78367 | ||
|
|
e887859cc0 | ||
|
|
942cfc4591 | ||
|
|
321fc8ee70 | ||
|
|
568ac54d3d | ||
|
|
432e86ec07 | ||
|
|
fef1b5b5e5 | ||
|
|
bfac0f92af | ||
|
|
ae6df78020 | ||
|
|
23d84cf29f | ||
|
|
c79019f960 | ||
|
|
dd78b6dbce | ||
|
|
81e64afe7a | ||
|
|
5ef675bcdd | ||
|
|
a4734a5de7 | ||
|
|
03812cf7bf | ||
|
|
515a34c180 | ||
|
|
8e688bad4d | ||
|
|
4905f7cc48 | ||
|
|
3d27b76051 | ||
|
|
2d6348bf5d | ||
|
|
2131a0cea1 | ||
|
|
9379e4d6c8 | ||
|
|
15922fb88c | ||
|
|
ba9526cce0 | ||
|
|
6dbed494e3 | ||
|
|
4eb1e7ae45 | ||
|
|
bdd53212a0 | ||
|
|
1a69d9d236 | ||
|
|
a860c33969 | ||
|
|
ffd73dc70f | ||
|
|
362a7bef0e | ||
|
|
d08a6f572c | ||
|
|
893c1aa669 | ||
|
|
c34873a8b0 | ||
|
|
17f60b611b | ||
|
|
cd0d609229 | ||
|
|
497e5f8d00 | ||
|
|
60e1efb8cb | ||
|
|
3d4b29e0f1 | ||
|
|
dad21a912e | ||
|
|
445d7495a3 | ||
|
|
aebbe91c34 | ||
|
|
2a9f9f6851 | ||
|
|
eec023aab3 | ||
|
|
71f389e1c0 | ||
|
|
d71b289025 | ||
|
|
f4da9b54e7 | ||
|
|
df84b94df0 | ||
|
|
42bf515c8a | ||
|
|
a7d39b5ef7 | ||
|
|
4c1417944d | ||
|
|
27aee1fb88 | ||
|
|
4319c16584 | ||
|
|
d68fc9a22e | ||
|
|
e3d6fc123f | ||
|
|
e6505b9cf9 | ||
|
|
c1ab9da740 | ||
|
|
b567cd5341 | ||
|
|
f2ac01bccf | ||
|
|
eecfba2a72 | ||
|
|
2cfaa2223e | ||
|
|
1a87bde88e | ||
|
|
4b92e3781f | ||
|
|
65bb8946f6 | ||
|
|
db4dcd164e | ||
|
|
384c47f436 | ||
|
|
dcc926402d | ||
|
|
6969e446d5 | ||
|
|
c8bb939357 | ||
|
|
02488c853d | ||
|
|
92f26c1b5a | ||
|
|
ded06d7435 | ||
|
|
6c59cef8f4 | ||
|
|
46072bb95e | ||
|
|
904b7312d6 | ||
|
|
68fc6b0600 | ||
|
|
47ea8699d1 | ||
|
|
9ba3f6dfe8 | ||
|
|
3483daef0d | ||
|
|
9943305640 | ||
|
|
8bb6f01e22 | ||
|
|
2cc7bbc0a4 | ||
|
|
e9366ac2e6 | ||
|
|
22f8a3eab9 | ||
|
|
896e5a517a | ||
|
|
281dcfc64a | ||
|
|
457cd53475 | ||
|
|
93e7dc772d | ||
|
|
228d095ee5 | ||
|
|
43189f8940 | ||
|
|
b615e4b901 | ||
|
|
ebd6457361 | ||
|
|
0d3bcc9b3a | ||
|
|
61e98a501a | ||
|
|
502012ddf7 | ||
|
|
a6eb479c93 | ||
|
|
e0d7b418dc | ||
|
|
ea97072479 | ||
|
|
7573ee75a2 | ||
|
|
0dc00eb2c5 | ||
|
|
b8ccc3b846 | ||
|
|
6f1cac7d76 | ||
|
|
5f3384b821 | ||
|
|
71cb462f49 | ||
|
|
27e859030d | ||
|
|
e7a82e4d31 | ||
|
|
6dc836307e | ||
|
|
0582cdf394 | ||
|
|
573d8423a3 | ||
|
|
3fa3318346 | ||
|
|
23dfb23a3b | ||
|
|
fc5ea06821 | ||
|
|
5642983f91 | ||
|
|
b208e5c066 | ||
|
|
aa135d5cd4 | ||
|
|
6756ee31b6 | ||
|
|
89c988d73c | ||
|
|
6d85d1567e | ||
|
|
7567639996 | ||
|
|
057bff2140 | ||
|
|
d7bb234072 | ||
|
|
c0f8dee4d4 | ||
|
|
eb10996517 | ||
|
|
31803f0eb9 | ||
|
|
ac9214f3c4 | ||
|
|
14347d86a8 | ||
|
|
1b855d2d41 | ||
|
|
3b5b15d1a6 | ||
|
|
2c5bc32f7e | ||
|
|
59b41c4b52 | ||
|
|
b6e8b55994 | ||
|
|
3a4fb5dd14 | ||
|
|
2a679ead32 | ||
|
|
7da9d480d0 | ||
|
|
a37aa2dd68 | ||
|
|
b29f70a0af | ||
|
|
2fc5e56a61 | ||
|
|
b1373c02bc | ||
|
|
1db12e78ef | ||
|
|
a25c8004e4 | ||
|
|
547d59d95e | ||
|
|
4e0477af32 | ||
|
|
1659bc3ffa | ||
|
|
84e9c8243c | ||
|
|
001d38caf5 | ||
|
|
020d7bb801 | ||
|
|
b6aeaffe8b | ||
|
|
65883122e3 | ||
|
|
cd651dbdb8 | ||
|
|
2dfb386b12 | ||
|
|
e9dac6710f | ||
|
|
d4660a70c3 | ||
|
|
16f9c35850 | ||
|
|
49511ebdd9 | ||
|
|
63511c56ae | ||
|
|
445ada0226 | ||
|
|
a5d4f69c6c | ||
|
|
f788fc8f8a | ||
|
|
67ef2ab552 | ||
|
|
f0191202a0 | ||
|
|
68b4a49e9d | ||
|
|
28a09d7aaa | ||
|
|
ee3b2d75ae | ||
|
|
ffe1fd89a7 | ||
|
|
06c4ba81e9 | ||
|
|
f2772cea6b | ||
|
|
b76a4bdd2f | ||
|
|
1c94381830 | ||
|
|
0209d40a38 | ||
|
|
297fae8f81 | ||
|
|
3e403194e4 | ||
|
|
0305790e69 | ||
|
|
25435aaa11 | ||
|
|
19895e54e3 | ||
|
|
4b3fe0b6fe | ||
|
|
4ef906b836 | ||
|
|
28aa4e8a8d | ||
|
|
9c6a6c265a | ||
|
|
dc0eb5f7a7 | ||
|
|
06367eca69 | ||
|
|
8d1acef95d | ||
|
|
55343379f2 | ||
|
|
a66b565150 | ||
|
|
ff160871ae | ||
|
|
92a3eca194 | ||
|
|
03ccca2c35 | ||
|
|
07135956df | ||
|
|
b5a363a2a7 | ||
|
|
31dc9ff873 | ||
|
|
408f9c2d39 | ||
|
|
910bd69b16 | ||
|
|
dfd5965d25 | ||
|
|
2a38eedc98 | ||
|
|
f875b965be | ||
|
|
2e57e5a868 | ||
|
|
69ce99d1b4 | ||
|
|
6b881d4aaf | ||
|
|
aad7d6dbca | ||
|
|
33ffe4aca7 | ||
|
|
77eb3d4bcd | ||
|
|
49989f38f7 | ||
|
|
18a49ccc44 | ||
|
|
73173a7f8c | ||
|
|
e3412bc554 | ||
|
|
c1e50c7abd | ||
|
|
6499f436ca | ||
|
|
55fbd77bf1 | ||
|
|
4046829fc6 | ||
|
|
57e218266f | ||
|
|
188dd5c423 | ||
|
|
d8d3f22883 | ||
|
|
f338a819e1 | ||
|
|
2e82f0c2ec | ||
|
|
15dc316535 | ||
|
|
7581019e76 | ||
|
|
00b5236dea | ||
|
|
c4fcec0a8a | ||
|
|
ca877fa71d | ||
|
|
425dd9639c | ||
|
|
df57b59786 | ||
|
|
eda189dbf1 | ||
|
|
e60391bdb3 | ||
|
|
57157a71b4 | ||
|
|
8a52975048 | ||
|
|
45e3267930 | ||
|
|
2b96d60c1e | ||
|
|
ee911e3df6 | ||
|
|
417bb22790 | ||
|
|
eb2c616089 | ||
|
|
1b70bc812b | ||
|
|
e7e429142f | ||
|
|
80bb9a1f33 | ||
|
|
774e3af61c | ||
|
|
5f5d9b4fcb | ||
|
|
7c56a76feb | ||
|
|
30373980f1 | ||
|
|
8cf12f685a | ||
|
|
f99cb20d05 | ||
|
|
a7724028b3 | ||
|
|
7026052e04 | ||
|
|
a0514d2aac | ||
|
|
ac137cee3d | ||
|
|
f4f4ad9c7d | ||
|
|
da7738edaa | ||
|
|
6c1f967509 | ||
|
|
58e091099f | ||
|
|
6410a245eb | ||
|
|
670edd845e | ||
|
|
834c18f9fa | ||
|
|
77930a0cec | ||
|
|
5d9201466b | ||
|
|
d583b5e228 | ||
|
|
c463a9a662 | ||
|
|
bd9f02b689 | ||
|
|
472e2c282d | ||
|
|
7a89779827 | ||
|
|
126de78ce2 | ||
|
|
e919896eef | ||
|
|
fd3475f945 | ||
|
|
9c8f565211 | ||
|
|
08f30224b6 | ||
|
|
6d02554f2a | ||
|
|
61c619f373 | ||
|
|
419d56b4b1 | ||
|
|
b32dd316e1 | ||
|
|
04f660e44b | ||
|
|
d2cda9770b | ||
|
|
22042b4749 | ||
|
|
aa17eeb6e3 | ||
|
|
1529357821 | ||
|
|
3a98361736 | ||
|
|
0c425dbac3 | ||
|
|
b4c1b96d43 | ||
|
|
44d8c66e95 | ||
|
|
6774de4eef | ||
|
|
ee0f15eed6 | ||
|
|
c54d1d29a5 | ||
|
|
69b861cd87 | ||
|
|
0599d23cc5 | ||
|
|
a37b4a40d9 | ||
|
|
7e37046959 | ||
|
|
f9efdfd98e | ||
|
|
d53090647b | ||
|
|
e8b8a8fe52 | ||
|
|
3b4196735a | ||
|
|
7e315f79cc | ||
|
|
336bd44826 | ||
|
|
881d96a653 | ||
|
|
874e5f61f0 | ||
|
|
39cf207df9 | ||
|
|
11f23d99fb | ||
|
|
00e00ff166 | ||
|
|
57c80d5967 | ||
|
|
f36dd2748c | ||
|
|
69430154e6 | ||
|
|
0601305545 | ||
|
|
73d3d15ed8 | ||
|
|
2baa33351d | ||
|
|
f7668e0411 | ||
|
|
b78be65376 | ||
|
|
5519086c29 | ||
|
|
33b9f33f44 | ||
|
|
0e75793bef | ||
|
|
2f4a362e1b | ||
|
|
cda843d8f6 | ||
|
|
62e881b7e9 | ||
|
|
d6a40a35ee | ||
|
|
e111b4cc18 | ||
|
|
c08acedf6a | ||
|
|
c03cd258ce | ||
|
|
9477a5d980 | ||
|
|
7e4d404549 | ||
|
|
c0c1d693c2 | ||
|
|
6e47fb6c70 | ||
|
|
8df22db3eb | ||
|
|
cdbd80ceda | ||
|
|
58751bbdf1 | ||
|
|
cca49f8f6a | ||
|
|
b0bd9e5b78 | ||
|
|
21334ecdff | ||
|
|
6f7cca7d68 | ||
|
|
47ea2893c3 | ||
|
|
bce3ce85d5 | ||
|
|
bbeca28026 | ||
|
|
9ab9f6d709 | ||
|
|
309fde38f7 | ||
|
|
3310d271bf | ||
|
|
4e12b6236e | ||
|
|
4df8157d74 | ||
|
|
37e76e52f6 | ||
|
|
cd824faf64 | ||
|
|
96b11b02e6 | ||
|
|
b7e2606b9f | ||
|
|
09d71e73af | ||
|
|
465912822f | ||
|
|
9f24cb4aa3 | ||
|
|
9e4e23ad3f | ||
|
|
ca43281fa9 | ||
|
|
c69382f29f | ||
|
|
d45d27b780 | ||
|
|
3e96f9c2f1 | ||
|
|
dd9f6ed820 | ||
|
|
f7f1bc8352 | ||
|
|
bf50fbeb6c | ||
|
|
1f7bfb3352 | ||
|
|
406181e887 | ||
|
|
a7b243aff9 | ||
|
|
dc85d44647 | ||
|
|
71621f4ccf | ||
|
|
68deee4a56 | ||
|
|
a20392d13a | ||
|
|
859d146e85 | ||
|
|
69d5ce0f55 | ||
|
|
e43292052c | ||
|
|
ee7103c4b0 | ||
|
|
1b1ef80b10 | ||
|
|
0a3877a900 | ||
|
|
33f90fcf6a | ||
|
|
31b9ff4f05 | ||
|
|
1c3f778da9 | ||
|
|
2f1eee390b | ||
|
|
35bf13cfab | ||
|
|
f814fd980b | ||
|
|
3ddfd9c3b0 | ||
|
|
75d8110ad9 | ||
|
|
c958f3f1cb | ||
|
|
06e3a144a3 | ||
|
|
36519c13a8 | ||
|
|
44ec8de6bf | ||
|
|
9fb1aaadaf | ||
|
|
d9ac4f2f2d | ||
|
|
6f16b56f31 | ||
|
|
e61f3c7a42 | ||
|
|
1b6d51a116 | ||
|
|
0058e88217 | ||
|
|
81b4065921 | ||
|
|
59f178d634 | ||
|
|
d094d6a081 | ||
|
|
cf90653deb | ||
|
|
bae194e866 | ||
|
|
144e88452e | ||
|
|
89980b1f0e | ||
|
|
16fb25ce5a | ||
|
|
7dea3f19a9 | ||
|
|
e78887001a | ||
|
|
aa4ecdf138 | ||
|
|
9bda83104d | ||
|
|
3783378288 | ||
|
|
937cac454a | ||
|
|
36eb28dc22 | ||
|
|
5aff78a619 | ||
|
|
e6781cb4c3 | ||
|
|
62b391ef68 | ||
|
|
6f1d2d094a | ||
|
|
36690229c5 | ||
|
|
fe5d9cc662 | ||
|
|
f46ec96040 | ||
|
|
88bd35ce98 | ||
|
|
2dc813342a | ||
|
|
fd08f7dd36 | ||
|
|
13f4aa9a80 | ||
|
|
22fba9defc | ||
|
|
fcbb073bcc | ||
|
|
db0dbeb27b | ||
|
|
0e27088e2a | ||
|
|
70a7deaa38 | ||
|
|
514934649e | ||
|
|
ee8e7333d8 | ||
|
|
8d94b6585e | ||
|
|
9fd92ca5ac | ||
|
|
6d9a6051bf | ||
|
|
461c5895a4 | ||
|
|
94fc0416e6 | ||
|
|
ad54a8c334 | ||
|
|
e2b72e1f58 | ||
|
|
e1a9eb4994 | ||
|
|
b0c3ab71c4 | ||
|
|
ce8f72bee9 | ||
|
|
ee4f3e2df4 | ||
|
|
930db2bcc4 | ||
|
|
fadcb39765 | ||
|
|
5034597750 | ||
|
|
f73b48d4ca | ||
|
|
0a7f31333d | ||
|
|
8964fbc821 | ||
|
|
8baa5cb659 | ||
|
|
2e5bea1aee | ||
|
|
759792c447 | ||
|
|
86deab8f6c | ||
|
|
3cf8f151c4 | ||
|
|
4e0945d0ed | ||
|
|
052c8d4ead | ||
|
|
3c3aa6d416 | ||
|
|
eabe06d981 | ||
|
|
8d4c6a8218 | ||
|
|
1a09913a27 | ||
|
|
e8e47746ea | ||
|
|
546cd6596a | ||
|
|
58be8f411a | ||
|
|
990209b624 | ||
|
|
850a834211 | ||
|
|
dfccbd0e16 | ||
|
|
cda5b73600 | ||
|
|
1f4534d674 | ||
|
|
9ba970b3c1 | ||
|
|
ef54603367 | ||
|
|
2c855427aa | ||
|
|
9b62d341d2 | ||
|
|
d3e0050739 | ||
|
|
af8a27c559 | ||
|
|
b986cd806c | ||
|
|
5ba7501842 | ||
|
|
421696a26e | ||
|
|
c6214ef4a8 | ||
|
|
b0b9c412fc | ||
|
|
1c165b8c8d | ||
|
|
6fbacb484b | ||
|
|
91a3ee0c9d | ||
|
|
49b1c3db79 | ||
|
|
dfb53895eb | ||
|
|
7cdb7af07c | ||
|
|
109967953e | ||
|
|
e3e7f56bb8 | ||
|
|
80057383e1 | ||
|
|
c7fda6cc3f | ||
|
|
5ae148f026 | ||
|
|
c744354973 | ||
|
|
ddb0d5db26 | ||
|
|
c7fd31285d | ||
|
|
8cd000c42d | ||
|
|
e931938b78 | ||
|
|
4b58e1a85e | ||
|
|
c6b6b7cdac | ||
|
|
2a61cacdf9 | ||
|
|
f4bca74a59 | ||
|
|
7e6846f8f8 | ||
|
|
03a19cc83c | ||
|
|
8a35762843 | ||
|
|
3869269e0c | ||
|
|
0d7eb88a45 | ||
|
|
cd90974e15 | ||
|
|
54ba59da64 | ||
|
|
f837e6be03 | ||
|
|
cec5cd7bdc | ||
|
|
63c2d72b2e | ||
|
|
e9d9aa653f | ||
|
|
801219374c | ||
|
|
dc3459edd0 | ||
|
|
ace9d86744 | ||
|
|
2d3e34fd5a | ||
|
|
69ad4ae930 | ||
|
|
0037305e16 | ||
|
|
da0b2a7517 | ||
|
|
6aafe2dd12 | ||
|
|
1a205c3f3b | ||
|
|
99966b5a24 | ||
|
|
2437d2be77 | ||
|
|
2043470bd0 | ||
|
|
667285d536 | ||
|
|
1199c1fc52 | ||
|
|
f270e47b48 | ||
|
|
5678860d13 | ||
|
|
b83125a41e | ||
|
|
5daacb7161 | ||
|
|
2dcb131778 | ||
|
|
30083598c6 | ||
|
|
a0599d2658 | ||
|
|
c50db111d6 | ||
|
|
9441295510 | ||
|
|
e4e6c84362 | ||
|
|
70395cfacc | ||
|
|
7d2a95c573 | ||
|
|
f3f3ac80f8 | ||
|
|
45d5f14b83 | ||
|
|
ce9a54fd4b | ||
|
|
8e434b710b | ||
|
|
37b43b0ce0 | ||
|
|
2052496635 | ||
|
|
1cc6c9c0f1 | ||
|
|
bbc76ffe37 | ||
|
|
6589c6a786 | ||
|
|
2cb86c57ef | ||
|
|
90fb5b6321 | ||
|
|
d4517384dc | ||
|
|
6787b0c7f1 | ||
|
|
a94ccdb4f5 | ||
|
|
9518764cae | ||
|
|
d174233cd6 | ||
|
|
278679ab10 | ||
|
|
72ae883587 | ||
|
|
3d0aa74ffe | ||
|
|
e4a7b1b103 | ||
|
|
c6ffa181a2 | ||
|
|
cd8e93db60 | ||
|
|
ceb2108012 | ||
|
|
383f0c1ef1 | ||
|
|
52ccbfcdf7 | ||
|
|
35fe622548 | ||
|
|
d502ab0cd5 | ||
|
|
72f86ce5ef | ||
|
|
062233cdbc | ||
|
|
102d3fd245 | ||
|
|
cb9705f0a2 | ||
|
|
61d1546d14 | ||
|
|
2e4ac091d1 | ||
|
|
ef5f0ed6b4 | ||
|
|
22a1a5a452 | ||
|
|
e738bae523 | ||
|
|
f765eb92e7 | ||
|
|
50c6a35079 | ||
|
|
f8c39c8f06 | ||
|
|
aa12ef6569 | ||
|
|
5912a4c4a0 | ||
|
|
e85079a42f | ||
|
|
4c9a94f5e2 | ||
|
|
7fc8c6ae43 | ||
|
|
61067bd8d3 | ||
|
|
fb3b3ae51e | ||
|
|
39aa4c4a9a | ||
|
|
b03aa8d632 | ||
|
|
0215577bc7 | ||
|
|
768d293cf5 | ||
|
|
bdfb9e305e | ||
|
|
6c1cbfddea | ||
|
|
e0a937912c | ||
|
|
8951a8465c | ||
|
|
2fa95b52a0 | ||
|
|
676d53b18e | ||
|
|
3a3c92140b | ||
|
|
cb3b22c9f9 | ||
|
|
e4ebf755e4 | ||
|
|
6a712e236d | ||
|
|
1cf8b103a8 | ||
|
|
3ca9468b11 | ||
|
|
6156b97ea0 | ||
|
|
0f84f21bb4 | ||
|
|
a14d5c584e | ||
|
|
689e61c84f | ||
|
|
aec7f27ce4 | ||
|
|
c7bdf2a99d | ||
|
|
b79ff7fbff | ||
|
|
0a0cf26269 | ||
|
|
425bd514c2 | ||
|
|
be5f066dc3 | ||
|
|
b2155acc56 | ||
|
|
a241eab792 | ||
|
|
8ed4fa6ff1 | ||
|
|
4289215620 | ||
|
|
7462b7ae17 | ||
|
|
decf2c7a1b | ||
|
|
43d57bbe4e | ||
|
|
a26efc7aa5 | ||
|
|
bd288ce650 | ||
|
|
9f2105b294 | ||
|
|
b3944c678c | ||
|
|
31c8c09060 | ||
|
|
4283671d49 | ||
|
|
c3a3717bde | ||
|
|
27ede0079f | ||
|
|
5cdb59142f | ||
|
|
bf6f784826 | ||
|
|
33f7fd259b | ||
|
|
bc99e04b57 | ||
|
|
10b8f97e96 | ||
|
|
4713ae5baa | ||
|
|
e47802d1e1 | ||
|
|
77face0f2d | ||
|
|
d483fbd43b | ||
|
|
13f886f07f | ||
|
|
c65e551697 | ||
|
|
b4f9d97a81 | ||
|
|
7041cae48c | ||
|
|
b5537bb714 | ||
|
|
7392f6aeff | ||
|
|
c34649cb50 | ||
|
|
fde30b6216 | ||
|
|
e0e9b2ba6f | ||
|
|
250a7da1ee | ||
|
|
f208aa206d | ||
|
|
d472978c1a | ||
|
|
1d0c522fad | ||
|
|
beea3abfc7 | ||
|
|
2dfa7d9542 | ||
|
|
22c90ca7ad | ||
|
|
2563a8f967 | ||
|
|
eb1c119c21 | ||
|
|
27d88a421e | ||
|
|
e4755c624f | ||
|
|
21ef33ea45 | ||
|
|
5b2fdf7e5b | ||
|
|
cf6605746d | ||
|
|
b061f95f6d | ||
|
|
c5a45aaded | ||
|
|
d3fac8ae07 | ||
|
|
013e961c37 | ||
|
|
b899e13763 | ||
|
|
5b3659af7c | ||
|
|
95e29d1046 | ||
|
|
aed0079cad | ||
|
|
67311fe5d6 | ||
|
|
5da1f791a5 | ||
|
|
f216ea5d65 | ||
|
|
fb95a05599 | ||
|
|
b76f01b144 | ||
|
|
35e3294dc4 | ||
|
|
81c2ff501b | ||
|
|
1bfd413040 | ||
|
|
c2ea1b5877 | ||
|
|
4dc4cc2c11 | ||
|
|
830eda7460 | ||
|
|
b69bf3d269 | ||
|
|
cee9906073 | ||
|
|
4ad249f918 | ||
|
|
b484dd8806 | ||
|
|
57758296f5 | ||
|
|
a0a1cda2e5 | ||
|
|
26f506b7ba | ||
|
|
536537553b | ||
|
|
521a72f448 | ||
|
|
d41746f29a | ||
|
|
b793a11cc8 | ||
|
|
9cda4d8928 | ||
|
|
6f351284e6 | ||
|
|
babddf2d1e | ||
|
|
73d8fbfebc | ||
|
|
7d568af9cc | ||
|
|
09933ce658 | ||
|
|
b225503dc5 | ||
|
|
a77a279539 | ||
|
|
b1e65725b5 | ||
|
|
84ebbc6993 | ||
|
|
94db75fd6c | ||
|
|
2e4397a951 | ||
|
|
2f255ce01c | ||
|
|
a94d33649b | ||
|
|
6d25eb81b6 | ||
|
|
80ad740d36 | ||
|
|
8d7c76a434 | ||
|
|
c927a1f459 | ||
|
|
88239a23f9 | ||
|
|
651af6a2c5 |
@@ -0,0 +1,31 @@
|
||||
# PR Workflow for Appwrite
|
||||
|
||||
## Branch Targeting
|
||||
|
||||
**Important:** Appwrite does NOT use `main` as the primary development branch.
|
||||
|
||||
PRs should target the **current version branch** (e.g., `1.8.x`, `1.9.x`), not `main`.
|
||||
|
||||
### How to determine the correct target branch
|
||||
|
||||
1. Check which version branch you're currently on: `git branch --show-current`
|
||||
2. Look for branches matching the pattern `X.Y.x` (e.g., `1.8.x`, `1.9.x`)
|
||||
3. The current active development branch is typically the highest version number with the `.x` suffix
|
||||
|
||||
### When creating PRs
|
||||
|
||||
Always use the version branch as the base:
|
||||
|
||||
```bash
|
||||
# Correct - targets the version branch
|
||||
gh pr create --base 1.8.x --title "Your PR title" --body "..."
|
||||
|
||||
# Wrong - do not target main
|
||||
gh pr create --base main ...
|
||||
```
|
||||
|
||||
### Branch naming convention
|
||||
|
||||
- `X.Y.x` branches (e.g., `1.8.x`) - Active development branches for each minor version
|
||||
- `main` - Not used for regular PRs; reserved for release management
|
||||
- Feature branches should be created from and merged back into the current version branch
|
||||
@@ -2,6 +2,7 @@ _APP_ENV=development
|
||||
_APP_EDITION=self-hosted
|
||||
_APP_LOCALE=en
|
||||
_APP_WORKER_PER_CORE=6
|
||||
_APP_COMPRESSION_ENABLED=enabled
|
||||
_APP_COMPRESSION_MIN_SIZE_BYTES=1024
|
||||
_APP_CONSOLE_WHITELIST_ROOT=disabled
|
||||
_APP_CONSOLE_WHITELIST_EMAILS=
|
||||
@@ -9,6 +10,7 @@ _APP_CONSOLE_SESSION_ALERTS=enabled
|
||||
_APP_CONSOLE_WHITELIST_IPS=
|
||||
_APP_CONSOLE_COUNTRIES_DENYLIST=AQ
|
||||
_APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io
|
||||
_APP_MIGRATION_HOST=appwrite
|
||||
_APP_SYSTEM_EMAIL_NAME=Appwrite
|
||||
_APP_SYSTEM_EMAIL_ADDRESS=noreply@appwrite.io
|
||||
_APP_SYSTEM_TEAM_EMAIL=team@appwrite.io
|
||||
@@ -21,13 +23,14 @@ _APP_OPTIONS_ROUTER_PROTECTION=disabled
|
||||
_APP_OPTIONS_FORCE_HTTPS=disabled
|
||||
_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled
|
||||
_APP_OPENSSL_KEY_V1=your-secret-key
|
||||
_APP_DNS=8.8.8.8
|
||||
_APP_DOMAIN=traefik
|
||||
_APP_DNS=172.16.238.100 # CoreDNS
|
||||
_APP_DOMAIN=appwrite.test
|
||||
_APP_CONSOLE_DOMAIN=localhost
|
||||
_APP_CONSOLE_TRUSTED_PROJECTS=trusted-project,another-trusted-project
|
||||
_APP_DOMAIN_FUNCTIONS=functions.localhost
|
||||
_APP_DOMAIN_SITES=sites.localhost
|
||||
_APP_DOMAIN_TARGET_CNAME=test.localhost
|
||||
_APP_DOMAIN_TARGET_A=127.0.0.1
|
||||
_APP_DOMAIN_SITES=sites.localhost,rebranded.localhost
|
||||
_APP_DOMAIN_TARGET_CNAME=cname.localhost
|
||||
_APP_DOMAIN_TARGET_A=203.0.0.1
|
||||
_APP_DOMAIN_TARGET_AAAA=::1
|
||||
_APP_DOMAIN_TARGET_CAA=digicert.com
|
||||
_APP_RULES_FORMAT=md5
|
||||
@@ -69,8 +72,8 @@ _APP_STORAGE_ANTIVIRUS_PORT=3310
|
||||
_APP_SMTP_HOST=maildev
|
||||
_APP_SMTP_PORT=1025
|
||||
_APP_SMTP_SECURE=
|
||||
_APP_SMTP_USERNAME=
|
||||
_APP_SMTP_PASSWORD=
|
||||
_APP_SMTP_USERNAME=user
|
||||
_APP_SMTP_PASSWORD=password
|
||||
_APP_SMS_PROVIDER=sms://username:password@mock
|
||||
_APP_SMS_FROM=+123456789
|
||||
_APP_SMS_PROJECTS_DENY_LIST=
|
||||
@@ -101,9 +104,12 @@ _APP_USAGE_AGGREGATION_INTERVAL=30
|
||||
_APP_STATS_RESOURCES_INTERVAL=30
|
||||
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
|
||||
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
|
||||
_APP_INTERVAL_DOMAIN_VERIFICATION=120
|
||||
_APP_INTERVAL_CLEANUP_STALE_EXECUTIONS=300
|
||||
_APP_USAGE_STATS=enabled
|
||||
_APP_LOGGING_CONFIG=
|
||||
_APP_LOGGING_CONFIG_REALTIME=
|
||||
_APP_GRAPHQL_INTROSPECTION=enabled
|
||||
_APP_GRAPHQL_MAX_BATCH_SIZE=10
|
||||
_APP_GRAPHQL_MAX_COMPLEXITY=250
|
||||
_APP_GRAPHQL_MAX_DEPTH=4
|
||||
@@ -124,4 +130,6 @@ _APP_MESSAGE_PUSH_TEST_DSN=
|
||||
_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
|
||||
_APP_PROJECT_REGIONS=default
|
||||
_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000
|
||||
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
|
||||
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
|
||||
_APP_TRUSTED_HEADERS=x-forwarded-for
|
||||
_APP_POOL_ADAPTER=stack
|
||||
Generated
+401
-215
File diff suppressed because it is too large
Load Diff
@@ -8,41 +8,68 @@ on:
|
||||
|
||||
permissions: read-all
|
||||
|
||||
# Add stricter error-detection patterns so issue text doesn't trigger agent error detection.
|
||||
# After merging this file run `gh aw compile` to regenerate the lock file.
|
||||
env:
|
||||
GH_AW_ERROR_PATTERNS: >-
|
||||
[
|
||||
{"id":"gh-action-error","pattern":"^::(error)(?:\\\\s+[^:]*)?::(.+)","level_group":1,"message_group":2,"description":"GitHub Actions workflow command - error"},
|
||||
{"id":"gh-action-warning","pattern":"^::(warning)(?:\\\\s+[^:]*)?::(.+)","level_group":1,"message_group":2,"description":"GitHub Actions workflow command - warning"},
|
||||
{"id":"bracketed-level","pattern":"^\\[(ERROR|CRITICAL|WARNING|WARN)\\]\\s+(.+)","level_group":1,"message_group":2,"description":"Bracketed log level at start of line"},
|
||||
{"id":"timestamped-copilot","pattern":"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z\\s+\\[(ERROR|WARN|WARNING|CRITICAL)\\]\\s+(.+)","level_group":1,"message_group":3,"description":"Timestamped Copilot CLI messages"}
|
||||
]
|
||||
|
||||
network: defaults
|
||||
|
||||
safe-outputs:
|
||||
add-labels:
|
||||
max: 5
|
||||
max: 100
|
||||
target: "*"
|
||||
add-comment:
|
||||
max: 10
|
||||
target: "*"
|
||||
|
||||
tools:
|
||||
web-fetch:
|
||||
web-search:
|
||||
github:
|
||||
toolsets:
|
||||
- default
|
||||
- labels
|
||||
|
||||
timeout_minutes: 10
|
||||
timeout-minutes: 10
|
||||
source: githubnext/agentics/workflows/issue-triage.md@0837fb7b24c3b84ee77fb7c8cfa8735c48be347a
|
||||
---
|
||||
# Agentic Triage
|
||||
|
||||
<!-- Note - this file can be customized to your needs. Replace this section directly, or add further instructions here. After editing run 'gh aw compile' -->
|
||||
|
||||
You're a triage assistant for GitHub issues. Your task is to analyze issues created in the last 24 hours and perform initial triage tasks for each of them.
|
||||
You're a triage assistant for GitHub issues. Your task is to analyze issues that were either created in the last 24 hours or updated (with a new comment) in the last 24 hours, and perform initial triage tasks for each of them.
|
||||
|
||||
1. First, use the `list_issues` tool to retrieve all issues created in the last 24 hours. Filter issues by using the `since` parameter with a timestamp from 24 hours ago (calculate: current time minus 24 hours in ISO 8601 format).
|
||||
1. First, use the `list_issues` tool to retrieve all issues created or updated in the last 24 hours. The `since` parameter filters by the issue's `updated_at` timestamp, which includes both newly created issues and recently commented issues. Calculate the timestamp from 24 hours ago (example: 2025-11-06T20:27:14Z for reference) and use it for the `since` parameter.
|
||||
|
||||
2. For each issue found, perform the following triage tasks:
|
||||
|
||||
3. Select appropriate labels for the issue from the provided list.
|
||||
3. Use the `get_comments` tool to retrieve all the comments on the issue.
|
||||
|
||||
4. Retrieve the issue content using the `get_issue` tool. If the issue is obviously spam, or generated by bot, or something else that is not an actual issue to be worked on, then add an issue comment to the issue with a one sentence analysis and move to the next issue.
|
||||
4. Check for spam and quality issue descriptions and comments first:
|
||||
|
||||
- **Non-English Content**: If the issue is primarily written in a non-English language, add a respectful and appreciative comment explaining that while you appreciate their contribution, the majority of the community communicates in English and kindly ask them to repost in English so everyone can follow along and help. Provide a friendly translation of your message in their language if possible.
|
||||
- **Multiple Topics**: If the issue discusses multiple unrelated topics or problems, add a comment explaining that each issue should focus on one clear topic so the team can effectively solve the right problem. Politely ask them to split it into separate issues.
|
||||
- **Obvious Spam or Bot-Generated Content**: If the issue/comment is obviously spam, generated by a bot, or something that is not an actual issue to be worked on, add an issue comment with a one-sentence analysis and move to the next issue.
|
||||
|
||||
5. Next, use the GitHub tools to gather additional context about the issue:
|
||||
5. Retrieve the issue content using the `get_issue` tool for any issues that pass the spam checks.
|
||||
|
||||
- Fetch the list of labels available in this repository. Use 'gh label list' bash command to fetch the labels. This will give you the labels you can use for triaging issues.
|
||||
- Fetch any comments on the issue using the `get_issue_comments` tool
|
||||
- **Search for duplicate and related issues**: Use the `search_issues` tool to find similar issues by searching for key terms from the issue title and description. Look for both open and closed issues that might be related or duplicates.
|
||||
6. Next, use the GitHub tools to gather additional context about the issue:
|
||||
|
||||
6. Analyze the issue content, considering:
|
||||
- Fetch the list of labels available in this repository using the `list_label` tool with `owner: "appwrite"` and `repo: "appwrite"` parameters. This will give you the labels you can use for triaging issues.
|
||||
- Fetch any comments on the issue using the `get_issue_comments` tool to understand recent activity
|
||||
- **Search for duplicate and related issues (repo first, then org-wide)**:
|
||||
- First search in this repository using the `search_issues` tool with a query like: `repo:appwrite/appwrite is:issue (is:open OR is:closed) <key terms>`.
|
||||
- Then perform an org-wide search across the entire Appwrite organization using: `org:appwrite is:issue (is:open OR is:closed) <key terms>`.
|
||||
- Prefer linking to OPEN issues when identifying potential duplicates; include CLOSED ones as related history when useful.
|
||||
|
||||
7. Analyze the issue content, considering:
|
||||
|
||||
- The issue title and description
|
||||
- The type of issue (bug report, feature request, question, etc.)
|
||||
@@ -51,35 +78,38 @@ You're a triage assistant for GitHub issues. Your task is to analyze issues crea
|
||||
- User impact
|
||||
- Components affected
|
||||
|
||||
7. Write notes, ideas, nudges, resource links, debugging strategies and/or reproduction steps for the team to consider relevant to the issue.
|
||||
8. Write notes, ideas, nudges, resource links, debugging strategies and/or reproduction steps for the team to consider relevant to the issue.
|
||||
|
||||
8. Select appropriate labels from the available labels list provided above:
|
||||
9. Select appropriate labels from the available labels list:
|
||||
|
||||
- Choose labels that accurately reflect the issue's nature
|
||||
- Be specific but comprehensive
|
||||
- Select priority labels if you can determine urgency (high-priority, med-priority, or low-priority)
|
||||
- Consider platform labels (android, ios) if applicable
|
||||
- Search for similar issues, and if you find similar issues consider using a "duplicate" label if appropriate. Only do so if the issue is a duplicate of another OPEN issue.
|
||||
- Only select labels from the provided list above
|
||||
- Search for similar issues. If you find a duplicate of another OPEN issue in THIS repository, you may use a "duplicate" label (if available) and reference the canonical issue.
|
||||
- If the closest match is in another repository within the Appwrite org, do NOT mark as duplicate here; instead, link it in your comment under a "Cross‑repo related issues" section.
|
||||
- Only select labels from the provided list
|
||||
- Don't apply the `good first issue` or `help wanted` labels
|
||||
- It's okay to not add any labels if none are clearly applicable
|
||||
|
||||
9. Apply the selected labels:
|
||||
10. Apply the selected labels:
|
||||
|
||||
- Use the `update_issue` tool to apply the labels to the issue
|
||||
- DO NOT communicate directly with users
|
||||
- If no labels are clearly applicable, do not apply any labels
|
||||
|
||||
10. Add an issue comment to the issue with your analysis:
|
||||
11. Add an issue comment to the issue with your analysis:
|
||||
- Start with "🎯 Agentic Issue Triage"
|
||||
- Provide a brief summary of the issue
|
||||
- **If duplicate or related issues were found**, add a section listing them with links (e.g., "### 🔗 Potentially Related Issues" followed by a bullet list of related issues with their titles and links)
|
||||
- **If duplicate or related issues were found**, add sections listing them with links:
|
||||
- "### 🔗 Potentially Related Issues (this repo)" – bullet list of same-repo issues with titles and links
|
||||
- If applicable: "### 🌐 Cross-repo related issues (org: appwrite)" – bullet list including `owner/repo#number` with titles and links
|
||||
- Mention any relevant details that might help the team understand the issue better
|
||||
- Include any debugging strategies or reproduction steps if applicable
|
||||
- Suggest resources or links that might be helpful for resolving the issue or learning skills related to the issue or the particular area of the codebase affected by it
|
||||
- Mention any nudges or ideas that could help the team in addressing the issue
|
||||
- If you have possible reproduction steps, include them in the comment
|
||||
- If you have any debugging strategies, include them in the comment
|
||||
- If appropriate break the issue down to sub-tasks and write a checklist of things to do.
|
||||
- Use collapsed-by-default sections in the GitHub markdown to keep the comment tidy. Collapse all sections except the short main summary at the top.
|
||||
- If appropriate break the issue down to sub-tasks and write a checklist of things to do
|
||||
- Use collapsed-by-default sections in the GitHub markdown to keep the comment tidy. Collapse all sections except the short main summary at the top. For bolded section titles, wrap the text with `<strong>` and `</strong>` to make it bold.
|
||||
- Do not indicate/encourage a community member to submit a PR for the issue.
|
||||
|
||||
11. After processing all issues, provide a summary of how many issues were triaged. If no issues were created in the last 24 hours, simply note that no new issues needed triage.
|
||||
12. After processing all issues, provide a summary of how many issues were triaged (created or updated in the last 24 hours). If no issues matched the criteria, simply note that no issues needed triage.
|
||||
|
||||
@@ -42,6 +42,7 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
target: production
|
||||
build-args: |
|
||||
VERSION=${{ steps.meta.outputs.version }}
|
||||
VITE_APPWRITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
|
||||
|
||||
@@ -20,10 +20,10 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
@@ -46,6 +46,7 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
target: production
|
||||
build-args: |
|
||||
VERSION=${{ steps.meta.outputs.version }}
|
||||
push: true
|
||||
|
||||
+11
-11
@@ -223,7 +223,7 @@ jobs:
|
||||
-e _APP_DATABASE_SHARED_TABLES \
|
||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
||||
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
|
||||
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
|
||||
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group abuseEnabled,screenshots
|
||||
|
||||
- name: Failure Logs
|
||||
if: failure()
|
||||
@@ -312,7 +312,7 @@ jobs:
|
||||
-e _APP_DATABASE_SHARED_TABLES \
|
||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
||||
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
|
||||
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
|
||||
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group abuseEnabled,screenshots
|
||||
|
||||
- name: Failure Logs
|
||||
if: failure()
|
||||
@@ -322,8 +322,8 @@ jobs:
|
||||
echo "=== OpenRuntimes Executor Logs ==="
|
||||
docker compose logs openruntimes-executor
|
||||
|
||||
e2e_dev_keys:
|
||||
name: E2E Service Test (Dev Keys)
|
||||
e2e_abuse_enabled:
|
||||
name: E2E Service Test (Abuse enabled)
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
steps:
|
||||
@@ -344,7 +344,7 @@ jobs:
|
||||
docker compose up -d
|
||||
sleep 30
|
||||
|
||||
- name: Run Projects tests with dev keys in dedicated table mode
|
||||
- name: Run Projects tests in dedicated table mode
|
||||
run: |
|
||||
echo "Using project tables"
|
||||
export _APP_DATABASE_SHARED_TABLES=
|
||||
@@ -354,7 +354,7 @@ jobs:
|
||||
-e _APP_DATABASE_SHARED_TABLES \
|
||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
||||
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
|
||||
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
|
||||
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=abuseEnabled
|
||||
|
||||
- name: Failure Logs
|
||||
if: failure()
|
||||
@@ -364,8 +364,8 @@ jobs:
|
||||
echo "=== OpenRuntimes Executor Logs ==="
|
||||
docker compose logs openruntimes-executor
|
||||
|
||||
e2e_dev_keys_shared_mode:
|
||||
name: E2E Shared Mode Service Test (Dev Keys)
|
||||
e2e_abuse_enabled_shared_mode:
|
||||
name: E2E Shared Mode Service Test (Abuse enabled)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ setup, check_database_changes ]
|
||||
if: needs.check_database_changes.outputs.database_changed == 'true'
|
||||
@@ -394,7 +394,7 @@ jobs:
|
||||
docker compose up -d
|
||||
sleep 30
|
||||
|
||||
- name: Run Projects tests with dev keys in ${{ matrix.tables-mode }} table mode
|
||||
- name: Run Projects tests in ${{ matrix.tables-mode }} table mode
|
||||
run: |
|
||||
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
|
||||
echo "Using shared tables V1"
|
||||
@@ -410,7 +410,7 @@ jobs:
|
||||
-e _APP_DATABASE_SHARED_TABLES \
|
||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
||||
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
|
||||
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
|
||||
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=abuseEnabled
|
||||
|
||||
- name: Failure Logs
|
||||
if: failure()
|
||||
@@ -420,7 +420,7 @@ jobs:
|
||||
echo "=== OpenRuntimes Executor Logs ==="
|
||||
docker compose logs openruntimes-executor
|
||||
|
||||
e2e_screenshots_keys:
|
||||
e2e_screenshots:
|
||||
name: E2E Service Test (Site Screenshots)
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# AGENTS.md
|
||||
|
||||
Appwrite is an end-to-end backend server for web, mobile, native, and backend apps. This guide provides context and instructions for AI coding agents working on the Appwrite codebase.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Appwrite is a self-hosted Backend-as-a-Service (BaaS) platform that provides developers with a set of APIs and tools to build secure, scalable applications. The project uses a hybrid monolithic-microservice architecture built with PHP, running on Swoole for high performance.
|
||||
|
||||
**Key Technologies:**
|
||||
- **Backend:** PHP 8.3+, Swoole
|
||||
- **Libraries:** Utopia PHP
|
||||
- **Database:** MariaDB, Redis
|
||||
- **Cache:** Redis
|
||||
- **Queue:** Redis
|
||||
- **Containers:** Docker
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
# Run Appwrite
|
||||
docker compose up -d --force-recreate --build
|
||||
|
||||
# Run specific test
|
||||
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName] --filter=[FunctionName]
|
||||
|
||||
# Format code
|
||||
composer format
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) coding standard
|
||||
- Use PSR-4 autoloading
|
||||
- Strict type declarations where applicable
|
||||
- Comprehensive PHPDoc comments
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
#### `resourceType` Naming Rule
|
||||
|
||||
When a collection has a combination of `resourceType`, `resourceId`, and/or `resourceInternalId`, the value of `resourceType` MUST always be **plural** - for example: `functions`, `sites`, `deployments`.
|
||||
|
||||
Examples:
|
||||
```php
|
||||
'resourceType' => 'functions'
|
||||
'resourceType' => 'sites'
|
||||
'resourceType' => 'deployments'
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Critical Security Practices
|
||||
|
||||
- **Never hardcode credentials** - Use environment variables
|
||||
- **Rate limiting** - Respect abuse prevention mechanisms
|
||||
|
||||
## Dependencies
|
||||
|
||||
Avoid introducing new dependencies other than utopia-php.
|
||||
|
||||
## Adding new endpoints
|
||||
|
||||
When adding new endpoints, make sure to use modules and follow its patterns. Find instruction in [Modules AGENTS.md](src/Appwrite/Platform/AGENTS.md) file.
|
||||
|
||||
## Pull Request Guidelines
|
||||
### Before Submitting
|
||||
|
||||
- Run `composer format`
|
||||
- Update documentation if adding features
|
||||
- Add/update tests for your changes
|
||||
- Check that Docker build succeeds
|
||||
`docs/specs/authentication.drawio.svg`
|
||||
|
||||
## Known Issues and Gotchas
|
||||
|
||||
- **Hot Reload:** Code changes require container restart in some cases
|
||||
- **Logging:** There is no central place for logs, so when debugging, ensure to check all possibly relevant containers
|
||||
+98
@@ -1,3 +1,101 @@
|
||||
# Version 1.8.1
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Notable changes
|
||||
|
||||
* Add branch deployments support in [#10486](https://github.com/appwrite/appwrite/pull/10486)
|
||||
* Add TanStack Start sites support in [#10681](https://github.com/appwrite/appwrite/pull/10681)
|
||||
* Add Next.js standalone support in [#10747](https://github.com/appwrite/appwrite/pull/10747)
|
||||
* Add Resend integration in [#10690](https://github.com/appwrite/appwrite/pull/10690)
|
||||
* Add option to enable/disable image transformations per-bucket in [#10722](https://github.com/appwrite/appwrite/pull/10722)
|
||||
* Add operators support in [#10735](https://github.com/appwrite/appwrite/pull/10735) and [#10800](https://github.com/appwrite/appwrite/pull/10800)
|
||||
* Add function and sites stats in [#10786](https://github.com/appwrite/appwrite/pull/10786)
|
||||
* Add disable count feature in [#10668](https://github.com/appwrite/appwrite/pull/10668)
|
||||
* Add ElevenLabs site template in [#10782](https://github.com/appwrite/appwrite/pull/10782)
|
||||
* Add suggested environment variables in [#10795](https://github.com/appwrite/appwrite/pull/10795)
|
||||
* Update GeoDB database in [#10890](https://github.com/appwrite/appwrite/pull/10890)
|
||||
* Update Flutter default build runtime in [#10807](https://github.com/appwrite/appwrite/pull/10807)
|
||||
* Upgrade runtimes in [#10804](https://github.com/appwrite/appwrite/pull/10804)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix duplicate document error while creating file in [#10891](https://github.com/appwrite/appwrite/pull/10891)
|
||||
* Fix "Update external deployment (authorize)" throwing 500 error due to invalid query in [#10888](https://github.com/appwrite/appwrite/pull/10888)
|
||||
* Fix error setting user password in [#10889](https://github.com/appwrite/appwrite/pull/10889)
|
||||
* Fix error generating email MFA challenges in [#10884](https://github.com/appwrite/appwrite/pull/10884)
|
||||
* Fix file token expiry in [#10877](https://github.com/appwrite/appwrite/pull/10877)
|
||||
* Fix TanStack Nitro default in [#10860](https://github.com/appwrite/appwrite/pull/10860)
|
||||
* Fix TanStack builds in [#10767](https://github.com/appwrite/appwrite/pull/10767)
|
||||
* Fix nullable validation in [#10819](https://github.com/appwrite/appwrite/pull/10819) and [#10778](https://github.com/appwrite/appwrite/pull/10778)
|
||||
* Fix WebP library in [#10738](https://github.com/appwrite/appwrite/pull/10738)
|
||||
* Fix batch writes in [#10812](https://github.com/appwrite/appwrite/pull/10812)
|
||||
* Fix error handler error in [#10719](https://github.com/appwrite/appwrite/pull/10719)
|
||||
* Fix Next 16 compatibility in [#10713](https://github.com/appwrite/appwrite/pull/10713)
|
||||
* Fix stats usage memory leak in [#10683](https://github.com/appwrite/appwrite/pull/10683)
|
||||
* Fix author URL in template deployments in [#10535](https://github.com/appwrite/appwrite/pull/10535)
|
||||
* Fix VCS lock deletion in [#10691](https://github.com/appwrite/appwrite/pull/10691)
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Add CSV export functionality in [#10546](https://github.com/appwrite/appwrite/pull/10546), [#10750](https://github.com/appwrite/appwrite/pull/10750), [#10813](https://github.com/appwrite/appwrite/pull/10813), and [#10847](https://github.com/appwrite/appwrite/pull/10847)
|
||||
* Add JWT disposition in [#10867](https://github.com/appwrite/appwrite/pull/10867)
|
||||
* Add screenshots endpoint in [#10675](https://github.com/appwrite/appwrite/pull/10675)
|
||||
* Add screenshot endpoint stats in [#10706](https://github.com/appwrite/appwrite/pull/10706)
|
||||
* Add users attributes in [#10688](https://github.com/appwrite/appwrite/pull/10688)
|
||||
* Add max build duration environment variable in [#10674](https://github.com/appwrite/appwrite/pull/10674)
|
||||
* Add custom realtime logger in [#10871](https://github.com/appwrite/appwrite/pull/10871)
|
||||
* Add logs in [#10869](https://github.com/appwrite/appwrite/pull/10869)
|
||||
* Improve MFA docs endpoint order in [#10793](https://github.com/appwrite/appwrite/pull/10793)
|
||||
* Auth refactor in [#10758](https://github.com/appwrite/appwrite/pull/10758), [#10837](https://github.com/appwrite/appwrite/pull/10837), [#10682](https://github.com/appwrite/appwrite/pull/10682), and [#10667](https://github.com/appwrite/appwrite/pull/10667)
|
||||
* Bump assistant to 0.8.4 in [#10887](https://github.com/appwrite/appwrite/pull/10887)
|
||||
* Bump database to 3.1.5 in [#10766](https://github.com/appwrite/appwrite/pull/10766)
|
||||
* Bump Utopia DNS in [#10761](https://github.com/appwrite/appwrite/pull/10761)
|
||||
* Update domains to 0.8.3 in [#10658](https://github.com/appwrite/appwrite/pull/10658)
|
||||
* Update domains to 0.9.1 in [#10678](https://github.com/appwrite/appwrite/pull/10678)
|
||||
* Update Apple Swift to 13.3.0 in [#10679](https://github.com/appwrite/appwrite/pull/10679)
|
||||
* Update Apple Swift in [#10663](https://github.com/appwrite/appwrite/pull/10663)
|
||||
* Update CLI to 10.2.2 in [#10672](https://github.com/appwrite/appwrite/pull/10672)
|
||||
* Update to CLI 12.0.0 in [#10853](https://github.com/appwrite/appwrite/pull/10853)
|
||||
* Update docs examples to use Permission class in [#10707](https://github.com/appwrite/appwrite/pull/10707)
|
||||
* Update SDK examples docs in [#10855](https://github.com/appwrite/appwrite/pull/10855)
|
||||
* Release Python SDK in [#10762](https://github.com/appwrite/appwrite/pull/10762)
|
||||
* Release Flutter 20.3.2 in [#10838](https://github.com/appwrite/appwrite/pull/10838)
|
||||
* Release Flutter/Dart add screenshot examples in [#10811](https://github.com/appwrite/appwrite/pull/10811)
|
||||
* Release PHP CLI in [#10791](https://github.com/appwrite/appwrite/pull/10791)
|
||||
* Release SDKs in [#10817](https://github.com/appwrite/appwrite/pull/10817)
|
||||
* Update SDKs in [#10694](https://github.com/appwrite/appwrite/pull/10694), [#10729](https://github.com/appwrite/appwrite/pull/10729), and [#10744](https://github.com/appwrite/appwrite/pull/10744)
|
||||
* Update SDK generator in [#10743](https://github.com/appwrite/appwrite/pull/10743)
|
||||
* Update database in [#10664](https://github.com/appwrite/appwrite/pull/10664)
|
||||
* Update README file in [#10763](https://github.com/appwrite/appwrite/pull/10763)
|
||||
* SDK release documentation in [#10745](https://github.com/appwrite/appwrite/pull/10745)
|
||||
* SDK release runtime config in [#10765](https://github.com/appwrite/appwrite/pull/10765)
|
||||
* Sync specs in [#10789](https://github.com/appwrite/appwrite/pull/10789)
|
||||
* Sync 1.8.0 in [#10677](https://github.com/appwrite/appwrite/pull/10677)
|
||||
* Add workflow for issue triage in [#10718](https://github.com/appwrite/appwrite/pull/10718)
|
||||
* Add issue auto-labeler in [#10700](https://github.com/appwrite/appwrite/pull/10700)
|
||||
* Add AI moderator repo in [#10717](https://github.com/appwrite/appwrite/pull/10717)
|
||||
* Browser bump in [#10850](https://github.com/appwrite/appwrite/pull/10850)
|
||||
* Template type enum override in [#10848](https://github.com/appwrite/appwrite/pull/10848)
|
||||
* VCS reference type in [#10852](https://github.com/appwrite/appwrite/pull/10852)
|
||||
* Index scope description in [#10851](https://github.com/appwrite/appwrite/pull/10851)
|
||||
* Config for environment in [#10833](https://github.com/appwrite/appwrite/pull/10833)
|
||||
* Format instance in [#10830](https://github.com/appwrite/appwrite/pull/10830)
|
||||
* Replace sleep in webhooks service in [#10656](https://github.com/appwrite/appwrite/pull/10656)
|
||||
* Update email composer in [#10720](https://github.com/appwrite/appwrite/pull/10720)
|
||||
* Update facts on GitHub sites and functions in [#10593](https://github.com/appwrite/appwrite/pull/10593) and [#10771](https://github.com/appwrite/appwrite/pull/10771)
|
||||
* Fix wrong user type in [#10875](https://github.com/appwrite/appwrite/pull/10875)
|
||||
* Fix limit and offset computation in [#10880](https://github.com/appwrite/appwrite/pull/10880)
|
||||
* Fix enum examples in [#10828](https://github.com/appwrite/appwrite/pull/10828)
|
||||
* Fix response models multi-methods in [#10815](https://github.com/appwrite/appwrite/pull/10815)
|
||||
* Fix undefined variable in [#10654](https://github.com/appwrite/appwrite/pull/10654)
|
||||
* Fix undefined sequence in [#10652](https://github.com/appwrite/appwrite/pull/10652)
|
||||
* Fix description in [#10702](https://github.com/appwrite/appwrite/pull/10702)
|
||||
* Fix warning in builds worker in [#10705](https://github.com/appwrite/appwrite/pull/10705)
|
||||
* Fix sites create deployment docs in [#10566](https://github.com/appwrite/appwrite/pull/10566)
|
||||
* Fix test dependencies projects in [#10655](https://github.com/appwrite/appwrite/pull/10655)
|
||||
* Fix list sites test in [#10726](https://github.com/appwrite/appwrite/pull/10726)
|
||||
|
||||
# Version 1.8.0
|
||||
|
||||
## What's Changed
|
||||
|
||||
+26
-10
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
|
||||
--no-plugins --no-scripts --prefer-dist \
|
||||
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
|
||||
|
||||
FROM appwrite/base:0.10.5 AS final
|
||||
FROM appwrite/base:0.11.5 AS base
|
||||
|
||||
LABEL maintainer="team@appwrite.io"
|
||||
|
||||
@@ -36,9 +36,7 @@ COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor
|
||||
COPY ./app /usr/src/code/app
|
||||
COPY ./public /usr/src/code/public
|
||||
COPY ./bin /usr/local/bin
|
||||
COPY ./docs /usr/src/code/docs
|
||||
COPY ./src /usr/src/code/src
|
||||
COPY ./dev /usr/src/code/dev
|
||||
|
||||
# Set Volumes
|
||||
RUN mkdir -p /storage/uploads && \
|
||||
@@ -59,6 +57,7 @@ RUN mkdir -p /storage/uploads && \
|
||||
# Executables
|
||||
RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/install && \
|
||||
chmod +x /usr/local/bin/interval && \
|
||||
chmod +x /usr/local/bin/maintenance && \
|
||||
chmod +x /usr/local/bin/migrate && \
|
||||
chmod +x /usr/local/bin/realtime && \
|
||||
@@ -78,9 +77,11 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/queue-count-success && \
|
||||
chmod +x /usr/local/bin/worker-audits && \
|
||||
chmod +x /usr/local/bin/worker-builds && \
|
||||
chmod +x /usr/local/bin/worker-screenshots && \
|
||||
chmod +x /usr/local/bin/worker-certificates && \
|
||||
chmod +x /usr/local/bin/worker-databases && \
|
||||
chmod +x /usr/local/bin/worker-deletes && \
|
||||
chmod +x /usr/local/bin/worker-executions && \
|
||||
chmod +x /usr/local/bin/worker-functions && \
|
||||
chmod +x /usr/local/bin/worker-mails && \
|
||||
chmod +x /usr/local/bin/worker-messaging && \
|
||||
@@ -90,15 +91,30 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/stats-resources && \
|
||||
chmod +x /usr/local/bin/worker-stats-resources
|
||||
|
||||
# Letsencrypt Permissions
|
||||
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
||||
|
||||
# Enable Extensions
|
||||
RUN if [ "$DEBUG" = "true" ]; then cp /usr/src/code/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini; fi
|
||||
RUN if [ "$DEBUG" = "true" ]; then mkdir -p /tmp/xdebug; fi
|
||||
RUN if [ "$DEBUG" = "true" ]; then apk add --update --no-cache openssh-client github-cli; fi
|
||||
RUN if [ "$DEBUG" = "false" ]; then rm -rf /usr/src/code/dev; fi
|
||||
RUN if [ "$DEBUG" = "false" ]; then rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20230831/xdebug.so; fi
|
||||
FROM base AS production
|
||||
|
||||
RUN rm -rf /usr/src/code/app/config/specs && \
|
||||
rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20240924/xdebug.so && \
|
||||
find /usr -name '*.a' -delete 2>/dev/null || true && \
|
||||
find /usr -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true && \
|
||||
find /usr -name '*.pyc' -delete 2>/dev/null || true
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD [ "php", "app/http.php" ]
|
||||
|
||||
FROM base AS development
|
||||
|
||||
COPY ./docs /usr/src/code/docs
|
||||
COPY ./dev /usr/src/code/dev
|
||||
|
||||
RUN if [ "$DEBUG" = "true" ]; then \
|
||||
cp /usr/src/code/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini && \
|
||||
mkdir -p /tmp/xdebug && \
|
||||
apk add --update --no-cache openssh-client github-cli; \
|
||||
fi
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
|
||||
+26
-29
@@ -2,7 +2,7 @@
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite banner with logo and slogan build like a team of hundreds""></a>
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite banner, with logo and text saying "The Developer's Cloud""></a>
|
||||
<br />
|
||||
<br />
|
||||
<b>适用于[Flutter/Vue/Angular/React/iOS/Android/* 等等平台 *]的完整后端服务</b>
|
||||
@@ -36,7 +36,6 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
|
||||
|
||||
内容:
|
||||
|
||||
|
||||
- [开始](#开始)
|
||||
- [安装](#安装)
|
||||
- [Unix](#unix)
|
||||
@@ -57,7 +56,8 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
|
||||
- [版权说明](#版权说明)
|
||||
|
||||
## 开始
|
||||
要轻松开始使用Appwrite,您可以[**免费注册Appwrite Cloud**](https://cloud.appwrite.io/)。在Appwrite Cloud公开测试版期间,您可以完全免费使用Appwrite,而且我们不会收集您的信用卡信息。
|
||||
|
||||
要轻松开始使用 Appwrite,您可以[**免费注册 Appwrite Cloud**](https://cloud.appwrite.io/)。在 Appwrite Cloud 公开测试版期间,您可以完全免费使用 Appwrite,而且我们不会收集您的信用卡信息。
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -72,7 +72,7 @@ docker run -it --rm \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.8.0
|
||||
appwrite/appwrite:1.8.1
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -84,7 +84,7 @@ docker run -it --rm ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.8.0
|
||||
appwrite/appwrite:1.8.1
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -94,7 +94,7 @@ docker run -it --rm `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.8.0
|
||||
appwrite/appwrite:1.8.1
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
@@ -119,22 +119,16 @@ docker run -it --rm `
|
||||
<br /><sub><b>DigitalOcean</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://gitpod.io/#https://github.com/appwrite/integration-for-gitpod">
|
||||
<img width="50" height="39" src="public/images/integrations/gitpod-logo.svg" alt="Gitpod Logo" />
|
||||
<br /><sub><b>Gitpod</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://www.linode.com/marketplace/apps/appwrite/appwrite/">
|
||||
<img width="50" height="39" src="public/images/integrations/akamai-logo.svg" alt="Akamai Logo" />
|
||||
<br /><sub><b>Akamai Compute</b></sub></a>
|
||||
<br /><sub><b>Akamai Compute</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://aws.amazon.com/marketplace/pp/prodview-2hiaeo2px4md6">
|
||||
<img width="50" height="39" src="public/images/integrations/aws-logo.svg" alt="AWS Logo" />
|
||||
<br /><sub><b>AWS Marketplace</b></sub></a>
|
||||
<br /><sub><b>AWS Marketplace</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -188,25 +182,28 @@ docker run -it --rm `
|
||||
以下是当前支持的平台和语言列表。如果您想帮助我们为您选择的平台添加支持,您可以访问我们的 [SDK 生成器](https://github.com/appwrite/sdk-generator) 项目并查看我们的 [贡献指南](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md)。
|
||||
|
||||
#### 客户端
|
||||
* :white_check_mark: [Web](https://github.com/appwrite/sdk-for-web) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Flutter](https://github.com/appwrite/sdk-for-flutter) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Android](https://github.com/appwrite/sdk-for-android) (由 Appwrite 团队维护)
|
||||
|
||||
- :white_check_mark: [Web](https://github.com/appwrite/sdk-for-web) (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Flutter](https://github.com/appwrite/sdk-for-flutter) (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Android](https://github.com/appwrite/sdk-for-android) (由 Appwrite 团队维护)
|
||||
|
||||
#### 服务器
|
||||
* :white_check_mark: [NodeJS](https://github.com/appwrite/sdk-for-node) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [PHP](https://github.com/appwrite/sdk-for-php) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Dart](https://github.com/appwrite/sdk-for-dart) - (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Deno](https://github.com/appwrite/sdk-for-deno) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Ruby](https://github.com/appwrite/sdk-for-ruby) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Python](https://github.com/appwrite/sdk-for-python) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Kotlin](https://github.com/appwrite/sdk-for-kotlin) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [.NET](https://github.com/appwrite/sdk-for-dotnet) - **公测** (由 Appwrite 团队维护)
|
||||
|
||||
- :white_check_mark: [NodeJS](https://github.com/appwrite/sdk-for-node) (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [PHP](https://github.com/appwrite/sdk-for-php) (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Dart](https://github.com/appwrite/sdk-for-dart) - (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Deno](https://github.com/appwrite/sdk-for-deno) - **公测** (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Ruby](https://github.com/appwrite/sdk-for-ruby) (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Python](https://github.com/appwrite/sdk-for-python) (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Kotlin](https://github.com/appwrite/sdk-for-kotlin) - **公测** (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [.NET](https://github.com/appwrite/sdk-for-dotnet) - **公测** (由 Appwrite 团队维护)
|
||||
|
||||
#### 开发者社区
|
||||
* :white_check_mark: [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (维护者 [Michael Gangolf](https://github.com/m1ga/))
|
||||
* :white_check_mark: [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (维护者 [fenix-hub @GodotNuts](https://github.com/fenix-hub))
|
||||
|
||||
- :white_check_mark: [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (维护者 [Michael Gangolf](https://github.com/m1ga/))
|
||||
- :white_check_mark: [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (维护者 [fenix-hub @GodotNuts](https://github.com/fenix-hub))
|
||||
|
||||
找不到需要的的 SDK? - 欢迎通过发起 PR 来帮助我们完善 Appwrite 的软件生态环境 [SDK 生成器](https://github.com/appwrite/sdk-generator)!
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite banner, with logo and text saying "Build Like a Team of Hundreds"></a>
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite banner, with logo and text saying "The Developer's Cloud"></a>
|
||||
<br />
|
||||
<br />
|
||||
<b>Appwrite is an all-in-one development platform for Web, Mobile, and Flutter applications. Use built-in backend infrastructure and web hosting, all from a single place. Built with the open source community and optimized for developer experience in the coding languages you love.</b>
|
||||
<b>Appwrite is a best-in-class, developer-first platform that gives builders everything they need to create scalable, stable, and production-ready software, fast.</b>
|
||||
<br />
|
||||
<br />
|
||||
</p>
|
||||
@@ -32,13 +32,6 @@ Appwrite is an end-to-end platform for building Web, Mobile, Native, or Backend
|
||||
|
||||
Using Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, messaging, and [more services](https://appwrite.io/docs).
|
||||
|
||||
<p align="center">
|
||||
<br />
|
||||
<a href="https://www.producthunt.com/posts/appwrite-2?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-appwrite-2" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=360315&theme=light&period=daily" alt="Appwrite - 100% open source alternative for Firebase | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
<br />
|
||||
<br />
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
Find out more at: [https://appwrite.io](https://appwrite.io).
|
||||
@@ -82,7 +75,7 @@ docker run -it --rm \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.8.0
|
||||
appwrite/appwrite:1.8.1
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -94,7 +87,7 @@ docker run -it --rm ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.8.0
|
||||
appwrite/appwrite:1.8.1
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -104,7 +97,7 @@ docker run -it --rm `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.8.0
|
||||
appwrite/appwrite:1.8.1
|
||||
```
|
||||
|
||||
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
|
||||
@@ -129,12 +122,6 @@ Choose from one of the providers below:
|
||||
<br /><sub><b>DigitalOcean</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://gitpod.io/#https://github.com/appwrite/integration-for-gitpod">
|
||||
<img width="50" height="39" src="public/images/integrations/gitpod-logo.svg" alt="Gitpod Logo" />
|
||||
<br /><sub><b>Gitpod</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://www.linode.com/marketplace/apps/appwrite/appwrite/">
|
||||
<img width="50" height="39" src="public/images/integrations/akamai-logo.svg" alt="Akamai Logo" />
|
||||
|
||||
+35
-10
@@ -30,6 +30,9 @@ use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Broker\Pool as BrokerPool;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Span\Exporter;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\Span\Storage;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter\None as NoTelemetry;
|
||||
|
||||
@@ -41,8 +44,7 @@ Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
|
||||
// require controllers after overwriting runtimes
|
||||
require_once __DIR__ . '/controllers/general.php';
|
||||
|
||||
Authorization::disable();
|
||||
|
||||
global $register;
|
||||
CLI::setResource('register', fn () => $register);
|
||||
|
||||
CLI::setResource('cache', function ($pools) {
|
||||
@@ -60,7 +62,13 @@ CLI::setResource('pools', function (Registry $register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
|
||||
CLI::setResource('dbForPlatform', function ($pools, $cache) {
|
||||
CLI::setResource('authorization', function () {
|
||||
$authorization = new Authorization();
|
||||
$authorization->disable();
|
||||
return $authorization;
|
||||
}, []);
|
||||
|
||||
CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
|
||||
$sleep = 3;
|
||||
$maxAttempts = 5;
|
||||
$attempts = 0;
|
||||
@@ -74,6 +82,8 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
|
||||
$dbForPlatform = new Database($adapter, $cache);
|
||||
|
||||
$dbForPlatform
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console');
|
||||
@@ -99,7 +109,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
|
||||
}
|
||||
|
||||
return $dbForPlatform;
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
CLI::setResource('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
@@ -110,10 +120,10 @@ CLI::setResource(
|
||||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
|
||||
);
|
||||
|
||||
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -146,6 +156,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
|
||||
|
||||
$adapter = new DatabasePool($pools->get($dsn->getHost()));
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$databases[$dsn->getHost()] = $database;
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
@@ -162,17 +173,19 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
|
||||
}
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForPlatform', 'cache']);
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$database = null;
|
||||
|
||||
return function (?Document $project = null) use ($pools, $cache, $database) {
|
||||
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant((int)$project->getSequence());
|
||||
return $database;
|
||||
@@ -182,6 +195,8 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setSharedTables(true)
|
||||
->setNamespace('logsV1')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
|
||||
@@ -194,7 +209,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
CLI::setResource('publisher', function (Group $pools) {
|
||||
return new BrokerPool(publisher: $pools->get('publisher'));
|
||||
}, ['pools']);
|
||||
@@ -257,6 +272,14 @@ CLI::setResource('logError', function (Registry $register) {
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $error->getTrace());
|
||||
|
||||
if ($error->getPrevious() !== null) {
|
||||
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
|
||||
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
|
||||
}
|
||||
$log->addExtra('previousFile', $error->getPrevious()->getFile());
|
||||
$log->addExtra('previousLine', $error->getPrevious()->getLine());
|
||||
}
|
||||
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
@@ -306,4 +329,6 @@ $cli
|
||||
$cli->shutdown()->action(fn () => Timer::clearAll());
|
||||
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
Span::setStorage(new Storage\Coroutine());
|
||||
Span::addExporter(new Exporter\Stdout());
|
||||
run($cli->run(...));
|
||||
|
||||
@@ -26,8 +26,8 @@ unset($common['files']);
|
||||
$collections = [
|
||||
'buckets' => $buckets,
|
||||
'databases' => $databases,
|
||||
'projects' => array_merge($projects, $common),
|
||||
'console' => array_merge($platform, $common),
|
||||
'projects' => array_merge_recursive($projects, $common),
|
||||
'console' => array_merge_recursive($platform, $common),
|
||||
'logs' => $logs,
|
||||
];
|
||||
|
||||
|
||||
@@ -1288,6 +1288,17 @@ return [
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('labels'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
||||
@@ -6,7 +6,7 @@ use Utopia\Database\Helpers\ID;
|
||||
|
||||
$providers = Config::getParam('oAuthProviders', []);
|
||||
|
||||
return [
|
||||
$platformCollections = [
|
||||
'projects' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('projects'),
|
||||
@@ -330,7 +330,18 @@ return [
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('labels'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -622,24 +633,35 @@ return [
|
||||
'name' => 'keys',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('projectInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('projectId'),
|
||||
'$id' => ID::custom('resourceType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('resourceId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('resourceInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
@@ -712,14 +734,14 @@ return [
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_project'),
|
||||
'$id' => ID::custom('_key_resource'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
'attributes' => ['resourceType', 'resourceInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_accessedAt',
|
||||
'$id' => ID::custom('_key_accessedAt'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['accessedAt'],
|
||||
'lengths' => [],
|
||||
@@ -1317,6 +1339,17 @@ return [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('logs'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 1000000,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -1351,21 +1384,21 @@ return [
|
||||
'$id' => '_key_type',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['type'],
|
||||
'lengths' => [32],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_trigger',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['trigger'],
|
||||
'lengths' => [32],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_deploymentResourceType',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['deploymentResourceType'],
|
||||
'lengths' => [32],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
@@ -1407,23 +1440,23 @@ return [
|
||||
'$id' => ID::custom('_key_owner'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['owner'],
|
||||
'lengths' => [16],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_region'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['region'],
|
||||
'lengths' => [16],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_piid_riid_rt'),
|
||||
'$id' => ID::custom('_key_piid_diid_drt'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId', 'deploymentInternalId', 'deploymentResourceType'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_region_status_createdAt',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['region', 'status', '$createdAt'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -1892,3 +1925,31 @@ return [
|
||||
'indexes' => []
|
||||
],
|
||||
];
|
||||
|
||||
// Organization API keys subquery
|
||||
$platformCollections['teams']['attributes'][] = [
|
||||
'$id' => ID::custom('keys'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['subQueryOrganizationKeys'],
|
||||
];
|
||||
|
||||
// Account API keys subquery
|
||||
$platformCollections['users']['attributes'][] = [
|
||||
'$id' => ID::custom('keys'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['subQueryAccountKeys'],
|
||||
];
|
||||
|
||||
return $platformCollections;
|
||||
|
||||
@@ -567,6 +567,17 @@ return [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deploymentRetention'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deploymentInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -765,6 +776,17 @@ return [
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('startCommand'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 20000,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('specification'),
|
||||
@@ -776,6 +798,28 @@ return [
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('buildSpecification'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('runtimeSpecification'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('scopes'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1035,6 +1079,17 @@ return [
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('startCommand'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 20000,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('fallbackFile'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1046,6 +1101,17 @@ return [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deploymentRetention'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deploymentInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1200,6 +1266,28 @@ return [
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('buildSpecification'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('runtimeSpecification'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('buildRuntime'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1357,6 +1445,17 @@ return [
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('startCommand'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 20000,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('buildOutput'),
|
||||
@@ -2098,6 +2197,13 @@ return [
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_resourceType'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['resourceType'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
+67
-38
@@ -357,6 +357,11 @@ return [
|
||||
'description' => 'API key and session used in the same request. Use either `setSession` or `setKey`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::USER_JWT_AND_COOKIE_SET => [
|
||||
'name' => Exception::USER_JWT_AND_COOKIE_SET,
|
||||
'description' => 'JWT and cookie used in the same request. Use either `setJWT` or `setCookie`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::API_KEY_EXPIRED => [
|
||||
'name' => Exception::API_KEY_EXPIRED,
|
||||
'description' => 'The dynamic API key has expired. Please don\'t use dynamic API keys for more than duration of the execution.',
|
||||
@@ -688,12 +693,12 @@ return [
|
||||
/** Databases */
|
||||
Exception::DATABASE_NOT_FOUND => [
|
||||
'name' => Exception::DATABASE_NOT_FOUND,
|
||||
'description' => 'Database not found',
|
||||
'description' => 'Database with the requested ID \'%s\' could not be found.',
|
||||
'code' => 404
|
||||
],
|
||||
Exception::DATABASE_ALREADY_EXISTS => [
|
||||
'name' => Exception::DATABASE_ALREADY_EXISTS,
|
||||
'description' => 'Database already exists',
|
||||
'description' => 'Database with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409
|
||||
],
|
||||
Exception::DATABASE_TIMEOUT => [
|
||||
@@ -710,41 +715,41 @@ return [
|
||||
/** Collections */
|
||||
Exception::COLLECTION_NOT_FOUND => [
|
||||
'name' => Exception::COLLECTION_NOT_FOUND,
|
||||
'description' => 'Collection with the requested ID could not be found.',
|
||||
'description' => 'Collection with the requested ID \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::COLLECTION_ALREADY_EXISTS => [
|
||||
'name' => Exception::COLLECTION_ALREADY_EXISTS,
|
||||
'description' => 'A collection with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'description' => 'A collection with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::COLLECTION_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::COLLECTION_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of collections has been reached.',
|
||||
'description' => 'The maximum number of collections for database \'%s\' has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Tables */
|
||||
Exception::TABLE_NOT_FOUND => [
|
||||
'name' => Exception::TABLE_NOT_FOUND,
|
||||
'description' => 'Table with the requested ID could not be found.',
|
||||
'description' => 'Table with the requested ID \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::TABLE_ALREADY_EXISTS => [
|
||||
'name' => Exception::TABLE_ALREADY_EXISTS,
|
||||
'description' => 'A table with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'description' => 'A table with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::TABLE_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::TABLE_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of tables has been reached.',
|
||||
'description' => 'The maximum number of tables for database \'%s\' has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Documents */
|
||||
Exception::DOCUMENT_NOT_FOUND => [
|
||||
'name' => Exception::DOCUMENT_NOT_FOUND,
|
||||
'description' => 'Document with the requested ID could not be found.',
|
||||
'description' => 'Document with the requested ID \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::DOCUMENT_INVALID_STRUCTURE => [
|
||||
@@ -764,7 +769,7 @@ return [
|
||||
],
|
||||
Exception::DOCUMENT_ALREADY_EXISTS => [
|
||||
'name' => Exception::DOCUMENT_ALREADY_EXISTS,
|
||||
'description' => 'Document with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'description' => 'Document with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::DOCUMENT_UPDATE_CONFLICT => [
|
||||
@@ -781,7 +786,7 @@ return [
|
||||
/** Rows */
|
||||
Exception::ROW_NOT_FOUND => [
|
||||
'name' => Exception::ROW_NOT_FOUND,
|
||||
'description' => 'Row with the requested ID could not be found.',
|
||||
'description' => 'Row with the requested ID \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::ROW_INVALID_STRUCTURE => [
|
||||
@@ -791,7 +796,7 @@ return [
|
||||
],
|
||||
Exception::ROW_MISSING_DATA => [
|
||||
'name' => Exception::ROW_MISSING_DATA,
|
||||
'description' => 'The row data is missing. Try again with row data populated',
|
||||
'description' => 'The row data is missing. Try again with row data populated.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ROW_MISSING_PAYLOAD => [
|
||||
@@ -801,7 +806,7 @@ return [
|
||||
],
|
||||
Exception::ROW_ALREADY_EXISTS => [
|
||||
'name' => Exception::ROW_ALREADY_EXISTS,
|
||||
'description' => 'Row with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'description' => 'Row with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::ROW_UPDATE_CONFLICT => [
|
||||
@@ -818,17 +823,17 @@ return [
|
||||
/** Attributes */
|
||||
Exception::ATTRIBUTE_NOT_FOUND => [
|
||||
'name' => Exception::ATTRIBUTE_NOT_FOUND,
|
||||
'description' => 'Attribute with the requested ID could not be found.',
|
||||
'description' => 'Attribute with the requested key \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::ATTRIBUTE_UNKNOWN => [
|
||||
'name' => Exception::ATTRIBUTE_UNKNOWN,
|
||||
'description' => 'The attribute required for the index could not be found. Please confirm all your attributes are in the available state.',
|
||||
'description' => 'The attribute \'%s\' required for the index could not be found. Please confirm all your attributes are in the available state.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_NOT_AVAILABLE => [
|
||||
'name' => Exception::ATTRIBUTE_NOT_AVAILABLE,
|
||||
'description' => 'The requested attribute is not yet available. Please try again later.',
|
||||
'description' => 'The requested attribute \'%s\' is not yet available. Please try again later.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_FORMAT_UNSUPPORTED => [
|
||||
@@ -843,12 +848,12 @@ return [
|
||||
],
|
||||
Exception::ATTRIBUTE_ALREADY_EXISTS => [
|
||||
'name' => Exception::ATTRIBUTE_ALREADY_EXISTS,
|
||||
'description' => 'Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.',
|
||||
'description' => 'Attribute with the requested key \'%s\' already exists. Attribute keys must be unique, try again with a different key.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::ATTRIBUTE_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::ATTRIBUTE_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number or size of attributes for this collection has been reached.',
|
||||
'description' => 'The maximum number or size of attributes for collection \'%s\' has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_VALUE_INVALID => [
|
||||
@@ -858,7 +863,7 @@ return [
|
||||
],
|
||||
Exception::ATTRIBUTE_TYPE_INVALID => [
|
||||
'name' => Exception::ATTRIBUTE_TYPE_INVALID,
|
||||
'description' => 'The attribute type is invalid.',
|
||||
'description' => 'The attribute \'%s\' type is invalid.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_INVALID_RESIZE => [
|
||||
@@ -869,7 +874,7 @@ return [
|
||||
|
||||
Exception::ATTRIBUTE_TYPE_NOT_SUPPORTED => [
|
||||
'name' => Exception::ATTRIBUTE_TYPE_NOT_SUPPORTED,
|
||||
'description' => 'Attribute type is not supported.',
|
||||
'description' => 'Attribute type \'%s\' is not supported.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
@@ -883,17 +888,17 @@ return [
|
||||
/** Columns */
|
||||
Exception::COLUMN_NOT_FOUND => [
|
||||
'name' => Exception::COLUMN_NOT_FOUND,
|
||||
'description' => 'Column with the requested ID could not be found.',
|
||||
'description' => 'Column with the requested key \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::COLUMN_UNKNOWN => [
|
||||
'name' => Exception::COLUMN_UNKNOWN,
|
||||
'description' => 'The column required for the index could not be found. Please confirm all your columns are in the available state.',
|
||||
'description' => 'The column \'%s\' required for the index could not be found. Please confirm all your columns are in the available state.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::COLUMN_NOT_AVAILABLE => [
|
||||
'name' => Exception::COLUMN_NOT_AVAILABLE,
|
||||
'description' => 'The requested column is not yet available. Please try again later.',
|
||||
'description' => 'The requested column \'%s\' is not yet available. Please try again later.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::COLUMN_FORMAT_UNSUPPORTED => [
|
||||
@@ -908,12 +913,12 @@ return [
|
||||
],
|
||||
Exception::COLUMN_ALREADY_EXISTS => [
|
||||
'name' => Exception::COLUMN_ALREADY_EXISTS,
|
||||
'description' => 'Column with the requested key already exists. Column keys must be unique, try again with a different key.',
|
||||
'description' => 'Column with the requested key \'%s\' already exists. Column keys must be unique, try again with a different key.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::COLUMN_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::COLUMN_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number or size of columns for this table has been reached.',
|
||||
'description' => 'The maximum number or size of columns for table \'%s\' has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::COLUMN_VALUE_INVALID => [
|
||||
@@ -923,7 +928,7 @@ return [
|
||||
],
|
||||
Exception::COLUMN_TYPE_INVALID => [
|
||||
'name' => Exception::COLUMN_TYPE_INVALID,
|
||||
'description' => 'The column type is invalid.',
|
||||
'description' => 'The column \'%s\' type is invalid.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::COLUMN_INVALID_RESIZE => [
|
||||
@@ -933,24 +938,24 @@ return [
|
||||
],
|
||||
Exception::COLUMN_TYPE_NOT_SUPPORTED => [
|
||||
'name' => Exception::COLUMN_TYPE_NOT_SUPPORTED,
|
||||
'description' => 'Column type is not supported.',
|
||||
'description' => 'Column type \'%s\' is not supported.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Indexes */
|
||||
Exception::INDEX_NOT_FOUND => [
|
||||
'name' => Exception::INDEX_NOT_FOUND,
|
||||
'description' => 'Index with the requested ID could not be found.',
|
||||
'description' => 'Index with the requested key \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::INDEX_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::INDEX_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of indexes has been reached.',
|
||||
'description' => 'The maximum number of indexes for collection \'%s\' has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::INDEX_ALREADY_EXISTS => [
|
||||
'name' => Exception::INDEX_ALREADY_EXISTS,
|
||||
'description' => 'Index with the requested key already exists. Try again with a different key.',
|
||||
'description' => 'Index with the requested key \'%s\' already exists. Try again with a different key.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::INDEX_INVALID => [
|
||||
@@ -960,24 +965,24 @@ return [
|
||||
],
|
||||
Exception::INDEX_DEPENDENCY => [
|
||||
'name' => Exception::INDEX_DEPENDENCY,
|
||||
'description' => 'Attribute cannot be renamed or deleted. Please remove the associated index first.',
|
||||
'description' => 'Attribute \'%s\' cannot be renamed or deleted. Please remove the associated index first.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Column Indexes, same as Indexes but with different type */
|
||||
Exception::COLUMN_INDEX_NOT_FOUND => [
|
||||
'name' => Exception::COLUMN_INDEX_NOT_FOUND,
|
||||
'description' => 'Index with the requested ID could not be found.',
|
||||
'description' => 'Index with the requested key \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::COLUMN_INDEX_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::COLUMN_INDEX_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of indexes has been reached.',
|
||||
'description' => 'The maximum number of indexes for table \'%s\' has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::COLUMN_INDEX_ALREADY_EXISTS => [
|
||||
'name' => Exception::COLUMN_INDEX_ALREADY_EXISTS,
|
||||
'description' => 'Index with the requested key already exists. Try again with a different key.',
|
||||
'description' => 'Index with the requested key \'%s\' already exists. Try again with a different key.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::COLUMN_INDEX_INVALID => [
|
||||
@@ -987,19 +992,19 @@ return [
|
||||
],
|
||||
Exception::COLUMN_INDEX_DEPENDENCY => [
|
||||
'name' => Exception::COLUMN_INDEX_DEPENDENCY,
|
||||
'description' => 'Column cannot be renamed or deleted. Please remove the associated index first.',
|
||||
'description' => 'Column \'%s\' cannot be renamed or deleted. Please remove the associated index first.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Transactions */
|
||||
Exception::TRANSACTION_NOT_FOUND => [
|
||||
'name' => Exception::TRANSACTION_NOT_FOUND,
|
||||
'description' => 'Transaction with the requested ID could not be found.',
|
||||
'description' => 'Transaction with the requested ID \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::TRANSACTION_ALREADY_EXISTS => [
|
||||
'name' => Exception::TRANSACTION_ALREADY_EXISTS,
|
||||
'description' => 'Transaction with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'description' => 'Transaction with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::TRANSACTION_INVALID => [
|
||||
@@ -1074,6 +1079,11 @@ return [
|
||||
'description' => 'The project key has expired. Please generate a new key using the Appwrite console.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::ACCOUNT_KEY_EXPIRED => [
|
||||
'name' => Exception::ACCOUNT_KEY_EXPIRED,
|
||||
'description' => 'The account API key has expired. Please generate a new key using the Appwrite console.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::ROUTER_HOST_NOT_FOUND => [
|
||||
'name' => Exception::ROUTER_HOST_NOT_FOUND,
|
||||
'description' => 'Host is not trusted. This could occur because you have not configured a custom domain. Add a custom domain to your project first and try again.',
|
||||
@@ -1103,7 +1113,6 @@ return [
|
||||
'name' => Exception::RULE_VERIFICATION_FAILED,
|
||||
'description' => 'Domain verification failed. Please check if your DNS records are correct and try again.',
|
||||
'code' => 400,
|
||||
'publish' => true
|
||||
],
|
||||
Exception::PROJECT_SMTP_CONFIG_INVALID => [
|
||||
'name' => Exception::PROJECT_SMTP_CONFIG_INVALID,
|
||||
@@ -1130,6 +1139,11 @@ return [
|
||||
'description' => 'Key with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::KEY_ALREADY_EXISTS => [
|
||||
'name' => Exception::KEY_ALREADY_EXISTS,
|
||||
'description' => 'Key with the same ID already exists. Try again with a different ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::PLATFORM_NOT_FOUND => [
|
||||
'name' => Exception::PLATFORM_NOT_FOUND,
|
||||
'description' => 'Platform with the requested ID could not be found.',
|
||||
@@ -1329,4 +1343,19 @@ return [
|
||||
'description' => 'Target has an invalid provider type.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::USER_ID_MISSING => [
|
||||
'name' => Exception::USER_ID_MISSING,
|
||||
'description' => 'When using account API key, make sure to pass x-appwrite-user header with your user ID.',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::ORGANIZATION_ID_MISSING => [
|
||||
'name' => Exception::ORGANIZATION_ID_MISSING,
|
||||
'description' => 'When using organization API key, make sure to pass x-appwrite-organization header with your organization ID.',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::PROJECT_ID_MISSING => [
|
||||
'name' => Exception::PROJECT_ID_MISSING,
|
||||
'description' => 'When using project API key, make sure to pass x-appwrite-project header with your project ID.',
|
||||
'code' => 403,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
<img
|
||||
height="26px"
|
||||
src="{{logoUrl}}"
|
||||
alt="Appwrite logo"
|
||||
alt="{{platform}} logo"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -225,7 +225,7 @@
|
||||
<tr>
|
||||
<td style="padding-left: 4px; padding-right: 4px">
|
||||
<a
|
||||
href="{{twitterUrl}}"
|
||||
href="{{twitter}}"
|
||||
class="social-icon"
|
||||
title="Twitter"
|
||||
>
|
||||
@@ -234,7 +234,7 @@
|
||||
</td>
|
||||
<td style="padding-left: 4px; padding-right: 4px">
|
||||
<a
|
||||
href="{{discordUrl}}"
|
||||
href="{{discord}}"
|
||||
class="social-icon"
|
||||
>
|
||||
<img src="https://cloud.appwrite.io/images/mails/discord.png" height="24" width="24" />
|
||||
@@ -242,7 +242,7 @@
|
||||
</td>
|
||||
<td style="padding-left: 4px; padding-right: 4px">
|
||||
<a
|
||||
href="{{githubUrl}}"
|
||||
href="{{github}}"
|
||||
class="social-icon"
|
||||
>
|
||||
<img src="https://cloud.appwrite.io/images/mails/github.png" height="24" width="24" />
|
||||
@@ -252,15 +252,15 @@
|
||||
</table>
|
||||
<table style="width: auto; margin: 0 auto; margin-top: 60px">
|
||||
<tr>
|
||||
<td><a href="{{termsUrl}}">Terms</a></td>
|
||||
<td><a href="{{terms}}">Terms</a></td>
|
||||
<td style="color: #e8e9f0">
|
||||
<div style="margin: 0 8px">|</div>
|
||||
</td>
|
||||
<td><a href="{{privacyUrl}}">Privacy</a></td>
|
||||
<td><a href="{{privacy}}">Privacy</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="text-align: center" align="center">
|
||||
© {{year}} Appwrite | 251 Little Falls Drive, Wilmington 19808,
|
||||
© {{year}} {{platform}} | 251 Little Falls Drive, Wilmington 19808,
|
||||
Delaware, United States
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -50,17 +50,12 @@
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
src: url('https://assets.appwrite.io/fonts/poppins/poppins-v23-latin-regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
body {
|
||||
.main {
|
||||
max-width: 650px;
|
||||
margin: 0 auto;
|
||||
margin-top: 32px;
|
||||
padding: 32px;
|
||||
line-height: 1.5;
|
||||
color: #616b7c;
|
||||
@@ -68,15 +63,12 @@
|
||||
font-weight: 400;
|
||||
font-family: "Inter", sans-serif;
|
||||
background-color: #ffffff;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 150%;
|
||||
}
|
||||
a {
|
||||
.main a {
|
||||
color: currentColor;
|
||||
word-break: break-all;
|
||||
}
|
||||
a.button {
|
||||
.main a.button {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
@@ -87,8 +79,8 @@
|
||||
border: 1px solid #414146;
|
||||
border-radius: 8px;
|
||||
}
|
||||
a.button:hover,
|
||||
a.button:focus {
|
||||
.main a.button:hover,
|
||||
.main a.button:focus {
|
||||
opacity: 0.8;
|
||||
}
|
||||
table {
|
||||
@@ -102,11 +94,6 @@
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
.main {
|
||||
max-width: 650px;
|
||||
margin: 0 auto;
|
||||
margin-top: 32px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 22px;
|
||||
margin-bottom: 0px;
|
||||
@@ -138,9 +125,6 @@
|
||||
border: none;
|
||||
border-top: 1px solid #e8e9f0;
|
||||
}
|
||||
h* {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -155,21 +139,20 @@
|
||||
|
||||
<body style="direction: {{direction}}">
|
||||
|
||||
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
|
||||
{{preview}}
|
||||
<div>{{previewWhitespace}}</div>
|
||||
</div>
|
||||
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
|
||||
{{preview}}
|
||||
<div>{{previewWhitespace}}</div>
|
||||
</div>
|
||||
|
||||
<div style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word;
|
||||
word-break: normal; margin:0 auto;">
|
||||
<table style="margin-top: 32px">
|
||||
<tr>
|
||||
<td>
|
||||
{{body}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="main" style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word; word-break: normal; margin:0 auto;">
|
||||
<table style="margin-top: 32px">
|
||||
<tr>
|
||||
<td>
|
||||
{{body}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -462,4 +462,15 @@ return [
|
||||
'mock' => true,
|
||||
'class' => 'Appwrite\\Auth\\OAuth2\\Mock',
|
||||
],
|
||||
'mock-unverified' => [
|
||||
'name' => 'MockUnverified',
|
||||
'developers' => 'https://appwrite.io',
|
||||
'icon' => 'icon-appwrite',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => true,
|
||||
'class' => 'Appwrite\\Auth\\OAuth2\\MockUnverified',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Utopia\System\System;
|
||||
|
||||
// For now, take first domain as primary (for previews)
|
||||
// Later-on this can become platform-specific with new env var (appwrite=this,imagine=that)
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
if (\str_contains($sitesDomain, ',')) {
|
||||
$sitesDomain = explode(',', $sitesDomain)[0];
|
||||
}
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
if (\str_contains($functionsDomain, ',')) {
|
||||
$functionsDomain = explode(',', $functionsDomain)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform configuration
|
||||
*/
|
||||
return [
|
||||
'apiHostname' => System::getEnv('_APP_DOMAIN', 'localhost'),
|
||||
'consoleHostname' => System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', 'localhost')),
|
||||
'hostnames' => array_filter(array_unique([
|
||||
System::getEnv('_APP_DOMAIN', 'localhost'),
|
||||
System::getEnv('_APP_CONSOLE_DOMAIN', 'localhost'),
|
||||
System::getEnv('_APP_MIGRATION_HOST'),
|
||||
])),
|
||||
'platformName' => APP_EMAIL_PLATFORM_NAME,
|
||||
'logoUrl' => APP_EMAIL_LOGO_URL,
|
||||
'accentColor' => APP_EMAIL_ACCENT_COLOR,
|
||||
'footerImageUrl' => APP_EMAIL_FOOTER_IMAGE_URL,
|
||||
'twitterUrl' => APP_SOCIAL_TWITTER,
|
||||
'discordUrl' => APP_SOCIAL_DISCORD,
|
||||
'githubUrl' => APP_SOCIAL_GITHUB,
|
||||
'termsUrl' => APP_EMAIL_TERMS_URL,
|
||||
'privacyUrl' => APP_EMAIL_PRIVACY_URL,
|
||||
'websiteUrl' => 'https://' . APP_DOMAIN,
|
||||
'emailSenderName' => APP_EMAIL_PLATFORM_NAME,
|
||||
'sitesDomain' => $sitesDomain,
|
||||
'functionsDomain' => $functionsDomain,
|
||||
];
|
||||
@@ -58,6 +58,8 @@ $admins = [
|
||||
'projects.write',
|
||||
'keys.read',
|
||||
'keys.write',
|
||||
'devKeys.read',
|
||||
'devKeys.write',
|
||||
'webhooks.read',
|
||||
'webhooks.write',
|
||||
'locale.read',
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
// List of scopes for Account API keys (Tokens)
|
||||
|
||||
return [
|
||||
"account" => [
|
||||
"description" => 'Access to manage account, its organizations, sessions, tokens, and billing.',
|
||||
],
|
||||
"teams.read" => [
|
||||
"description" => 'Access to read account\'s organizations.',
|
||||
],
|
||||
"teams.write" => [
|
||||
"description" => 'Access to create, update and delete account\'s organizations and its memberships.',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
// List of scopes for organization (teams) API keys
|
||||
|
||||
return [
|
||||
"platforms.read" => [
|
||||
"description" => 'Access to read project\'s platforms',
|
||||
],
|
||||
"platforms.write" => [
|
||||
"description" =>
|
||||
'Access to create, update, and delete project\'s platforms',
|
||||
],
|
||||
"projects.read" => [
|
||||
"description" => 'Access to read organization\'s projects',
|
||||
],
|
||||
"projects.write" => [
|
||||
"description" =>
|
||||
"Access to create, update, and delete projects in organization",
|
||||
],
|
||||
"keys.read" => [
|
||||
"description" => 'Access to read project\'s API keys',
|
||||
],
|
||||
"keys.write" => [
|
||||
"description" =>
|
||||
"Access to create, update, and delete project\'s API keys",
|
||||
],
|
||||
"devKeys.read" => [
|
||||
"description" => 'Access to read project\'s development keys',
|
||||
],
|
||||
"devKeys.write" => [
|
||||
"description" =>
|
||||
"Access to create, update, and delete project\'s development keys",
|
||||
],
|
||||
"webhooks.read" => [
|
||||
"description" =>
|
||||
"Access to read project\'s webhooks",
|
||||
],
|
||||
"webhooks.write" => [
|
||||
"description" =>
|
||||
"Access to create, update, and delete project\'s webhooks",
|
||||
],
|
||||
];
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
APP_PLATFORM_CLIENT => [
|
||||
'key' => APP_PLATFORM_CLIENT,
|
||||
APP_SDK_PLATFORM_CLIENT => [
|
||||
'key' => APP_SDK_PLATFORM_CLIENT,
|
||||
'name' => 'Client',
|
||||
'description' => 'Client libraries for integrating with Appwrite to build client-based applications and websites. Read the [getting started for web](https://appwrite.io/docs/getting-started-for-web) or [getting started for Flutter](https://appwrite.io/docs/getting-started-for-flutter) tutorials to start building your first application.',
|
||||
'enabled' => true,
|
||||
@@ -11,14 +11,14 @@ return [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '21.5.0',
|
||||
'version' => '22.0.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_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-web'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git',
|
||||
@@ -60,14 +60,14 @@ return [
|
||||
[
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'version' => '20.3.2',
|
||||
'version' => '21.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-flutter',
|
||||
'package' => 'https://pub.dev/packages/appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'dart',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-flutter'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-flutter.git',
|
||||
@@ -79,14 +79,14 @@ return [
|
||||
[
|
||||
'key' => 'apple',
|
||||
'name' => 'Apple',
|
||||
'version' => '13.5.0',
|
||||
'version' => '14.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'swift',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-apple'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-apple.git',
|
||||
@@ -104,7 +104,7 @@ return [
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => '',
|
||||
'source' => false,
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-objective-c.git',
|
||||
@@ -116,14 +116,15 @@ return [
|
||||
[
|
||||
'key' => 'android',
|
||||
'name' => 'Android',
|
||||
'version' => '11.4.0',
|
||||
'namespace' => 'io.appwrite',
|
||||
'version' => '12.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-android',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'kotlin',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-android'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-android.git',
|
||||
@@ -139,14 +140,14 @@ return [
|
||||
[
|
||||
'key' => 'react-native',
|
||||
'name' => 'React Native',
|
||||
'version' => '0.19.0',
|
||||
'version' => '0.20.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-react-native',
|
||||
'package' => 'https://npmjs.com/package/react-native-appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-react-native'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-react-native.git',
|
||||
@@ -165,7 +166,7 @@ return [
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'graphql',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-graphql'),
|
||||
'gitUrl' => '',
|
||||
@@ -185,7 +186,7 @@ return [
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'http',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-rest'),
|
||||
'gitUrl' => '',
|
||||
@@ -198,8 +199,8 @@ return [
|
||||
],
|
||||
],
|
||||
|
||||
APP_PLATFORM_CONSOLE => [
|
||||
'key' => APP_PLATFORM_CONSOLE,
|
||||
APP_SDK_PLATFORM_CONSOLE => [
|
||||
'key' => APP_SDK_PLATFORM_CONSOLE,
|
||||
'name' => 'Console',
|
||||
'enabled' => false,
|
||||
'beta' => false,
|
||||
@@ -207,14 +208,14 @@ return [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Console',
|
||||
'version' => '0.2.0',
|
||||
'version' => '0.3.0',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CONSOLE,
|
||||
'family' => APP_SDK_PLATFORM_CONSOLE,
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
|
||||
'gitUrl' => '',
|
||||
@@ -226,14 +227,14 @@ return [
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '12.0.1',
|
||||
'version' => '13.3.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CONSOLE,
|
||||
'family' => APP_SDK_PLATFORM_CONSOLE,
|
||||
'prism' => 'bash',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-cli'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-cli.git',
|
||||
@@ -249,11 +250,31 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'markdown',
|
||||
'name' => 'Markdown',
|
||||
'version' => '0.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-md.git',
|
||||
'package' => 'https://www.npmjs.com/package/@appwrite.io/docs',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_SDK_PLATFORM_CONSOLE,
|
||||
'prism' => 'markdown',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-md'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-md.git',
|
||||
'gitRepoName' => 'sdk-for-md',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'repoBranch' => 'main',
|
||||
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/md/CHANGELOG.md'),
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
APP_PLATFORM_SERVER => [
|
||||
'key' => APP_PLATFORM_SERVER,
|
||||
APP_SDK_PLATFORM_SERVER => [
|
||||
'key' => APP_SDK_PLATFORM_SERVER,
|
||||
'name' => 'Server',
|
||||
'description' => 'Libraries for integrating with Appwrite to build server side integrations. Read the [getting started for server](https://appwrite.io/docs/getting-started-for-server) tutorial to start building your first server integration.',
|
||||
'enabled' => true,
|
||||
@@ -262,14 +283,14 @@ return [
|
||||
[
|
||||
'key' => 'nodejs',
|
||||
'name' => 'Node.js',
|
||||
'version' => '21.0.0',
|
||||
'version' => '22.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-node',
|
||||
'package' => 'https://www.npmjs.com/package/node-appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-nodejs'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-node.git',
|
||||
@@ -281,14 +302,14 @@ return [
|
||||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '19.0.0',
|
||||
'version' => '20.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'php',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-php'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-php.git',
|
||||
@@ -300,14 +321,14 @@ return [
|
||||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '14.0.0',
|
||||
'version' => '15.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'python',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-python'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-python.git',
|
||||
@@ -319,14 +340,14 @@ return [
|
||||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '20.0.0',
|
||||
'version' => '21.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'ruby',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-ruby'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-ruby.git',
|
||||
@@ -338,14 +359,14 @@ return [
|
||||
[
|
||||
'key' => 'go',
|
||||
'name' => 'Go',
|
||||
'version' => 'v0.15.0',
|
||||
'version' => 'v0.16.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'go',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-go'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-go.git',
|
||||
@@ -357,14 +378,14 @@ return [
|
||||
[
|
||||
'key' => 'dotnet',
|
||||
'name' => '.NET',
|
||||
'version' => '0.23.0',
|
||||
'version' => '0.25.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
|
||||
'package' => 'https://www.nuget.org/packages/Appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'csharp',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-dotnet'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-dotnet.git',
|
||||
@@ -376,14 +397,14 @@ return [
|
||||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '20.0.0',
|
||||
'version' => '21.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'dart',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-dart'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-dart.git',
|
||||
@@ -395,14 +416,15 @@ return [
|
||||
[
|
||||
'key' => 'kotlin',
|
||||
'name' => 'Kotlin',
|
||||
'version' => '13.0.0',
|
||||
'namespace' => 'io.appwrite',
|
||||
'version' => '14.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'kotlin',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-kotlin'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-kotlin.git',
|
||||
@@ -418,14 +440,14 @@ return [
|
||||
[
|
||||
'key' => 'swift',
|
||||
'name' => 'Swift',
|
||||
'version' => '14.0.0',
|
||||
'version' => '15.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'swift',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-swift'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-swift.git',
|
||||
@@ -444,7 +466,7 @@ return [
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'graphql',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-graphql'),
|
||||
'gitUrl' => '',
|
||||
@@ -464,7 +486,7 @@ return [
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'http',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-rest'),
|
||||
'gitUrl' => '',
|
||||
+28
-6
@@ -13,19 +13,21 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'web/console' => [
|
||||
'key' => 'web/console',
|
||||
'name' => 'Console',
|
||||
'subtitle' => '',
|
||||
'description' => '',
|
||||
'controller' => 'web/console.php',
|
||||
'controller' => '', // Uses modules
|
||||
'sdk' => false,
|
||||
'docs' => false,
|
||||
'docsUrl' => '',
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'account' => [
|
||||
'key' => 'account',
|
||||
@@ -39,19 +41,21 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/account.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'avatars' => [
|
||||
'key' => 'avatars',
|
||||
'name' => 'Avatars',
|
||||
'subtitle' => 'The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars.',
|
||||
'description' => '/docs/services/avatars.md',
|
||||
'controller' => 'api/avatars.php',
|
||||
'controller' => '', // Uses modules
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/client/avatars',
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/avatars.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'databases' => [
|
||||
'key' => 'databases',
|
||||
@@ -65,6 +69,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/databases.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'tablesdb' => [
|
||||
'key' => 'tablesdb',
|
||||
@@ -78,6 +83,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/databases.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'locale' => [
|
||||
'key' => 'locale',
|
||||
@@ -91,19 +97,21 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/locale.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'health' => [
|
||||
'key' => 'health',
|
||||
'name' => 'Health',
|
||||
'subtitle' => 'The Health service allows you to both validate and monitor your Appwrite server\'s health.',
|
||||
'description' => '/docs/services/health.md',
|
||||
'controller' => 'api/health.php',
|
||||
'controller' => '', // Uses modules
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/server/health',
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/health.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'projects' => [
|
||||
'key' => 'projects',
|
||||
@@ -117,6 +125,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'project' => [
|
||||
'key' => 'project',
|
||||
@@ -130,19 +139,21 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'storage' => [
|
||||
'key' => 'storage',
|
||||
'name' => 'Storage',
|
||||
'subtitle' => 'The Storage service allows you to manage your project files.',
|
||||
'description' => '/docs/services/storage.md',
|
||||
'controller' => 'api/storage.php',
|
||||
'controller' => '', // Uses modules
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/client/storage',
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/storage.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'teams' => [
|
||||
'key' => 'teams',
|
||||
@@ -156,6 +167,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/teams.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'users' => [
|
||||
'key' => 'users',
|
||||
@@ -169,6 +181,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/users.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'vcs' => [
|
||||
'key' => 'vcs',
|
||||
@@ -182,6 +195,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'sites' => [
|
||||
'key' => 'sites',
|
||||
@@ -195,6 +209,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/sites.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'functions' => [
|
||||
'key' => 'functions',
|
||||
@@ -208,6 +223,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/functions.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'proxy' => [
|
||||
'key' => 'proxy',
|
||||
@@ -221,6 +237,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '/images/services/proxy.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'mock' => [
|
||||
'key' => 'mock',
|
||||
@@ -234,6 +251,7 @@ return [
|
||||
'tests' => true,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'graphql' => [
|
||||
'key' => 'graphql',
|
||||
@@ -247,19 +265,21 @@ return [
|
||||
'tests' => true,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/graphql.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'console' => [
|
||||
'key' => 'console',
|
||||
'name' => 'Console',
|
||||
'subtitle' => 'The Console service allows you to interact with console relevant informations.',
|
||||
'subtitle' => 'The Console service allows you to interact with console relevant information.',
|
||||
'description' => '',
|
||||
'controller' => 'api/console.php',
|
||||
'controller' => '', // Uses modules
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => '',
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'migrations' => [
|
||||
'key' => 'migrations',
|
||||
@@ -273,6 +293,7 @@ return [
|
||||
'tests' => true,
|
||||
'optional' => false,
|
||||
'icon' => '/images/services/migrations.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'messaging' => [
|
||||
'key' => 'messaging',
|
||||
@@ -286,5 +307,6 @@ return [
|
||||
'tests' => true,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/messaging.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
]
|
||||
];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,4 +3,6 @@
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\System\System;
|
||||
|
||||
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
|
||||
if (\class_exists('Imagick')) {
|
||||
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
|
||||
}
|
||||
|
||||
@@ -20,6 +20,34 @@ function getRuntimes($runtimes, $commands, $entrypoint, $providerRootDirectory,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
class FunctionUseCases
|
||||
{
|
||||
public const STARTER = 'starter';
|
||||
public const DATABASES = 'databases';
|
||||
public const AI = 'ai';
|
||||
public const MESSAGING = 'messaging';
|
||||
public const UTILITIES = 'utilities';
|
||||
public const DEV_TOOLS = 'dev-tools';
|
||||
public const AUTH = 'auth';
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
public static function getAll(): array
|
||||
{
|
||||
return [
|
||||
self::STARTER,
|
||||
self::DATABASES,
|
||||
self::AI,
|
||||
self::MESSAGING,
|
||||
self::UTILITIES,
|
||||
self::DEV_TOOLS,
|
||||
self::AUTH,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'icon' => 'icon-lightning-bolt',
|
||||
@@ -32,7 +60,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['starter'],
|
||||
'useCases' => [FunctionUseCases::STARTER],
|
||||
'runtimes' => [
|
||||
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/starter', $allowList),
|
||||
...getRuntimes(
|
||||
@@ -73,7 +101,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['databases'],
|
||||
'useCases' => [FunctionUseCases::DATABASES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -119,7 +147,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['databases'],
|
||||
'useCases' => [FunctionUseCases::DATABASES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -164,7 +192,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['databases'],
|
||||
'useCases' => [FunctionUseCases::DATABASES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -218,7 +246,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['databases'],
|
||||
'useCases' => [FunctionUseCases::DATABASES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -257,7 +285,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['databases'],
|
||||
'useCases' => [FunctionUseCases::DATABASES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -326,7 +354,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -392,7 +420,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['messaging'],
|
||||
'useCases' => [FunctionUseCases::MESSAGING],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -459,7 +487,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -497,7 +525,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -548,7 +576,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['utilities'],
|
||||
'useCases' => [FunctionUseCases::UTILITIES],
|
||||
'runtimes' => [
|
||||
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf', $allowList)
|
||||
],
|
||||
@@ -571,7 +599,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['dev-tools'],
|
||||
'useCases' => [FunctionUseCases::DEV_TOOLS],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -616,7 +644,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['utilities'],
|
||||
'useCases' => [FunctionUseCases::UTILITIES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -669,7 +697,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['databases'],
|
||||
'useCases' => [FunctionUseCases::DATABASES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -754,7 +782,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['databases'],
|
||||
'useCases' => [FunctionUseCases::DATABASES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -853,7 +881,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['messaging'],
|
||||
'useCases' => [FunctionUseCases::MESSAGING],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -946,7 +974,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['messaging'],
|
||||
'useCases' => [FunctionUseCases::MESSAGING],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1004,7 +1032,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['utilities'],
|
||||
'useCases' => [FunctionUseCases::UTILITIES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1091,7 +1119,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['utilities'],
|
||||
'useCases' => [FunctionUseCases::UTILITIES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1134,7 +1162,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['utilities'],
|
||||
'useCases' => [FunctionUseCases::UTILITIES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1193,7 +1221,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 30,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1229,7 +1257,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 30,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1265,7 +1293,7 @@ return [
|
||||
'events' => ['buckets.*.files.*.create'],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1325,7 +1353,7 @@ return [
|
||||
'events' => ['buckets.*.files.*.create'],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1385,7 +1413,7 @@ return [
|
||||
'events' => ['buckets.*.files.*.create'],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1448,7 +1476,7 @@ return [
|
||||
],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1508,7 +1536,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 300,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1545,7 +1573,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 300,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1589,7 +1617,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1632,7 +1660,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 300,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1669,7 +1697,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 30,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1734,7 +1762,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 30,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1799,7 +1827,7 @@ return [
|
||||
'cron' => '',
|
||||
'events' => [],
|
||||
'timeout' => 15,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1856,7 +1884,7 @@ return [
|
||||
'cron' => '',
|
||||
'events' => [],
|
||||
'timeout' => 15,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1899,7 +1927,7 @@ return [
|
||||
'cron' => '',
|
||||
'events' => [],
|
||||
'timeout' => 15,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1942,7 +1970,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -1986,7 +2014,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 300,
|
||||
'useCases' => ['ai'],
|
||||
'useCases' => [FunctionUseCases::AI],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -2023,7 +2051,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['utilities'],
|
||||
'useCases' => [FunctionUseCases::UTILITIES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -2080,7 +2108,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['utilities'],
|
||||
'useCases' => [FunctionUseCases::UTILITIES],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
$templateRuntimes['NODE'],
|
||||
@@ -2153,7 +2181,7 @@ return [
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
'timeout' => 15,
|
||||
'useCases' => ['auth'],
|
||||
'useCases' => [FunctionUseCases::AUTH],
|
||||
'runtimes' => [
|
||||
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/sign_in_with_apple', $allowList)
|
||||
],
|
||||
|
||||
+230
-82
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\System\System;
|
||||
|
||||
/**
|
||||
@@ -7,16 +8,12 @@ use Utopia\System\System;
|
||||
*/
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
// Temporary fix until we can set _APP_DOMAIN to "localhost" instead of "traefik"
|
||||
if (System::getEnv('_APP_ENV', 'development') === 'development') {
|
||||
$hostname = 'localhost';
|
||||
}
|
||||
$platform = Config::getParam('platform', []);
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
|
||||
$url = $protocol . '://' . $hostname;
|
||||
|
||||
class UseCases
|
||||
class SiteUseCases
|
||||
{
|
||||
public const PORTFOLIO = 'portfolio';
|
||||
public const STARTER = 'starter';
|
||||
@@ -24,7 +21,27 @@ class UseCases
|
||||
public const ECOMMERCE = 'ecommerce';
|
||||
public const DOCUMENTATION = 'documentation';
|
||||
public const BLOG = 'blog';
|
||||
public const AI = 'artificial intelligence';
|
||||
public const AI = 'ai';
|
||||
public const FORMS = 'forms';
|
||||
public const DASHBOARD = 'dashboard';
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
public static function getAll(): array
|
||||
{
|
||||
return [
|
||||
self::PORTFOLIO,
|
||||
self::STARTER,
|
||||
self::EVENTS,
|
||||
self::ECOMMERCE,
|
||||
self::DOCUMENTATION,
|
||||
self::BLOG,
|
||||
self::AI,
|
||||
self::FORMS,
|
||||
self::DASHBOARD,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const TEMPLATE_FRAMEWORKS = [
|
||||
@@ -189,7 +206,7 @@ return [
|
||||
'name' => 'Documentation template',
|
||||
'tagline' => 'Modern site to store your knowledge with a clean design, full-text search, dark mode, and more.',
|
||||
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::DOCUMENTATION],
|
||||
'useCases' => [SiteUseCases::DOCUMENTATION],
|
||||
'screenshotDark' => $url . '/images/sites/templates/template-for-documentation-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/template-for-documentation-light.png',
|
||||
'frameworks' => [
|
||||
@@ -210,7 +227,7 @@ return [
|
||||
// When we add Lynx with Appwrite SDK, use following tagline for it:
|
||||
// 'tagline' => 'Sample application built with Lynx, a cross-platform framework focused on performance.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-lynx-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-lynx-light.png',
|
||||
'frameworks' => [
|
||||
@@ -221,7 +238,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -229,7 +246,7 @@ return [
|
||||
'name' => 'Vitepress',
|
||||
'tagline' => 'Platform for documentation and knowledge sharing powered by Vite.',
|
||||
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::DOCUMENTATION],
|
||||
'useCases' => [SiteUseCases::DOCUMENTATION],
|
||||
'screenshotDark' => $url . '/images/sites/templates/vitepress-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/vitepress-light.png',
|
||||
'frameworks' => [
|
||||
@@ -244,7 +261,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -252,7 +269,7 @@ return [
|
||||
'name' => 'Vuepress',
|
||||
'tagline' => 'Platform for documentation and knowledge sharing powered by Vue.',
|
||||
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::DOCUMENTATION],
|
||||
'useCases' => [SiteUseCases::DOCUMENTATION],
|
||||
'screenshotDark' => $url . '/images/sites/templates/vuepress-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/vuepress-light.png',
|
||||
'frameworks' => [
|
||||
@@ -267,7 +284,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -275,7 +292,7 @@ return [
|
||||
'name' => 'Docusaurus',
|
||||
'tagline' => 'Platform for documentation and knowledge sharing powered by React.',
|
||||
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::DOCUMENTATION],
|
||||
'useCases' => [SiteUseCases::DOCUMENTATION],
|
||||
'screenshotDark' => $url . '/images/sites/templates/docusaurus-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/docusaurus-light.png',
|
||||
'frameworks' => [
|
||||
@@ -290,7 +307,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -298,7 +315,7 @@ return [
|
||||
'name' => 'Nxt Lnk',
|
||||
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
|
||||
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::PORTFOLIO],
|
||||
'useCases' => [SiteUseCases::PORTFOLIO],
|
||||
'screenshotDark' => $url . '/images/sites/templates/nxt-lnk-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/nxt-lnk-light.png',
|
||||
'frameworks' => [
|
||||
@@ -309,7 +326,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -317,7 +334,7 @@ return [
|
||||
'name' => 'Magic Portfolio',
|
||||
'tagline' => 'Complex personal website to showcase your projects, articles, and more.',
|
||||
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::PORTFOLIO],
|
||||
'useCases' => [SiteUseCases::PORTFOLIO],
|
||||
'screenshotDark' => $url . '/images/sites/templates/magic-portfolio-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/magic-portfolio-light.png',
|
||||
'frameworks' => [
|
||||
@@ -328,7 +345,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -336,7 +353,7 @@ return [
|
||||
'name' => 'LittleLink',
|
||||
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::PORTFOLIO],
|
||||
'useCases' => [SiteUseCases::PORTFOLIO],
|
||||
'screenshotDark' => $url . '/images/sites/templates/littlelink-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/littlelink-light.png',
|
||||
'frameworks' => [
|
||||
@@ -347,7 +364,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -355,7 +372,7 @@ return [
|
||||
'name' => 'Logspot',
|
||||
'tagline' => 'Website to publish changelogs of your application.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::BLOG],
|
||||
'useCases' => [SiteUseCases::BLOG],
|
||||
'screenshotDark' => $url . '/images/sites/templates/logspot-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/logspot-light.png',
|
||||
'frameworks' => [
|
||||
@@ -369,7 +386,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -377,7 +394,7 @@ return [
|
||||
'name' => 'Astro Nano',
|
||||
'tagline' => 'Minimal personal website to showcase your projects, articles, and more.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::PORTFOLIO],
|
||||
'useCases' => [SiteUseCases::PORTFOLIO],
|
||||
'screenshotDark' => $url . '/images/sites/templates/astro-nano-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/astro-nano-light.png',
|
||||
'frameworks' => [
|
||||
@@ -390,7 +407,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -398,7 +415,7 @@ return [
|
||||
'name' => 'Astro Starlight',
|
||||
'tagline' => 'Platform for documentation and knowledge sharing powered by Astro.',
|
||||
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::DOCUMENTATION],
|
||||
'useCases' => [SiteUseCases::DOCUMENTATION],
|
||||
'screenshotDark' => $url . '/images/sites/templates/astro-starlight-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/astro-starlight-light.png',
|
||||
'frameworks' => [
|
||||
@@ -411,7 +428,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -419,7 +436,7 @@ return [
|
||||
'name' => 'Astro Sphere',
|
||||
'tagline' => 'Modern personal website to showcase your projects, articles, and more.',
|
||||
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::PORTFOLIO],
|
||||
'useCases' => [SiteUseCases::PORTFOLIO],
|
||||
'screenshotDark' => $url . '/images/sites/templates/astro-sphere-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/astro-sphere-light.png',
|
||||
'frameworks' => [
|
||||
@@ -432,7 +449,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -440,7 +457,7 @@ return [
|
||||
'name' => 'Astro Starlog',
|
||||
'tagline' => 'Platform for publishing written content and media powered by Astro.',
|
||||
'score' => 5, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::BLOG],
|
||||
'useCases' => [SiteUseCases::BLOG],
|
||||
'screenshotDark' => $url . '/images/sites/templates/astro-starlog-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/astro-starlog-light.png',
|
||||
'frameworks' => [
|
||||
@@ -453,7 +470,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -461,7 +478,7 @@ return [
|
||||
'name' => 'Onelink',
|
||||
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::PORTFOLIO],
|
||||
'useCases' => [SiteUseCases::PORTFOLIO],
|
||||
'screenshotDark' => $url . '/images/sites/templates/onelink-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/onelink-light.png',
|
||||
'frameworks' => [
|
||||
@@ -475,13 +492,13 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
'key' => 'starter-for-flutter',
|
||||
'name' => 'Flutter starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple Flutter application integrated with Appwrite SDK.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-flutter-dark.png',
|
||||
@@ -526,7 +543,7 @@ return [
|
||||
[
|
||||
'key' => 'starter-for-js',
|
||||
'name' => 'JavaScript starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple JavaScript application integrated with Appwrite SDK.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-js-dark.png',
|
||||
@@ -570,7 +587,7 @@ return [
|
||||
[
|
||||
'key' => 'starter-for-angular',
|
||||
'name' => 'Angular starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple Angular application integrated with Appwrite SDK.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-angular-dark.png',
|
||||
@@ -616,7 +633,7 @@ return [
|
||||
[
|
||||
'key' => 'starter-for-astro',
|
||||
'name' => 'Astro starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple Astro application integrated with Appwrite SDK.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-astro-dark.png',
|
||||
@@ -661,7 +678,7 @@ return [
|
||||
[
|
||||
'key' => 'starter-for-analog',
|
||||
'name' => 'Analog starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple Analog application integrated with Appwrite SDK.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-analog-dark.png',
|
||||
@@ -705,7 +722,7 @@ return [
|
||||
[
|
||||
'key' => 'starter-for-remix',
|
||||
'name' => 'Remix starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple Remix application integrated with Appwrite SDK.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-remix-dark.png',
|
||||
@@ -749,7 +766,7 @@ return [
|
||||
[
|
||||
'key' => 'starter-for-svelte',
|
||||
'name' => 'Svelte starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple Svelte application integrated with Appwrite SDK.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-svelte-dark.png',
|
||||
@@ -793,7 +810,7 @@ return [
|
||||
[
|
||||
'key' => 'starter-for-react',
|
||||
'name' => 'React starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple React application integrated with Appwrite SDK.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-dark.png',
|
||||
@@ -837,7 +854,7 @@ return [
|
||||
[
|
||||
'key' => 'starter-for-vue',
|
||||
'name' => 'Vue starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple Vue application integrated with Appwrite SDK.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-vue-dark.png',
|
||||
@@ -881,7 +898,7 @@ return [
|
||||
[
|
||||
'key' => 'starter-for-react-native',
|
||||
'name' => 'React Native starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple React Native application integrated with Appwrite SDK.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-native-dark.png',
|
||||
@@ -925,7 +942,7 @@ return [
|
||||
[
|
||||
'key' => 'starter-for-nextjs',
|
||||
'name' => 'Next.js starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple Next.js application integrated with Appwrite SDK.',
|
||||
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-nextjs-dark.png',
|
||||
@@ -969,7 +986,7 @@ return [
|
||||
[
|
||||
'key' => 'starter-for-tanstack-start',
|
||||
'name' => 'TanStack Start starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple TanStack Start application integrated with Appwrite SDK.',
|
||||
'score' => 9, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-tanstack-start-dark.png',
|
||||
@@ -1013,7 +1030,7 @@ return [
|
||||
[
|
||||
'key' => 'starter-for-nuxt',
|
||||
'name' => 'Nuxt starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'tagline' => 'Simple Nuxt application integrated with Appwrite SDK.',
|
||||
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-nuxt-dark.png',
|
||||
@@ -1059,7 +1076,7 @@ return [
|
||||
'name' => 'Event template',
|
||||
'tagline' => 'Hackathon landing page with support for project submissions.',
|
||||
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::EVENTS],
|
||||
'useCases' => [SiteUseCases::EVENTS],
|
||||
'screenshotDark' => $url . '/images/sites/templates/template-for-event-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/template-for-event-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1097,7 +1114,7 @@ return [
|
||||
'name' => 'Portfolio template',
|
||||
'tagline' => 'Simple personal website to showcase your projects, articles, and more.',
|
||||
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::PORTFOLIO],
|
||||
'useCases' => [SiteUseCases::PORTFOLIO],
|
||||
'screenshotDark' => $url . '/images/sites/templates/template-for-portfolio-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/template-for-portfolio-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1116,7 +1133,7 @@ return [
|
||||
'name' => 'Store template',
|
||||
'tagline' => 'E-commerce platform for selling products with Stripe integration.',
|
||||
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::ECOMMERCE],
|
||||
'useCases' => [SiteUseCases::ECOMMERCE],
|
||||
'screenshotDark' => $url . '/images/sites/templates/template-for-store-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/template-for-store-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1160,7 +1177,7 @@ return [
|
||||
'name' => 'Blog template',
|
||||
'tagline' => 'Platform for publishing written content and media.',
|
||||
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::BLOG],
|
||||
'useCases' => [SiteUseCases::BLOG],
|
||||
'screenshotDark' => $url . '/images/sites/templates/template-for-blog-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/template-for-blog-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1179,7 +1196,7 @@ return [
|
||||
'name' => 'Astro playground',
|
||||
'tagline' => 'A basic Astro website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-astro-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-astro-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1190,7 +1207,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
@@ -1198,7 +1215,7 @@ return [
|
||||
'name' => 'Remix playground',
|
||||
'tagline' => 'A basic Remix website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-remix-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-remix-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1209,7 +1226,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
@@ -1217,7 +1234,7 @@ return [
|
||||
'name' => 'Next.js playground',
|
||||
'tagline' => 'A basic Next.js website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-nextjs-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-nextjs-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1228,7 +1245,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
@@ -1236,7 +1253,7 @@ return [
|
||||
'name' => 'Flutter playground',
|
||||
'tagline' => 'A basic Flutter website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-flutter-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-flutter-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1247,7 +1264,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
@@ -1255,7 +1272,7 @@ return [
|
||||
'name' => 'Vite playground',
|
||||
'tagline' => 'A basic Vite website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-vite-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-vite-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1266,7 +1283,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
@@ -1274,7 +1291,7 @@ return [
|
||||
'name' => 'Angular playground',
|
||||
'tagline' => 'A basic Angular website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-angular-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-angular-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1286,7 +1303,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
@@ -1294,7 +1311,7 @@ return [
|
||||
'name' => 'Analog playground',
|
||||
'tagline' => 'A basic Analog website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-analog-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-analog-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1305,7 +1322,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
@@ -1313,7 +1330,7 @@ return [
|
||||
'name' => 'Svelte playground',
|
||||
'tagline' => 'A basic Svelte website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-svelte-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-svelte-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1324,7 +1341,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
|
||||
@@ -1333,7 +1350,7 @@ return [
|
||||
'name' => 'React playground',
|
||||
'tagline' => 'A basic React website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-react-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-react-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1345,7 +1362,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
|
||||
@@ -1354,7 +1371,7 @@ return [
|
||||
'name' => 'Vue playground',
|
||||
'tagline' => 'A basic Vue website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-vue-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-vue-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1365,7 +1382,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
@@ -1373,7 +1390,7 @@ return [
|
||||
'name' => 'Nuxt playground',
|
||||
'tagline' => 'A basic Nuxt website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-nuxt-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-nuxt-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1384,7 +1401,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
@@ -1392,7 +1409,7 @@ return [
|
||||
'name' => 'TanStack Start playground',
|
||||
'tagline' => 'A basic TanStack Start website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-tanstack-start-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-tanstack-start-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1403,7 +1420,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.5.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
@@ -1411,7 +1428,7 @@ return [
|
||||
'name' => 'React Native playground',
|
||||
'tagline' => 'A basic React Native website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-react-native-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-react-native-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1422,7 +1439,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
@@ -1430,7 +1447,7 @@ return [
|
||||
'name' => 'Lynx gallery',
|
||||
'tagline' => 'A Lynx website showcasing gallery with smooth animations.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'useCases' => [SiteUseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/gallery-for-lynx-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/gallery-for-lynx-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1441,7 +1458,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.3.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
@@ -1449,7 +1466,7 @@ return [
|
||||
'name' => 'Text-to-speech with ElevenLabs',
|
||||
'tagline' => 'Next.js app that transforms text into natural, human-like speech using ElevenLabs',
|
||||
'score' => 10, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::AI],
|
||||
'useCases' => [SiteUseCases::AI],
|
||||
'screenshotDark' => $url . '/images/sites/templates/text-to-speech-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/text-to-speech-light.png',
|
||||
'frameworks' => [
|
||||
@@ -1460,7 +1477,7 @@ return [
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.6.*',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => 'ELEVENLABS_API_KEY',
|
||||
@@ -1472,4 +1489,135 @@ return [
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'crm-dashboard-react-admin',
|
||||
'name' => 'CRM dashboard with React Admin',
|
||||
'tagline' => 'A React-based admin dashboard template with CRM features.',
|
||||
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [SiteUseCases::DASHBOARD],
|
||||
'screenshotDark' => $url . '/images/sites/templates/crm-dashboard-react-admin-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/crm-dashboard-react-admin-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('REACT', [
|
||||
'providerRootDirectory' => './react/react-admin',
|
||||
'installCommand' => 'pnpm install',
|
||||
'buildCommand' => 'pnpm build && pnpm db-seed',
|
||||
'outputDirectory' => './dist',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_ENDPOINT',
|
||||
'description' => 'Endpoint of Appwrite server',
|
||||
'value' => '{apiEndpoint}',
|
||||
'placeholder' => '{apiEndpoint}',
|
||||
'required' => true,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_PROJECT_ID',
|
||||
'description' => 'Your Appwrite project ID',
|
||||
'value' => '{projectId}',
|
||||
'placeholder' => '{projectId}',
|
||||
'required' => true,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'APPWRITE_API_KEY',
|
||||
'description' => 'Your Appwrite API key (for seeding only)',
|
||||
'value' => '',
|
||||
'placeholder' => 'a0b1...',
|
||||
'required' => true,
|
||||
'type' => 'password'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_DATABASE_ID',
|
||||
'description' => 'Database ID (default: admin)',
|
||||
'value' => 'admin',
|
||||
'placeholder' => 'admin',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_REVIEWS',
|
||||
'description' => 'Table ID for reviews table',
|
||||
'value' => 'reviews',
|
||||
'placeholder' => 'reviews',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_INVOICES',
|
||||
'description' => 'Table ID for invoices table',
|
||||
'value' => 'invoices',
|
||||
'placeholder' => 'invoices',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_ORDERS',
|
||||
'description' => 'Table ID for orders table',
|
||||
'value' => 'orders',
|
||||
'placeholder' => 'orders',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_PRODUCTS',
|
||||
'description' => 'Table ID for products table',
|
||||
'value' => 'products',
|
||||
'placeholder' => 'products',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_CATEGORIES',
|
||||
'description' => 'Table ID for categories table',
|
||||
'value' => 'categories',
|
||||
'placeholder' => 'categories',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_CUSTOMERS',
|
||||
'description' => 'Table ID for customers table',
|
||||
'value' => 'customers',
|
||||
'placeholder' => 'customers',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'job-applications-formspree',
|
||||
'name' => 'Job applications form with Formspree',
|
||||
'tagline' => 'A simple form submission template using Formspree.',
|
||||
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [SiteUseCases::FORMS],
|
||||
'screenshotDark' => $url . '/images/sites/templates/job-applications-formspree-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/job-applications-formspree-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('REACT', [
|
||||
'providerRootDirectory' => './react/formspree',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => 'VITE_FORMSPREE_FORM_ID',
|
||||
'description' => 'Your Formspree form ID',
|
||||
'value' => '',
|
||||
'placeholder' => 'xrgkpqld',
|
||||
'required' => true,
|
||||
'type' => 'text'
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
@@ -81,7 +81,7 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_DOMAIN',
|
||||
'description' => 'Your Appwrite domain address. When setting a public suffix domain, Appwrite will attempt to issue a valid SSL certificate automatically. When used with a dev domain, Appwrite will assign a self-signed SSL certificate. The default value is \'localhost\'.',
|
||||
'description' => 'Your Appwrite domain address. When setting a public suffix domain, Appwrite will attempt to issue a valid SSL certificate automatically. When used with a dev domain, Appwrite will assign a self-signed SSL certificate. The default value is \'localhost\'. Multiple domains can be separated by commas.',
|
||||
'introduction' => '',
|
||||
'default' => 'localhost',
|
||||
'required' => true,
|
||||
@@ -357,6 +357,15 @@ return [
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_TRUSTED_HEADERS',
|
||||
'description' => 'This option allows you to set the list of trusted headers, the value is a comma‑separated list of HTTP header names, evaluated left-to-right for the first valid IP. Header names are treated case-insensitively.',
|
||||
'introduction' => '1.8.0',
|
||||
'default' => 'x-forwarded-for',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
]
|
||||
],
|
||||
],
|
||||
@@ -1276,6 +1285,15 @@ return [
|
||||
'category' => 'GraphQL',
|
||||
'description' => '',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_GRAPHQL_INTROSPECTION',
|
||||
'description' => 'Enable or disable GraphQL introspection. Set to \'enabled\' to allow schema introspection, or \'disabled\' to block it. The default value is \'enabled\'.',
|
||||
'introduction' => '',
|
||||
'default' => 'enabled',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_GRAPHQL_MAX_BATCH_SIZE',
|
||||
'description' => 'Maximum number of batched queries per request. The default value is 10.',
|
||||
|
||||
+391
-280
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,152 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
App::init()
|
||||
->groups(['console'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
if ($project->getId() !== 'console') {
|
||||
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
App::get('/v1/console/variables')
|
||||
->desc('Get variables')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'console',
|
||||
group: 'console',
|
||||
name: 'variables',
|
||||
description: '/docs/references/console/variables.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_CONSOLE_VARIABLES,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$validator = new Domain(System::getEnv('_APP_DOMAIN'));
|
||||
$isDomainValid = !empty(System::getEnv('_APP_DOMAIN', '')) && $validator->isKnown() && !$validator->isTest();
|
||||
|
||||
$validator = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME'));
|
||||
$isCNAMEValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')) && $validator->isKnown() && !$validator->isTest();
|
||||
|
||||
$validator = new IP(IP::V4);
|
||||
$isAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_A', '')) && ($validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_A')));
|
||||
|
||||
$validator = new IP(IP::V6);
|
||||
$isAAAAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_AAAA', '')) && $validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA'));
|
||||
|
||||
$isDomainEnabled = $isDomainValid && (
|
||||
$isAAAAValid || $isAValid || $isCNAMEValid
|
||||
);
|
||||
|
||||
$isVcsEnabled = !empty(System::getEnv('_APP_VCS_GITHUB_APP_NAME', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_APP_ID', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''));
|
||||
|
||||
$isAssistantEnabled = !empty(System::getEnv('_APP_ASSISTANT_OPENAI_API_KEY', ''));
|
||||
|
||||
$variables = new Document([
|
||||
'_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'),
|
||||
'_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'),
|
||||
'_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'),
|
||||
// Combine CAA domain with most common flags and tag (no parameters)
|
||||
'_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"',
|
||||
'_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'),
|
||||
'_APP_COMPUTE_BUILD_TIMEOUT' => +System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'),
|
||||
'_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'),
|
||||
'_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'),
|
||||
'_APP_VCS_ENABLED' => $isVcsEnabled,
|
||||
'_APP_DOMAIN_ENABLED' => $isDomainEnabled,
|
||||
'_APP_ASSISTANT_ENABLED' => $isAssistantEnabled,
|
||||
'_APP_DOMAIN_SITES' => System::getEnv('_APP_DOMAIN_SITES'),
|
||||
'_APP_DOMAIN_FUNCTIONS' => System::getEnv('_APP_DOMAIN_FUNCTIONS'),
|
||||
'_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS'),
|
||||
'_APP_DOMAINS_NAMESERVERS' => System::getEnv('_APP_DOMAINS_NAMESERVERS'),
|
||||
]);
|
||||
|
||||
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
|
||||
});
|
||||
|
||||
App::post('/v1/console/assistant')
|
||||
->desc('Create assistant query')
|
||||
->groups(['api', 'assistant'])
|
||||
->label('scope', 'assistant.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'assistant',
|
||||
group: 'console',
|
||||
name: 'chat',
|
||||
description: '/docs/references/assistant/chat.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::TEXT
|
||||
))
|
||||
->label('abuse-limit', 15)
|
||||
->label('abuse-key', 'userId:{userId}')
|
||||
->param('prompt', '', new Text(2000), 'Prompt. A string containing questions asked to the AI assistant.')
|
||||
->inject('response')
|
||||
->action(function (string $prompt, Response $response) {
|
||||
$ch = curl_init('http://appwrite-assistant:3003/v1/models/assistant/prompt');
|
||||
$responseHeaders = [];
|
||||
$query = json_encode(['prompt' => $prompt]);
|
||||
$headers = ['accept: text/event-stream'];
|
||||
$handleEvent = function ($ch, $data) use ($response) {
|
||||
$response->chunk($data);
|
||||
|
||||
return \strlen($data);
|
||||
};
|
||||
|
||||
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $handleEvent);
|
||||
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 9000);
|
||||
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
|
||||
$len = strlen($header);
|
||||
$header = explode(':', $header, 2);
|
||||
|
||||
if (count($header) < 2) { // ignore invalid headers
|
||||
return $len;
|
||||
}
|
||||
|
||||
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
|
||||
|
||||
return $len;
|
||||
});
|
||||
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
|
||||
|
||||
curl_exec($ch);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
$response->chunk('', true);
|
||||
});
|
||||
@@ -18,27 +18,28 @@ use GraphQL\Validator\Rules\DisableIntrospection;
|
||||
use GraphQL\Validator\Rules\QueryComplexity;
|
||||
use GraphQL\Validator\Rules\QueryDepth;
|
||||
use Swoole\Coroutine\WaitGroup;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Http;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\JSON;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
App::init()
|
||||
Http::init()
|
||||
->groups(['graphql'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
->inject('authorization')
|
||||
->action(function (Document $project, Authorization $authorization) {
|
||||
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::isApp($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
});
|
||||
|
||||
App::get('/v1/graphql')
|
||||
Http::get('/v1/graphql')
|
||||
->desc('GraphQL endpoint')
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
@@ -46,7 +47,7 @@ App::get('/v1/graphql')
|
||||
namespace: 'graphql',
|
||||
group: 'graphql',
|
||||
name: 'get',
|
||||
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
hide: true,
|
||||
description: '/docs/references/graphql/get.md',
|
||||
responses: [
|
||||
@@ -85,7 +86,7 @@ App::get('/v1/graphql')
|
||||
->json($output);
|
||||
});
|
||||
|
||||
App::post('/v1/graphql/mutation')
|
||||
Http::post('/v1/graphql/mutation')
|
||||
->desc('GraphQL endpoint')
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
@@ -93,7 +94,7 @@ App::post('/v1/graphql/mutation')
|
||||
namespace: 'graphql',
|
||||
group: 'graphql',
|
||||
name: 'mutation',
|
||||
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
description: '/docs/references/graphql/post.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
@@ -136,7 +137,7 @@ App::post('/v1/graphql/mutation')
|
||||
->json($output);
|
||||
});
|
||||
|
||||
App::post('/v1/graphql')
|
||||
Http::post('/v1/graphql')
|
||||
->desc('GraphQL endpoint')
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
@@ -144,7 +145,7 @@ App::post('/v1/graphql')
|
||||
namespace: 'graphql',
|
||||
group: 'graphql',
|
||||
name: 'query',
|
||||
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
description: '/docs/references/graphql/post.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
@@ -223,12 +224,15 @@ function execute(
|
||||
$flags = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
|
||||
$validations = GraphQL::getStandardValidationRules();
|
||||
|
||||
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
|
||||
if (System::getEnv('_APP_GRAPHQL_INTROSPECTION', 'enabled') === 'disabled') {
|
||||
$validations[] = new DisableIntrospection();
|
||||
}
|
||||
|
||||
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
|
||||
$validations[] = new QueryComplexity($maxComplexity);
|
||||
$validations[] = new QueryDepth($maxDepth);
|
||||
}
|
||||
if (App::getMode() === App::MODE_TYPE_PRODUCTION) {
|
||||
if (Http::getMode() === Http::MODE_TYPE_PRODUCTION) {
|
||||
$flags = DebugFlag::NONE;
|
||||
}
|
||||
|
||||
@@ -329,7 +333,7 @@ function processResult($result, $debugFlags): array
|
||||
);
|
||||
}
|
||||
|
||||
App::shutdown()
|
||||
Http::shutdown()
|
||||
->groups(['schema'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,12 +6,12 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Http;
|
||||
use Utopia\Locale\Locale;
|
||||
|
||||
App::get('/v1/locale')
|
||||
Http::get('/v1/locale')
|
||||
->desc('Get user locale')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -20,7 +20,7 @@ App::get('/v1/locale')
|
||||
group: null,
|
||||
name: 'get',
|
||||
description: '/docs/references/locale/get-locale.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -70,7 +70,7 @@ App::get('/v1/locale')
|
||||
$response->dynamic(new Document($output), Response::MODEL_LOCALE);
|
||||
});
|
||||
|
||||
App::get('/v1/locale/codes')
|
||||
Http::get('/v1/locale/codes')
|
||||
->desc('List locale codes')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -79,7 +79,7 @@ App::get('/v1/locale/codes')
|
||||
group: null,
|
||||
name: 'listCodes',
|
||||
description: '/docs/references/locale/list-locale-codes.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -96,7 +96,7 @@ App::get('/v1/locale/codes')
|
||||
]), Response::MODEL_LOCALE_CODE_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/locale/countries')
|
||||
Http::get('/v1/locale/countries')
|
||||
->desc('List countries')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -105,7 +105,7 @@ App::get('/v1/locale/countries')
|
||||
group: null,
|
||||
name: 'listCountries',
|
||||
description: '/docs/references/locale/list-countries.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -133,7 +133,7 @@ App::get('/v1/locale/countries')
|
||||
$response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/locale/countries/eu')
|
||||
Http::get('/v1/locale/countries/eu')
|
||||
->desc('List EU countries')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -142,7 +142,7 @@ App::get('/v1/locale/countries/eu')
|
||||
group: null,
|
||||
name: 'listCountriesEU',
|
||||
description: '/docs/references/locale/list-countries-eu.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -172,7 +172,7 @@ App::get('/v1/locale/countries/eu')
|
||||
$response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/locale/countries/phones')
|
||||
Http::get('/v1/locale/countries/phones')
|
||||
->desc('List countries phone codes')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -181,7 +181,7 @@ App::get('/v1/locale/countries/phones')
|
||||
group: null,
|
||||
name: 'listCountriesPhones',
|
||||
description: '/docs/references/locale/list-countries-phones.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -210,7 +210,7 @@ App::get('/v1/locale/countries/phones')
|
||||
$response->dynamic(new Document(['phones' => $output, 'total' => \count($output)]), Response::MODEL_PHONE_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/locale/continents')
|
||||
Http::get('/v1/locale/continents')
|
||||
->desc('List continents')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -219,7 +219,7 @@ App::get('/v1/locale/continents')
|
||||
group: null,
|
||||
name: 'listContinents',
|
||||
description: '/docs/references/locale/list-continents.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -246,7 +246,7 @@ App::get('/v1/locale/continents')
|
||||
$response->dynamic(new Document(['continents' => $output, 'total' => \count($output)]), Response::MODEL_CONTINENT_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/locale/currencies')
|
||||
Http::get('/v1/locale/currencies')
|
||||
->desc('List currencies')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -255,7 +255,7 @@ App::get('/v1/locale/currencies')
|
||||
group: null,
|
||||
name: 'listCurrencies',
|
||||
description: '/docs/references/locale/list-currencies.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -273,7 +273,7 @@ App::get('/v1/locale/currencies')
|
||||
});
|
||||
|
||||
|
||||
App::get('/v1/locale/languages')
|
||||
Http::get('/v1/locale/languages')
|
||||
->desc('List languages')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -282,7 +282,7 @@ App::get('/v1/locale/languages')
|
||||
group: null,
|
||||
name: 'listLanguages',
|
||||
description: '/docs/references/locale/list-languages.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
|
||||
+146
-164
@@ -25,7 +25,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Targets;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Topics;
|
||||
use Appwrite\Utopia\Response;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -36,6 +35,7 @@ use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
@@ -43,6 +43,7 @@ use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\Roles;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
@@ -56,7 +57,7 @@ use Utopia\Validator\WhiteList;
|
||||
|
||||
use function Swoole\Coroutine\batch;
|
||||
|
||||
App::post('/v1/messaging/providers/mailgun')
|
||||
Http::post('/v1/messaging/providers/mailgun')
|
||||
->desc('Create Mailgun provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
@@ -150,7 +151,7 @@ App::post('/v1/messaging/providers/mailgun')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/providers/sendgrid')
|
||||
Http::post('/v1/messaging/providers/sendgrid')
|
||||
->desc('Create Sendgrid provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
@@ -232,7 +233,7 @@ App::post('/v1/messaging/providers/sendgrid')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/providers/resend')
|
||||
Http::post('/v1/messaging/providers/resend')
|
||||
->desc('Create Resend provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
@@ -314,7 +315,7 @@ App::post('/v1/messaging/providers/resend')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/providers/smtp')
|
||||
Http::post('/v1/messaging/providers/smtp')
|
||||
->desc('Create SMTP provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
@@ -339,6 +340,7 @@ App::post('/v1/messaging/providers/smtp')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.createSMTPProvider',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -428,7 +430,7 @@ App::post('/v1/messaging/providers/smtp')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/providers/msg91')
|
||||
Http::post('/v1/messaging/providers/msg91')
|
||||
->desc('Create Msg91 provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
@@ -511,7 +513,7 @@ App::post('/v1/messaging/providers/msg91')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/providers/telesign')
|
||||
Http::post('/v1/messaging/providers/telesign')
|
||||
->desc('Create Telesign provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
@@ -595,7 +597,7 @@ App::post('/v1/messaging/providers/telesign')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/providers/textmagic')
|
||||
Http::post('/v1/messaging/providers/textmagic')
|
||||
->desc('Create Textmagic provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
@@ -679,7 +681,7 @@ App::post('/v1/messaging/providers/textmagic')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/providers/twilio')
|
||||
Http::post('/v1/messaging/providers/twilio')
|
||||
->desc('Create Twilio provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
@@ -763,7 +765,7 @@ App::post('/v1/messaging/providers/twilio')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/providers/vonage')
|
||||
Http::post('/v1/messaging/providers/vonage')
|
||||
->desc('Create Vonage provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
@@ -847,7 +849,7 @@ App::post('/v1/messaging/providers/vonage')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/providers/fcm')
|
||||
Http::post('/v1/messaging/providers/fcm')
|
||||
->desc('Create FCM provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
@@ -872,6 +874,7 @@ App::post('/v1/messaging/providers/fcm')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.createFCMProvider',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -936,7 +939,7 @@ App::post('/v1/messaging/providers/fcm')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/providers/apns')
|
||||
Http::post('/v1/messaging/providers/apns')
|
||||
->desc('Create APNS provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
@@ -961,6 +964,7 @@ App::post('/v1/messaging/providers/apns')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.createAPNSProvider',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -1048,7 +1052,7 @@ App::post('/v1/messaging/providers/apns')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/providers')
|
||||
Http::get('/v1/messaging/providers')
|
||||
->desc('List providers')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'providers.read')
|
||||
@@ -1070,8 +1074,9 @@ App::get('/v1/messaging/providers')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -1082,22 +1087,17 @@ App::get('/v1/messaging/providers')
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$providerId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId));
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Provider '{$providerId}' for the 'cursor' value not found.");
|
||||
@@ -1117,7 +1117,7 @@ App::get('/v1/messaging/providers')
|
||||
]), Response::MODEL_PROVIDER_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/providers/:providerId/logs')
|
||||
Http::get('/v1/messaging/providers/:providerId/logs')
|
||||
->desc('List provider logs')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'providers.read')
|
||||
@@ -1142,7 +1142,8 @@ App::get('/v1/messaging/providers/:providerId/logs')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $providerId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->inject('audit')
|
||||
->action(function (string $providerId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
|
||||
$provider = $dbForProject->getDocument('providers', $providerId);
|
||||
|
||||
if ($provider->isEmpty()) {
|
||||
@@ -1155,15 +1156,12 @@ App::get('/v1/messaging/providers/:providerId/logs')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'provider/' . $providerId;
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
|
||||
$output = [];
|
||||
|
||||
foreach ($logs as $i => &$log) {
|
||||
@@ -1210,12 +1208,12 @@ App::get('/v1/messaging/providers/:providerId/logs')
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/providers/:providerId')
|
||||
Http::get('/v1/messaging/providers/:providerId')
|
||||
->desc('Get provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'providers.read')
|
||||
@@ -1246,7 +1244,7 @@ App::get('/v1/messaging/providers/:providerId')
|
||||
$response->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/providers/mailgun/:providerId')
|
||||
Http::patch('/v1/messaging/providers/mailgun/:providerId')
|
||||
->desc('Update Mailgun provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.update')
|
||||
@@ -1359,7 +1357,7 @@ App::patch('/v1/messaging/providers/mailgun/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/providers/sendgrid/:providerId')
|
||||
Http::patch('/v1/messaging/providers/sendgrid/:providerId')
|
||||
->desc('Update Sendgrid provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.update')
|
||||
@@ -1457,7 +1455,7 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/providers/resend/:providerId')
|
||||
Http::patch('/v1/messaging/providers/resend/:providerId')
|
||||
->desc('Update Resend provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.update')
|
||||
@@ -1555,7 +1553,7 @@ App::patch('/v1/messaging/providers/resend/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/providers/smtp/:providerId')
|
||||
Http::patch('/v1/messaging/providers/smtp/:providerId')
|
||||
->desc('Update SMTP provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.update')
|
||||
@@ -1580,6 +1578,7 @@ App::patch('/v1/messaging/providers/smtp/:providerId')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.updateSMTPProvider',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -1703,7 +1702,7 @@ App::patch('/v1/messaging/providers/smtp/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/providers/msg91/:providerId')
|
||||
Http::patch('/v1/messaging/providers/msg91/:providerId')
|
||||
->desc('Update Msg91 provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.update')
|
||||
@@ -1790,7 +1789,7 @@ App::patch('/v1/messaging/providers/msg91/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/providers/telesign/:providerId')
|
||||
Http::patch('/v1/messaging/providers/telesign/:providerId')
|
||||
->desc('Update Telesign provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.update')
|
||||
@@ -1879,7 +1878,7 @@ App::patch('/v1/messaging/providers/telesign/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/providers/textmagic/:providerId')
|
||||
Http::patch('/v1/messaging/providers/textmagic/:providerId')
|
||||
->desc('Update Textmagic provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.update')
|
||||
@@ -1968,7 +1967,7 @@ App::patch('/v1/messaging/providers/textmagic/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/providers/twilio/:providerId')
|
||||
Http::patch('/v1/messaging/providers/twilio/:providerId')
|
||||
->desc('Update Twilio provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.update')
|
||||
@@ -2057,7 +2056,7 @@ App::patch('/v1/messaging/providers/twilio/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/providers/vonage/:providerId')
|
||||
Http::patch('/v1/messaging/providers/vonage/:providerId')
|
||||
->desc('Update Vonage provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.update')
|
||||
@@ -2146,7 +2145,7 @@ App::patch('/v1/messaging/providers/vonage/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/providers/fcm/:providerId')
|
||||
Http::patch('/v1/messaging/providers/fcm/:providerId')
|
||||
->desc('Update FCM provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.update')
|
||||
@@ -2171,6 +2170,7 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.updateFCMProvider',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -2241,7 +2241,7 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
|
||||
});
|
||||
|
||||
|
||||
App::patch('/v1/messaging/providers/apns/:providerId')
|
||||
Http::patch('/v1/messaging/providers/apns/:providerId')
|
||||
->desc('Update APNS provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.update')
|
||||
@@ -2266,6 +2266,7 @@ App::patch('/v1/messaging/providers/apns/:providerId')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.updateAPNSProvider',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -2362,7 +2363,7 @@ App::patch('/v1/messaging/providers/apns/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::delete('/v1/messaging/providers/:providerId')
|
||||
Http::delete('/v1/messaging/providers/:providerId')
|
||||
->desc('Delete provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.delete')
|
||||
@@ -2405,7 +2406,7 @@ App::delete('/v1/messaging/providers/:providerId')
|
||||
->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/topics')
|
||||
Http::post('/v1/messaging/topics')
|
||||
->desc('Create topic')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'topic.create')
|
||||
@@ -2455,7 +2456,7 @@ App::post('/v1/messaging/topics')
|
||||
->dynamic($topic, Response::MODEL_TOPIC);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/topics')
|
||||
Http::get('/v1/messaging/topics')
|
||||
->desc('List topics')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'topics.read')
|
||||
@@ -2477,8 +2478,9 @@ App::get('/v1/messaging/topics')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -2489,22 +2491,17 @@ App::get('/v1/messaging/topics')
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$topicId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Topic '{$topicId}' for the 'cursor' value not found.");
|
||||
@@ -2524,7 +2521,7 @@ App::get('/v1/messaging/topics')
|
||||
]), Response::MODEL_TOPIC_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/topics/:topicId/logs')
|
||||
Http::get('/v1/messaging/topics/:topicId/logs')
|
||||
->desc('List topic logs')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'topics.read')
|
||||
@@ -2549,7 +2546,8 @@ App::get('/v1/messaging/topics/:topicId/logs')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $topicId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->inject('audit')
|
||||
->action(function (string $topicId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
|
||||
$topic = $dbForProject->getDocument('topics', $topicId);
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
@@ -2562,15 +2560,12 @@ App::get('/v1/messaging/topics/:topicId/logs')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'topic/' . $topicId;
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
|
||||
|
||||
$output = [];
|
||||
|
||||
@@ -2618,12 +2613,12 @@ App::get('/v1/messaging/topics/:topicId/logs')
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/topics/:topicId')
|
||||
Http::get('/v1/messaging/topics/:topicId')
|
||||
->desc('Get topic')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'topics.read')
|
||||
@@ -2655,7 +2650,7 @@ App::get('/v1/messaging/topics/:topicId')
|
||||
->dynamic($topic, Response::MODEL_TOPIC);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/topics/:topicId')
|
||||
Http::patch('/v1/messaging/topics/:topicId')
|
||||
->desc('Update topic')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'topic.update')
|
||||
@@ -2706,7 +2701,7 @@ App::patch('/v1/messaging/topics/:topicId')
|
||||
->dynamic($topic, Response::MODEL_TOPIC);
|
||||
});
|
||||
|
||||
App::delete('/v1/messaging/topics/:topicId')
|
||||
Http::delete('/v1/messaging/topics/:topicId')
|
||||
->desc('Delete topic')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'topic.delete')
|
||||
@@ -2754,7 +2749,7 @@ App::delete('/v1/messaging/topics/:topicId')
|
||||
->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
Http::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
->desc('Create subscriber')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'subscriber.create')
|
||||
@@ -2780,29 +2775,27 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
->param('targetId', '', new UID(), 'Target ID. The target ID to link to the specified Topic ID.')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
$subscriberId = $subscriberId == 'unique()' ? ID::unique() : $subscriberId;
|
||||
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
}
|
||||
|
||||
$validator = new Authorization('subscribe');
|
||||
|
||||
if (!$validator->isValid($topic->getAttribute('subscribe'))) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
|
||||
if (!$authorization->isValid(new Input('subscribe', $topic->getAttribute('subscribe')))) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
|
||||
$subscriber = new Document([
|
||||
'$id' => $subscriberId,
|
||||
@@ -2835,7 +2828,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
|
||||
};
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute(
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
@@ -2857,7 +2850,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
->dynamic($subscriber, Response::MODEL_SUBSCRIBER);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
Http::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
->desc('List subscribers')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'subscribers.read')
|
||||
@@ -2880,8 +2873,9 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
->action(function (string $topicId, array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -2892,7 +2886,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
@@ -2900,22 +2894,17 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
|
||||
$queries[] = Query::equal('topicInternalId', [$topic->getSequence()]);
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$subscriberId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Subscriber '{$subscriberId}' for the 'cursor' value not found.");
|
||||
@@ -2929,10 +2918,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) {
|
||||
return function () use ($subscriber, $dbForProject) {
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject, $authorization) {
|
||||
return function () use ($subscriber, $dbForProject, $authorization) {
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
|
||||
return $subscriber
|
||||
->setAttribute('target', $target)
|
||||
@@ -2947,7 +2936,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
]), Response::MODEL_SUBSCRIBER_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/subscribers/:subscriberId/logs')
|
||||
Http::get('/v1/messaging/subscribers/:subscriberId/logs')
|
||||
->desc('List subscriber logs')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'subscribers.read')
|
||||
@@ -2972,7 +2961,8 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $subscriberId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->inject('audit')
|
||||
->action(function (string $subscriberId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
|
||||
$subscriber = $dbForProject->getDocument('subscribers', $subscriberId);
|
||||
|
||||
if ($subscriber->isEmpty()) {
|
||||
@@ -2985,15 +2975,12 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'subscriber/' . $subscriberId;
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
|
||||
|
||||
$output = [];
|
||||
|
||||
@@ -3041,12 +3028,12 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->desc('Get subscriber')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'subscribers.read')
|
||||
@@ -3067,9 +3054,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.')
|
||||
->param('subscriberId', '', new UID(), 'Subscriber ID.')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response) {
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
->action(function (string $topicId, string $subscriberId, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
@@ -3081,8 +3069,8 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
throw new Exception(Exception::SUBSCRIBER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
|
||||
$subscriber
|
||||
->setAttribute('target', $target)
|
||||
@@ -3092,7 +3080,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->dynamic($subscriber, Response::MODEL_SUBSCRIBER);
|
||||
});
|
||||
|
||||
App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->desc('Delete subscriber')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'subscriber.delete')
|
||||
@@ -3118,9 +3106,10 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->param('subscriberId', '', new UID(), 'Subscriber ID.')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
@@ -3143,7 +3132,7 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
|
||||
};
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute(
|
||||
$authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
@@ -3159,7 +3148,7 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/messages/email')
|
||||
Http::post('/v1/messaging/messages/email')
|
||||
->desc('Create email')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.create')
|
||||
@@ -3318,7 +3307,7 @@ App::post('/v1/messaging/messages/email')
|
||||
->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/messages/sms')
|
||||
Http::post('/v1/messaging/messages/sms')
|
||||
->desc('Create SMS')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.create')
|
||||
@@ -3343,6 +3332,7 @@ App::post('/v1/messaging/messages/sms')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.createSMS',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -3460,7 +3450,7 @@ App::post('/v1/messaging/messages/sms')
|
||||
->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/messages/push')
|
||||
Http::post('/v1/messaging/messages/push')
|
||||
->desc('Create push notification')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.create')
|
||||
@@ -3506,7 +3496,8 @@ App::post('/v1/messaging/messages/push')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('response')
|
||||
->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) {
|
||||
->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) {
|
||||
$messageId = $messageId == 'unique()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
@@ -3562,8 +3553,8 @@ App::post('/v1/messaging/messages/push')
|
||||
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$host = System::getEnv('_APP_DOMAIN', 'localhost');
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$endpoint = "$protocol://{$platform['apiHostname']}/v1";
|
||||
|
||||
$scheduleTime = $currentScheduledAt ?? $scheduledAt;
|
||||
if (!\is_null($scheduleTime)) {
|
||||
@@ -3583,7 +3574,7 @@ App::post('/v1/messaging/messages/push')
|
||||
$image = [
|
||||
'bucketId' => $bucket->getId(),
|
||||
'fileId' => $file->getId(),
|
||||
'url' => "{$protocol}://{$host}/v1/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
|
||||
'url' => "{$endpoint}/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -3678,7 +3669,7 @@ App::post('/v1/messaging/messages/push')
|
||||
->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/messages')
|
||||
Http::get('/v1/messaging/messages')
|
||||
->desc('List messages')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'messages.read')
|
||||
@@ -3700,8 +3691,9 @@ App::get('/v1/messaging/messages')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -3712,22 +3704,17 @@ App::get('/v1/messaging/messages')
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$messageId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('messages', $messageId));
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('messages', $messageId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Message '{$messageId}' for the 'cursor' value not found.");
|
||||
@@ -3747,7 +3734,7 @@ App::get('/v1/messaging/messages')
|
||||
]), Response::MODEL_MESSAGE_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/messages/:messageId/logs')
|
||||
Http::get('/v1/messaging/messages/:messageId/logs')
|
||||
->desc('List message logs')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'messages.read')
|
||||
@@ -3772,7 +3759,8 @@ App::get('/v1/messaging/messages/:messageId/logs')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $messageId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->inject('audit')
|
||||
->action(function (string $messageId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -3785,15 +3773,12 @@ App::get('/v1/messaging/messages/:messageId/logs')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'message/' . $messageId;
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
|
||||
|
||||
$output = [];
|
||||
|
||||
@@ -3841,12 +3826,12 @@ App::get('/v1/messaging/messages/:messageId/logs')
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/messages/:messageId/targets')
|
||||
Http::get('/v1/messaging/messages/:messageId/targets')
|
||||
->desc('List message targets')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'messages.read')
|
||||
@@ -3894,15 +3879,10 @@ App::get('/v1/messaging/messages/:messageId/targets')
|
||||
|
||||
$queries[] = Query::equal('$id', $targetIDs);
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
@@ -3929,7 +3909,7 @@ App::get('/v1/messaging/messages/:messageId/targets')
|
||||
]), Response::MODEL_TARGET_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/messaging/messages/:messageId')
|
||||
Http::get('/v1/messaging/messages/:messageId')
|
||||
->desc('Get message')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'messages.read')
|
||||
@@ -3960,7 +3940,7 @@ App::get('/v1/messaging/messages/:messageId')
|
||||
$response->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/messages/email/:messageId')
|
||||
Http::patch('/v1/messaging/messages/email/:messageId')
|
||||
->desc('Update email')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.update')
|
||||
@@ -4167,7 +4147,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
|
||||
->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
Http::patch('/v1/messaging/messages/sms/:messageId')
|
||||
->desc('Update SMS')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.update')
|
||||
@@ -4192,6 +4172,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.updateSMS',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -4348,7 +4329,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/messages/push/:messageId')
|
||||
Http::patch('/v1/messaging/messages/push/:messageId')
|
||||
->desc('Update push notification')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.update')
|
||||
@@ -4394,7 +4375,8 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('response')
|
||||
->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) {
|
||||
->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) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -4562,9 +4544,6 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$host = System::getEnv('_APP_DOMAIN', 'localhost');
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
|
||||
$scheduleTime = $currentScheduledAt ?? $scheduledAt;
|
||||
if (!\is_null($scheduleTime)) {
|
||||
$expiry = (new \DateTime($scheduleTime))->add(new \DateInterval('P15D'))->format('U');
|
||||
@@ -4580,10 +4559,13 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
'projectId' => $project->getId(),
|
||||
]);
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$endpoint = "$protocol://{$platform['apiHostname']}/v1";
|
||||
|
||||
$pushData['image'] = [
|
||||
'bucketId' => $bucket->getId(),
|
||||
'fileId' => $file->getId(),
|
||||
'url' => "{$protocol}://{$host}/v1/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}"
|
||||
'url' => "{$endpoint}/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4608,7 +4590,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
App::delete('/v1/messaging/messages/:messageId')
|
||||
Http::delete('/v1/messaging/messages/:messageId')
|
||||
->desc('Delete message')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.delete')
|
||||
|
||||
@@ -11,7 +11,6 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CompoundUID;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Migrations;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
@@ -22,6 +21,7 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Queries\Documents;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http;
|
||||
use Utopia\Migration\Resource;
|
||||
use Utopia\Migration\Sources\Appwrite;
|
||||
use Utopia\Migration\Sources\CSV;
|
||||
@@ -43,7 +43,7 @@ use Utopia\Validator\WhiteList;
|
||||
|
||||
include_once __DIR__ . '/../shared/api.php';
|
||||
|
||||
App::post('/v1/migrations/appwrite')
|
||||
Http::post('/v1/migrations/appwrite')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Create Appwrite migration')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -69,10 +69,11 @@ App::post('/v1/migrations/appwrite')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $endpoint, string $projectId, string $apiKey, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
->action(function (array $resources, string $endpoint, string $projectId, string $apiKey, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
@@ -96,6 +97,7 @@ App::post('/v1/migrations/appwrite')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
|
||||
@@ -104,7 +106,7 @@ App::post('/v1/migrations/appwrite')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
App::post('/v1/migrations/firebase')
|
||||
Http::post('/v1/migrations/firebase')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Create Firebase migration')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -128,10 +130,11 @@ App::post('/v1/migrations/firebase')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$serviceAccountData = json_decode($serviceAccount, true);
|
||||
|
||||
if (empty($serviceAccountData)) {
|
||||
@@ -163,6 +166,7 @@ App::post('/v1/migrations/firebase')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
|
||||
@@ -171,7 +175,7 @@ App::post('/v1/migrations/firebase')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
App::post('/v1/migrations/supabase')
|
||||
Http::post('/v1/migrations/supabase')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Create Supabase migration')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -200,10 +204,11 @@ App::post('/v1/migrations/supabase')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
@@ -230,6 +235,7 @@ App::post('/v1/migrations/supabase')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
|
||||
@@ -238,7 +244,7 @@ App::post('/v1/migrations/supabase')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
App::post('/v1/migrations/nhost')
|
||||
Http::post('/v1/migrations/nhost')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Create NHost migration')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -268,10 +274,11 @@ App::post('/v1/migrations/nhost')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
@@ -299,6 +306,7 @@ App::post('/v1/migrations/nhost')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
|
||||
@@ -307,7 +315,7 @@ App::post('/v1/migrations/nhost')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
App::post('/v1/migrations/csv/imports')
|
||||
Http::post('/v1/migrations/csv/imports')
|
||||
->alias('/v1/migrations/csv')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Import documents from a CSV')
|
||||
@@ -334,7 +342,9 @@ App::post('/v1/migrations/csv/imports')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForMigrations')
|
||||
->inject('queueForEvents')
|
||||
@@ -347,13 +357,15 @@ App::post('/v1/migrations/csv/imports')
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
array $platform,
|
||||
Device $deviceForFiles,
|
||||
Device $deviceForMigrations,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
) {
|
||||
$bucket = Authorization::skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
|
||||
$bucket = $authorization->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
|
||||
if ($internalFile) {
|
||||
return $dbForPlatform->getDocument('buckets', 'default');
|
||||
}
|
||||
@@ -364,7 +376,7 @@ App::post('/v1/migrations/csv/imports')
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$file = Authorization::skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$file = $authorization->skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
@@ -441,6 +453,7 @@ App::post('/v1/migrations/csv/imports')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
|
||||
$response
|
||||
@@ -448,7 +461,7 @@ App::post('/v1/migrations/csv/imports')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
App::post('/v1/migrations/csv/exports')
|
||||
Http::post('/v1/migrations/csv/exports')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Export documents to CSV')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -480,7 +493,9 @@ App::post('/v1/migrations/csv/exports')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (
|
||||
@@ -497,7 +512,9 @@ App::post('/v1/migrations/csv/exports')
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
array $platform,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
) {
|
||||
@@ -507,7 +524,7 @@ App::post('/v1/migrations/csv/exports')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
|
||||
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
|
||||
if ($bucket->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
@@ -520,12 +537,12 @@ App::post('/v1/migrations/csv/exports')
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
@@ -571,6 +588,7 @@ App::post('/v1/migrations/csv/exports')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->trigger();
|
||||
|
||||
$response
|
||||
@@ -578,7 +596,7 @@ App::post('/v1/migrations/csv/exports')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations')
|
||||
Http::get('/v1/migrations')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('List migrations')
|
||||
->label('scope', 'migrations.read')
|
||||
@@ -611,16 +629,10 @@ App::get('/v1/migrations')
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
@@ -649,7 +661,7 @@ App::get('/v1/migrations')
|
||||
]), Response::MODEL_MIGRATION_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/:migrationId')
|
||||
Http::get('/v1/migrations/:migrationId')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Get migration')
|
||||
->label('scope', 'migrations.read')
|
||||
@@ -679,7 +691,7 @@ App::get('/v1/migrations/:migrationId')
|
||||
$response->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/appwrite/report')
|
||||
Http::get('/v1/migrations/appwrite/report')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Get Appwrite migration report')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -728,7 +740,7 @@ App::get('/v1/migrations/appwrite/report')
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/firebase/report')
|
||||
Http::get('/v1/migrations/firebase/report')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Get Firebase migration report')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -781,7 +793,7 @@ App::get('/v1/migrations/firebase/report')
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/supabase/report')
|
||||
Http::get('/v1/migrations/supabase/report')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Get Supabase migration report')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -830,7 +842,7 @@ App::get('/v1/migrations/supabase/report')
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/nhost/report')
|
||||
Http::get('/v1/migrations/nhost/report')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Get NHost migration report')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -879,7 +891,7 @@ App::get('/v1/migrations/nhost/report')
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::patch('/v1/migrations/:migrationId')
|
||||
Http::patch('/v1/migrations/:migrationId')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Update retry migration')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -903,9 +915,10 @@ App::patch('/v1/migrations/:migrationId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (string $migrationId, Response $response, Database $dbForProject, Document $project, Document $user, Migration $queueForMigrations) {
|
||||
->action(function (string $migrationId, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Migration $queueForMigrations) {
|
||||
$migration = $dbForProject->getDocument('migrations', $migrationId);
|
||||
|
||||
if ($migration->isEmpty()) {
|
||||
@@ -924,13 +937,14 @@ App::patch('/v1/migrations/:migrationId')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/migrations/:migrationId')
|
||||
Http::delete('/v1/migrations/:migrationId')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Delete migration')
|
||||
->label('scope', 'migrations.write')
|
||||
|
||||
@@ -6,7 +6,6 @@ use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
@@ -17,12 +16,13 @@ use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Datetime as DateTimeValidator;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
App::get('/v1/project/usage')
|
||||
Http::get('/v1/project/usage')
|
||||
->desc('Get project usage stats')
|
||||
->groups(['api', 'usage'])
|
||||
->label('scope', 'projects.read')
|
||||
@@ -45,9 +45,10 @@ App::get('/v1/project/usage')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('getLogsDB')
|
||||
->inject('smsRates')
|
||||
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, callable $getLogsDB, array $smsRates) {
|
||||
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, Authorization $authorization, callable $getLogsDB, array $smsRates) {
|
||||
$stats = $total = $usage = [];
|
||||
$format = 'Y-m-d 00:00:00';
|
||||
$firstDay = (new DateTime($startDate))->format($format);
|
||||
@@ -102,7 +103,7 @@ App::get('/v1/project/usage')
|
||||
'1d' => 'Y-m-d\T00:00:00.000P',
|
||||
};
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
|
||||
$authorization->skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
|
||||
foreach ($metrics['total'] as $metric) {
|
||||
$db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
|
||||
|
||||
@@ -286,7 +287,7 @@ App::get('/v1/project/usage')
|
||||
}, $dbForProject->find('functions'));
|
||||
|
||||
// This total is includes free and paid SMS usage
|
||||
$authPhoneTotal = Authorization::skip(fn () => $dbForProject->sum('stats', 'value', [
|
||||
$authPhoneTotal = $authorization->skip(fn () => $dbForProject->sum('stats', 'value', [
|
||||
Query::equal('metric', [METRIC_AUTH_METHOD_PHONE]),
|
||||
Query::equal('period', ['1d']),
|
||||
Query::greaterThanEqual('time', $firstDay),
|
||||
@@ -294,7 +295,7 @@ App::get('/v1/project/usage')
|
||||
]));
|
||||
|
||||
// This estimate is only for paid SMS usage
|
||||
$authPhoneMetrics = Authorization::skip(fn () => $dbForProject->find('stats', [
|
||||
$authPhoneMetrics = $authorization->skip(fn () => $dbForProject->find('stats', [
|
||||
Query::startsWith('metric', METRIC_AUTH_METHOD_PHONE . '.'),
|
||||
Query::equal('period', ['1d']),
|
||||
Query::greaterThanEqual('time', $firstDay),
|
||||
@@ -384,7 +385,7 @@ App::get('/v1/project/usage')
|
||||
|
||||
|
||||
// Variables
|
||||
App::post('/v1/project/variables')
|
||||
Http::post('/v1/project/variables')
|
||||
->desc('Create variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -447,7 +448,7 @@ App::post('/v1/project/variables')
|
||||
->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
});
|
||||
|
||||
App::get('/v1/project/variables')
|
||||
Http::get('/v1/project/variables')
|
||||
->desc('List variables')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.read')
|
||||
@@ -478,7 +479,7 @@ App::get('/v1/project/variables')
|
||||
]), Response::MODEL_VARIABLE_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/project/variables/:variableId')
|
||||
Http::get('/v1/project/variables/:variableId')
|
||||
->desc('Get variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.read')
|
||||
@@ -508,7 +509,7 @@ App::get('/v1/project/variables/:variableId')
|
||||
$response->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
});
|
||||
|
||||
App::put('/v1/project/variables/:variableId')
|
||||
Http::put('/v1/project/variables/:variableId')
|
||||
->desc('Update variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -566,7 +567,7 @@ App::put('/v1/project/variables/:variableId')
|
||||
$response->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
});
|
||||
|
||||
App::delete('/v1/project/variables/:variableId')
|
||||
Http::delete('/v1/project/variables/:variableId')
|
||||
->desc('Delete variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
|
||||
+120
-446
@@ -6,7 +6,6 @@ use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Validator\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Hooks\Hooks;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -15,30 +14,25 @@ use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Database\Validator\ProjectId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Keys;
|
||||
use Appwrite\Utopia\Response;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\Pool as DatabasePool;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Validator\PublicDomain;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Http;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
@@ -51,7 +45,7 @@ use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
App::init()
|
||||
Http::init()
|
||||
->groups(['projects'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
@@ -60,248 +54,7 @@ App::init()
|
||||
}
|
||||
});
|
||||
|
||||
App::post('/v1/projects')
|
||||
->desc('Create project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('audits.event', 'projects.create')
|
||||
->label('audits.resource', 'project/{response.$id}')
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'projects',
|
||||
name: 'create',
|
||||
description: '/docs/references/projects/create.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new ProjectId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, and hyphen. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
|
||||
->param('teamId', '', new UID(), 'Team unique ID.')
|
||||
->param('region', System::getEnv('_APP_REGION', 'default'), new Whitelist(array_keys(array_filter(Config::getParam('regions'), fn ($config) => !$config['disabled']))), 'Project Region.', true)
|
||||
->param('description', '', new Text(256), 'Project description. Max length: 256 chars.', true)
|
||||
->param('logo', '', new Text(1024), 'Project logo.', true)
|
||||
->param('url', '', new URL(), 'Project URL.', true)
|
||||
->param('legalName', '', new Text(256), 'Project legal Name. Max length: 256 chars.', true)
|
||||
->param('legalCountry', '', new Text(256), 'Project legal Country. Max length: 256 chars.', true)
|
||||
->param('legalState', '', new Text(256), 'Project legal State. Max length: 256 chars.', true)
|
||||
->param('legalCity', '', new Text(256), 'Project legal City. Max length: 256 chars.', true)
|
||||
->param('legalAddress', '', new Text(256), 'Project legal Address. Max length: 256 chars.', true)
|
||||
->param('legalTaxId', '', new Text(256), 'Project legal Tax ID. Max length: 256 chars.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('cache')
|
||||
->inject('pools')
|
||||
->inject('hooks')
|
||||
->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForPlatform, Cache $cache, Group $pools, Hooks $hooks) {
|
||||
|
||||
$team = $dbForPlatform->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$allowList = \array_filter(\explode(',', System::getEnv('_APP_PROJECT_REGIONS', '')));
|
||||
|
||||
if (!empty($allowList) && !\in_array($region, $allowList)) {
|
||||
throw new Exception(Exception::PROJECT_REGION_UNSUPPORTED, 'Region "' . $region . '" is not supported');
|
||||
}
|
||||
|
||||
$auth = Config::getParam('auth', []);
|
||||
$auths = [
|
||||
'limit' => 0,
|
||||
'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT,
|
||||
'passwordHistory' => 0,
|
||||
'passwordDictionary' => false,
|
||||
'duration' => TOKEN_EXPIRATION_LOGIN_LONG,
|
||||
'personalDataCheck' => false,
|
||||
'mockNumbers' => [],
|
||||
'sessionAlerts' => false,
|
||||
'membershipsUserName' => false,
|
||||
'membershipsUserEmail' => false,
|
||||
'membershipsMfa' => false,
|
||||
'invalidateSessions' => true
|
||||
];
|
||||
|
||||
foreach ($auth as $method) {
|
||||
$auths[$method['key'] ?? ''] = true;
|
||||
}
|
||||
|
||||
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
|
||||
|
||||
if ($projectId === 'console') {
|
||||
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
|
||||
}
|
||||
|
||||
$databases = Config::getParam('pools-database', []);
|
||||
|
||||
if ($region !== 'default') {
|
||||
$databaseKeys = System::getEnv('_APP_DATABASE_KEYS', '');
|
||||
$keys = explode(',', $databaseKeys);
|
||||
$databases = array_filter($keys, function ($value) use ($region) {
|
||||
return str_contains($value, $region);
|
||||
});
|
||||
}
|
||||
|
||||
$databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE');
|
||||
$index = \array_search($databaseOverride, $databases);
|
||||
if ($index !== false) {
|
||||
$dsn = $databases[$index];
|
||||
} else {
|
||||
$dsn = $databases[array_rand($databases)];
|
||||
}
|
||||
|
||||
// TODO: Temporary until all projects are using shared tables.
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn, $sharedTables)) {
|
||||
$schema = 'appwrite';
|
||||
$database = 'appwrite';
|
||||
$namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', '');
|
||||
$dsn = $schema . '://' . $dsn . '?database=' . $database;
|
||||
|
||||
if (!empty($namespace)) {
|
||||
$dsn .= '&namespace=' . $namespace;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$project = $dbForPlatform->createDocument('projects', new Document([
|
||||
'$id' => $projectId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
||||
],
|
||||
'name' => $name,
|
||||
'teamInternalId' => $team->getSequence(),
|
||||
'teamId' => $team->getId(),
|
||||
'region' => $region,
|
||||
'description' => $description,
|
||||
'logo' => $logo,
|
||||
'url' => $url,
|
||||
'version' => APP_VERSION_STABLE,
|
||||
'legalName' => $legalName,
|
||||
'legalCountry' => $legalCountry,
|
||||
'legalState' => $legalState,
|
||||
'legalCity' => $legalCity,
|
||||
'legalAddress' => $legalAddress,
|
||||
'legalTaxId' => ID::custom($legalTaxId),
|
||||
'services' => new stdClass(),
|
||||
'platforms' => null,
|
||||
'oAuthProviders' => [],
|
||||
'webhooks' => null,
|
||||
'keys' => null,
|
||||
'auths' => $auths,
|
||||
'accessedAt' => DateTime::now(),
|
||||
'search' => implode(' ', [$projectId, $name]),
|
||||
'database' => $dsn,
|
||||
]));
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
try {
|
||||
$dsn = new DSN($dsn);
|
||||
} catch (\InvalidArgumentException) {
|
||||
// TODO: Temporary until all projects are using shared tables
|
||||
$dsn = new DSN('mysql://' . $dsn);
|
||||
}
|
||||
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
$sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', ''));
|
||||
$projectTables = !\in_array($dsn->getHost(), $sharedTables);
|
||||
$sharedTablesV1 = \in_array($dsn->getHost(), $sharedTablesV1);
|
||||
$sharedTablesV2 = !$projectTables && !$sharedTablesV1;
|
||||
$sharedTables = $sharedTablesV1 || $sharedTablesV2;
|
||||
|
||||
if (!$sharedTablesV2) {
|
||||
$adapter = new DatabasePool($pools->get($dsn->getHost()));
|
||||
$dbForProject = new Database($adapter, $cache);
|
||||
|
||||
if ($sharedTables) {
|
||||
$dbForProject
|
||||
->setSharedTables(true)
|
||||
->setTenant($sharedTablesV1 ? (int)$project->getSequence() : null)
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$dbForProject
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
$create = true;
|
||||
|
||||
try {
|
||||
$dbForProject->create();
|
||||
} catch (Duplicate) {
|
||||
$create = false;
|
||||
}
|
||||
|
||||
if ($create || $projectTables) {
|
||||
$audit = new Audit($dbForProject);
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
if (!$create && $sharedTablesV1) {
|
||||
$attributes = \array_map(fn ($attribute) => new Document($attribute), Audit::ATTRIBUTES);
|
||||
$indexes = \array_map(fn (array $index) => new Document($index), Audit::INDEXES);
|
||||
$dbForProject->createDocument(Database::METADATA, new Document([
|
||||
'$id' => ID::custom('audit'),
|
||||
'$permissions' => [Permission::create(Role::any())],
|
||||
'name' => 'audit',
|
||||
'attributes' => $attributes,
|
||||
'indexes' => $indexes,
|
||||
'documentSecurity' => true
|
||||
]));
|
||||
}
|
||||
|
||||
if ($create || $sharedTablesV1) {
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', [])['projects'] ?? [];
|
||||
|
||||
foreach ($collections as $key => $collection) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
|
||||
$indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
|
||||
|
||||
try {
|
||||
$dbForProject->createCollection($key, $attributes, $indexes);
|
||||
} catch (Duplicate) {
|
||||
$dbForProject->createDocument(Database::METADATA, new Document([
|
||||
'$id' => ID::custom($key),
|
||||
'$permissions' => [Permission::create(Role::any())],
|
||||
'name' => $key,
|
||||
'attributes' => $attributes,
|
||||
'indexes' => $indexes,
|
||||
'documentSecurity' => true
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hook allowing instant project mirroring during migration
|
||||
// Outside of migration, hook is not registered and has no effect
|
||||
$hooks->trigger('afterProjectCreation', [ $project, $pools, $cache ]);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId')
|
||||
Http::get('/v1/projects/:projectId')
|
||||
->desc('Get project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
@@ -332,138 +85,7 @@ App::get('/v1/projects/:projectId')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId')
|
||||
->desc('Update project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('audits.event', 'projects.update')
|
||||
->label('audits.resource', 'project/{request.projectId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'projects',
|
||||
name: 'update',
|
||||
description: '/docs/references/projects/update.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
|
||||
->param('description', '', new Text(256), 'Project description. Max length: 256 chars.', true)
|
||||
->param('logo', '', new Text(1024), 'Project logo.', true)
|
||||
->param('url', '', new URL(), 'Project URL.', true)
|
||||
->param('legalName', '', new Text(256), 'Project legal name. Max length: 256 chars.', true)
|
||||
->param('legalCountry', '', new Text(256), 'Project legal country. Max length: 256 chars.', true)
|
||||
->param('legalState', '', new Text(256), 'Project legal state. Max length: 256 chars.', true)
|
||||
->param('legalCity', '', new Text(256), 'Project legal city. Max length: 256 chars.', true)
|
||||
->param('legalAddress', '', new Text(256), 'Project legal address. Max length: 256 chars.', true)
|
||||
->param('legalTaxId', '', new Text(256), 'Project legal tax ID. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('description', $description)
|
||||
->setAttribute('logo', $logo)
|
||||
->setAttribute('url', $url)
|
||||
->setAttribute('legalName', $legalName)
|
||||
->setAttribute('legalCountry', $legalCountry)
|
||||
->setAttribute('legalState', $legalState)
|
||||
->setAttribute('legalCity', $legalCity)
|
||||
->setAttribute('legalAddress', $legalAddress)
|
||||
->setAttribute('legalTaxId', $legalTaxId)
|
||||
->setAttribute('search', implode(' ', [$projectId, $name])));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/team')
|
||||
->desc('Update project team')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'projects',
|
||||
name: 'updateTeam',
|
||||
description: '/docs/references/projects/update-team.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('teamId', '', new UID(), 'Team ID of the team to transfer project to.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $teamId, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
$team = $dbForPlatform->getDocument('teams', $teamId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$permissions = [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
||||
];
|
||||
|
||||
$project
|
||||
->setAttribute('teamId', $teamId)
|
||||
->setAttribute('teamInternalId', $team->getSequence())
|
||||
->setAttribute('$permissions', $permissions);
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project);
|
||||
|
||||
$installations = $dbForPlatform->find('installations', [
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
foreach ($installations as $installation) {
|
||||
$installation->getAttribute('$permissions', $permissions);
|
||||
$dbForPlatform->updateDocument('installations', $installation->getId(), $installation);
|
||||
}
|
||||
|
||||
$repositories = $dbForPlatform->find('repositories', [
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
foreach ($repositories as $repository) {
|
||||
$repository->getAttribute('$permissions', $permissions);
|
||||
$dbForPlatform->updateDocument('repositories', $repository->getId(), $repository);
|
||||
}
|
||||
|
||||
$vcsComments = $dbForPlatform->find('vcsComments', [
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
foreach ($vcsComments as $vcsComment) {
|
||||
$vcsComment->getAttribute('$permissions', $permissions);
|
||||
$dbForPlatform->updateDocument('vcsComments', $vcsComment->getId(), $vcsComment);
|
||||
}
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/service')
|
||||
Http::patch('/v1/projects/:projectId/service')
|
||||
->desc('Update service status')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -501,7 +123,7 @@ App::patch('/v1/projects/:projectId/service')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/service/all')
|
||||
Http::patch('/v1/projects/:projectId/service/all')
|
||||
->desc('Update all service status')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -542,7 +164,7 @@ App::patch('/v1/projects/:projectId/service/all')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/api')
|
||||
Http::patch('/v1/projects/:projectId/api')
|
||||
->desc('Update API status')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -563,6 +185,7 @@ App::patch('/v1/projects/:projectId/api')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.updateAPIStatus',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -599,7 +222,7 @@ App::patch('/v1/projects/:projectId/api')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/api/all')
|
||||
Http::patch('/v1/projects/:projectId/api/all')
|
||||
->desc('Update all API status')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -620,6 +243,7 @@ App::patch('/v1/projects/:projectId/api/all')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.updateAPIStatusAll',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -659,7 +283,7 @@ App::patch('/v1/projects/:projectId/api/all')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/oauth2')
|
||||
Http::patch('/v1/projects/:projectId/oauth2')
|
||||
->desc('Update project OAuth2')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -710,7 +334,7 @@ App::patch('/v1/projects/:projectId/oauth2')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/session-alerts')
|
||||
Http::patch('/v1/projects/:projectId/auth/session-alerts')
|
||||
->desc('Update project sessions emails')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -748,7 +372,7 @@ App::patch('/v1/projects/:projectId/auth/session-alerts')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/memberships-privacy')
|
||||
Http::patch('/v1/projects/:projectId/auth/memberships-privacy')
|
||||
->desc('Update project memberships privacy attributes')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -790,7 +414,7 @@ App::patch('/v1/projects/:projectId/auth/memberships-privacy')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/limit')
|
||||
Http::patch('/v1/projects/:projectId/auth/limit')
|
||||
->desc('Update project users limit')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -828,7 +452,7 @@ App::patch('/v1/projects/:projectId/auth/limit')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/duration')
|
||||
Http::patch('/v1/projects/:projectId/auth/duration')
|
||||
->desc('Update project authentication duration')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -866,7 +490,7 @@ App::patch('/v1/projects/:projectId/auth/duration')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/:method')
|
||||
Http::patch('/v1/projects/:projectId/auth/:method')
|
||||
->desc('Update project auth method status. Use this endpoint to enable or disable a given auth method for this project.')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -907,7 +531,7 @@ App::patch('/v1/projects/:projectId/auth/:method')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/password-history')
|
||||
Http::patch('/v1/projects/:projectId/auth/password-history')
|
||||
->desc('Update authentication password history. Use this endpoint to set the number of password history to save and 0 to disable password history.')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -945,7 +569,7 @@ App::patch('/v1/projects/:projectId/auth/password-history')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/password-dictionary')
|
||||
Http::patch('/v1/projects/:projectId/auth/password-dictionary')
|
||||
->desc('Update authentication password dictionary status. Use this endpoint to enable or disable the dicitonary check for user password')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -983,7 +607,7 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/personal-data')
|
||||
Http::patch('/v1/projects/:projectId/auth/personal-data')
|
||||
->desc('Update personal data check')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1021,7 +645,7 @@ App::patch('/v1/projects/:projectId/auth/personal-data')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/max-sessions')
|
||||
Http::patch('/v1/projects/:projectId/auth/max-sessions')
|
||||
->desc('Update project user sessions limit')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1059,7 +683,7 @@ App::patch('/v1/projects/:projectId/auth/max-sessions')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/mock-numbers')
|
||||
Http::patch('/v1/projects/:projectId/auth/mock-numbers')
|
||||
->desc('Update the mock numbers for the project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1105,7 +729,7 @@ App::patch('/v1/projects/:projectId/auth/mock-numbers')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId')
|
||||
Http::delete('/v1/projects/:projectId')
|
||||
->desc('Delete project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('audits.event', 'projects.delete')
|
||||
@@ -1151,7 +775,7 @@ App::delete('/v1/projects/:projectId')
|
||||
|
||||
// Webhooks
|
||||
|
||||
App::post('/v1/projects/:projectId/webhooks')
|
||||
Http::post('/v1/projects/:projectId/webhooks')
|
||||
->desc('Create webhook')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1216,7 +840,7 @@ App::post('/v1/projects/:projectId/webhooks')
|
||||
->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/webhooks')
|
||||
Http::get('/v1/projects/:projectId/webhooks')
|
||||
->desc('List webhooks')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
@@ -1256,7 +880,7 @@ App::get('/v1/projects/:projectId/webhooks')
|
||||
]), Response::MODEL_WEBHOOK_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
Http::get('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->desc('Get webhook')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
@@ -1297,7 +921,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
App::put('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
Http::put('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->desc('Update webhook')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1363,7 +987,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
|
||||
Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
|
||||
->desc('Update webhook signature key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1409,7 +1033,7 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
Http::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->desc('Delete webhook')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1457,7 +1081,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
|
||||
// Keys
|
||||
|
||||
App::post('/v1/projects/:projectId/keys')
|
||||
Http::post('/v1/projects/:projectId/keys')
|
||||
->desc('Create key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'keys.write')
|
||||
@@ -1475,12 +1099,15 @@ App::post('/v1/projects/:projectId/keys')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
// TODO: When migrating to Platform API, mark keyId required for consistency
|
||||
->param('keyId', 'unique()', new CustomId(), 'Key ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true)
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) {
|
||||
->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) {
|
||||
$keyId = $keyId == 'unique()' ? ID::unique() : $keyId;
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
@@ -1489,14 +1116,15 @@ App::post('/v1/projects/:projectId/keys')
|
||||
}
|
||||
|
||||
$key = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$id' => $keyId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $project->getId(),
|
||||
'resourceInternalId' => $project->getSequence(),
|
||||
'resourceId' => $project->getId(),
|
||||
'resourceType' => 'projects',
|
||||
'name' => $name,
|
||||
'scopes' => $scopes,
|
||||
'expire' => $expire,
|
||||
@@ -1505,7 +1133,11 @@ App::post('/v1/projects/:projectId/keys')
|
||||
'secret' => API_KEY_STANDARD . '_' . \bin2hex(\random_bytes(128)),
|
||||
]);
|
||||
|
||||
$key = $dbForPlatform->createDocument('keys', $key);
|
||||
try {
|
||||
$key = $dbForPlatform->createDocument('keys', $key);
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::KEY_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
@@ -1514,7 +1146,7 @@ App::post('/v1/projects/:projectId/keys')
|
||||
->dynamic($key, Response::MODEL_KEY);
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/keys')
|
||||
Http::get('/v1/projects/:projectId/keys')
|
||||
->desc('List keys')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'keys.read')
|
||||
@@ -1532,10 +1164,11 @@ App::get('/v1/projects/:projectId/keys')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('queries', [], new Keys(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Keys::ALLOWED_ATTRIBUTES), true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) {
|
||||
->action(function (string $projectId, array $queries, bool $includeTotal, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
@@ -1543,18 +1176,50 @@ App::get('/v1/projects/:projectId/keys')
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$keys = $dbForPlatform->find('keys', [
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::limit(5000),
|
||||
]);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Backwards compatibility
|
||||
if (\count(Query::getByType($queries, [Query::TYPE_LIMIT])) === 0) {
|
||||
$queries[] = Query::limit(5000);
|
||||
}
|
||||
|
||||
$queries[] = Query::equal('resourceType', ['projects']);
|
||||
$queries[] = Query::equal('resourceInternalId', [$project->getSequence()]);
|
||||
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$keyId = $cursor->getValue();
|
||||
$cursorDocument = $dbForPlatform->getDocument('keys', $keyId);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Key '{$keyId}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$keys = $dbForPlatform->find('keys', $queries);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'keys' => $keys,
|
||||
'total' => $includeTotal ? count($keys) : 0,
|
||||
'total' => $includeTotal ? $dbForPlatform->count('keys', $filterQueries, APP_LIMIT_COUNT) : 0,
|
||||
]), Response::MODEL_KEY_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/keys/:keyId')
|
||||
Http::get('/v1/projects/:projectId/keys/:keyId')
|
||||
->desc('Get key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'keys.read')
|
||||
@@ -1585,7 +1250,8 @@ App::get('/v1/projects/:projectId/keys/:keyId')
|
||||
|
||||
$key = $dbForPlatform->findOne('keys', [
|
||||
Query::equal('$id', [$keyId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::equal('resourceType', ['projects']),
|
||||
Query::equal('resourceInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
|
||||
if ($key->isEmpty()) {
|
||||
@@ -1595,7 +1261,7 @@ App::get('/v1/projects/:projectId/keys/:keyId')
|
||||
$response->dynamic($key, Response::MODEL_KEY);
|
||||
});
|
||||
|
||||
App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
Http::put('/v1/projects/:projectId/keys/:keyId')
|
||||
->desc('Update key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'keys.write')
|
||||
@@ -1615,7 +1281,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('keyId', '', new UID(), 'Key unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
@@ -1629,7 +1295,8 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
|
||||
$key = $dbForPlatform->findOne('keys', [
|
||||
Query::equal('$id', [$keyId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::equal('resourceType', ['projects']),
|
||||
Query::equal('resourceInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
|
||||
if ($key->isEmpty()) {
|
||||
@@ -1648,7 +1315,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
$response->dynamic($key, Response::MODEL_KEY);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId/keys/:keyId')
|
||||
Http::delete('/v1/projects/:projectId/keys/:keyId')
|
||||
->desc('Delete key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'keys.write')
|
||||
@@ -1680,7 +1347,8 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
|
||||
|
||||
$key = $dbForPlatform->findOne('keys', [
|
||||
Query::equal('$id', [$keyId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::equal('resourceType', ['projects']),
|
||||
Query::equal('resourceInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
|
||||
if ($key->isEmpty()) {
|
||||
@@ -1696,7 +1364,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
|
||||
|
||||
// JWT Keys
|
||||
|
||||
App::post('/v1/projects/:projectId/jwts')
|
||||
Http::post('/v1/projects/:projectId/jwts')
|
||||
->groups(['api', 'projects'])
|
||||
->desc('Create JWT')
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1714,7 +1382,7 @@ App::post('/v1/projects/:projectId/jwts')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
@@ -1738,7 +1406,7 @@ App::post('/v1/projects/:projectId/jwts')
|
||||
|
||||
// Platforms
|
||||
|
||||
App::post('/v1/projects/:projectId/platforms')
|
||||
Http::post('/v1/projects/:projectId/platforms')
|
||||
->desc('Create platform')
|
||||
->groups(['api', 'projects'])
|
||||
->label('audits.event', 'platforms.create')
|
||||
@@ -1818,7 +1486,7 @@ App::post('/v1/projects/:projectId/platforms')
|
||||
->dynamic($platform, Response::MODEL_PLATFORM);
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/platforms')
|
||||
Http::get('/v1/projects/:projectId/platforms')
|
||||
->desc('List platforms')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'platforms.read')
|
||||
@@ -1858,7 +1526,7 @@ App::get('/v1/projects/:projectId/platforms')
|
||||
]), Response::MODEL_PLATFORM_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/platforms/:platformId')
|
||||
Http::get('/v1/projects/:projectId/platforms/:platformId')
|
||||
->desc('Get platform')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'platforms.read')
|
||||
@@ -1899,7 +1567,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
|
||||
$response->dynamic($platform, Response::MODEL_PLATFORM);
|
||||
});
|
||||
|
||||
App::put('/v1/projects/:projectId/platforms/:platformId')
|
||||
Http::put('/v1/projects/:projectId/platforms/:platformId')
|
||||
->desc('Update platform')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'platforms.write')
|
||||
@@ -1953,7 +1621,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
|
||||
$response->dynamic($platform, Response::MODEL_PLATFORM);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId/platforms/:platformId')
|
||||
Http::delete('/v1/projects/:projectId/platforms/:platformId')
|
||||
->desc('Delete platform')
|
||||
->groups(['api', 'projects'])
|
||||
->label('audits.event', 'platforms.delete')
|
||||
@@ -2003,7 +1671,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
|
||||
|
||||
|
||||
// CUSTOM SMTP and Templates
|
||||
App::patch('/v1/projects/:projectId/smtp')
|
||||
Http::patch('/v1/projects/:projectId/smtp')
|
||||
->desc('Update SMTP')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -2024,6 +1692,7 @@ App::patch('/v1/projects/:projectId/smtp')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.updateSMTP',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -2076,6 +1745,7 @@ App::patch('/v1/projects/:projectId/smtp')
|
||||
if ($enabled) {
|
||||
$mail = new PHPMailer(true);
|
||||
$mail->isSMTP();
|
||||
$mail->SMTPAuth = (!empty($username) && !empty($password));
|
||||
$mail->Username = $username;
|
||||
$mail->Password = $password;
|
||||
$mail->Host = $host;
|
||||
@@ -2091,7 +1761,7 @@ App::patch('/v1/projects/:projectId/smtp')
|
||||
throw new Exception('Connection is not valid.');
|
||||
}
|
||||
} catch (Throwable $error) {
|
||||
throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, 'Could not connect to SMTP server: ' . $error->getMessage());
|
||||
throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2119,7 +1789,7 @@ App::patch('/v1/projects/:projectId/smtp')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::post('/v1/projects/:projectId/smtp/tests')
|
||||
Http::post('/v1/projects/:projectId/smtp/tests')
|
||||
->desc('Create SMTP test')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -2140,6 +1810,7 @@ App::post('/v1/projects/:projectId/smtp/tests')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.createSMTPTest',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -2213,7 +1884,7 @@ App::post('/v1/projects/:projectId/smtp/tests')
|
||||
return $response->noContent();
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
Http::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->desc('Get custom SMS template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -2234,6 +1905,7 @@ App::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.getSMSTemplate',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -2280,7 +1952,7 @@ App::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
});
|
||||
|
||||
|
||||
App::get('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
->desc('Get custom email template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -2379,7 +2051,7 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
$response->dynamic(new Document($template), Response::MODEL_EMAIL_TEMPLATE);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->desc('Update custom SMS template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -2400,6 +2072,7 @@ App::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.updateSMSTemplate',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -2445,7 +2118,7 @@ App::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
]), Response::MODEL_SMS_TEMPLATE);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
Http::patch('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
->desc('Update custom email templates')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -2502,7 +2175,7 @@ App::patch('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
]), Response::MODEL_EMAIL_TEMPLATE);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->desc('Reset custom SMS template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -2524,6 +2197,7 @@ App::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.deleteSMSTemplate',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -2573,7 +2247,7 @@ App::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
]), Response::MODEL_SMS_TEMPLATE);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
Http::delete('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
->desc('Delete custom email template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -2626,7 +2300,7 @@ App::delete('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
]), Response::MODEL_EMAIL_TEMPLATE);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/session-invalidation')
|
||||
Http::patch('/v1/projects/:projectId/auth/session-invalidation')
|
||||
->desc('Update invalidate session option of the project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -2658,7 +2332,7 @@ App::patch('/v1/projects/:projectId/auth/session-invalidation')
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['invalidateSessions'] = $enabled;
|
||||
$dbForPlatform->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+102
-127
@@ -23,10 +23,9 @@ use Appwrite\Utopia\Database\Validator\Queries\Memberships;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Teams;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use libphonenumber\NumberParseException;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Auth\Proofs\Password;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
@@ -52,16 +51,15 @@ use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Emails\Email;
|
||||
use Utopia\Http;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
App::post('/v1/teams')
|
||||
Http::post('/v1/teams')
|
||||
->desc('Create team')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].create')
|
||||
@@ -73,7 +71,7 @@ App::post('/v1/teams')
|
||||
group: 'teams',
|
||||
name: 'create',
|
||||
description: '/docs/references/teams/create-team.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -87,22 +85,24 @@ App::post('/v1/teams')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
|
||||
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
$isAppUser = User::isApp($authorization->getRoles());
|
||||
|
||||
$teamId = $teamId == 'unique()' ? ID::unique() : $teamId;
|
||||
|
||||
try {
|
||||
$team = Authorization::skip(fn () => $dbForProject->createDocument('teams', new Document([
|
||||
$team = $authorization->skip(fn () => $dbForProject->createDocument('teams', new Document([
|
||||
'$id' => $teamId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team($teamId)),
|
||||
Permission::update(Role::team($teamId, 'owner')),
|
||||
Permission::delete(Role::team($teamId, 'owner')),
|
||||
],
|
||||
'labels' => [],
|
||||
'name' => $name,
|
||||
'total' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
|
||||
'prefs' => new \stdClass(),
|
||||
@@ -155,7 +155,7 @@ App::post('/v1/teams')
|
||||
->dynamic($team, Response::MODEL_TEAM);
|
||||
});
|
||||
|
||||
App::get('/v1/teams')
|
||||
Http::get('/v1/teams')
|
||||
->desc('List teams')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
@@ -164,7 +164,7 @@ App::get('/v1/teams')
|
||||
group: 'teams',
|
||||
name: 'list',
|
||||
description: '/docs/references/teams/list-teams.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -190,16 +190,10 @@ App::get('/v1/teams')
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
@@ -229,7 +223,7 @@ App::get('/v1/teams')
|
||||
]), Response::MODEL_TEAM_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/teams/:teamId')
|
||||
Http::get('/v1/teams/:teamId')
|
||||
->desc('Get team')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
@@ -238,7 +232,7 @@ App::get('/v1/teams/:teamId')
|
||||
group: 'teams',
|
||||
name: 'get',
|
||||
description: '/docs/references/teams/get-team.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -260,7 +254,7 @@ App::get('/v1/teams/:teamId')
|
||||
$response->dynamic($team, Response::MODEL_TEAM);
|
||||
});
|
||||
|
||||
App::get('/v1/teams/:teamId/prefs')
|
||||
Http::get('/v1/teams/:teamId/prefs')
|
||||
->desc('Get team preferences')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
@@ -269,7 +263,7 @@ App::get('/v1/teams/:teamId/prefs')
|
||||
group: 'teams',
|
||||
name: 'getPrefs',
|
||||
description: '/docs/references/teams/get-team-prefs.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -299,7 +293,7 @@ App::get('/v1/teams/:teamId/prefs')
|
||||
$response->dynamic($prefs, Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::put('/v1/teams/:teamId')
|
||||
Http::put('/v1/teams/:teamId')
|
||||
->desc('Update name')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].update')
|
||||
@@ -311,7 +305,7 @@ App::put('/v1/teams/:teamId')
|
||||
group: 'teams',
|
||||
name: 'updateName',
|
||||
description: '/docs/references/teams/update-team-name.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -344,7 +338,7 @@ App::put('/v1/teams/:teamId')
|
||||
$response->dynamic($team, Response::MODEL_TEAM);
|
||||
});
|
||||
|
||||
App::put('/v1/teams/:teamId/prefs')
|
||||
Http::put('/v1/teams/:teamId/prefs')
|
||||
->desc('Update preferences')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].update.prefs')
|
||||
@@ -357,7 +351,7 @@ App::put('/v1/teams/:teamId/prefs')
|
||||
group: 'teams',
|
||||
name: 'updatePrefs',
|
||||
description: '/docs/references/teams/update-team-prefs.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -392,7 +386,7 @@ App::put('/v1/teams/:teamId/prefs')
|
||||
$response->dynamic($prefs, Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::delete('/v1/teams/:teamId')
|
||||
Http::delete('/v1/teams/:teamId')
|
||||
->desc('Delete team')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].delete')
|
||||
@@ -404,7 +398,7 @@ App::delete('/v1/teams/:teamId')
|
||||
group: 'teams',
|
||||
name: 'delete',
|
||||
description: '/docs/references/teams/delete-team.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
@@ -432,15 +426,22 @@ App::delete('/v1/teams/:teamId')
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove team from DB');
|
||||
}
|
||||
|
||||
// Sync delete
|
||||
$deletes = new Deletes();
|
||||
$deletes->deleteMemberships($getProjectDB, $team, $project);
|
||||
|
||||
// Async delete
|
||||
if ($project->getId() === 'console') {
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_TEAM_PROJECTS)
|
||||
->setDocument($team);
|
||||
->setDocument($team)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($team);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('teamId', $team->getId())
|
||||
->setPayload($response->output($team, Response::MODEL_TEAM))
|
||||
@@ -449,7 +450,7 @@ App::delete('/v1/teams/:teamId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/teams/:teamId/memberships')
|
||||
Http::post('/v1/teams/:teamId/memberships')
|
||||
->desc('Create team membership')
|
||||
->groups(['api', 'teams', 'auth'])
|
||||
->label('event', 'teams.[teamId].memberships.[membershipId].create')
|
||||
@@ -463,7 +464,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
group: 'memberships',
|
||||
name: 'createMembership',
|
||||
description: '/docs/references/teams/create-team-membership.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -476,22 +477,14 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->param('email', '', new EmailValidator(), 'Email of the new team member.', true)
|
||||
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('roles', [], function (Document $project) {
|
||||
if ($project->getId() === 'console') {
|
||||
$roles = array_keys(Config::getParam('roles', []));
|
||||
$roles = array_filter($roles, function ($role) {
|
||||
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
|
||||
});
|
||||
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}
|
||||
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
|
||||
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey']) // TODO add our own built-in confirm page
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
|
||||
->param('url', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator']) // TODO add our own built-in confirm page
|
||||
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('locale')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForMessaging')
|
||||
@@ -501,9 +494,9 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->inject('plan')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Authorization $authorization, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
|
||||
$isAppUser = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
$url = htmlentities($url);
|
||||
if (empty($url)) {
|
||||
@@ -620,13 +613,13 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
]);
|
||||
|
||||
try {
|
||||
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', $userDocument));
|
||||
$invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', $userDocument));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
$isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team');
|
||||
@@ -662,11 +655,11 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
]);
|
||||
|
||||
$membership = ($isPrivilegedUser || $isAppUser) ?
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
|
||||
$authorization->skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
|
||||
$dbForProject->createDocument('memberships', $membership);
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) {
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
}
|
||||
} elseif ($membership->getAttribute('confirm') === false) {
|
||||
$membership->setAttribute('secret', $proofForToken->hash($secret));
|
||||
@@ -678,7 +671,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
}
|
||||
|
||||
$membership = ($isPrivilegedUser || $isAppUser) ?
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) :
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) :
|
||||
$dbForProject->updateDocument('memberships', $membership->getId(), $membership);
|
||||
} else {
|
||||
throw new Exception(Exception::MEMBERSHIP_ALREADY_CONFIRMED);
|
||||
@@ -770,7 +763,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->setPreview($preview)
|
||||
->setRecipient($invitee->getAttribute('email'))
|
||||
->setName($invitee->getAttribute('name', ''))
|
||||
->setVariables($emailVariables)
|
||||
->appendVariables($emailVariables)
|
||||
->trigger();
|
||||
} elseif (!empty($phone)) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
@@ -800,26 +793,21 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->setRecipients([$phone])
|
||||
->setProviderType('SMS');
|
||||
|
||||
if (isset($plan['authPhone'])) {
|
||||
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
|
||||
$timelimit
|
||||
->setParam('{organizationId}', $project->getAttribute('teamId'));
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
try {
|
||||
$countryCode = $helper->parse($phone)->getCountryCode();
|
||||
|
||||
$abuse = new Abuse($timelimit);
|
||||
if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
$countryCode = $helper->parse($phone)->getCountryCode();
|
||||
|
||||
if (!empty($countryCode)) {
|
||||
$queueForStatsUsage
|
||||
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
|
||||
}
|
||||
if (!empty($countryCode)) {
|
||||
$queueForStatsUsage
|
||||
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
|
||||
}
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
} catch (NumberParseException $e) {
|
||||
// Ignore invalid phone number for country code stats
|
||||
}
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -840,7 +828,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
);
|
||||
});
|
||||
|
||||
App::get('/v1/teams/:teamId/memberships')
|
||||
Http::get('/v1/teams/:teamId/memberships')
|
||||
->desc('List team memberships')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
@@ -849,7 +837,7 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
group: 'memberships',
|
||||
name: 'listMemberships',
|
||||
description: '/docs/references/teams/list-team-members.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -864,7 +852,8 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
@@ -884,22 +873,15 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
// Set internal queries
|
||||
$queries[] = Query::equal('teamInternalId', [$team->getSequence()]);
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
|
||||
$membershipId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('memberships', $membershipId);
|
||||
|
||||
@@ -934,7 +916,7 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
|
||||
];
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
$isPrivilegedUser = User::isPrivileged($roles);
|
||||
$isAppUser = User::isApp($roles);
|
||||
|
||||
@@ -983,7 +965,7 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
]), Response::MODEL_MEMBERSHIP_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
Http::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->desc('Get team membership')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
@@ -992,7 +974,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
group: 'memberships',
|
||||
name: 'getMembership',
|
||||
description: '/docs/references/teams/get-team-member.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1005,7 +987,8 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
@@ -1025,7 +1008,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
|
||||
];
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
$isPrivilegedUser = User::isPrivileged($roles);
|
||||
$isAppUser = User::isApp($roles);
|
||||
|
||||
@@ -1067,7 +1050,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
|
||||
});
|
||||
|
||||
App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
Http::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->desc('Update membership')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].memberships.[membershipId].update')
|
||||
@@ -1079,7 +1062,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
group: 'memberships',
|
||||
name: 'updateMembership',
|
||||
description: '/docs/references/teams/update-team-membership.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1089,23 +1072,15 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
))
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||
->param('roles', [], function (Document $project) {
|
||||
if ($project->getId() === 'console') {
|
||||
$roles = array_keys(Config::getParam('roles', []));
|
||||
$roles = array_filter($roles, function ($role) {
|
||||
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
|
||||
});
|
||||
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}
|
||||
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}, 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
if ($team->isEmpty()) {
|
||||
@@ -1122,9 +1097,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
$isAppUser = User::isApp($authorization->getRoles());
|
||||
$isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
// Quick check: fetch up to 2 owners to determine if only one exists
|
||||
@@ -1176,7 +1151,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
);
|
||||
});
|
||||
|
||||
App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->desc('Update team membership status')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].memberships.[membershipId].update.status')
|
||||
@@ -1189,7 +1164,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
group: 'memberships',
|
||||
name: 'updateMembershipStatus',
|
||||
description: '/docs/references/teams/update-team-membership-status.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1205,12 +1180,13 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Authorization $authorization, $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
@@ -1219,7 +1195,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$team = Authorization::skip(fn () => $dbForProject->getDocument('teams', $teamId));
|
||||
$team = $authorization->skip(fn () => $dbForProject->getDocument('teams', $teamId));
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
@@ -1255,11 +1231,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->setAttribute('confirm', true)
|
||||
;
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
|
||||
// Create session for the user if not logged in
|
||||
if (!$hasSession) {
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
@@ -1287,7 +1263,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session);
|
||||
|
||||
Authorization::setRole(Role::user($userId)->toString());
|
||||
$authorization->addRole(Role::user($userId)->toString());
|
||||
|
||||
$encoded = $store
|
||||
->setProperty('id', $user->getId())
|
||||
@@ -1325,7 +1301,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
@@ -1342,7 +1318,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
);
|
||||
});
|
||||
|
||||
App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
Http::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->desc('Delete team membership')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].memberships.[membershipId].delete')
|
||||
@@ -1354,7 +1330,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
group: 'memberships',
|
||||
name: 'deleteMembership',
|
||||
description: '/docs/references/teams/delete-team-membership.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
@@ -1369,8 +1345,9 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
|
||||
@@ -1428,7 +1405,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
$dbForProject->purgeCachedDocument('users', $profile->getId());
|
||||
|
||||
if ($membership->getAttribute('confirm')) { // Count only confirmed members
|
||||
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
|
||||
$authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
@@ -1441,7 +1418,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::get('/v1/teams/:teamId/logs')
|
||||
Http::get('/v1/teams/:teamId/logs')
|
||||
->desc('List team logs')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
@@ -1465,7 +1442,8 @@ App::get('/v1/teams/:teamId/logs')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $teamId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->inject('audit')
|
||||
->action(function (string $teamId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
@@ -1479,15 +1457,12 @@ App::get('/v1/teams/:teamId/logs')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'team/' . $team->getId();
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
|
||||
|
||||
$output = [];
|
||||
|
||||
@@ -1534,7 +1509,7 @@ App::get('/v1/teams/:teamId/logs')
|
||||
}
|
||||
}
|
||||
$response->dynamic(new Document([
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
+120
-126
@@ -29,7 +29,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Users;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Auth\Hash;
|
||||
use Utopia\Auth\Hashes\Argon2;
|
||||
@@ -61,6 +60,7 @@ use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Emails\Email;
|
||||
use Utopia\Http;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
@@ -227,7 +227,7 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
|
||||
return $user;
|
||||
}
|
||||
|
||||
App::post('/v1/users')
|
||||
Http::post('/v1/users')
|
||||
->desc('Create user')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.write')
|
||||
@@ -238,7 +238,7 @@ App::post('/v1/users')
|
||||
group: 'users',
|
||||
name: 'create',
|
||||
description: '/docs/references/users/create-user.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -264,7 +264,7 @@ App::post('/v1/users')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::post('/v1/users/bcrypt')
|
||||
Http::post('/v1/users/bcrypt')
|
||||
->desc('Create user with bcrypt password')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.write')
|
||||
@@ -275,7 +275,7 @@ App::post('/v1/users/bcrypt')
|
||||
group: 'users',
|
||||
name: 'createBcryptUser',
|
||||
description: '/docs/references/users/create-bcrypt-user.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -302,7 +302,7 @@ App::post('/v1/users/bcrypt')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::post('/v1/users/md5')
|
||||
Http::post('/v1/users/md5')
|
||||
->desc('Create user with MD5 password')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.write')
|
||||
@@ -313,7 +313,7 @@ App::post('/v1/users/md5')
|
||||
group: 'users',
|
||||
name: 'createMD5User',
|
||||
description: '/docs/references/users/create-md5-user.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -339,7 +339,7 @@ App::post('/v1/users/md5')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::post('/v1/users/argon2')
|
||||
Http::post('/v1/users/argon2')
|
||||
->desc('Create user with Argon2 password')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.write')
|
||||
@@ -350,7 +350,7 @@ App::post('/v1/users/argon2')
|
||||
group: 'users',
|
||||
name: 'createArgon2User',
|
||||
description: '/docs/references/users/create-argon2-user.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -376,7 +376,7 @@ App::post('/v1/users/argon2')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::post('/v1/users/sha')
|
||||
Http::post('/v1/users/sha')
|
||||
->desc('Create user with SHA password')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.write')
|
||||
@@ -387,7 +387,7 @@ App::post('/v1/users/sha')
|
||||
group: 'users',
|
||||
name: 'createSHAUser',
|
||||
description: '/docs/references/users/create-sha-user.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -417,7 +417,7 @@ App::post('/v1/users/sha')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::post('/v1/users/phpass')
|
||||
Http::post('/v1/users/phpass')
|
||||
->desc('Create user with PHPass password')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.write')
|
||||
@@ -428,7 +428,7 @@ App::post('/v1/users/phpass')
|
||||
group: 'users',
|
||||
name: 'createPHPassUser',
|
||||
description: '/docs/references/users/create-phpass-user.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -454,7 +454,7 @@ App::post('/v1/users/phpass')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::post('/v1/users/scrypt')
|
||||
Http::post('/v1/users/scrypt')
|
||||
->desc('Create user with Scrypt password')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.write')
|
||||
@@ -465,7 +465,7 @@ App::post('/v1/users/scrypt')
|
||||
group: 'users',
|
||||
name: 'createScryptUser',
|
||||
description: '/docs/references/users/create-scrypt-user.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -502,7 +502,7 @@ App::post('/v1/users/scrypt')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::post('/v1/users/scrypt-modified')
|
||||
Http::post('/v1/users/scrypt-modified')
|
||||
->desc('Create user with Scrypt modified password')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.write')
|
||||
@@ -513,7 +513,7 @@ App::post('/v1/users/scrypt-modified')
|
||||
group: 'users',
|
||||
name: 'createScryptModifiedUser',
|
||||
description: '/docs/references/users/create-scrypt-modified-user.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -546,7 +546,7 @@ App::post('/v1/users/scrypt-modified')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::post('/v1/users/:userId/targets')
|
||||
Http::post('/v1/users/:userId/targets')
|
||||
->desc('Create user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.create')
|
||||
@@ -641,7 +641,7 @@ App::post('/v1/users/:userId/targets')
|
||||
->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
App::get('/v1/users')
|
||||
Http::get('/v1/users')
|
||||
->desc('List users')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -650,7 +650,7 @@ App::get('/v1/users')
|
||||
group: 'users',
|
||||
name: 'list',
|
||||
description: '/docs/references/users/list-users.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -675,16 +675,10 @@ App::get('/v1/users')
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
@@ -720,7 +714,7 @@ App::get('/v1/users')
|
||||
]), Response::MODEL_USER_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId')
|
||||
Http::get('/v1/users/:userId')
|
||||
->desc('Get user')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -729,7 +723,7 @@ App::get('/v1/users/:userId')
|
||||
group: 'users',
|
||||
name: 'get',
|
||||
description: '/docs/references/users/get-user.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -751,7 +745,7 @@ App::get('/v1/users/:userId')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/prefs')
|
||||
Http::get('/v1/users/:userId/prefs')
|
||||
->desc('Get user preferences')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -760,7 +754,7 @@ App::get('/v1/users/:userId/prefs')
|
||||
group: 'users',
|
||||
name: 'getPrefs',
|
||||
description: '/docs/references/users/get-user-prefs.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -784,7 +778,7 @@ App::get('/v1/users/:userId/prefs')
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/targets/:targetId')
|
||||
Http::get('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Get user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'targets.read')
|
||||
@@ -822,7 +816,7 @@ App::get('/v1/users/:userId/targets/:targetId')
|
||||
$response->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/sessions')
|
||||
Http::get('/v1/users/:userId/sessions')
|
||||
->desc('List user sessions')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -831,7 +825,7 @@ App::get('/v1/users/:userId/sessions')
|
||||
group: 'sessions',
|
||||
name: 'listSessions',
|
||||
description: '/docs/references/users/list-user-sessions.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -866,7 +860,7 @@ App::get('/v1/users/:userId/sessions')
|
||||
]), Response::MODEL_SESSION_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/memberships')
|
||||
Http::get('/v1/users/:userId/memberships')
|
||||
->desc('List user memberships')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -875,7 +869,7 @@ App::get('/v1/users/:userId/memberships')
|
||||
group: 'memberships',
|
||||
name: 'listMemberships',
|
||||
description: '/docs/references/users/list-user-memberships.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -921,7 +915,7 @@ App::get('/v1/users/:userId/memberships')
|
||||
]), Response::MODEL_MEMBERSHIP_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/logs')
|
||||
Http::get('/v1/users/:userId/logs')
|
||||
->desc('List user logs')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -930,7 +924,7 @@ App::get('/v1/users/:userId/logs')
|
||||
group: 'logs',
|
||||
name: 'listLogs',
|
||||
description: '/docs/references/users/list-user-logs.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -945,7 +939,8 @@ App::get('/v1/users/:userId/logs')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $userId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->inject('audit')
|
||||
->action(function (string $userId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -957,13 +952,12 @@ App::get('/v1/users/:userId/logs')
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
$audit = new Audit($dbForProject);
|
||||
$logs = $audit->getLogsByUser($user->getSequence(), $queries);
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$logs = $audit->getLogsByUser($user->getSequence(), limit: $limit, offset: $offset);
|
||||
$output = [];
|
||||
foreach ($logs as $i => &$log) {
|
||||
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
|
||||
@@ -1003,12 +997,12 @@ App::get('/v1/users/:userId/logs')
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence(), $queries) : 0,
|
||||
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence()) : 0,
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/targets')
|
||||
Http::get('/v1/users/:userId/targets')
|
||||
->desc('List user targets')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'targets.read')
|
||||
@@ -1042,14 +1036,11 @@ App::get('/v1/users/:userId/targets')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
$queries[] = Query::equal('userId', [$userId]);
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
@@ -1073,7 +1064,7 @@ App::get('/v1/users/:userId/targets')
|
||||
]), Response::MODEL_TARGET_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/users/identities')
|
||||
Http::get('/v1/users/identities')
|
||||
->desc('List identities')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -1082,7 +1073,7 @@ App::get('/v1/users/identities')
|
||||
group: 'identities',
|
||||
name: 'listIdentities',
|
||||
description: '/docs/references/users/list-identities.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1106,15 +1097,11 @@ App::get('/v1/users/identities')
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
@@ -1139,7 +1126,7 @@ App::get('/v1/users/identities')
|
||||
]), Response::MODEL_IDENTITY_LIST);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/status')
|
||||
Http::patch('/v1/users/:userId/status')
|
||||
->desc('Update user status')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.status')
|
||||
@@ -1152,7 +1139,7 @@ App::patch('/v1/users/:userId/status')
|
||||
group: 'users',
|
||||
name: 'updateStatus',
|
||||
description: '/docs/references/users/update-user-status.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1181,7 +1168,7 @@ App::patch('/v1/users/:userId/status')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::put('/v1/users/:userId/labels')
|
||||
Http::put('/v1/users/:userId/labels')
|
||||
->desc('Update user labels')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.labels')
|
||||
@@ -1193,7 +1180,7 @@ App::put('/v1/users/:userId/labels')
|
||||
group: 'users',
|
||||
name: 'updateLabels',
|
||||
description: '/docs/references/users/update-user-labels.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1224,7 +1211,7 @@ App::put('/v1/users/:userId/labels')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/verification/phone')
|
||||
Http::patch('/v1/users/:userId/verification/phone')
|
||||
->desc('Update phone verification')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.verification')
|
||||
@@ -1236,7 +1223,7 @@ App::patch('/v1/users/:userId/verification/phone')
|
||||
group: 'users',
|
||||
name: 'updatePhoneVerification',
|
||||
description: '/docs/references/users/update-user-phone-verification.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1265,7 +1252,7 @@ App::patch('/v1/users/:userId/verification/phone')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/name')
|
||||
Http::patch('/v1/users/:userId/name')
|
||||
->desc('Update name')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.name')
|
||||
@@ -1278,7 +1265,7 @@ App::patch('/v1/users/:userId/name')
|
||||
group: 'users',
|
||||
name: 'updateName',
|
||||
description: '/docs/references/users/update-user-name.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1308,7 +1295,7 @@ App::patch('/v1/users/:userId/name')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/password')
|
||||
Http::patch('/v1/users/:userId/password')
|
||||
->desc('Update password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.password')
|
||||
@@ -1321,7 +1308,7 @@ App::patch('/v1/users/:userId/password')
|
||||
group: 'users',
|
||||
name: 'updatePassword',
|
||||
description: '/docs/references/users/update-user-password.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1407,7 +1394,7 @@ App::patch('/v1/users/:userId/password')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/email')
|
||||
Http::patch('/v1/users/:userId/email')
|
||||
->desc('Update email')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.email')
|
||||
@@ -1420,7 +1407,7 @@ App::patch('/v1/users/:userId/email')
|
||||
group: 'users',
|
||||
name: 'updateEmail',
|
||||
description: '/docs/references/users/update-user-email.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1519,7 +1506,7 @@ App::patch('/v1/users/:userId/email')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/phone')
|
||||
Http::patch('/v1/users/:userId/phone')
|
||||
->desc('Update phone')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.phone')
|
||||
@@ -1531,7 +1518,7 @@ App::patch('/v1/users/:userId/phone')
|
||||
group: 'users',
|
||||
name: 'updatePhone',
|
||||
description: '/docs/references/users/update-user-phone.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1608,7 +1595,7 @@ App::patch('/v1/users/:userId/phone')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/verification')
|
||||
Http::patch('/v1/users/:userId/verification')
|
||||
->desc('Update email verification')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.verification')
|
||||
@@ -1621,7 +1608,7 @@ App::patch('/v1/users/:userId/verification')
|
||||
group: 'users',
|
||||
name: 'updateEmailVerification',
|
||||
description: '/docs/references/users/update-user-email-verification.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1649,7 +1636,7 @@ App::patch('/v1/users/:userId/verification')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/prefs')
|
||||
Http::patch('/v1/users/:userId/prefs')
|
||||
->desc('Update user preferences')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.prefs')
|
||||
@@ -1659,7 +1646,7 @@ App::patch('/v1/users/:userId/prefs')
|
||||
group: 'users',
|
||||
name: 'updatePrefs',
|
||||
description: '/docs/references/users/update-user-prefs.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1688,7 +1675,7 @@ App::patch('/v1/users/:userId/prefs')
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/targets/:targetId')
|
||||
Http::patch('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Update user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.update')
|
||||
@@ -1791,7 +1778,7 @@ App::patch('/v1/users/:userId/targets/:targetId')
|
||||
->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/mfa')
|
||||
Http::patch('/v1/users/:userId/mfa')
|
||||
->desc('Update MFA')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.mfa')
|
||||
@@ -1806,7 +1793,7 @@ App::patch('/v1/users/:userId/mfa')
|
||||
group: 'users',
|
||||
name: 'updateMfa',
|
||||
description: '/docs/references/users/update-user-mfa.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1817,13 +1804,14 @@ App::patch('/v1/users/:userId/mfa')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'users.updateMFA',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'users',
|
||||
group: 'users',
|
||||
name: 'updateMFA',
|
||||
description: '/docs/references/users/update-user-mfa.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1854,7 +1842,7 @@ App::patch('/v1/users/:userId/mfa')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/mfa/factors')
|
||||
Http::get('/v1/users/:userId/mfa/factors')
|
||||
->desc('List factors')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -1865,7 +1853,7 @@ App::get('/v1/users/:userId/mfa/factors')
|
||||
group: 'mfa',
|
||||
name: 'listMfaFactors',
|
||||
description: '/docs/references/users/list-mfa-factors.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1876,13 +1864,14 @@ App::get('/v1/users/:userId/mfa/factors')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'users.listMFAFactors',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'users',
|
||||
group: 'mfa',
|
||||
name: 'listMFAFactors',
|
||||
description: '/docs/references/users/list-mfa-factors.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1912,7 +1901,7 @@ App::get('/v1/users/:userId/mfa/factors')
|
||||
$response->dynamic($factors, Response::MODEL_MFA_FACTORS);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/mfa/recovery-codes')
|
||||
Http::get('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Get MFA recovery codes')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -1923,7 +1912,7 @@ App::get('/v1/users/:userId/mfa/recovery-codes')
|
||||
group: 'mfa',
|
||||
name: 'getMfaRecoveryCodes',
|
||||
description: '/docs/references/users/get-mfa-recovery-codes.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1934,13 +1923,14 @@ App::get('/v1/users/:userId/mfa/recovery-codes')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'users.getMFARecoveryCodes',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'users',
|
||||
group: 'mfa',
|
||||
name: 'getMFARecoveryCodes',
|
||||
description: '/docs/references/users/get-mfa-recovery-codes.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1972,7 +1962,7 @@ App::get('/v1/users/:userId/mfa/recovery-codes')
|
||||
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/mfa/recovery-codes')
|
||||
Http::patch('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Create MFA recovery codes')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create.mfa.recovery-codes')
|
||||
@@ -1987,7 +1977,7 @@ App::patch('/v1/users/:userId/mfa/recovery-codes')
|
||||
group: 'mfa',
|
||||
name: 'createMfaRecoveryCodes',
|
||||
description: '/docs/references/users/create-mfa-recovery-codes.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -1998,13 +1988,14 @@ App::patch('/v1/users/:userId/mfa/recovery-codes')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'users.createMFARecoveryCodes',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'users',
|
||||
group: 'mfa',
|
||||
name: 'createMFARecoveryCodes',
|
||||
description: '/docs/references/users/create-mfa-recovery-codes.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -2043,7 +2034,7 @@ App::patch('/v1/users/:userId/mfa/recovery-codes')
|
||||
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
|
||||
});
|
||||
|
||||
App::put('/v1/users/:userId/mfa/recovery-codes')
|
||||
Http::put('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Update MFA recovery codes (regenerate)')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.mfa.recovery-codes')
|
||||
@@ -2058,7 +2049,7 @@ App::put('/v1/users/:userId/mfa/recovery-codes')
|
||||
group: 'mfa',
|
||||
name: 'updateMfaRecoveryCodes',
|
||||
description: '/docs/references/users/update-mfa-recovery-codes.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -2069,19 +2060,21 @@ App::put('/v1/users/:userId/mfa/recovery-codes')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'users.updateMFARecoveryCodes',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'users',
|
||||
group: 'mfa',
|
||||
name: 'updateMFARecoveryCodes',
|
||||
description: '/docs/references/users/update-mfa-recovery-codes.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_MFA_RECOVERY_CODES,
|
||||
)
|
||||
]
|
||||
],
|
||||
public: false,
|
||||
)
|
||||
])
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
@@ -2113,7 +2106,7 @@ App::put('/v1/users/:userId/mfa/recovery-codes')
|
||||
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/mfa/authenticators/:type')
|
||||
Http::delete('/v1/users/:userId/mfa/authenticators/:type')
|
||||
->desc('Delete authenticator')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].delete.mfa')
|
||||
@@ -2128,7 +2121,7 @@ App::delete('/v1/users/:userId/mfa/authenticators/:type')
|
||||
group: 'mfa',
|
||||
name: 'deleteMfaAuthenticator',
|
||||
description: '/docs/references/users/delete-mfa-authenticator.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
@@ -2140,13 +2133,14 @@ App::delete('/v1/users/:userId/mfa/authenticators/:type')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'users.deleteMFAAuthenticator',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'users',
|
||||
group: 'mfa',
|
||||
name: 'deleteMFAAuthenticator',
|
||||
description: '/docs/references/users/delete-mfa-authenticator.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
@@ -2182,7 +2176,7 @@ App::delete('/v1/users/:userId/mfa/authenticators/:type')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/users/:userId/sessions')
|
||||
Http::post('/v1/users/:userId/sessions')
|
||||
->desc('Create session')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
@@ -2195,7 +2189,7 @@ App::post('/v1/users/:userId/sessions')
|
||||
group: 'sessions',
|
||||
name: 'createSession',
|
||||
description: '/docs/references/users/create-session.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -2275,7 +2269,7 @@ App::post('/v1/users/:userId/sessions')
|
||||
->dynamic($session, Response::MODEL_SESSION);
|
||||
});
|
||||
|
||||
App::post('/v1/users/:userId/tokens')
|
||||
Http::post('/v1/users/:userId/tokens')
|
||||
->desc('Create token')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].tokens.[tokenId].create')
|
||||
@@ -2287,7 +2281,7 @@ App::post('/v1/users/:userId/tokens')
|
||||
group: 'sessions',
|
||||
name: 'createToken',
|
||||
description: '/docs/references/users/create-token.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -2340,7 +2334,7 @@ App::post('/v1/users/:userId/tokens')
|
||||
->dynamic($token, Response::MODEL_TOKEN);
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
Http::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
->desc('Delete user session')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].sessions.[sessionId].delete')
|
||||
@@ -2352,7 +2346,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
group: 'sessions',
|
||||
name: 'deleteSession',
|
||||
description: '/docs/references/users/delete-user-session.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
@@ -2391,7 +2385,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/sessions')
|
||||
Http::delete('/v1/users/:userId/sessions')
|
||||
->desc('Delete user sessions')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].sessions.delete')
|
||||
@@ -2403,7 +2397,7 @@ App::delete('/v1/users/:userId/sessions')
|
||||
group: 'sessions',
|
||||
name: 'deleteSessions',
|
||||
description: '/docs/references/users/delete-user-sessions.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
@@ -2441,7 +2435,7 @@ App::delete('/v1/users/:userId/sessions')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId')
|
||||
Http::delete('/v1/users/:userId')
|
||||
->desc('Delete user')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].delete')
|
||||
@@ -2453,7 +2447,7 @@ App::delete('/v1/users/:userId')
|
||||
group: 'users',
|
||||
name: 'delete',
|
||||
description: '/docs/references/users/delete.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
@@ -2493,7 +2487,7 @@ App::delete('/v1/users/:userId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/targets/:targetId')
|
||||
Http::delete('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Delete user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.delete')
|
||||
@@ -2551,7 +2545,7 @@ App::delete('/v1/users/:userId/targets/:targetId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/users/identities/:identityId')
|
||||
Http::delete('/v1/users/identities/:identityId')
|
||||
->desc('Delete identity')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].identities.[identityId].delete')
|
||||
@@ -2563,7 +2557,7 @@ App::delete('/v1/users/identities/:identityId')
|
||||
group: 'identities',
|
||||
name: 'deleteIdentity',
|
||||
description: '/docs/references/users/delete-identity.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
@@ -2594,7 +2588,7 @@ App::delete('/v1/users/identities/:identityId')
|
||||
return $response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/users/:userId/jwts')
|
||||
Http::post('/v1/users/:userId/jwts')
|
||||
->desc('Create user JWT')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.write')
|
||||
@@ -2603,7 +2597,7 @@ App::post('/v1/users/:userId/jwts')
|
||||
group: 'sessions',
|
||||
name: 'createJWT',
|
||||
description: '/docs/references/users/create-user-jwt.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -2651,7 +2645,7 @@ App::post('/v1/users/:userId/jwts')
|
||||
])]), Response::MODEL_JWT);
|
||||
});
|
||||
|
||||
App::get('/v1/users/usage')
|
||||
Http::get('/v1/users/usage')
|
||||
->desc('Get users usage stats')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -2671,8 +2665,8 @@ App::get('/v1/users/usage')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('register')
|
||||
->action(function (string $range, Response $response, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
|
||||
$periods = Config::getParam('usage', []);
|
||||
$stats = $usage = [];
|
||||
@@ -2682,7 +2676,7 @@ App::get('/v1/users/usage')
|
||||
METRIC_SESSIONS,
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $count => $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
|
||||
+51
-1193
File diff suppressed because it is too large
Load Diff
+375
-243
File diff suppressed because it is too large
Load Diff
+39
-17
@@ -5,7 +5,6 @@ global $utopia, $request, $response;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
@@ -13,21 +12,21 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
|
||||
App::get('/v1/mock/tests/general/oauth2')
|
||||
Http::get('/v1/mock/tests/general/oauth2')
|
||||
->desc('OAuth Login')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->label('mock', true)
|
||||
->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
|
||||
->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.') // Important to deny an open redirect attack
|
||||
->param('redirect_uri', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator']) // Important to deny an open redirect attack
|
||||
->param('scope', '', new Text(100), 'OAuth2 scope list.')
|
||||
->param('state', '', new Text(1024), 'OAuth2 state.')
|
||||
->inject('response')
|
||||
@@ -36,7 +35,7 @@ App::get('/v1/mock/tests/general/oauth2')
|
||||
$response->redirect($redirectURI . '?' . \http_build_query(['code' => 'abcdef', 'state' => $state]));
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/locale')
|
||||
Http::get('/v1/mock/tests/locale')
|
||||
->desc('Mock locale translation key')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
@@ -55,7 +54,7 @@ App::get('/v1/mock/tests/locale')
|
||||
$response->send($locale->getText('mock'));
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/oauth2/token')
|
||||
Http::get('/v1/mock/tests/general/oauth2/token')
|
||||
->desc('OAuth2 Token')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
@@ -64,7 +63,7 @@ App::get('/v1/mock/tests/general/oauth2/token')
|
||||
->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
|
||||
->param('client_secret', '', new Text(100), 'OAuth2 scope list.')
|
||||
->param('grant_type', 'authorization_code', new WhiteList(['refresh_token', 'authorization_code']), 'OAuth2 Grant Type.', true)
|
||||
->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.', true)
|
||||
->param('redirect_uri', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
|
||||
->param('code', '', new Text(100), 'OAuth2 state.', true)
|
||||
->param('refresh_token', '', new Text(100), 'OAuth2 refresh token.', true)
|
||||
->inject('response')
|
||||
@@ -101,7 +100,7 @@ App::get('/v1/mock/tests/general/oauth2/token')
|
||||
}
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/oauth2/user')
|
||||
Http::get('/v1/mock/tests/general/oauth2/user')
|
||||
->desc('OAuth2 User')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
@@ -118,10 +117,32 @@ App::get('/v1/mock/tests/general/oauth2/user')
|
||||
'id' => 1,
|
||||
'name' => 'User Name',
|
||||
'email' => 'useroauth@localhost.test',
|
||||
'verified' => true,
|
||||
]);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/oauth2/success')
|
||||
Http::get('/v1/mock/tests/general/oauth2/user-unverified')
|
||||
->desc('OAuth2 User Unverified')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->param('token', '', new Text(100), 'OAuth2 Access Token.')
|
||||
->inject('response')
|
||||
->action(function (string $token, Response $response) {
|
||||
|
||||
if ($token != '123456') {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid token');
|
||||
}
|
||||
|
||||
$response->json([
|
||||
'id' => 2,
|
||||
'name' => 'User Name Unverified',
|
||||
'email' => 'useroauthunverified@localhost.test',
|
||||
'verified' => false,
|
||||
]);
|
||||
});
|
||||
|
||||
Http::get('/v1/mock/tests/general/oauth2/success')
|
||||
->desc('OAuth2 Success')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
@@ -134,7 +155,7 @@ App::get('/v1/mock/tests/general/oauth2/success')
|
||||
]);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/oauth2/failure')
|
||||
Http::get('/v1/mock/tests/general/oauth2/failure')
|
||||
->desc('OAuth2 Failure')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
@@ -149,7 +170,7 @@ App::get('/v1/mock/tests/general/oauth2/failure')
|
||||
]);
|
||||
});
|
||||
|
||||
App::post('/v1/mock/api-key-unprefixed')
|
||||
Http::post('/v1/mock/api-key-unprefixed')
|
||||
->desc('Create API Key (without standard prefix)')
|
||||
->groups(['mock', 'api', 'projects'])
|
||||
->label('scope', 'public')
|
||||
@@ -170,7 +191,7 @@ App::post('/v1/mock/api-key-unprefixed')
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$scopes = array_keys(Config::getParam('scopes'));
|
||||
$scopes = array_keys(Config::getParam('projectScopes'));
|
||||
|
||||
$key = new Document([
|
||||
'$id' => ID::unique(),
|
||||
@@ -179,8 +200,9 @@ App::post('/v1/mock/api-key-unprefixed')
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $project->getId(),
|
||||
'resourceInternalId' => $project->getSequence(),
|
||||
'resourceId' => $project->getId(),
|
||||
'resourceType' => 'projects',
|
||||
'name' => 'Outdated key',
|
||||
'scopes' => $scopes,
|
||||
'expire' => null,
|
||||
@@ -198,7 +220,7 @@ App::post('/v1/mock/api-key-unprefixed')
|
||||
->dynamic($key, Response::MODEL_KEY);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/github/callback')
|
||||
Http::get('/v1/mock/github/callback')
|
||||
->desc('Create installation document using GitHub installation id')
|
||||
->groups(['mock', 'api', 'vcs'])
|
||||
->label('scope', 'public')
|
||||
@@ -258,12 +280,12 @@ App::get('/v1/mock/github/callback')
|
||||
]);
|
||||
});
|
||||
|
||||
App::shutdown()
|
||||
Http::shutdown()
|
||||
->groups(['mock'])
|
||||
->inject('utopia')
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->action(function (App $utopia, Response $response, Request $request) {
|
||||
->action(function (Http $utopia, Response $response, Request $request) {
|
||||
|
||||
$result = [];
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
+158
-234
@@ -8,18 +8,19 @@ 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\StatsUsage;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Functions\EventProcessor;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
@@ -28,7 +29,8 @@ use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Http;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -72,152 +74,7 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
|
||||
return $label;
|
||||
};
|
||||
|
||||
/**
|
||||
* This isolated event handling for `users.*.create` which is based on a `Database::EVENT_DOCUMENT_CREATE` listener may look odd, but it is **intentional**.
|
||||
*
|
||||
* 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) {
|
||||
// Only trigger events for user creation with the database listener.
|
||||
if ($document->getCollection() !== 'users') {
|
||||
return;
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setEvent('users.[userId].create')
|
||||
->setParam('userId', $document->getId())
|
||||
->setPayload($response->output($document, Response::MODEL_USER));
|
||||
|
||||
// Trigger functions, webhooks, and realtime events
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
|
||||
/** Trigger webhooks events only if a project has them enabled */
|
||||
if (!empty($project->getAttribute('webhooks'))) {
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
/** Trigger realtime events only for non console events */
|
||||
if ($queueForEvents->getProject()->getId() !== 'console') {
|
||||
$queueForRealtime
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
};
|
||||
|
||||
$usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) {
|
||||
$value = 1;
|
||||
|
||||
switch ($event) {
|
||||
case Database::EVENT_DOCUMENT_DELETE:
|
||||
$value = -1;
|
||||
break;
|
||||
case Database::EVENT_DOCUMENTS_DELETE:
|
||||
$value = -1 * $document->getAttribute('modified', 0);
|
||||
break;
|
||||
case Database::EVENT_DOCUMENTS_CREATE:
|
||||
$value = $document->getAttribute('modified', 0);
|
||||
break;
|
||||
case Database::EVENT_DOCUMENTS_UPSERT:
|
||||
$value = $document->getAttribute('created', 0);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case $document->getCollection() === 'teams':
|
||||
$queueForStatsUsage->addMetric(METRIC_TEAMS, $value); // per project
|
||||
break;
|
||||
case $document->getCollection() === 'users':
|
||||
$queueForStatsUsage->addMetric(METRIC_USERS, $value); // per project
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForStatsUsage->addReduce($document);
|
||||
}
|
||||
break;
|
||||
case $document->getCollection() === 'sessions': // sessions
|
||||
$queueForStatsUsage->addMetric(METRIC_SESSIONS, $value); //per project
|
||||
break;
|
||||
case $document->getCollection() === 'databases': // databases
|
||||
$queueForStatsUsage->addMetric(METRIC_DATABASES, $value); // per project
|
||||
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForStatsUsage->addReduce($document);
|
||||
}
|
||||
break;
|
||||
case str_starts_with($document->getCollection(), 'database_') && !str_contains($document->getCollection(), 'collection'): //collections
|
||||
$parts = explode('_', $document->getCollection());
|
||||
$databaseInternalId = $parts[1] ?? 0;
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_COLLECTIONS, $value) // per project
|
||||
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value);
|
||||
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForStatsUsage->addReduce($document);
|
||||
}
|
||||
break;
|
||||
case str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_'): //documents
|
||||
$parts = explode('_', $document->getCollection());
|
||||
$databaseInternalId = $parts[1] ?? 0;
|
||||
$collectionInternalId = $parts[3] ?? 0;
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DOCUMENTS, $value) // per project
|
||||
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS), $value) // per database
|
||||
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), $value); // per collection
|
||||
break;
|
||||
case $document->getCollection() === 'buckets': //buckets
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_BUCKETS, $value); // per project
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForStatsUsage
|
||||
->addReduce($document);
|
||||
}
|
||||
break;
|
||||
case str_starts_with($document->getCollection(), 'bucket_'): // files
|
||||
$parts = explode('_', $document->getCollection());
|
||||
$bucketInternalId = $parts[1];
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_FILES, $value) // per project
|
||||
->addMetric(METRIC_FILES_STORAGE, $document->getAttribute('sizeOriginal') * $value) // per project
|
||||
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES), $value) // per bucket
|
||||
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES_STORAGE), $document->getAttribute('sizeOriginal') * $value); // per bucket
|
||||
break;
|
||||
case $document->getCollection() === 'functions':
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_FUNCTIONS, $value); // per project
|
||||
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForStatsUsage
|
||||
->addReduce($document);
|
||||
}
|
||||
break;
|
||||
case $document->getCollection() === 'sites':
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_SITES, $value); // per project
|
||||
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForStatsUsage
|
||||
->addReduce($document);
|
||||
}
|
||||
break;
|
||||
case $document->getCollection() === 'deployments':
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DEPLOYMENTS, $value) // per project
|
||||
->addMetric(METRIC_DEPLOYMENTS_STORAGE, $document->getAttribute('size') * $value) // per project
|
||||
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS), $value) // per function
|
||||
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value)
|
||||
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS), $value) // per function
|
||||
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
App::init()
|
||||
Http::init()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
@@ -231,7 +88,8 @@ App::init()
|
||||
->inject('mode')
|
||||
->inject('team')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey) {
|
||||
->inject('authorization')
|
||||
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
/**
|
||||
@@ -299,10 +157,6 @@ App::init()
|
||||
|
||||
// Step 5: API Key Authentication
|
||||
if (!empty($apiKey)) {
|
||||
// Verify no user session exists simultaneously
|
||||
if (!$user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
|
||||
}
|
||||
// Check if key is expired
|
||||
if ($apiKey->isExpired()) {
|
||||
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
|
||||
@@ -316,7 +170,7 @@ App::init()
|
||||
// Handle special app role case
|
||||
if ($apiKey->getRole() === User::ROLE_APPS) {
|
||||
// Disable authorization checks for API keys
|
||||
Authorization::setDefaultStatus(false);
|
||||
$authorization->setDefaultStatus(false);
|
||||
|
||||
$user = new User([
|
||||
'$id' => '',
|
||||
@@ -331,23 +185,38 @@ App::init()
|
||||
}
|
||||
|
||||
// For standard keys, update last accessed time
|
||||
if ($apiKey->getType() === API_KEY_STANDARD) {
|
||||
$dbKey = $project->find(
|
||||
key: 'secret',
|
||||
find: $request->getHeader('x-appwrite-key', ''),
|
||||
subject: 'keys'
|
||||
);
|
||||
if (\in_array($apiKey->getType(), [API_KEY_STANDARD, API_KEY_ORGANIZATION, API_KEY_ACCOUNT])) {
|
||||
$dbKey = null;
|
||||
if (!empty($apiKey->getProjectId())) {
|
||||
$dbKey = $project->find(
|
||||
key: 'secret',
|
||||
find: $request->getHeader('x-appwrite-key', ''),
|
||||
subject: 'keys'
|
||||
);
|
||||
} elseif (!empty($apiKey->getUserId())) {
|
||||
$dbKey = $user->find(
|
||||
key: 'secret',
|
||||
find: $request->getHeader('x-appwrite-key', ''),
|
||||
subject: 'keys'
|
||||
);
|
||||
} elseif (!empty($apiKey->getTeamId())) {
|
||||
$dbKey = $team->find(
|
||||
key: 'secret',
|
||||
find: $request->getHeader('x-appwrite-key', ''),
|
||||
subject: 'keys'
|
||||
);
|
||||
}
|
||||
|
||||
if (!$dbKey) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$updates = new Document();
|
||||
|
||||
$accessedAt = $dbKey->getAttribute('accessedAt', 0);
|
||||
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
|
||||
$dbKey->setAttribute('accessedAt', DateTime::now());
|
||||
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
$updates->setAttribute('accessedAt', DateTime::now());
|
||||
}
|
||||
|
||||
$sdkValidator = new WhiteList($servers, true);
|
||||
@@ -358,12 +227,21 @@ App::init()
|
||||
|
||||
if (!in_array($sdk, $sdks)) {
|
||||
$sdks[] = $sdk;
|
||||
$dbKey->setAttribute('sdks', $sdks);
|
||||
|
||||
/** Update access time as well */
|
||||
$dbKey->setAttribute('accessedAt', Datetime::now());
|
||||
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
$updates->setAttribute('sdks', $sdks);
|
||||
$updates->setAttribute('accessedAt', Datetime::now());
|
||||
}
|
||||
}
|
||||
|
||||
if (!$updates->isEmpty()) {
|
||||
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->updateDocument('keys', $dbKey->getId(), $updates));
|
||||
|
||||
if (!empty($apiKey->getProjectId())) {
|
||||
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
} elseif (!empty($apiKey->getUserId())) {
|
||||
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('users', $user->getId()));
|
||||
} elseif (!empty($apiKey->getTeamId())) {
|
||||
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('teams', $team->getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,14 +268,14 @@ App::init()
|
||||
$scopes = \array_merge($scopes, $roles[$role]['scopes']);
|
||||
}
|
||||
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
$authorization->setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
}
|
||||
|
||||
$scopes = \array_unique($scopes);
|
||||
|
||||
Authorization::setRole($role);
|
||||
foreach ($user->getRoles() as $authRole) {
|
||||
Authorization::setRole($authRole);
|
||||
$authorization->addRole($role);
|
||||
foreach ($user->getRoles($authorization) as $authRole) {
|
||||
$authorization->addRole($authRole);
|
||||
}
|
||||
|
||||
// Step 6: Update project and user last activity
|
||||
@@ -405,7 +283,7 @@ App::init()
|
||||
$accessedAt = $project->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,7 +318,7 @@ App::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::isApp($authorization->getRoles()))
|
||||
) {
|
||||
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
|
||||
}
|
||||
@@ -478,16 +356,13 @@ App::init()
|
||||
}
|
||||
});
|
||||
|
||||
App::init()
|
||||
Http::init()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('publisher')
|
||||
->inject('publisherFunctions')
|
||||
->inject('publisherWebhooks')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForAudits')
|
||||
@@ -495,6 +370,8 @@ App::init()
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForBuilds')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForMails')
|
||||
->inject('dbForProject')
|
||||
->inject('timelimit')
|
||||
->inject('resourceToken')
|
||||
@@ -503,14 +380,16 @@ App::init()
|
||||
->inject('plan')
|
||||
->inject('devKey')
|
||||
->inject('telemetry')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
->inject('platform')
|
||||
->inject('authorization')
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) {
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
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::isApp($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
@@ -540,7 +419,7 @@ App::init()
|
||||
|
||||
$closestLimit = null;
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
$isPrivilegedUser = User::isPrivileged($roles);
|
||||
$isAppUser = User::isApp($roles);
|
||||
|
||||
@@ -577,6 +456,10 @@ App::init()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: (@loks0n)
|
||||
* Avoid mutating the message across file boundaries - it's difficult to reason about at scale.
|
||||
*/
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
@@ -607,43 +490,28 @@ App::init()
|
||||
}
|
||||
}
|
||||
|
||||
/* Auto-set projects */
|
||||
$queueForDeletes->setProject($project);
|
||||
$queueForDatabase->setProject($project);
|
||||
$queueForBuilds->setProject($project);
|
||||
$queueForMessaging->setProject($project);
|
||||
$queueForFunctions->setProject($project);
|
||||
$queueForBuilds->setProject($project);
|
||||
|
||||
// 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();
|
||||
/* Auto-set platforms */
|
||||
$queueForFunctions->setPlatform($platform);
|
||||
$queueForBuilds->setPlatform($platform);
|
||||
$queueForMails->setPlatform($platform);
|
||||
|
||||
$dbForProject
|
||||
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
|
||||
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
|
||||
->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
|
||||
->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
|
||||
->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
|
||||
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
|
||||
$project,
|
||||
$document,
|
||||
$response,
|
||||
$queueForEventsClone->from($queueForEvents),
|
||||
$queueForFunctions->from($queueForEvents),
|
||||
$queueForWebhooks->from($queueForEvents),
|
||||
$queueForRealtime->from($queueForEvents)
|
||||
));
|
||||
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
$storageCacheOperationsCounter = $telemetry->createCounter('storage.cache.operations.load');
|
||||
if ($useCache) {
|
||||
$route = $utopia->match($request);
|
||||
$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($authorization->getRoles());
|
||||
|
||||
$key = $request->cacheIdentifier();
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$cache = new Cache(
|
||||
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
|
||||
);
|
||||
@@ -660,10 +528,10 @@ App::init()
|
||||
|
||||
if ($type === 'bucket' && (!$isImageTransformation || !$isDisabled)) {
|
||||
$bucketId = $parts[1] ?? null;
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
@@ -674,8 +542,7 @@ App::init()
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
if (!$fileSecurity && !$valid && !$isToken) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
@@ -686,7 +553,7 @@ App::init()
|
||||
if ($fileSecurity && !$valid && !$isToken) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
|
||||
} else {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
}
|
||||
|
||||
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
|
||||
@@ -697,11 +564,11 @@ App::init()
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
//Do not update transformedAt if it's a console user
|
||||
if (!User::isPrivileged(Authorization::getRoles())) {
|
||||
if (!User::isPrivileged($authorization->getRoles())) {
|
||||
$transformedAt = $file->getAttribute('transformedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
|
||||
$file->setAttribute('transformedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -725,7 +592,7 @@ App::init()
|
||||
}
|
||||
});
|
||||
|
||||
App::init()
|
||||
Http::init()
|
||||
->groups(['session'])
|
||||
->inject('user')
|
||||
->inject('request')
|
||||
@@ -745,14 +612,14 @@ App::init()
|
||||
* Delete older sessions if the number of sessions have crossed
|
||||
* the session limit set for the project
|
||||
*/
|
||||
App::shutdown()
|
||||
Http::shutdown()
|
||||
->groups(['session'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Database $dbForProject) {
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, Database $dbForProject) {
|
||||
$sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT;
|
||||
$session = $response->getPayload();
|
||||
$userId = $session['userId'] ?? '';
|
||||
@@ -779,7 +646,7 @@ App::shutdown()
|
||||
$dbForProject->purgeCachedDocument('users', $userId);
|
||||
});
|
||||
|
||||
App::shutdown()
|
||||
Http::shutdown()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
@@ -797,7 +664,10 @@ App::shutdown()
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForRealtime')
|
||||
->inject('dbForProject')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject) use ($parseLabel) {
|
||||
->inject('authorization')
|
||||
->inject('timelimit')
|
||||
->inject('eventProcessor')
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor) use ($parseLabel) {
|
||||
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
@@ -806,9 +676,15 @@ App::shutdown()
|
||||
$queueForEvents->setPayload($responsePayload);
|
||||
}
|
||||
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
// Get project and function/webhook events (cached)
|
||||
$functionsEvents = $eventProcessor->getFunctionsEvents($project, $dbForProject);
|
||||
$webhooksEvents = $eventProcessor->getWebhooksEvents($project);
|
||||
|
||||
// Generate events for this operation
|
||||
$generatedEvents = Event::generateEvents(
|
||||
$queueForEvents->getEvent(),
|
||||
$queueForEvents->getParams()
|
||||
);
|
||||
|
||||
if ($project->getId() !== 'console') {
|
||||
$queueForRealtime
|
||||
@@ -816,21 +692,69 @@ App::shutdown()
|
||||
->trigger();
|
||||
}
|
||||
|
||||
/** Trigger webhooks events only if a project has them enabled
|
||||
* A future optimisation is to only trigger webhooks if the webhook is "enabled"
|
||||
* But it might have performance implications on the API due to the number of webhooks etc.
|
||||
* Some profiling is needed to see if this is a problem.
|
||||
*/
|
||||
if (!empty($project->getAttribute('webhooks'))) {
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
// Only trigger functions if there are matching function events
|
||||
if (!empty($functionsEvents)) {
|
||||
foreach ($generatedEvents as $event) {
|
||||
if (isset($functionsEvents[$event])) {
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only trigger webhooks if there are matching webhook events
|
||||
if (!empty($webhooksEvents)) {
|
||||
foreach ($generatedEvents as $event) {
|
||||
if (isset($webhooksEvents[$event])) {
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
$requestParams = $route->getParamsValues();
|
||||
|
||||
/**
|
||||
* Abuse labels
|
||||
*/
|
||||
$abuseEnabled = System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled';
|
||||
$abuseResetCode = $route->getLabel('abuse-reset', []);
|
||||
$abuseResetCode = \is_array($abuseResetCode) ? $abuseResetCode : [$abuseResetCode];
|
||||
|
||||
if ($abuseEnabled && \count($abuseResetCode) > 0 && \in_array($response->getStatusCode(), $abuseResetCode)) {
|
||||
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
|
||||
$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)));
|
||||
|
||||
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);
|
||||
$abuse->reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Audit labels
|
||||
*/
|
||||
@@ -923,11 +847,11 @@ App::shutdown()
|
||||
|
||||
$key = $request->cacheIdentifier();
|
||||
$signature = md5($data['payload']);
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$accessedAt = $cacheLog->getAttribute('accessedAt', 0);
|
||||
$now = DateTime::now();
|
||||
if ($cacheLog->isEmpty()) {
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
|
||||
$authorization->skip(fn () => $dbForProject->createDocument('cache', new Document([
|
||||
'$id' => $key,
|
||||
'resource' => $resource,
|
||||
'resourceType' => $resourceType,
|
||||
@@ -937,7 +861,7 @@ App::shutdown()
|
||||
])));
|
||||
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
|
||||
$cacheLog->setAttribute('accessedAt', $now);
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
|
||||
// Overwrite the file every APP_CACHE_UPDATE seconds to update the file modified time that is used in the TTL checks in cache->load()
|
||||
$cache->save($key, $data['payload']);
|
||||
}
|
||||
@@ -949,7 +873,7 @@ App::shutdown()
|
||||
}
|
||||
|
||||
if ($project->getId() !== 'console') {
|
||||
if (!User::isPrivileged(Authorization::getRoles())) {
|
||||
if (!User::isPrivileged($authorization->getRoles())) {
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
if (!empty($file)) {
|
||||
@@ -968,7 +892,7 @@ App::shutdown()
|
||||
}
|
||||
});
|
||||
|
||||
App::init()
|
||||
Http::init()
|
||||
->groups(['usage'])
|
||||
->action(function () {
|
||||
if (System::getEnv('_APP_USAGE_STATS', 'enabled') !== 'enabled') {
|
||||
|
||||
@@ -4,14 +4,14 @@ use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Request;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Http;
|
||||
use Utopia\System\System;
|
||||
|
||||
App::init()
|
||||
Http::init()
|
||||
->groups(['mfaProtected'])
|
||||
->inject('session')
|
||||
->action(function (Document $session) {
|
||||
@@ -30,13 +30,14 @@ App::init()
|
||||
}
|
||||
});
|
||||
|
||||
App::init()
|
||||
Http::init()
|
||||
->groups(['auth'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->action(function (App $utopia, Request $request, Document $project, Reader $geodb) {
|
||||
->inject('authorization')
|
||||
->action(function (Http $utopia, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
|
||||
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
|
||||
if (!empty($denylist && $project->getId() === 'console')) {
|
||||
$countries = explode(',', $denylist);
|
||||
@@ -49,8 +50,8 @@ App::init()
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
$isAppUser = User::isApp($authorization->getRoles());
|
||||
|
||||
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||
return;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
|
||||
App::init()
|
||||
->groups(['web'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
$response
|
||||
->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
|
||||
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
|
||||
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
|
||||
;
|
||||
});
|
||||
|
||||
App::get('/')
|
||||
->alias('auth/*')
|
||||
->alias('/invite')
|
||||
->alias('/login')
|
||||
->alias('/mfa')
|
||||
->alias('/card/*')
|
||||
->alias('/recover')
|
||||
->alias('/register/*')
|
||||
->groups(['web'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
$url = parse_url($request->getURI());
|
||||
$target = "/console{$url['path']}";
|
||||
$params = $request->getParams();
|
||||
if (!empty($params)) {
|
||||
$target .= "?" . \http_build_query($params);
|
||||
}
|
||||
if ($url['fragment'] ?? false) {
|
||||
$target .= "#{$url['fragment']}";
|
||||
}
|
||||
$response->redirect($target);
|
||||
});
|
||||
@@ -1,16 +1,16 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Http;
|
||||
|
||||
App::get('/versions')
|
||||
Http::get('/versions')
|
||||
->desc('Get Version')
|
||||
->groups(['home', 'web'])
|
||||
->label('scope', 'public')
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$platforms = Config::getParam('platforms');
|
||||
$platforms = Config::getParam('sdks');
|
||||
|
||||
$versions = [
|
||||
'server' => APP_VERSION_STABLE,
|
||||
|
||||
+71
-29
@@ -11,7 +11,8 @@ use Swoole\Http\Server;
|
||||
use Swoole\Process;
|
||||
use Swoole\Table;
|
||||
use Swoole\Timer;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Adapter\Database as AdapterDatabase;
|
||||
use Utopia\Audit\Adapter\SQL as AuditAdapterSQL;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Compression\Compression;
|
||||
@@ -25,7 +26,7 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Http;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
use Utopia\Pools\Group;
|
||||
@@ -99,11 +100,19 @@ function dispatch(Server $server, int $fd, int $type, $data = null): int
|
||||
$risky = false;
|
||||
if (str_starts_with($request, 'POST') && str_contains($request, '/executions')) {
|
||||
$risky = true;
|
||||
} elseif (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) {
|
||||
$risky = true;
|
||||
} elseif ($domains->get(md5($domain), 'value') === 1) {
|
||||
// executions request coming from custom domain
|
||||
$risky = true;
|
||||
} else {
|
||||
foreach (\explode(',', System::getEnv('_APP_DOMAIN_FUNCTIONS')) as $riskyDomain) {
|
||||
if (empty($riskyDomain)) {
|
||||
continue;
|
||||
}
|
||||
if (str_ends_with($domain, $riskyDomain)) {
|
||||
$risky = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($risky) {
|
||||
@@ -172,7 +181,7 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server) {
|
||||
|
||||
include __DIR__ . '/controllers/general.php';
|
||||
|
||||
function createDatabase(App $app, string $resourceKey, string $dbName, array $collections, mixed $pools, callable $extraSetup = null): void
|
||||
function createDatabase(Http $app, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void
|
||||
{
|
||||
$max = 10;
|
||||
$sleep = 1;
|
||||
@@ -245,12 +254,12 @@ function createDatabase(App $app, string $resourceKey, string $dbName, array $co
|
||||
}
|
||||
|
||||
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
|
||||
$app = new App('UTC');
|
||||
$app = new Http('UTC');
|
||||
|
||||
go(function () use ($register, $app) {
|
||||
$pools = $register->get('pools');
|
||||
/** @var Group $pools */
|
||||
App::setResource('pools', fn () => $pools);
|
||||
Http::setResource('pools', fn () => $pools);
|
||||
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
@@ -259,9 +268,12 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
createDatabase($app, '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) {
|
||||
if ($dbForPlatform->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($dbForPlatform);
|
||||
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $app) {
|
||||
$authorization = $app->getResource('authorization');
|
||||
|
||||
if ($dbForPlatform->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
|
||||
$adapter = new AdapterDatabase($dbForPlatform);
|
||||
$audit = new Audit($adapter);
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
@@ -318,9 +330,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
$dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes);
|
||||
}
|
||||
|
||||
if (Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
|
||||
if ($authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
|
||||
Console::info(" └── Creating screenshots bucket...");
|
||||
Authorization::skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
|
||||
$authorization->skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('screenshots'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'name' => 'Screenshots',
|
||||
@@ -335,7 +347,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
'search' => 'buckets Screenshots',
|
||||
])));
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
|
||||
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
|
||||
|
||||
Console::info(" └── Creating files collection for screenshots bucket...");
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
@@ -363,7 +375,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
'orders' => $index['orders'],
|
||||
]), $files['indexes']);
|
||||
|
||||
Authorization::skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes));
|
||||
$authorization->skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -389,8 +401,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
}
|
||||
|
||||
if ($dbForProject->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($dbForProject);
|
||||
if ($dbForProject->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
|
||||
$adapter = new AdapterDatabase($dbForProject);
|
||||
$audit = new Audit($adapter);
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
@@ -428,8 +441,8 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
});
|
||||
|
||||
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
|
||||
App::setResource('swooleRequest', fn () => $swooleRequest);
|
||||
App::setResource('swooleResponse', fn () => $swooleResponse);
|
||||
Http::setResource('swooleRequest', fn () => $swooleRequest);
|
||||
Http::setResource('swooleResponse', fn () => $swooleResponse);
|
||||
|
||||
$request = new Request($swooleRequest);
|
||||
$response = new Response($swooleResponse);
|
||||
@@ -446,16 +459,20 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
return;
|
||||
}
|
||||
|
||||
$app = new App('UTC');
|
||||
$app = new Http('UTC');
|
||||
$app->setCompression(System::getEnv('_APP_COMPRESSION_ENABLED', 'enabled') === 'enabled');
|
||||
$app->setCompressionMinSize(intval(System::getEnv('_APP_COMPRESSION_MIN_SIZE_BYTES', '1024'))); // 1KB
|
||||
|
||||
$pools = $register->get('pools');
|
||||
App::setResource('pools', fn () => $pools);
|
||||
Http::setResource('pools', fn () => $pools);
|
||||
|
||||
try {
|
||||
Authorization::cleanRoles();
|
||||
Authorization::setRole(Role::any()->toString());
|
||||
$authorization = $app->getResource('authorization');
|
||||
|
||||
$request->setAuthorization($authorization);
|
||||
$response->setAuthorization($authorization);
|
||||
$authorization->cleanRoles();
|
||||
$authorization->addRole(Role::any()->toString());
|
||||
|
||||
$app->run($request, $response);
|
||||
} catch (\Throwable $th) {
|
||||
@@ -497,7 +514,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
$log->addExtra('file', $th->getFile());
|
||||
$log->addExtra('line', $th->getLine());
|
||||
$log->addExtra('trace', $th->getTraceAsString());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
$log->addExtra('roles', isset($authorization) ? $authorization->getRoles() : []);
|
||||
|
||||
$sdk = $route->getLabel("sdk", false);
|
||||
|
||||
@@ -529,7 +546,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
|
||||
$swooleResponse->setStatusCode(500);
|
||||
|
||||
$output = ((App::isDevelopment())) ? [
|
||||
$output = ((Http::isDevelopment())) ? [
|
||||
'message' => 'Error: ' . $th->getMessage(),
|
||||
'code' => 500,
|
||||
'file' => $th->getFile(),
|
||||
@@ -550,13 +567,13 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
$http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
|
||||
$lastSyncUpdate = null;
|
||||
$pools = $register->get('pools');
|
||||
App::setResource('pools', fn () => $pools);
|
||||
$app = new App('UTC');
|
||||
Http::setResource('pools', fn () => $pools);
|
||||
$app = new Http('UTC');
|
||||
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
$dbForPlatform = $app->getResource('dbForPlatform');
|
||||
|
||||
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate) {
|
||||
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate, $app) {
|
||||
try {
|
||||
$time = DateTime::now();
|
||||
$limit = 1000;
|
||||
@@ -573,7 +590,8 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
|
||||
}
|
||||
$results = [];
|
||||
try {
|
||||
$results = Authorization::skip(fn () => $dbForPlatform->find('rules', $queries));
|
||||
$authorization = $app->getResource('authorization');
|
||||
$results = $authorization->skip(fn () => $dbForPlatform->find('rules', $queries));
|
||||
} catch (Throwable $th) {
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
@@ -581,9 +599,33 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
|
||||
$sum = count($results);
|
||||
foreach ($results as $document) {
|
||||
$domain = $document->getAttribute('domain');
|
||||
if (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS')) || str_ends_with($domain, System::getEnv('_APP_DOMAIN_SITES'))) {
|
||||
|
||||
$denyDomains = [];
|
||||
$denyEnvVars = [
|
||||
System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', ''),
|
||||
System::getEnv('_APP_DOMAIN_FUNCTIONS', ''),
|
||||
System::getEnv('_APP_DOMAIN_SITES', ''),
|
||||
];
|
||||
foreach ($denyEnvVars as $denyEnvVar) {
|
||||
foreach (\explode(',', $denyEnvVar) as $denyDomain) {
|
||||
if (empty($denyDomain)) {
|
||||
continue;
|
||||
}
|
||||
$denyDomains[] = $denyDomain;
|
||||
}
|
||||
}
|
||||
|
||||
$isDenyDomain = false;
|
||||
foreach ($denyDomains as $denyDomain) {
|
||||
if (str_ends_with($domain, $denyDomain)) {
|
||||
$isDenyDomain = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isDenyDomain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$domains->set(md5($domain), ['value' => 1]);
|
||||
}
|
||||
$latestDocument = !empty(array_key_last($results)) ? $results[array_key_last($results)] : null;
|
||||
|
||||
@@ -26,6 +26,7 @@ require_once __DIR__ . '/init/database/filters.php';
|
||||
require_once __DIR__ . '/init/database/formats.php';
|
||||
require_once __DIR__ . '/init/locales.php';
|
||||
require_once __DIR__ . '/init/registers.php';
|
||||
require_once __DIR__ . '/init/models.php';
|
||||
require_once __DIR__ . '/init/resources.php';
|
||||
|
||||
\stream_context_set_default([ // Set global user agent and http settings
|
||||
|
||||
@@ -15,13 +15,16 @@ Config::load('auth', __DIR__ . '/../config/auth.php', $configAdapter);
|
||||
Config::load('apis', __DIR__ . '/../config/apis.php', $configAdapter); // List of APIs
|
||||
Config::load('errors', __DIR__ . '/../config/errors.php', $configAdapter);
|
||||
Config::load('oAuthProviders', __DIR__ . '/../config/oAuthProviders.php', $configAdapter);
|
||||
Config::load('platforms', __DIR__ . '/../config/platforms.php', $configAdapter);
|
||||
Config::load('sdks', __DIR__ . '/../config/sdks.php', $configAdapter);
|
||||
Config::load('platform', __DIR__ . '/../config/platform.php', $configAdapter);
|
||||
Config::load('console', __DIR__ . '/../config/console.php', $configAdapter);
|
||||
Config::load('collections', __DIR__ . '/../config/collections.php', $configAdapter);
|
||||
Config::load('frameworks', __DIR__ . '/../config/frameworks.php', $configAdapter);
|
||||
Config::load('usage', __DIR__ . '/../config/usage.php', $configAdapter);
|
||||
Config::load('roles', __DIR__ . '/../config/roles.php', $configAdapter); // User roles and scopes
|
||||
Config::load('scopes', __DIR__ . '/../config/scopes.php', $configAdapter); // User roles and scopes
|
||||
Config::load('projectScopes', __DIR__ . '/../config/scopes/project.php', $configAdapter);
|
||||
Config::load('organizationScopes', __DIR__ . '/../config/scopes/organization.php', $configAdapter);
|
||||
Config::load('accountScopes', __DIR__ . '/../config/scopes/account.php', $configAdapter);
|
||||
Config::load('services', __DIR__ . '/../config/services.php', $configAdapter); // List of services
|
||||
Config::load('variables', __DIR__ . '/../config/variables.php', $configAdapter); // List of env variables
|
||||
Config::load('regions', __DIR__ . '/../config/regions.php', $configAdapter); // List of available regions
|
||||
|
||||
+33
-6
@@ -4,12 +4,19 @@ use Appwrite\Platform\Modules\Compute\Specification;
|
||||
|
||||
const APP_NAME = 'Appwrite';
|
||||
const APP_DOMAIN = 'appwrite.io';
|
||||
|
||||
const APP_VIEWS_DIR = __DIR__ . '/../views';
|
||||
|
||||
// Email
|
||||
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
|
||||
const APP_EMAIL_SECURITY = ''; // Default security email address
|
||||
const APP_EMAIL_LOGO_URL = 'https://cloud.appwrite.io/images/mails/logo.png';
|
||||
const APP_EMAIL_ACCENT_COLOR = '#fd366e';
|
||||
const APP_EMAIL_TERMS_URL = 'https://appwrite.io/terms';
|
||||
const APP_EMAIL_PRIVACY_URL = 'https://appwrite.io/privacy';
|
||||
const APP_EMAIL_PLATFORM_NAME = 'Appwrite';
|
||||
const APP_EMAIL_FOOTER_IMAGE_URL = 'https://appwrite.io/email/footer.png';
|
||||
|
||||
const APP_USERAGENT = APP_NAME . '-Server v%s. Please report abuse at %s';
|
||||
const APP_MODE_DEFAULT = 'default';
|
||||
const APP_MODE_ADMIN = 'admin';
|
||||
@@ -39,7 +46,7 @@ const APP_RESOURCE_TOKEN_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 4321;
|
||||
const APP_VERSION_STABLE = '1.8.0';
|
||||
const APP_VERSION_STABLE = '1.8.1';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
@@ -55,6 +62,7 @@ const APP_DATABASE_TIMEOUT_MILLISECONDS_API = 15 * 1000; // 15 seconds
|
||||
const APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER = 300 * 1000; // 5 minutes
|
||||
const APP_DATABASE_TIMEOUT_MILLISECONDS_TASK = 300 * 1000; // 5 minutes
|
||||
const APP_DATABASE_QUERY_MAX_VALUES = 500;
|
||||
const APP_DATABASE_QUERY_MAX_VALUES_WORKER = 5000;
|
||||
const APP_DATABASE_ENCRYPT_SIZE_MIN = 150;
|
||||
const APP_DATABASE_TXN_TTL_MIN = 60; // 1 minute
|
||||
const APP_DATABASE_TXN_TTL_MAX = 3600; // 1 hour
|
||||
@@ -81,13 +89,12 @@ const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
|
||||
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
|
||||
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
|
||||
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
|
||||
const APP_HOSTNAME_INTERNAL = 'appwrite';
|
||||
const APP_COMPUTE_CPUS_DEFAULT = 0.5;
|
||||
const APP_COMPUTE_MEMORY_DEFAULT = 512;
|
||||
const APP_COMPUTE_SPECIFICATION_DEFAULT = Specification::S_1VCPU_512MB;
|
||||
const APP_PLATFORM_SERVER = 'server';
|
||||
const APP_PLATFORM_CLIENT = 'client';
|
||||
const APP_PLATFORM_CONSOLE = 'console';
|
||||
const APP_SDK_PLATFORM_SERVER = 'server';
|
||||
const APP_SDK_PLATFORM_CLIENT = 'client';
|
||||
const APP_SDK_PLATFORM_CONSOLE = 'console';
|
||||
const APP_VCS_GITHUB_USERNAME = 'Appwrite';
|
||||
const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io';
|
||||
const APP_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite';
|
||||
@@ -156,6 +163,9 @@ const ACTIVITY_TYPE_GUEST = 'guest';
|
||||
const MFA_RECENT_DURATION = 1800; // 30 mins
|
||||
|
||||
|
||||
// Database name
|
||||
const APP_DATABASE = 'appwrite';
|
||||
|
||||
// Database Reconnect
|
||||
const DATABASE_RECONNECT_SLEEP = 2;
|
||||
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
|
||||
@@ -173,8 +183,10 @@ const BUILD_TYPE_DEPLOYMENT = 'deployment';
|
||||
const BUILD_TYPE_RETRY = 'retry';
|
||||
|
||||
// Deletion Types
|
||||
const DELETE_TYPE_DATABASES = 'databases';
|
||||
|
||||
const ENABLE_EXECUTIONS_LIMIT_ON_ROUTE = false;
|
||||
|
||||
const DELETE_TYPE_DATABASES = 'databases';
|
||||
const DELETE_TYPE_DOCUMENT = 'document';
|
||||
const DELETE_TYPE_COLLECTIONS = 'collections';
|
||||
const DELETE_TYPE_TRANSACTION = 'transaction';
|
||||
@@ -184,8 +196,10 @@ const DELETE_TYPE_SITES = 'sites';
|
||||
const DELETE_TYPE_FUNCTIONS = 'functions';
|
||||
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
|
||||
const DELETE_TYPE_USERS = 'users';
|
||||
const DELETE_TYPE_TEAMS = 'teams';
|
||||
const DELETE_TYPE_TEAM_PROJECTS = 'teams_projects';
|
||||
const DELETE_TYPE_EXECUTIONS = 'executions';
|
||||
const DELETE_TYPE_EXECUTIONS_LIMIT = 'executionsLimit';
|
||||
const DELETE_TYPE_AUDIT = 'audit';
|
||||
const DELETE_TYPE_ABUSE = 'abuse';
|
||||
const DELETE_TYPE_USAGE = 'usage';
|
||||
@@ -204,6 +218,12 @@ const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
|
||||
const DELETE_TYPE_CSV_EXPORTS = 'csv_exports';
|
||||
const DELETE_TYPE_MAINTENANCE = 'maintenance';
|
||||
|
||||
// Rule statuses
|
||||
const RULE_STATUS_CREATED = 'created'; // This is also the status when domain DNS verification fails.
|
||||
const RULE_STATUS_CERTIFICATE_GENERATING = 'verifying';
|
||||
const RULE_STATUS_CERTIFICATE_GENERATION_FAILED = 'unverified';
|
||||
const RULE_STATUS_VERIFIED = 'verified';
|
||||
|
||||
// Message types
|
||||
const MESSAGE_SEND_TYPE_INTERNAL = 'internal';
|
||||
const MESSAGE_SEND_TYPE_EXTERNAL = 'external';
|
||||
@@ -232,6 +252,8 @@ const MESSAGE_TYPE_PUSH = 'push';
|
||||
// API key types
|
||||
const API_KEY_STANDARD = 'standard';
|
||||
const API_KEY_DYNAMIC = 'dynamic';
|
||||
const API_KEY_ORGANIZATION = 'organization';
|
||||
const API_KEY_ACCOUNT = 'account';
|
||||
// Usage metrics
|
||||
const METRIC_TEAMS = 'teams';
|
||||
const METRIC_USERS = 'users';
|
||||
@@ -349,6 +371,7 @@ const RESOURCE_TYPE_TOPICS = 'topics';
|
||||
const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers';
|
||||
const RESOURCE_TYPE_MESSAGES = 'messages';
|
||||
const RESOURCE_TYPE_EXECUTIONS = 'executions';
|
||||
const RESOURCE_TYPE_VCS = 'vcs';
|
||||
|
||||
// Resource types for Tokens
|
||||
const TOKENS_RESOURCE_TYPE_FILES = 'files';
|
||||
@@ -363,3 +386,7 @@ const SCHEDULE_RESOURCE_TYPE_MESSAGE = 'message';
|
||||
|
||||
/** Preview cookie */
|
||||
const COOKIE_NAME_PREVIEW = 'a_jwt_console';
|
||||
|
||||
// Cache Reconnect
|
||||
const CACHE_RECONNECT_MAX_RETRIES = 2;
|
||||
const CACHE_RECONNECT_RETRY_DELAY = 1000;
|
||||
|
||||
@@ -4,7 +4,6 @@ use Appwrite\OpenSSL\OpenSSL;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\System\System;
|
||||
|
||||
Database::addFilter(
|
||||
@@ -70,11 +69,11 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
$attributes = $database->find('attributes', [
|
||||
$attributes = $database->getAuthorization()->skip(fn () => $database->find('attributes', [
|
||||
Query::equal('collectionInternalId', [$document->getSequence()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit($database->getLimitForAttributes()),
|
||||
]);
|
||||
]));
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
$attributeType = $attribute->getAttribute('type');
|
||||
@@ -105,12 +104,12 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('indexes', [
|
||||
Query::equal('collectionInternalId', [$document->getSequence()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit($database->getLimitForIndexes()),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -120,11 +119,11 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('platforms', [
|
||||
Query::equal('projectInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -134,11 +133,12 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('keys', [
|
||||
Query::equal('projectInternalId', [$document->getSequence()]),
|
||||
Query::equal('resourceType', ['projects']),
|
||||
Query::equal('resourceInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -148,11 +148,11 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('devKeys', [
|
||||
Query::equal('projectInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -162,11 +162,11 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('webhooks', [
|
||||
Query::equal('projectInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -176,7 +176,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database->find('sessions', [
|
||||
return $database->getAuthorization()->skip(fn () => $database->find('sessions', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
@@ -189,7 +189,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('tokens', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
@@ -203,7 +203,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('challenges', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
@@ -217,7 +217,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('authenticators', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
@@ -231,7 +231,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('memberships', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
@@ -251,14 +251,14 @@ Database::addFilter(
|
||||
default => ['function', 'site']
|
||||
};
|
||||
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('variables', [
|
||||
Query::equal('resourceInternalId', [$document->getSequence()]),
|
||||
Query::equal('resourceType', $resourceType),
|
||||
Query::orderAsc('resourceType'),
|
||||
Query::orderAsc(),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -294,11 +294,11 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('variables', [
|
||||
Query::equal('resourceType', ['project']),
|
||||
Query::limit(APP_LIMIT_SUBQUERY)
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -331,7 +331,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('targets', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY)
|
||||
@@ -345,7 +345,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
$targetIds = Authorization::skip(fn () => \array_map(
|
||||
$targetIds = $database->getAuthorization()->skip(fn () => \array_map(
|
||||
fn ($document) => $document->getAttribute('targetInternalId'),
|
||||
$database->find('subscribers', [
|
||||
Query::equal('topicInternalId', [$document->getSequence()]),
|
||||
@@ -433,3 +433,34 @@ Database::addFilter(
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryOrganizationKeys',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('keys', [
|
||||
Query::equal('resourceType', ['teams']),
|
||||
Query::equal('resourceInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryAccountKeys',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('keys', [
|
||||
Query::equal('resourceType', ['users']),
|
||||
Query::equal('resourceInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model\Account;
|
||||
use Appwrite\Utopia\Response\Model\AlgoArgon2;
|
||||
use Appwrite\Utopia\Response\Model\AlgoBcrypt;
|
||||
use Appwrite\Utopia\Response\Model\AlgoMd5;
|
||||
use Appwrite\Utopia\Response\Model\AlgoPhpass;
|
||||
use Appwrite\Utopia\Response\Model\AlgoScrypt;
|
||||
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\AttributeBoolean;
|
||||
use Appwrite\Utopia\Response\Model\AttributeDatetime;
|
||||
use Appwrite\Utopia\Response\Model\AttributeEmail;
|
||||
use Appwrite\Utopia\Response\Model\AttributeEnum;
|
||||
use Appwrite\Utopia\Response\Model\AttributeFloat;
|
||||
use Appwrite\Utopia\Response\Model\AttributeInteger;
|
||||
use Appwrite\Utopia\Response\Model\AttributeIP;
|
||||
use Appwrite\Utopia\Response\Model\AttributeLine;
|
||||
use Appwrite\Utopia\Response\Model\AttributeList;
|
||||
use Appwrite\Utopia\Response\Model\AttributeLongtext;
|
||||
use Appwrite\Utopia\Response\Model\AttributeMediumtext;
|
||||
use Appwrite\Utopia\Response\Model\AttributePoint;
|
||||
use Appwrite\Utopia\Response\Model\AttributePolygon;
|
||||
use Appwrite\Utopia\Response\Model\AttributeRelationship;
|
||||
use Appwrite\Utopia\Response\Model\AttributeString;
|
||||
use Appwrite\Utopia\Response\Model\AttributeText;
|
||||
use Appwrite\Utopia\Response\Model\AttributeURL;
|
||||
use Appwrite\Utopia\Response\Model\AttributeVarchar;
|
||||
use Appwrite\Utopia\Response\Model\AuthProvider;
|
||||
use Appwrite\Utopia\Response\Model\BaseList;
|
||||
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\ColumnBoolean;
|
||||
use Appwrite\Utopia\Response\Model\ColumnDatetime;
|
||||
use Appwrite\Utopia\Response\Model\ColumnEmail;
|
||||
use Appwrite\Utopia\Response\Model\ColumnEnum;
|
||||
use Appwrite\Utopia\Response\Model\ColumnFloat;
|
||||
use Appwrite\Utopia\Response\Model\ColumnIndex;
|
||||
use Appwrite\Utopia\Response\Model\ColumnInteger;
|
||||
use Appwrite\Utopia\Response\Model\ColumnIP;
|
||||
use Appwrite\Utopia\Response\Model\ColumnLine;
|
||||
use Appwrite\Utopia\Response\Model\ColumnList;
|
||||
use Appwrite\Utopia\Response\Model\ColumnLongtext;
|
||||
use Appwrite\Utopia\Response\Model\ColumnMediumtext;
|
||||
use Appwrite\Utopia\Response\Model\ColumnPoint;
|
||||
use Appwrite\Utopia\Response\Model\ColumnPolygon;
|
||||
use Appwrite\Utopia\Response\Model\ColumnRelationship;
|
||||
use Appwrite\Utopia\Response\Model\ColumnString;
|
||||
use Appwrite\Utopia\Response\Model\ColumnText;
|
||||
use Appwrite\Utopia\Response\Model\ColumnURL;
|
||||
use Appwrite\Utopia\Response\Model\ColumnVarchar;
|
||||
use Appwrite\Utopia\Response\Model\ConsoleVariables;
|
||||
use Appwrite\Utopia\Response\Model\Continent;
|
||||
use Appwrite\Utopia\Response\Model\Country;
|
||||
use Appwrite\Utopia\Response\Model\Currency;
|
||||
use Appwrite\Utopia\Response\Model\Database;
|
||||
use Appwrite\Utopia\Response\Model\Deployment;
|
||||
use Appwrite\Utopia\Response\Model\DetectionFramework;
|
||||
use Appwrite\Utopia\Response\Model\DetectionRuntime;
|
||||
use Appwrite\Utopia\Response\Model\DetectionVariable;
|
||||
use Appwrite\Utopia\Response\Model\DevKey;
|
||||
use Appwrite\Utopia\Response\Model\Document as ModelDocument;
|
||||
use Appwrite\Utopia\Response\Model\Error;
|
||||
use Appwrite\Utopia\Response\Model\ErrorDev;
|
||||
use Appwrite\Utopia\Response\Model\Execution;
|
||||
use Appwrite\Utopia\Response\Model\File;
|
||||
use Appwrite\Utopia\Response\Model\Framework;
|
||||
use Appwrite\Utopia\Response\Model\FrameworkAdapter;
|
||||
use Appwrite\Utopia\Response\Model\Func;
|
||||
use Appwrite\Utopia\Response\Model\Headers;
|
||||
use Appwrite\Utopia\Response\Model\HealthAntivirus;
|
||||
use Appwrite\Utopia\Response\Model\HealthCertificate;
|
||||
use Appwrite\Utopia\Response\Model\HealthQueue;
|
||||
use Appwrite\Utopia\Response\Model\HealthStatus;
|
||||
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\Installation;
|
||||
use Appwrite\Utopia\Response\Model\JWT;
|
||||
use Appwrite\Utopia\Response\Model\Key;
|
||||
use Appwrite\Utopia\Response\Model\Language;
|
||||
use Appwrite\Utopia\Response\Model\Locale;
|
||||
use Appwrite\Utopia\Response\Model\LocaleCode;
|
||||
use Appwrite\Utopia\Response\Model\Log;
|
||||
use Appwrite\Utopia\Response\Model\Membership;
|
||||
use Appwrite\Utopia\Response\Model\Message;
|
||||
use Appwrite\Utopia\Response\Model\Metric;
|
||||
use Appwrite\Utopia\Response\Model\MetricBreakdown;
|
||||
use Appwrite\Utopia\Response\Model\MFAChallenge;
|
||||
use Appwrite\Utopia\Response\Model\MFAFactors;
|
||||
use Appwrite\Utopia\Response\Model\MFARecoveryCodes;
|
||||
use Appwrite\Utopia\Response\Model\MFAType;
|
||||
use Appwrite\Utopia\Response\Model\Migration;
|
||||
use Appwrite\Utopia\Response\Model\MigrationFirebaseProject;
|
||||
use Appwrite\Utopia\Response\Model\MigrationReport;
|
||||
use Appwrite\Utopia\Response\Model\Mock;
|
||||
use Appwrite\Utopia\Response\Model\MockNumber;
|
||||
use Appwrite\Utopia\Response\Model\None;
|
||||
use Appwrite\Utopia\Response\Model\Phone;
|
||||
use Appwrite\Utopia\Response\Model\Platform;
|
||||
use Appwrite\Utopia\Response\Model\Preferences;
|
||||
use Appwrite\Utopia\Response\Model\Project;
|
||||
use Appwrite\Utopia\Response\Model\Provider;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepository;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepositoryFramework;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepositoryRuntime;
|
||||
use Appwrite\Utopia\Response\Model\ResourceToken;
|
||||
use Appwrite\Utopia\Response\Model\Row;
|
||||
use Appwrite\Utopia\Response\Model\Rule;
|
||||
use Appwrite\Utopia\Response\Model\Runtime;
|
||||
use Appwrite\Utopia\Response\Model\Session;
|
||||
use Appwrite\Utopia\Response\Model\Site;
|
||||
use Appwrite\Utopia\Response\Model\Specification;
|
||||
use Appwrite\Utopia\Response\Model\Subscriber;
|
||||
use Appwrite\Utopia\Response\Model\Table;
|
||||
use Appwrite\Utopia\Response\Model\Target;
|
||||
use Appwrite\Utopia\Response\Model\Team;
|
||||
use Appwrite\Utopia\Response\Model\TemplateEmail;
|
||||
use Appwrite\Utopia\Response\Model\TemplateFramework;
|
||||
use Appwrite\Utopia\Response\Model\TemplateFunction;
|
||||
use Appwrite\Utopia\Response\Model\TemplateRuntime;
|
||||
use Appwrite\Utopia\Response\Model\TemplateSite;
|
||||
use Appwrite\Utopia\Response\Model\TemplateSMS;
|
||||
use Appwrite\Utopia\Response\Model\TemplateVariable;
|
||||
use Appwrite\Utopia\Response\Model\Token;
|
||||
use Appwrite\Utopia\Response\Model\Topic;
|
||||
use Appwrite\Utopia\Response\Model\Transaction;
|
||||
use Appwrite\Utopia\Response\Model\UsageBuckets;
|
||||
use Appwrite\Utopia\Response\Model\UsageCollection;
|
||||
use Appwrite\Utopia\Response\Model\UsageDatabase;
|
||||
use Appwrite\Utopia\Response\Model\UsageDatabases;
|
||||
use Appwrite\Utopia\Response\Model\UsageFunction;
|
||||
use Appwrite\Utopia\Response\Model\UsageFunctions;
|
||||
use Appwrite\Utopia\Response\Model\UsageProject;
|
||||
use Appwrite\Utopia\Response\Model\UsageSite;
|
||||
use Appwrite\Utopia\Response\Model\UsageSites;
|
||||
use Appwrite\Utopia\Response\Model\UsageStorage;
|
||||
use Appwrite\Utopia\Response\Model\UsageTable;
|
||||
use Appwrite\Utopia\Response\Model\UsageUsers;
|
||||
use Appwrite\Utopia\Response\Model\User;
|
||||
use Appwrite\Utopia\Response\Model\Variable;
|
||||
use Appwrite\Utopia\Response\Model\VcsContent;
|
||||
use Appwrite\Utopia\Response\Model\Webhook;
|
||||
|
||||
// General
|
||||
Response::setModel(new None());
|
||||
Response::setModel(new Any());
|
||||
Response::setModel(new Error());
|
||||
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('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));
|
||||
Response::setModel(new BaseList('Indexes List', Response::MODEL_INDEX_LIST, 'indexes', Response::MODEL_INDEX));
|
||||
Response::setModel(new BaseList('Column Indexes List', Response::MODEL_COLUMN_INDEX_LIST, 'indexes', Response::MODEL_COLUMN_INDEX));
|
||||
Response::setModel(new BaseList('Users List', Response::MODEL_USER_LIST, 'users', Response::MODEL_USER));
|
||||
Response::setModel(new BaseList('Sessions List', Response::MODEL_SESSION_LIST, 'sessions', Response::MODEL_SESSION));
|
||||
Response::setModel(new BaseList('Identities List', Response::MODEL_IDENTITY_LIST, 'identities', Response::MODEL_IDENTITY));
|
||||
Response::setModel(new BaseList('Logs List', Response::MODEL_LOG_LIST, 'logs', Response::MODEL_LOG));
|
||||
Response::setModel(new BaseList('Files List', Response::MODEL_FILE_LIST, 'files', Response::MODEL_FILE));
|
||||
Response::setModel(new BaseList('Buckets List', Response::MODEL_BUCKET_LIST, 'buckets', Response::MODEL_BUCKET));
|
||||
Response::setModel(new BaseList('Resource Tokens List', Response::MODEL_RESOURCE_TOKEN_LIST, 'tokens', Response::MODEL_RESOURCE_TOKEN));
|
||||
Response::setModel(new BaseList('Teams List', Response::MODEL_TEAM_LIST, 'teams', Response::MODEL_TEAM));
|
||||
Response::setModel(new BaseList('Memberships List', Response::MODEL_MEMBERSHIP_LIST, 'memberships', Response::MODEL_MEMBERSHIP));
|
||||
Response::setModel(new BaseList('Sites List', Response::MODEL_SITE_LIST, 'sites', Response::MODEL_SITE));
|
||||
Response::setModel(new BaseList('Site Templates List', Response::MODEL_TEMPLATE_SITE_LIST, 'templates', Response::MODEL_TEMPLATE_SITE));
|
||||
Response::setModel(new BaseList('Functions List', Response::MODEL_FUNCTION_LIST, 'functions', Response::MODEL_FUNCTION));
|
||||
Response::setModel(new BaseList('Function Templates List', Response::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', Response::MODEL_TEMPLATE_FUNCTION));
|
||||
Response::setModel(new BaseList('Installations List', Response::MODEL_INSTALLATION_LIST, 'installations', Response::MODEL_INSTALLATION));
|
||||
Response::setModel(new BaseList('Framework Provider Repositories List', Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST, 'frameworkProviderRepositories', Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK));
|
||||
Response::setModel(new BaseList('Runtime Provider Repositories List', Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST, 'runtimeProviderRepositories', Response::MODEL_PROVIDER_REPOSITORY_RUNTIME));
|
||||
Response::setModel(new BaseList('Branches List', Response::MODEL_BRANCH_LIST, 'branches', Response::MODEL_BRANCH));
|
||||
Response::setModel(new BaseList('Frameworks List', Response::MODEL_FRAMEWORK_LIST, 'frameworks', Response::MODEL_FRAMEWORK));
|
||||
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('Webhooks List', Response::MODEL_WEBHOOK_LIST, 'webhooks', Response::MODEL_WEBHOOK, true, false));
|
||||
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));
|
||||
Response::setModel(new BaseList('Auth Providers List', Response::MODEL_AUTH_PROVIDER_LIST, 'platforms', Response::MODEL_AUTH_PROVIDER, true, false));
|
||||
Response::setModel(new BaseList('Platforms List', Response::MODEL_PLATFORM_LIST, 'platforms', Response::MODEL_PLATFORM, true, false));
|
||||
Response::setModel(new BaseList('Countries List', Response::MODEL_COUNTRY_LIST, 'countries', Response::MODEL_COUNTRY));
|
||||
Response::setModel(new BaseList('Continents List', Response::MODEL_CONTINENT_LIST, 'continents', Response::MODEL_CONTINENT));
|
||||
Response::setModel(new BaseList('Languages List', Response::MODEL_LANGUAGE_LIST, 'languages', Response::MODEL_LANGUAGE));
|
||||
Response::setModel(new BaseList('Currencies List', Response::MODEL_CURRENCY_LIST, 'currencies', Response::MODEL_CURRENCY));
|
||||
Response::setModel(new BaseList('Phones List', Response::MODEL_PHONE_LIST, 'phones', Response::MODEL_PHONE));
|
||||
Response::setModel(new BaseList('Metric List', Response::MODEL_METRIC_LIST, 'metrics', Response::MODEL_METRIC, true, false));
|
||||
Response::setModel(new BaseList('Variables List', Response::MODEL_VARIABLE_LIST, 'variables', Response::MODEL_VARIABLE));
|
||||
Response::setModel(new BaseList('Status List', Response::MODEL_HEALTH_STATUS_LIST, 'statuses', Response::MODEL_HEALTH_STATUS));
|
||||
Response::setModel(new BaseList('Rule List', Response::MODEL_PROXY_RULE_LIST, 'rules', Response::MODEL_PROXY_RULE));
|
||||
Response::setModel(new BaseList('Locale codes list', Response::MODEL_LOCALE_CODE_LIST, 'localeCodes', Response::MODEL_LOCALE_CODE));
|
||||
Response::setModel(new BaseList('Provider list', Response::MODEL_PROVIDER_LIST, 'providers', Response::MODEL_PROVIDER));
|
||||
Response::setModel(new BaseList('Message list', Response::MODEL_MESSAGE_LIST, 'messages', Response::MODEL_MESSAGE));
|
||||
Response::setModel(new BaseList('Topic list', Response::MODEL_TOPIC_LIST, 'topics', Response::MODEL_TOPIC));
|
||||
Response::setModel(new BaseList('Subscriber list', Response::MODEL_SUBSCRIBER_LIST, 'subscribers', Response::MODEL_SUBSCRIBER));
|
||||
Response::setModel(new BaseList('Target list', Response::MODEL_TARGET_LIST, 'targets', Response::MODEL_TARGET));
|
||||
Response::setModel(new BaseList('Transaction List', Response::MODEL_TRANSACTION_LIST, 'transactions', Response::MODEL_TRANSACTION));
|
||||
Response::setModel(new BaseList('Migrations List', Response::MODEL_MIGRATION_LIST, 'migrations', Response::MODEL_MIGRATION));
|
||||
Response::setModel(new BaseList('Migrations Firebase Projects List', Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', Response::MODEL_MIGRATION_FIREBASE_PROJECT));
|
||||
Response::setModel(new BaseList('Specifications List', Response::MODEL_SPECIFICATION_LIST, 'specifications', Response::MODEL_SPECIFICATION));
|
||||
Response::setModel(new BaseList('VCS Content List', Response::MODEL_VCS_CONTENT_LIST, 'contents', Response::MODEL_VCS_CONTENT));
|
||||
|
||||
// Entities
|
||||
Response::setModel(new Database());
|
||||
|
||||
// Collection API Models
|
||||
Response::setModel(new Collection());
|
||||
Response::setModel(new Attribute());
|
||||
Response::setModel(new AttributeList());
|
||||
Response::setModel(new AttributeString());
|
||||
Response::setModel(new AttributeInteger());
|
||||
Response::setModel(new AttributeFloat());
|
||||
Response::setModel(new AttributeBoolean());
|
||||
Response::setModel(new AttributeEmail());
|
||||
Response::setModel(new AttributeEnum());
|
||||
Response::setModel(new AttributeIP());
|
||||
Response::setModel(new AttributeURL());
|
||||
Response::setModel(new AttributeDatetime());
|
||||
Response::setModel(new AttributeRelationship());
|
||||
Response::setModel(new AttributePoint());
|
||||
Response::setModel(new AttributeLine());
|
||||
Response::setModel(new AttributePolygon());
|
||||
Response::setModel(new AttributeVarchar());
|
||||
Response::setModel(new AttributeText());
|
||||
Response::setModel(new AttributeMediumtext());
|
||||
Response::setModel(new AttributeLongtext());
|
||||
|
||||
// Table API Models
|
||||
Response::setModel(new Table());
|
||||
Response::setModel(new Column());
|
||||
Response::setModel(new ColumnList());
|
||||
Response::setModel(new ColumnString());
|
||||
Response::setModel(new ColumnInteger());
|
||||
Response::setModel(new ColumnFloat());
|
||||
Response::setModel(new ColumnBoolean());
|
||||
Response::setModel(new ColumnEmail());
|
||||
Response::setModel(new ColumnEnum());
|
||||
Response::setModel(new ColumnIP());
|
||||
Response::setModel(new ColumnURL());
|
||||
Response::setModel(new ColumnDatetime());
|
||||
Response::setModel(new ColumnRelationship());
|
||||
Response::setModel(new ColumnPoint());
|
||||
Response::setModel(new ColumnLine());
|
||||
Response::setModel(new ColumnPolygon());
|
||||
Response::setModel(new ColumnVarchar());
|
||||
Response::setModel(new ColumnText());
|
||||
Response::setModel(new ColumnMediumtext());
|
||||
Response::setModel(new ColumnLongtext());
|
||||
Response::setModel(new Index());
|
||||
Response::setModel(new ColumnIndex());
|
||||
Response::setModel(new Row());
|
||||
Response::setModel(new ModelDocument());
|
||||
Response::setModel(new Log());
|
||||
Response::setModel(new User());
|
||||
Response::setModel(new AlgoMd5());
|
||||
Response::setModel(new AlgoSha());
|
||||
Response::setModel(new AlgoPhpass());
|
||||
Response::setModel(new AlgoBcrypt());
|
||||
Response::setModel(new AlgoScrypt());
|
||||
Response::setModel(new AlgoScryptModified());
|
||||
Response::setModel(new AlgoArgon2());
|
||||
Response::setModel(new Account());
|
||||
Response::setModel(new Preferences());
|
||||
Response::setModel(new Session());
|
||||
Response::setModel(new Identity());
|
||||
Response::setModel(new Token());
|
||||
Response::setModel(new JWT());
|
||||
Response::setModel(new Locale());
|
||||
Response::setModel(new LocaleCode());
|
||||
Response::setModel(new File());
|
||||
Response::setModel(new Bucket());
|
||||
Response::setModel(new ResourceToken());
|
||||
Response::setModel(new Team());
|
||||
Response::setModel(new Membership());
|
||||
Response::setModel(new Site());
|
||||
Response::setModel(new TemplateSite());
|
||||
Response::setModel(new TemplateFramework());
|
||||
Response::setModel(new Func());
|
||||
Response::setModel(new TemplateFunction());
|
||||
Response::setModel(new TemplateRuntime());
|
||||
Response::setModel(new TemplateVariable());
|
||||
Response::setModel(new Installation());
|
||||
Response::setModel(new ProviderRepository());
|
||||
Response::setModel(new ProviderRepositoryFramework());
|
||||
Response::setModel(new ProviderRepositoryRuntime());
|
||||
Response::setModel(new DetectionFramework());
|
||||
Response::setModel(new DetectionRuntime());
|
||||
Response::setModel(new DetectionVariable());
|
||||
Response::setModel(new VcsContent());
|
||||
Response::setModel(new Branch());
|
||||
Response::setModel(new Runtime());
|
||||
Response::setModel(new Framework());
|
||||
Response::setModel(new FrameworkAdapter());
|
||||
Response::setModel(new Deployment());
|
||||
Response::setModel(new Execution());
|
||||
Response::setModel(new Project());
|
||||
Response::setModel(new Webhook());
|
||||
Response::setModel(new Key());
|
||||
Response::setModel(new DevKey());
|
||||
Response::setModel(new MockNumber());
|
||||
Response::setModel(new AuthProvider());
|
||||
Response::setModel(new Platform());
|
||||
Response::setModel(new Variable());
|
||||
Response::setModel(new Country());
|
||||
Response::setModel(new Continent());
|
||||
Response::setModel(new Language());
|
||||
Response::setModel(new Currency());
|
||||
Response::setModel(new Phone());
|
||||
Response::setModel(new HealthAntivirus());
|
||||
Response::setModel(new HealthQueue());
|
||||
Response::setModel(new HealthStatus());
|
||||
Response::setModel(new HealthCertificate());
|
||||
Response::setModel(new HealthTime());
|
||||
Response::setModel(new HealthVersion());
|
||||
Response::setModel(new Metric());
|
||||
Response::setModel(new MetricBreakdown());
|
||||
Response::setModel(new UsageDatabases());
|
||||
Response::setModel(new UsageDatabase());
|
||||
Response::setModel(new UsageTable());
|
||||
Response::setModel(new UsageCollection());
|
||||
Response::setModel(new UsageUsers());
|
||||
Response::setModel(new UsageStorage());
|
||||
Response::setModel(new UsageBuckets());
|
||||
Response::setModel(new UsageFunctions());
|
||||
Response::setModel(new UsageFunction());
|
||||
Response::setModel(new UsageSites());
|
||||
Response::setModel(new UsageSite());
|
||||
Response::setModel(new UsageProject());
|
||||
Response::setModel(new Headers());
|
||||
Response::setModel(new Specification());
|
||||
Response::setModel(new Rule());
|
||||
Response::setModel(new TemplateSMS());
|
||||
Response::setModel(new TemplateEmail());
|
||||
Response::setModel(new ConsoleVariables());
|
||||
Response::setModel(new MFAChallenge());
|
||||
Response::setModel(new MFARecoveryCodes());
|
||||
Response::setModel(new MFAType());
|
||||
Response::setModel(new MFAFactors());
|
||||
Response::setModel(new Provider());
|
||||
Response::setModel(new Message());
|
||||
Response::setModel(new Topic());
|
||||
Response::setModel(new Transaction());
|
||||
Response::setModel(new Subscriber());
|
||||
Response::setModel(new Target());
|
||||
Response::setModel(new Migration());
|
||||
Response::setModel(new MigrationReport());
|
||||
Response::setModel(new MigrationFirebaseProject());
|
||||
|
||||
// Tests (keep last)
|
||||
Response::setModel(new Mock());
|
||||
+25
-5
@@ -8,7 +8,6 @@ use Appwrite\URL\URL as AppwriteURL;
|
||||
use MaxMind\Db\Reader;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Swoole\Database\PDOProxy;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
@@ -18,22 +17,26 @@ use Utopia\Database\Adapter\SQL;
|
||||
use Utopia\Database\PDO;
|
||||
use Utopia\Domains\Validator\PublicDomain;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Http;
|
||||
use Utopia\Logger\Adapter\AppSignal;
|
||||
use Utopia\Logger\Adapter\LogOwl;
|
||||
use Utopia\Logger\Adapter\Raygun;
|
||||
use Utopia\Logger\Adapter\Sentry;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Pools\Adapter\Stack as StackPool;
|
||||
use Utopia\Pools\Adapter\Swoole as SwoolePool;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Pools\Pool;
|
||||
use Utopia\Queue;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\System\System;
|
||||
|
||||
global $register;
|
||||
$register = new Registry();
|
||||
|
||||
App::setMode(System::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
|
||||
Http::setMode(System::getEnv('_APP_ENV', Http::MODE_TYPE_PRODUCTION));
|
||||
|
||||
if (!App::isProduction()) {
|
||||
if (!Http::isProduction()) {
|
||||
// Allow specific domains to skip public domain validation in dev environment
|
||||
// Useful for existing tests involving webhooks
|
||||
PublicDomain::allow(['request-catcher-sms']);
|
||||
@@ -285,7 +288,9 @@ $register->set('pools', function () {
|
||||
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'),
|
||||
};
|
||||
|
||||
$pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) {
|
||||
$poolAdapter = System::getEnv('_APP_POOL_ADAPTER', default: 'stack') === 'swoole' ? new SwoolePool() : new StackPool();
|
||||
|
||||
$pool = new Pool($poolAdapter, $name, $poolSize, function () use ($type, $resource, $dsn) {
|
||||
// Get Adapter
|
||||
switch ($type) {
|
||||
case 'database':
|
||||
@@ -309,10 +314,17 @@ $register->set('pools', function () {
|
||||
default => null
|
||||
};
|
||||
case 'cache':
|
||||
return match ($dsn->getScheme()) {
|
||||
$adapter = match ($dsn->getScheme()) {
|
||||
'redis' => new RedisCache($resource()),
|
||||
default => null
|
||||
};
|
||||
|
||||
if ($adapter !== null) {
|
||||
$adapter->setMaxRetries(CACHE_RECONNECT_MAX_RETRIES);
|
||||
$adapter->setRetryDelay(CACHE_RECONNECT_RETRY_DELAY);
|
||||
}
|
||||
|
||||
return $adapter;
|
||||
default:
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
|
||||
}
|
||||
@@ -324,6 +336,12 @@ $register->set('pools', function () {
|
||||
Config::setParam('pools-' . $key, $config);
|
||||
}
|
||||
|
||||
$reconnectAttempts = (int) System::getEnv('_APP_CONNECTIONS_RECONNECT_ATTEMPTS', 5);
|
||||
$reconnectSleep = (int) System::getEnv('_APP_CONNECTIONS_RECONNECT_SLEEP', 2);
|
||||
|
||||
$group->setReconnectAttempts($reconnectAttempts);
|
||||
$group->setReconnectSleep($reconnectSleep);
|
||||
|
||||
return $group;
|
||||
});
|
||||
|
||||
@@ -360,6 +378,8 @@ $register->set('smtp', function () {
|
||||
$mail->SMTPSecure = System::getEnv('_APP_SMTP_SECURE', '');
|
||||
$mail->SMTPAutoTLS = false;
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->Timeout = 10; /* Connection timeout */
|
||||
$mail->getSMTPInstance()->Timelimit = 30; /* Timeout for each individual SMTP command (e.g. HELO, EHLO, etc.) */
|
||||
|
||||
$from = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
|
||||
$email = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
|
||||
|
||||
+589
-221
File diff suppressed because it is too large
Load Diff
+150
-43
@@ -16,7 +16,6 @@ use Swoole\Table;
|
||||
use Swoole\Timer;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
use Utopia\App;
|
||||
use Utopia\Auth\Hashes\Sha;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
@@ -29,11 +28,12 @@ use Utopia\Database\Adapter\Pool as DatabasePool;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Http;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Registry\Registry;
|
||||
@@ -67,6 +67,7 @@ if (!function_exists('getConsoleDB')) {
|
||||
$adapter = new DatabasePool($pools->get('console'));
|
||||
$database = new Database($adapter, getCache());
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', '_console');
|
||||
@@ -123,6 +124,7 @@ if (!function_exists('getProjectDB')) {
|
||||
}
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
@@ -309,7 +311,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
'value' => '{}'
|
||||
]);
|
||||
|
||||
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
|
||||
$statsDocument = $database->getAuthorization()->skip(fn () => $database->createDocument('realtime', $document));
|
||||
break;
|
||||
} catch (Throwable) {
|
||||
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
|
||||
@@ -339,7 +341,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
->setAttribute('timestamp', DateTime::now())
|
||||
->setAttribute('value', json_encode($payload));
|
||||
|
||||
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
$database->getAuthorization()->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
} catch (Throwable $th) {
|
||||
$logError($th, "updateWorkerDocument");
|
||||
}
|
||||
@@ -370,7 +372,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
|
||||
$payload = [];
|
||||
|
||||
$list = Authorization::skip(fn () => $database->find('realtime', [
|
||||
$list = $database->getAuthorization()->skip(fn () => $database->find('realtime', [
|
||||
Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)),
|
||||
]));
|
||||
|
||||
@@ -429,10 +431,24 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
]
|
||||
];
|
||||
|
||||
$server->send($realtime->getSubscribers($event), json_encode([
|
||||
'type' => 'event',
|
||||
'data' => $event['data']
|
||||
]));
|
||||
$subscribers = $realtime->getSubscribers($event);
|
||||
|
||||
$groups = [];
|
||||
foreach ($subscribers as $id => $matched) {
|
||||
$key = implode(',', array_keys($matched));
|
||||
$groups[$key]['ids'][] = $id;
|
||||
$groups[$key]['subscriptions'] = array_keys($matched);
|
||||
}
|
||||
|
||||
foreach ($groups as $group) {
|
||||
$data = $event['data'];
|
||||
$data['subscriptions'] = $group['subscriptions'];
|
||||
|
||||
$server->send($group['ids'], json_encode([
|
||||
'type' => 'event',
|
||||
'data' => $data
|
||||
]));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -464,39 +480,70 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) {
|
||||
$connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId]));
|
||||
$consoleDatabase = getConsoleDB();
|
||||
$project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
|
||||
$project = $consoleDatabase->getAuthorization()->skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
|
||||
$database = getProjectDB($project);
|
||||
|
||||
/** @var Appwrite\Utopia\Database\Documents\User $user */
|
||||
$user = $database->getDocument('users', $userId);
|
||||
|
||||
$roles = $user->getRoles();
|
||||
$channels = $realtime->connections[$connection]['channels'];
|
||||
$roles = $user->getRoles($database->getAuthorization());
|
||||
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
|
||||
|
||||
$meta = $realtime->getSubscriptionMetadata($connection);
|
||||
|
||||
$realtime->unsubscribe($connection);
|
||||
$realtime->subscribe($projectId, $connection, $roles, $channels);
|
||||
|
||||
foreach ($meta as $subscriptionId => $subscription) {
|
||||
$queries = Query::parseQueries($subscription['queries'] ?? []);
|
||||
$realtime->subscribe(
|
||||
$projectId,
|
||||
$connection,
|
||||
$subscriptionId,
|
||||
$roles,
|
||||
$subscription['channels'] ?? [],
|
||||
$queries
|
||||
);
|
||||
}
|
||||
|
||||
// Restore authorization after subscribe
|
||||
if ($authorization !== null) {
|
||||
$realtime->connections[$connection]['authorization'] = $authorization;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$receivers = $realtime->getSubscribers($event);
|
||||
|
||||
if (App::isDevelopment() && !empty($receivers)) {
|
||||
if (Http::isDevelopment() && !empty($receivers)) {
|
||||
Console::log("[Debug][Worker {$workerId}] Receivers: " . count($receivers));
|
||||
Console::log("[Debug][Worker {$workerId}] Receivers Connection IDs: " . json_encode($receivers));
|
||||
Console::log("[Debug][Worker {$workerId}] Connection IDs: " . json_encode(array_keys($receivers)));
|
||||
Console::log("[Debug][Worker {$workerId}] Matched: " . json_encode(array_values($receivers)));
|
||||
Console::log("[Debug][Worker {$workerId}] Event: " . $payload);
|
||||
}
|
||||
|
||||
$server->send(
|
||||
$receivers,
|
||||
json_encode([
|
||||
'type' => 'event',
|
||||
'data' => $event['data']
|
||||
])
|
||||
);
|
||||
// Group connections by matched subscription IDs for batch sending
|
||||
$groups = [];
|
||||
foreach ($receivers as $id => $matched) {
|
||||
$key = implode(',', array_keys($matched));
|
||||
$groups[$key]['ids'][] = $id;
|
||||
$groups[$key]['subscriptions'] = array_keys($matched);
|
||||
}
|
||||
|
||||
if (($num = count($receivers)) > 0) {
|
||||
$register->get('telemetry.messageSentCounter')->add($num);
|
||||
$stats->incr($event['project'], 'messages', $num);
|
||||
$total = 0;
|
||||
foreach ($groups as $group) {
|
||||
$data = $event['data'];
|
||||
$data['subscriptions'] = $group['subscriptions'];
|
||||
|
||||
$server->send($group['ids'], json_encode([
|
||||
'type' => 'event',
|
||||
'data' => $data
|
||||
]));
|
||||
$total += count($group['ids']);
|
||||
}
|
||||
|
||||
if ($total > 0) {
|
||||
$register->get('telemetry.messageSentCounter')->add($total);
|
||||
$stats->incr($event['project'], 'messages', $total);
|
||||
}
|
||||
});
|
||||
} catch (Throwable $th) {
|
||||
@@ -513,19 +560,20 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
});
|
||||
|
||||
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $logError) {
|
||||
$app = new App('UTC');
|
||||
$app = new Http('UTC');
|
||||
$request = new Request($request);
|
||||
$response = new Response(new SwooleResponse());
|
||||
|
||||
Console::info("Connection open (user: {$connection})");
|
||||
|
||||
App::setResource('pools', fn () => $register->get('pools'));
|
||||
App::setResource('request', fn () => $request);
|
||||
App::setResource('response', fn () => $response);
|
||||
Http::setResource('pools', fn () => $register->get('pools'));
|
||||
Http::setResource('request', fn () => $request);
|
||||
Http::setResource('response', fn () => $response);
|
||||
|
||||
try {
|
||||
/** @var Document $project */
|
||||
$project = $app->getResource('project');
|
||||
$authorization = $app->getResource('authorization');
|
||||
|
||||
/*
|
||||
* Project Check
|
||||
@@ -537,13 +585,12 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
if (
|
||||
array_key_exists('realtime', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['realtime']
|
||||
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
|
||||
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
|
||||
$timelimit = $app->getResource('timelimit');
|
||||
$platforms = $app->getResource('platforms');
|
||||
$user = $app->getResource('user'); /** @var User $user */
|
||||
|
||||
/*
|
||||
@@ -568,13 +615,13 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
* Skip this check for non-web platforms which are not required to send an origin header.
|
||||
*/
|
||||
$origin = $request->getOrigin();
|
||||
$originValidator = new Origin($platforms);
|
||||
$originValidator = $app->getResource('originValidator');
|
||||
|
||||
if (!empty($origin) && !$originValidator->isValid($origin) && $project->getId() !== 'console') {
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
|
||||
}
|
||||
|
||||
$roles = $user->getRoles();
|
||||
$roles = $user->getRoles($authorization);
|
||||
|
||||
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
|
||||
|
||||
@@ -585,14 +632,42 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing channels');
|
||||
}
|
||||
|
||||
$realtime->subscribe($project->getId(), $connection, $roles, $channels);
|
||||
$names = array_keys($channels);
|
||||
|
||||
try {
|
||||
$subscriptions = Realtime::constructSubscriptions(
|
||||
$names,
|
||||
fn ($channel) => $request->getQuery($channel, null)
|
||||
);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $e->getMessage());
|
||||
}
|
||||
|
||||
$mapping = [];
|
||||
foreach ($subscriptions as $index => $subscription) {
|
||||
$subscriptionId = ID::unique();
|
||||
|
||||
$realtime->subscribe(
|
||||
$project->getId(),
|
||||
$connection,
|
||||
$subscriptionId,
|
||||
$roles,
|
||||
$subscription['channels'],
|
||||
$subscription['queries']
|
||||
);
|
||||
|
||||
$mapping[$index] = $subscriptionId;
|
||||
}
|
||||
|
||||
$realtime->connections[$connection]['authorization'] = $authorization;
|
||||
|
||||
$user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_ACCOUNT);
|
||||
|
||||
$server->send([$connection], json_encode([
|
||||
'type' => 'connected',
|
||||
'data' => [
|
||||
'channels' => array_keys($channels),
|
||||
'channels' => $names,
|
||||
'subscriptions' => $mapping,
|
||||
'user' => $user
|
||||
]
|
||||
]));
|
||||
@@ -615,10 +690,11 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$code = 500;
|
||||
}
|
||||
|
||||
|
||||
$message = $th->getMessage();
|
||||
|
||||
// sanitize 0 && 5xx errors
|
||||
if (($code === 0 || $code >= 500) && !App::isDevelopment()) {
|
||||
if (($code === 0 || $code >= 500) && !Http::isDevelopment()) {
|
||||
$message = 'Error: Server Error';
|
||||
}
|
||||
|
||||
@@ -633,7 +709,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$server->send([$connection], json_encode($response));
|
||||
$server->close($connection, $code);
|
||||
|
||||
if (App::isDevelopment()) {
|
||||
if (Http::isDevelopment()) {
|
||||
Console::error('[Error] Connection Error');
|
||||
Console::error('[Error] Code: ' . $response['data']['code']);
|
||||
Console::error('[Error] Message: ' . $response['data']['message']);
|
||||
@@ -644,12 +720,19 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
|
||||
try {
|
||||
$response = new Response(new SwooleResponse());
|
||||
$projectId = $realtime->connections[$connection]['projectId'];
|
||||
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
|
||||
|
||||
// Get authorization from connection (stored during onOpen)
|
||||
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
|
||||
|
||||
$database = getConsoleDB();
|
||||
$database->setAuthorization($authorization);
|
||||
|
||||
if ($projectId !== 'console') {
|
||||
$project = Authorization::skip(fn () => $database->getDocument('projects', $projectId));
|
||||
$project = $authorization->skip(fn () => $database->getDocument('projects', $projectId));
|
||||
|
||||
$database = getProjectDB($project);
|
||||
$database->setAuthorization($authorization);
|
||||
} else {
|
||||
$project = null;
|
||||
}
|
||||
@@ -713,9 +796,33 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
|
||||
}
|
||||
|
||||
$roles = $user->getRoles();
|
||||
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
|
||||
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
|
||||
$roles = $user->getRoles($database->getAuthorization());
|
||||
|
||||
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
|
||||
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
|
||||
|
||||
$meta = $realtime->getSubscriptionMetadata($connection);
|
||||
|
||||
$realtime->unsubscribe($connection);
|
||||
|
||||
if (!empty($projectId)) {
|
||||
foreach ($meta as $subscriptionId => $subscription) {
|
||||
$queries = Query::parseQueries($subscription['queries'] ?? []);
|
||||
|
||||
$realtime->subscribe(
|
||||
$projectId,
|
||||
$connection,
|
||||
$subscriptionId,
|
||||
$roles,
|
||||
$subscription['channels'] ?? [],
|
||||
$queries
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($authorization !== null) {
|
||||
$realtime->connections[$connection]['authorization'] = $authorization;
|
||||
}
|
||||
|
||||
$user = $response->output($user, Response::MODEL_ACCOUNT);
|
||||
$server->send([$connection], json_encode([
|
||||
@@ -741,7 +848,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
$message = $th->getMessage();
|
||||
|
||||
// sanitize 0 && 5xx errors
|
||||
if (($code === 0 || $code >= 500) && !App::isDevelopment()) {
|
||||
if (($code === 0 || $code >= 500) && !Http::isDevelopment()) {
|
||||
$message = 'Error: Server Error';
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\System\System;
|
||||
|
||||
$development = $this->getParam('development', false);
|
||||
@@ -15,7 +16,8 @@ $labelClass = '';
|
||||
$buttons = [];
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$platform = Config::getParam('platform', []);
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
// TODO: remove this later
|
||||
if (System::getEnv('_APP_ENV') === 'development') {
|
||||
$hostname = 'localhost';
|
||||
@@ -537,4 +539,4 @@ switch ($type) {
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -11,9 +11,10 @@ $httpsPort = $this->getParam('httpsPort', '');
|
||||
$version = $this->getParam('version', '');
|
||||
$organization = $this->getParam('organization', '');
|
||||
$image = $this->getParam('image', '');
|
||||
$enableAssistant = $this->getParam('enableAssistant', false);
|
||||
?>services:
|
||||
traefik:
|
||||
image: traefik:2.11
|
||||
image: traefik:3.6
|
||||
container_name: appwrite-traefik
|
||||
<<: *x-logging
|
||||
command:
|
||||
@@ -164,6 +165,7 @@ $image = $this->getParam('image', '');
|
||||
- _APP_MAINTENANCE_RETENTION_SCHEDULES
|
||||
- _APP_SMS_PROVIDER
|
||||
- _APP_SMS_FROM
|
||||
- _APP_GRAPHQL_INTROSPECTION
|
||||
- _APP_GRAPHQL_MAX_BATCH_SIZE
|
||||
- _APP_GRAPHQL_MAX_COMPLEXITY
|
||||
- _APP_GRAPHQL_MAX_DEPTH
|
||||
@@ -179,7 +181,7 @@ $image = $this->getParam('image', '');
|
||||
appwrite-console:
|
||||
<<: *x-logging
|
||||
container_name: appwrite-console
|
||||
image: <?php echo $organization; ?>/console:7.4.7
|
||||
image: <?php echo $organization; ?>/console:7.5.7
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -848,6 +850,7 @@ $image = $this->getParam('image', '');
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
|
||||
<?php if ($enableAssistant): ?>
|
||||
appwrite-assistant:
|
||||
image: appwrite/assistant:0.8.4
|
||||
container_name: appwrite-assistant
|
||||
@@ -857,6 +860,7 @@ $image = $this->getParam('image', '');
|
||||
- appwrite
|
||||
environment:
|
||||
- _APP_ASSISTANT_OPENAI_API_KEY
|
||||
<?php endif; ?>
|
||||
|
||||
appwrite-browser:
|
||||
image: appwrite/browser:0.3.2
|
||||
|
||||
+93
-24
@@ -9,11 +9,13 @@ use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Execution;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Screenshot;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
@@ -21,6 +23,8 @@ use Appwrite\Utopia\Database\Documents\User;
|
||||
use Executor\Executor;
|
||||
use Swoole\Runtime;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
use Utopia\Audit\Adapter\Database as AdapterDatabase;
|
||||
use Utopia\Audit\Audit as UtopiaAudit;
|
||||
use Utopia\Cache\Adapter\Pool as CachePool;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
@@ -41,24 +45,42 @@ use Utopia\Queue\Message;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Server;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Span\Exporter;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\Span\Storage;
|
||||
use Utopia\Storage\Device\Telemetry as TelemetryDevice;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
use Utopia\Telemetry\Adapter\None as NoTelemetry;
|
||||
|
||||
Authorization::disable();
|
||||
Runtime::enableCoroutine();
|
||||
Span::setStorage(new Storage\Coroutine());
|
||||
Span::addExporter(new Exporter\Stdout());
|
||||
|
||||
global $register;
|
||||
Server::setResource('register', fn () => $register);
|
||||
|
||||
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register) {
|
||||
Server::setResource('authorization', function () {
|
||||
$authorization = new Authorization();
|
||||
$authorization->disable();
|
||||
return $authorization;
|
||||
}, []);
|
||||
|
||||
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register, Authorization $authorization) {
|
||||
$pools = $register->get('pools');
|
||||
$adapter = new DatabasePool($pools->get('console'));
|
||||
$dbForPlatform = new Database($adapter, $cache);
|
||||
$dbForPlatform->setNamespace('_console');
|
||||
$dbForPlatform->setDocumentType('users', User::class);
|
||||
|
||||
$dbForPlatform
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setNamespace('_console')
|
||||
->setDocumentType('users', User::class)
|
||||
;
|
||||
|
||||
|
||||
return $dbForPlatform;
|
||||
}, ['cache', 'register']);
|
||||
}, ['cache', 'register', 'authorization']);
|
||||
|
||||
Server::setResource('project', function (Message $message, Database $dbForPlatform) {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
@@ -71,7 +93,7 @@ Server::setResource('project', function (Message $message, Database $dbForPlatfo
|
||||
return $dbForPlatform->getDocument('projects', $project->getId());
|
||||
}, ['message', 'dbForPlatform']);
|
||||
|
||||
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform) {
|
||||
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform, Authorization $authorization) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -103,15 +125,18 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
|
||||
return $database;
|
||||
}, ['cache', 'register', 'message', 'project', 'dbForPlatform']);
|
||||
}, ['cache', 'register', 'message', 'project', 'dbForPlatform', 'authorization']);
|
||||
|
||||
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases): Database {
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases): Database {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -125,7 +150,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
|
||||
|
||||
if (isset($databases[$dsn->getHost()])) {
|
||||
$database = $databases[$dsn->getHost()];
|
||||
|
||||
$database->setAuthorization($authorization);
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
@@ -162,15 +187,18 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForPlatform', 'cache']);
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$database = null;
|
||||
return function (?Document $project = null) use ($pools, $cache, $database) {
|
||||
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant((int)$project->getSequence());
|
||||
return $database;
|
||||
@@ -180,10 +208,12 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setSharedTables(true)
|
||||
->setNamespace('logsV1')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER);
|
||||
|
||||
// set tenant
|
||||
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
@@ -192,7 +222,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
Server::setResource('abuseRetention', function () {
|
||||
return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
|
||||
@@ -305,6 +335,10 @@ Server::setResource('queueForBuilds', function (Publisher $publisher) {
|
||||
return new Build($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForScreenshots', function (Publisher $publisher) {
|
||||
return new Screenshot($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForDeletes', function (Publisher $publisher) {
|
||||
return new Delete($publisher);
|
||||
}, ['publisher']);
|
||||
@@ -325,6 +359,10 @@ Server::setResource('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForExecutions', function (Publisher $publisher) {
|
||||
return new Execution($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForRealtime', function () {
|
||||
return new Realtime();
|
||||
}, []);
|
||||
@@ -411,6 +449,13 @@ Server::setResource('logError', function (Registry $register, Document $project)
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
|
||||
if ($error->getPrevious() !== null) {
|
||||
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
|
||||
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
|
||||
}
|
||||
$log->addExtra('previousFile', $error->getPrevious()->getFile());
|
||||
$log->addExtra('previousLine', $error->getPrevious()->getLine());
|
||||
}
|
||||
|
||||
foreach (($extras ?? []) as $key => $value) {
|
||||
$log->addExtra($key, $value);
|
||||
@@ -431,11 +476,39 @@ Server::setResource('logError', function (Registry $register, Document $project)
|
||||
|
||||
Console::warning("Failed: {$error->getMessage()}");
|
||||
Console::warning($error->getTraceAsString());
|
||||
|
||||
if ($error->getPrevious() !== null) {
|
||||
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
|
||||
Console::warning("Previous Failed: {$error->getPrevious()->getMessage()}");
|
||||
}
|
||||
Console::warning("Previous File: {$error->getPrevious()->getFile()} Line: {$error->getPrevious()->getLine()}");
|
||||
}
|
||||
};
|
||||
}, ['register', 'project']);
|
||||
|
||||
Server::setResource('executor', fn () => new Executor());
|
||||
|
||||
Server::setResource('getAudit', function (Database $dbForPlatform, callable $getProjectDB) {
|
||||
return function (Document $project) use ($dbForPlatform, $getProjectDB) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
$adapter = new AdapterDatabase($dbForPlatform);
|
||||
return new UtopiaAudit($adapter);
|
||||
}
|
||||
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$adapter = new AdapterDatabase($dbForProject);
|
||||
return new UtopiaAudit($adapter);
|
||||
};
|
||||
}, ['dbForPlatform', 'getProjectDB']);
|
||||
|
||||
Server::setResource('executionsRetentionCount', function (Document $project, array $plan) {
|
||||
if ($project->getId() === 'console' || empty($plan)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) ($plan['executionsRetentionCount'] ?? 100);
|
||||
}, ['project', 'plan']);
|
||||
|
||||
$pools = $register->get('pools');
|
||||
$platform = new Appwrite();
|
||||
$args = $platform->getEnv('argv');
|
||||
@@ -480,7 +553,8 @@ $worker
|
||||
->inject('log')
|
||||
->inject('pools')
|
||||
->inject('project')
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($worker, $queueName) {
|
||||
->inject('authorization')
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project, Authorization $authorization) use ($worker, $queueName) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
if ($logger) {
|
||||
@@ -496,7 +570,7 @@ $worker
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
$log->addExtra('roles', $authorization->getRoles());
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
@@ -515,9 +589,4 @@ $worker
|
||||
Console::error('[Error] Line: ' . $error->getLine());
|
||||
});
|
||||
|
||||
$worker->workerStart()
|
||||
->action(function () use ($workerName) {
|
||||
Console::info("Worker $workerName started");
|
||||
});
|
||||
|
||||
$worker->start();
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php doctor $@
|
||||
exec php /usr/src/code/app/cli.php doctor "$@"
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php install $@
|
||||
exec php /usr/src/code/app/cli.php install "$@"
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec php /usr/src/code/app/cli.php interval "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php maintenance $@
|
||||
exec php /usr/src/code/app/cli.php maintenance "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php migrate $@
|
||||
exec php /usr/src/code/app/cli.php migrate "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php queue-count --type=failed $@
|
||||
exec php /usr/src/code/app/cli.php queue-count --type=failed "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php queue-count --type=processing $@
|
||||
exec php /usr/src/code/app/cli.php queue-count --type=processing "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php queue-count --type=success $@
|
||||
exec php /usr/src/code/app/cli.php queue-count --type=success "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php queue-retry $@
|
||||
exec php /usr/src/code/app/cli.php queue-retry "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/realtime.php $@
|
||||
exec php /usr/src/code/app/realtime.php "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php schedule-executions $@
|
||||
exec php /usr/src/code/app/cli.php schedule-executions "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php schedule-functions $@
|
||||
exec php /usr/src/code/app/cli.php schedule-functions "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php schedule-messages $@
|
||||
exec php /usr/src/code/app/cli.php schedule-messages "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php screenshot $@
|
||||
exec php /usr/src/code/app/cli.php screenshot "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php sdks $@
|
||||
exec php /usr/src/code/app/cli.php sdks "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php specs $@
|
||||
exec php /usr/src/code/app/cli.php specs "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php ssl $@
|
||||
exec php /usr/src/code/app/cli.php ssl "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php stats-resources $@
|
||||
exec php /usr/src/code/app/cli.php stats-resources "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
/usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml $@
|
||||
exec /usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php upgrade $@
|
||||
exec php /usr/src/code/app/cli.php upgrade "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php vars $@
|
||||
exec php /usr/src/code/app/cli.php vars "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/worker.php audits $@
|
||||
exec php /usr/src/code/app/worker.php audits "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/worker.php builds $@
|
||||
exec php /usr/src/code/app/worker.php builds "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/worker.php certificates $@
|
||||
exec php /usr/src/code/app/worker.php certificates "$@"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user