mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
3399
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03bfed4e25 | ||
|
|
495a17a290 | ||
|
|
097030c348 | ||
|
|
725ebfcd44 | ||
|
|
ba1e5a7159 | ||
|
|
7d119aa68c | ||
|
|
590ae9cfe4 | ||
|
|
a769a18ba0 | ||
|
|
08e5dc1c40 | ||
|
|
85b8728e71 | ||
|
|
b3c2273780 | ||
|
|
62bdc77b93 | ||
|
|
b6c43e2ccf | ||
|
|
166b446fe7 | ||
|
|
e5fa4883b2 | ||
|
|
7f377f8d32 | ||
|
|
35b76a9597 | ||
|
|
7ca70b2ab9 | ||
|
|
7cec672a63 | ||
|
|
8872d9db17 | ||
|
|
d2b2d18134 | ||
|
|
34d27fb4d0 | ||
|
|
52d7dcf082 | ||
|
|
3009168350 | ||
|
|
07017e0835 | ||
|
|
f952d57c88 | ||
|
|
c96b32d7b6 | ||
|
|
472ddd190c | ||
|
|
af7cbef024 | ||
|
|
be609f4ee2 | ||
|
|
f2ea043c3b | ||
|
|
ceadcbf113 | ||
|
|
f39e52e1d5 | ||
|
|
211f1bb117 | ||
|
|
7ea990e1d9 | ||
|
|
15080fee1b | ||
|
|
979fa36b02 | ||
|
|
927c2a2988 | ||
|
|
d63ce7319c | ||
|
|
e2a0b8dcc1 | ||
|
|
98bab42414 | ||
|
|
924b7b90a4 | ||
|
|
02c5df1676 | ||
|
|
683aa8a325 | ||
|
|
3c6f8d2326 | ||
|
|
d050ae40a7 | ||
|
|
d47bea764c | ||
|
|
3b4b412733 | ||
|
|
ed702d0bc1 | ||
|
|
d47b32856b | ||
|
|
72330d0869 | ||
|
|
ce64bdb7f7 | ||
|
|
67be2c10eb | ||
|
|
871f158bb6 | ||
|
|
641a47f195 | ||
|
|
7f14db493d | ||
|
|
f4a77fcf11 | ||
|
|
3670b5e9ad | ||
|
|
d86d604624 | ||
|
|
2a8911011b | ||
|
|
f869252d90 | ||
|
|
26eaddee82 | ||
|
|
71b96452c6 | ||
|
|
bafcfcfbb9 | ||
|
|
a01dd3fc41 | ||
|
|
07b8f0fe3f | ||
|
|
117e416b80 | ||
|
|
69b5fae3bf | ||
|
|
ffb91eca68 | ||
|
|
6b497a31c8 | ||
|
|
e3b8f0b302 | ||
|
|
9b7d032eda | ||
|
|
7fe14ca10b | ||
|
|
9a76507185 | ||
|
|
aa20b685a7 | ||
|
|
bf73b1a55f | ||
|
|
de2ea95099 | ||
|
|
43946cade5 | ||
|
|
b0a6190394 | ||
|
|
d46f3092e5 | ||
|
|
fdd18fdc7d | ||
|
|
700abb3e47 | ||
|
|
a2cb3c135f | ||
|
|
0f1c1fda9e | ||
|
|
c60d7764e4 | ||
|
|
28a9a373b1 | ||
|
|
f6bd9d4150 | ||
|
|
c077636d40 | ||
|
|
2157acd63f | ||
|
|
655ae89423 | ||
|
|
45e636ab2d | ||
|
|
5cbb08d6f1 | ||
|
|
391e114cdd | ||
|
|
4a314c0a58 | ||
|
|
58ba41a9c5 | ||
|
|
01b066e317 | ||
|
|
dc626ea336 | ||
|
|
9604cd1d94 | ||
|
|
f00bd7dac7 | ||
|
|
87864d2365 | ||
|
|
84f4c757c3 | ||
|
|
7f8f34793e | ||
|
|
d58ce741f3 | ||
|
|
5485346b5e | ||
|
|
6e9e6aa6b3 | ||
|
|
acbdb06b3c | ||
|
|
5354e5ee44 | ||
|
|
93a994a95d | ||
|
|
e123188b0a | ||
|
|
8d3c96d7eb | ||
|
|
a91ee7e80f | ||
|
|
b39b018a10 | ||
|
|
577ba9b0ec | ||
|
|
d44f69df09 | ||
|
|
deb2751cbf | ||
|
|
bb9ae6f326 | ||
|
|
b252fe847f | ||
|
|
9e11e00c6d | ||
|
|
6591778f90 | ||
|
|
08b013353b | ||
|
|
1bf15a7cc2 | ||
|
|
599a0d0781 | ||
|
|
4624241d32 | ||
|
|
2153f65893 | ||
|
|
72f203b365 | ||
|
|
ed8911e0a6 | ||
|
|
6aba92bf8b | ||
|
|
13c77b6927 | ||
|
|
460d52f948 | ||
|
|
6664377fb8 | ||
|
|
af890a4e4b | ||
|
|
3d95f7e129 | ||
|
|
3cfbdf07b7 | ||
|
|
cbcf110560 | ||
|
|
bd7d28f7d1 | ||
|
|
28758d4794 | ||
|
|
e8b6ff2bb3 | ||
|
|
42df2435ec | ||
|
|
3860602697 | ||
|
|
f9adffc569 | ||
|
|
dd2f980539 | ||
|
|
eb8258d548 | ||
|
|
9010320cd9 | ||
|
|
fec374fe59 | ||
|
|
bcf5b4bfab | ||
|
|
3bbe6d4da8 | ||
|
|
cf122054aa | ||
|
|
578d4d1619 | ||
|
|
c7201cc417 | ||
|
|
939a0c5591 | ||
|
|
bf4e2df45b | ||
|
|
05d7badd60 | ||
|
|
2f709a2e89 | ||
|
|
bd64ace713 | ||
|
|
39acf18bc3 | ||
|
|
07d90aa4c4 | ||
|
|
ed54d9861f | ||
|
|
a86c1c0a2b | ||
|
|
72d090f196 | ||
|
|
4de45bf9a7 | ||
|
|
a7a3d85bcb | ||
|
|
9468c238de | ||
|
|
9025f97939 | ||
|
|
e2d3c95fa6 | ||
|
|
5aff6969db | ||
|
|
19d5ea03b9 | ||
|
|
7b35d3739b | ||
|
|
0638fec40d | ||
|
|
1af33d1af7 | ||
|
|
bfc54c4590 | ||
|
|
161eb0b66f | ||
|
|
f25bfa805b | ||
|
|
d23a2e2040 | ||
|
|
1958cef3d2 | ||
|
|
c09ab6cc9d | ||
|
|
970ae8d0ad | ||
|
|
96e1440ee6 | ||
|
|
ecaca00463 | ||
|
|
e80d13076f | ||
|
|
15d1badafb | ||
|
|
ac82726364 | ||
|
|
3c9cb0ab80 | ||
|
|
505ffc91ae | ||
|
|
f69d222a5a | ||
|
|
5f7f44f939 | ||
|
|
18be424fc0 | ||
|
|
fa64cc33ea | ||
|
|
948f04e71c | ||
|
|
1cf25199ab | ||
|
|
0465fb2268 | ||
|
|
275c6bb3a0 | ||
|
|
64ee75e80d | ||
|
|
9ac08b496b | ||
|
|
0ef6f32a10 | ||
|
|
276b1de2b0 | ||
|
|
79c8ed3ce4 | ||
|
|
07c383e17a | ||
|
|
ac8d4fcb2a | ||
|
|
d76d573e7c | ||
|
|
0797df8414 | ||
|
|
ca186543ca | ||
|
|
8578985515 | ||
|
|
b8b6ac75fc | ||
|
|
daa5c7e59f | ||
|
|
d43d0cf293 | ||
|
|
47586fedb5 | ||
|
|
702000ac2f | ||
|
|
c65800b3a3 | ||
|
|
0071b9d885 | ||
|
|
48b018fd9d | ||
|
|
6cddb38290 | ||
|
|
c4b11afd08 | ||
|
|
f6aa4a4ab1 | ||
|
|
a48ce855c7 | ||
|
|
1885f9e87c | ||
|
|
24d1163703 | ||
|
|
2775a92cdf | ||
|
|
b6cfe43681 | ||
|
|
947ad20bad | ||
|
|
c310434eeb | ||
|
|
e62e5c405a | ||
|
|
3a7d00519f | ||
|
|
d6a7e26678 | ||
|
|
671888d975 | ||
|
|
6539f15178 | ||
|
|
617a2c5fc8 | ||
|
|
fad9ecd389 | ||
|
|
969cff5e41 | ||
|
|
0b9526fc50 | ||
|
|
7020c85f78 | ||
|
|
c1ba898cec | ||
|
|
10cd97e978 | ||
|
|
e8583d9387 | ||
|
|
75b687786d | ||
|
|
0fc4d5f49a | ||
|
|
010481bafc | ||
|
|
818d111068 | ||
|
|
f43ff0d2e9 | ||
|
|
588d6651a2 | ||
|
|
c18af032ae | ||
|
|
035180c4da | ||
|
|
7a089f8861 | ||
|
|
2e44434ab8 | ||
|
|
f974477ecc | ||
|
|
f9f7af8759 | ||
|
|
0643a94b12 | ||
|
|
18bdb6ae3e | ||
|
|
5f965e6dad | ||
|
|
2b1e35659c | ||
|
|
5689d47d41 | ||
|
|
60c54e2d10 | ||
|
|
6122a25e56 | ||
|
|
319698dab4 | ||
|
|
34467de87d | ||
|
|
c31d60dc1f | ||
|
|
4c52458684 | ||
|
|
a824b46df0 | ||
|
|
433b3be9ed | ||
|
|
53dc3a9dee | ||
|
|
5c2c968933 | ||
|
|
8b5e0e1f1a | ||
|
|
e06e890279 | ||
|
|
a3ddfd98d8 | ||
|
|
48065033b5 | ||
|
|
8e987112b9 | ||
|
|
5f1fb5a5ef | ||
|
|
b77967dc1f | ||
|
|
3139130fdd | ||
|
|
66f18a494b | ||
|
|
e114da1d9a | ||
|
|
a5f3af62bd | ||
|
|
f7c9e9c650 | ||
|
|
d59b4d17bb | ||
|
|
232dc7aef7 | ||
|
|
6640c52849 | ||
|
|
0c6f9bdb01 | ||
|
|
eee29940bb | ||
|
|
644832f0b3 | ||
|
|
841a8d3fae | ||
|
|
a40dba1b49 | ||
|
|
c094a40a53 | ||
|
|
6e4592dab4 | ||
|
|
038f4fc073 | ||
|
|
00c1418246 | ||
|
|
79bfa025cb | ||
|
|
28af0669f6 | ||
|
|
d26d0d5943 | ||
|
|
44de884ee5 | ||
|
|
9aaef931ca | ||
|
|
348c2d4ffe | ||
|
|
08d588aeb1 | ||
|
|
c1bce185fd | ||
|
|
a0a3e3bd2c | ||
|
|
137c2d663e | ||
|
|
31fad24603 | ||
|
|
40152fbd6c | ||
|
|
6306ec59ba | ||
|
|
f255484fd4 | ||
|
|
bfdb7830d0 | ||
|
|
adcbb08374 | ||
|
|
341830b3d7 | ||
|
|
9c4b269669 | ||
|
|
ea1c0d0a1f | ||
|
|
2f1dda891b | ||
|
|
b1cc4ada4c | ||
|
|
8b79438755 | ||
|
|
f5f51c4fc9 | ||
|
|
5fc3f5bc3a | ||
|
|
b6c279b576 | ||
|
|
b116d24486 | ||
|
|
ba6439375c | ||
|
|
f525104963 | ||
|
|
ca3efdf562 | ||
|
|
10150aad3e | ||
|
|
2178bfc126 | ||
|
|
f568f9f1ad | ||
|
|
7b1853eb96 | ||
|
|
077ad544b6 | ||
|
|
cb065e4228 | ||
|
|
31a903d144 | ||
|
|
fcd9b060fd | ||
|
|
01edc57806 | ||
|
|
ad8ae92a6d | ||
|
|
64244d9f8f | ||
|
|
4f08d8e82a | ||
|
|
9ea3f0317f | ||
|
|
b16c789282 | ||
|
|
7a4cea9f79 | ||
|
|
7f3000439a | ||
|
|
85a2d623a6 | ||
|
|
0121a95cee | ||
|
|
e11ef18f1e | ||
|
|
cbc365a702 | ||
|
|
496da61cd9 | ||
|
|
eaf31fdb78 | ||
|
|
df9f1ee4c4 | ||
|
|
5df53e3e76 | ||
|
|
bfd23665cb | ||
|
|
0dd666c227 | ||
|
|
6e5b8fab4b | ||
|
|
d2c55afdcf | ||
|
|
861e980420 | ||
|
|
41277864fd | ||
|
|
86134768fc | ||
|
|
ae32536be2 | ||
|
|
d5449cb498 | ||
|
|
e8c74d7204 | ||
|
|
8714f11fd2 | ||
|
|
d604746f9b | ||
|
|
b331fe4ab9 | ||
|
|
7372983c9d | ||
|
|
196ecff5dc | ||
|
|
c06b38773c | ||
|
|
a28eaa80cf | ||
|
|
66a7b652e6 | ||
|
|
84f4813135 | ||
|
|
04c1d3ca7f | ||
|
|
73a47b5624 | ||
|
|
c4a27c79ab | ||
|
|
18d938ce54 | ||
|
|
4023a4bb2f | ||
|
|
454fd1cd43 | ||
|
|
c5953d1e1b | ||
|
|
fb3a7bc0e9 | ||
|
|
acd18b4d9d | ||
|
|
ec7e28b3e7 | ||
|
|
5a2bbd0976 | ||
|
|
740e1956b5 | ||
|
|
8ae974e6dd | ||
|
|
fadad0ee4f | ||
|
|
f1fd44cee3 | ||
|
|
293c0e176b | ||
|
|
dd9eab65c8 | ||
|
|
0790fe04cc | ||
|
|
8b7dd6eaa6 | ||
|
|
d043a74082 | ||
|
|
dacb713450 | ||
|
|
7d7ac9757b | ||
|
|
c60f78fdeb | ||
|
|
0c6ddb8c99 | ||
|
|
c5ebcbab2b | ||
|
|
941cb173c3 | ||
|
|
b8efa441fb | ||
|
|
7f542e2052 | ||
|
|
6bdd3906c9 | ||
|
|
7c0eb7bf8b | ||
|
|
8c5220f455 | ||
|
|
0b07fb2060 | ||
|
|
b2fc7cc25a | ||
|
|
6b6f9edbd6 | ||
|
|
32fd00031d | ||
|
|
b8c11a5643 | ||
|
|
6540749559 | ||
|
|
cf0b039653 | ||
|
|
104d99e636 | ||
|
|
6635ebf27f | ||
|
|
17fec2690a | ||
|
|
94c58c367b | ||
|
|
ae3535de0d | ||
|
|
1b9ee249be | ||
|
|
81add700c6 | ||
|
|
c990f686dd | ||
|
|
4948aedfb5 | ||
|
|
9f3e815167 | ||
|
|
e1cf464bcb | ||
|
|
5863ba6285 | ||
|
|
4e1a4b961c | ||
|
|
1904e3df81 | ||
|
|
7e055c585d | ||
|
|
452ee5c086 | ||
|
|
5d1ada4652 | ||
|
|
35687e72d9 | ||
|
|
ed8c0ac02b | ||
|
|
6696373d28 | ||
|
|
3e3eaaf209 | ||
|
|
a730d1ddc7 | ||
|
|
5ebd40ced8 | ||
|
|
8eb4c1dd7f | ||
|
|
b43ca1a539 | ||
|
|
e1af86af98 | ||
|
|
92f597cd49 | ||
|
|
66beaf2cca | ||
|
|
c0968c9002 | ||
|
|
fc87a0a281 | ||
|
|
bfc594b56e | ||
|
|
244c4e1795 | ||
|
|
89560c4b82 | ||
|
|
650d1af339 | ||
|
|
d543705a54 | ||
|
|
0f2349faa2 | ||
|
|
6012171258 | ||
|
|
b3e29c6086 | ||
|
|
2d702b62b2 | ||
|
|
0180aff4f5 | ||
|
|
92a20a2241 | ||
|
|
397d4937ea | ||
|
|
439467347e | ||
|
|
9b017140c7 | ||
|
|
161d3b485e | ||
|
|
94b311f14f | ||
|
|
265eea0599 | ||
|
|
05eb4eee78 | ||
|
|
3f171fbdb9 | ||
|
|
637e4098be | ||
|
|
f6fc9a8d8c | ||
|
|
c340fae696 | ||
|
|
bbd39582ba | ||
|
|
7790079bb2 | ||
|
|
e90e007da1 | ||
|
|
037d330641 | ||
|
|
57c445868e | ||
|
|
88e9099e87 | ||
|
|
08fca62767 | ||
|
|
1e9bbdbda0 | ||
|
|
5a665f3ad1 | ||
|
|
00bad84d48 | ||
|
|
488d3ec7e8 | ||
|
|
da873b2876 | ||
|
|
0045eefd1e | ||
|
|
bff39aea08 | ||
|
|
cdf7731508 | ||
|
|
6026358fb2 | ||
|
|
6d85dd6d85 | ||
|
|
3abb0013e7 | ||
|
|
799c527661 | ||
|
|
8f8e2b4aa3 | ||
|
|
5ddce5fbda | ||
|
|
e2a58dfade | ||
|
|
e9d53cbc46 | ||
|
|
661581a7f3 | ||
|
|
213d3dc721 | ||
|
|
abeb4973e4 | ||
|
|
6a26d9db39 | ||
|
|
9e4f66cac4 | ||
|
|
45bc0f4066 | ||
|
|
8149405207 | ||
|
|
daa121262b | ||
|
|
91abc2b079 | ||
|
|
2070cccd54 | ||
|
|
9890b5934d | ||
|
|
63b54ead62 | ||
|
|
ff5c5c7138 | ||
|
|
31b4c73b16 | ||
|
|
f15dd063fd | ||
|
|
e5a6db6133 | ||
|
|
feef53a43a | ||
|
|
569776a1f6 | ||
|
|
e9e254bb41 | ||
|
|
844690e904 | ||
|
|
0a73a76919 | ||
|
|
2d8ba5438a | ||
|
|
17ce9f532a | ||
|
|
e1dd5295f4 | ||
|
|
35ba33cb49 | ||
|
|
148473e662 | ||
|
|
00bd3bb9bb | ||
|
|
24a7caf936 | ||
|
|
eee2c0c4ff | ||
|
|
c393e130ac | ||
|
|
c56f8277c9 | ||
|
|
c9f4b7ca23 | ||
|
|
81b08de407 | ||
|
|
ade2e10994 | ||
|
|
54f4bef67a | ||
|
|
860542deae | ||
|
|
7c2c68c5ec | ||
|
|
4ea82fb2d0 | ||
|
|
b3481e08d3 | ||
|
|
efcaa8ba77 | ||
|
|
b25dc38a63 | ||
|
|
2f252ecee7 | ||
|
|
e14f4404e0 | ||
|
|
b645f42881 | ||
|
|
58c4c3da1e | ||
|
|
0a98ad6042 | ||
|
|
1466e7b770 | ||
|
|
33dab2ea46 | ||
|
|
60bfae7e00 | ||
|
|
e72c5511b6 | ||
|
|
4cc135e8e2 | ||
|
|
536d36be7d | ||
|
|
2c23f7f149 | ||
|
|
d53456c6ed | ||
|
|
625aa8ada6 | ||
|
|
f7e35cce2c | ||
|
|
309d67066e | ||
|
|
7f3a2dcce7 | ||
|
|
48e2bf1c3e | ||
|
|
f5cc2e43f4 | ||
|
|
c4ae047833 | ||
|
|
aa5a45ba79 | ||
|
|
aefe59a437 | ||
|
|
92e020b209 | ||
|
|
b7683364dd | ||
|
|
4f35353289 | ||
|
|
16b80e1ef8 | ||
|
|
fe472e5b36 | ||
|
|
4975a769e7 | ||
|
|
d9edc65e9d | ||
|
|
45aea5b941 | ||
|
|
92a384e31c | ||
|
|
f5715f4df8 | ||
|
|
7910149053 | ||
|
|
31b718d829 | ||
|
|
4639f2c753 | ||
|
|
f6744fc07a | ||
|
|
afaa64b54b | ||
|
|
3146ec363d | ||
|
|
cb9b94ff04 | ||
|
|
004bb82688 | ||
|
|
d49de9c6cf | ||
|
|
a35cf0b4c0 | ||
|
|
f0bb8dcc33 | ||
|
|
c9664e2d19 | ||
|
|
be77b10549 | ||
|
|
a1cb81abb6 | ||
|
|
2b4dbfb4b9 | ||
|
|
4b0ef4598b | ||
|
|
da8af68e1e | ||
|
|
d2ca803753 | ||
|
|
b06ce91297 | ||
|
|
8d40220114 | ||
|
|
11b3739360 | ||
|
|
a2642ecc67 | ||
|
|
b238a1ef2d | ||
|
|
87c7e76a16 | ||
|
|
34b2280207 | ||
|
|
5088c9ea99 | ||
|
|
8f351ad7b2 | ||
|
|
bc7b98821a | ||
|
|
32b8a1edfe | ||
|
|
29ef377b60 | ||
|
|
63a981591d | ||
|
|
a812bfb686 | ||
|
|
2e154c06bc | ||
|
|
514b42bea2 | ||
|
|
3ca0ff66d6 | ||
|
|
04fa436dca | ||
|
|
fe84af90b6 | ||
|
|
ae6736222c | ||
|
|
5e090c4cd4 | ||
|
|
eccae13651 | ||
|
|
7eae5c300d | ||
|
|
bc2d8c44c5 | ||
|
|
b7ceae67b9 | ||
|
|
76cc9af5a3 | ||
|
|
a79e9efa78 | ||
|
|
528bae3c53 | ||
|
|
79cfb49320 | ||
|
|
14fb586c4b | ||
|
|
014457636c | ||
|
|
0aa853fdcf | ||
|
|
070fb9b7cb | ||
|
|
7931253033 | ||
|
|
9e74be61d4 | ||
|
|
5bd90c57f1 | ||
|
|
4d9901babb | ||
|
|
2405377593 | ||
|
|
0865b240c4 | ||
|
|
991c6dea64 | ||
|
|
c74e5dcb1a | ||
|
|
7c7e7c7bca | ||
|
|
f77e150fe0 | ||
|
|
55031f8f43 | ||
|
|
df0409d25c | ||
|
|
0e80a548c7 | ||
|
|
ee432192de | ||
|
|
f9491a73a4 | ||
|
|
8681acfeab | ||
|
|
fa969dedbc | ||
|
|
070edcf55a | ||
|
|
a528b66232 | ||
|
|
c2d597e8c6 | ||
|
|
616348a312 | ||
|
|
0384b705f9 | ||
|
|
d722bf01ea | ||
|
|
02d86cee84 | ||
|
|
0347a8a1df | ||
|
|
a9e9fe3e67 | ||
|
|
7532309bbb | ||
|
|
a922f7f0e1 | ||
|
|
39cb846ae0 | ||
|
|
d63c429d3e | ||
|
|
ccc9432219 | ||
|
|
804dbb867c | ||
|
|
a0a08b3588 | ||
|
|
62dd5c275d | ||
|
|
d4091e82e0 | ||
|
|
65fec6b954 | ||
|
|
3826937a7b | ||
|
|
4d33ba6db1 | ||
|
|
14b6aa0e8e | ||
|
|
bbe29e9aa7 | ||
|
|
18c4f2b92a | ||
|
|
213d17ffad | ||
|
|
8927c71b69 | ||
|
|
96cf2e6330 | ||
|
|
0760ea90d6 | ||
|
|
5649bc8060 | ||
|
|
f03035451f | ||
|
|
614d7d878f | ||
|
|
a9078cded5 | ||
|
|
50a5329b30 | ||
|
|
87b3df919f | ||
|
|
abde948b43 | ||
|
|
44082a92f7 | ||
|
|
276032b3f7 | ||
|
|
574ffa4d4b | ||
|
|
e9710bdb76 | ||
|
|
124403b804 | ||
|
|
3bcb1f846e | ||
|
|
1ff8ce2382 | ||
|
|
f9371ee4f3 | ||
|
|
2603f91a6a | ||
|
|
5d61ff5fab | ||
|
|
1a92bf1923 | ||
|
|
054ebc699c | ||
|
|
27b1ae912a | ||
|
|
43c850bd9b | ||
|
|
9694b79f03 | ||
|
|
cbf1d9deb6 | ||
|
|
4699d78112 | ||
|
|
89fb5dceb2 | ||
|
|
eb9e7c737e | ||
|
|
e86994b597 | ||
|
|
6adbeb8b0d | ||
|
|
dc49e1dcf3 | ||
|
|
9167b10c81 | ||
|
|
93b489fe2c | ||
|
|
94c9d68e4f | ||
|
|
f707221ee4 | ||
|
|
b301a64dc4 | ||
|
|
524a6db72d | ||
|
|
772481451c | ||
|
|
1388f58b59 | ||
|
|
2ee9f2607f | ||
|
|
ba5e0b4ae4 | ||
|
|
82d20233e2 | ||
|
|
8239113b95 | ||
|
|
37fc5114ab | ||
|
|
37ce513b87 | ||
|
|
8fdafdce45 | ||
|
|
cb0a927e34 | ||
|
|
6b31edfdef | ||
|
|
1e94400a7b | ||
|
|
36e6b7e5c0 | ||
|
|
a8b36f8259 | ||
|
|
adb21683e0 | ||
|
|
7cf49ac1f6 | ||
|
|
fc1b0b0196 | ||
|
|
3e3cb638f4 | ||
|
|
616bead19f | ||
|
|
dfd122efaa | ||
|
|
91e17e4e94 | ||
|
|
91db596b42 | ||
|
|
679fd66507 | ||
|
|
2019deb454 | ||
|
|
b35e5a04be | ||
|
|
09a6726afa | ||
|
|
56a903577f | ||
|
|
11ef344216 | ||
|
|
bcceee9a7d | ||
|
|
f72d075c40 | ||
|
|
5b1c54c440 | ||
|
|
95791c85ff | ||
|
|
28ac6c9355 | ||
|
|
30ece2d36a | ||
|
|
f7dd37a9d1 | ||
|
|
7a9b9f2710 | ||
|
|
d098c8c698 | ||
|
|
3f26e66d26 | ||
|
|
15f52b933b | ||
|
|
9114a61fb2 | ||
|
|
ba77b35f05 | ||
|
|
4abc30eed1 | ||
|
|
6612e6edf0 | ||
|
|
0eae73e7b8 | ||
|
|
51ad9c6479 | ||
|
|
e22ad54e39 | ||
|
|
0b1b7b8170 | ||
|
|
cfd5a91c43 | ||
|
|
61e03eeedf | ||
|
|
8e7b89ec03 | ||
|
|
4ee8fd5629 | ||
|
|
12302d5b3d | ||
|
|
b7d17e82da | ||
|
|
495da1bfd4 | ||
|
|
ea241ceaf5 | ||
|
|
5d195659bd | ||
|
|
bdc3e9f935 | ||
|
|
d107b92748 | ||
|
|
ab08f0ca40 | ||
|
|
64b74d4100 | ||
|
|
d7a833faef | ||
|
|
8ade726932 | ||
|
|
f96128e14f | ||
|
|
1b37bd82c9 | ||
|
|
5741a960c7 | ||
|
|
42c855cb41 | ||
|
|
ff33068906 | ||
|
|
e05fbb23a9 | ||
|
|
cf9d67e9cc | ||
|
|
5e9eccacd1 | ||
|
|
255c4043d6 | ||
|
|
ae24689b89 | ||
|
|
2c46cdcc3c | ||
|
|
478c37ef97 | ||
|
|
aa7e326ce9 | ||
|
|
4868b8f8ec | ||
|
|
c60a3a66a0 | ||
|
|
ca39ed37d8 | ||
|
|
e0be09ded6 | ||
|
|
3885e0452c | ||
|
|
a6afc41c99 | ||
|
|
fd93c8aebd | ||
|
|
509aef16bd | ||
|
|
65bacc6983 | ||
|
|
cda8e53386 | ||
|
|
cbcc17ded6 | ||
|
|
f070f0974a | ||
|
|
ccc5f7776d | ||
|
|
ddbd03388e | ||
|
|
fd52cdc45f | ||
|
|
da2821b4a8 | ||
|
|
0a177713f0 | ||
|
|
4de3289ebb | ||
|
|
e4f546eae0 | ||
|
|
15654f8c99 | ||
|
|
2b72771781 | ||
|
|
27d7f4f083 | ||
|
|
1da3f7770f | ||
|
|
a12eaf12b4 | ||
|
|
b6723b7c83 | ||
|
|
ea58cb4aff | ||
|
|
4dfaa5e69a | ||
|
|
77d4627d42 | ||
|
|
31089b76fb | ||
|
|
0cd67229a1 | ||
|
|
8dfdef6006 | ||
|
|
5dbed8329e | ||
|
|
a983630e0e | ||
|
|
f44a642998 | ||
|
|
cefd5660b9 | ||
|
|
9d0cbd3b68 | ||
|
|
6070e1de54 | ||
|
|
de38fed306 | ||
|
|
30eda18ac1 | ||
|
|
25f3d4a59b | ||
|
|
8b71e318c2 | ||
|
|
0cb2ed14f4 | ||
|
|
5e872f2fe5 | ||
|
|
efe093ce3c | ||
|
|
bb01a6d817 | ||
|
|
b44a100439 | ||
|
|
6d3adeb4ed | ||
|
|
01556184c7 | ||
|
|
dcbdbfa053 | ||
|
|
4d4ed16839 | ||
|
|
b819381bed | ||
|
|
20c538ec83 | ||
|
|
991d8749b2 | ||
|
|
5f0f03c170 | ||
|
|
2e9087285d | ||
|
|
b382031ae7 | ||
|
|
8406551cef | ||
|
|
0d0de17d15 | ||
|
|
c02d04b5fa | ||
|
|
2a7fae54e0 | ||
|
|
1e1ab2a8ae | ||
|
|
bd0139ef3b | ||
|
|
5f80f87ba9 | ||
|
|
56d8c176dd | ||
|
|
ad1587872b | ||
|
|
1f7a4b86d1 | ||
|
|
a231a9853f | ||
|
|
64ba896738 | ||
|
|
4813e6b4d5 | ||
|
|
315eaaa562 | ||
|
|
fc752039a4 | ||
|
|
d2b0218aba | ||
|
|
3e0456291f | ||
|
|
de8552e901 | ||
|
|
7b09fa3f17 | ||
|
|
7f95ff31bd | ||
|
|
eda112755a | ||
|
|
dd99ab7b00 | ||
|
|
e7fc427291 | ||
|
|
d2362aeab0 | ||
|
|
831960245f | ||
|
|
948f09bea9 | ||
|
|
8feb3dc43c | ||
|
|
0ee49e7895 | ||
|
|
a80da7526d | ||
|
|
ce1b41b312 | ||
|
|
4a61718cff | ||
|
|
3353bad8ff | ||
|
|
5a84a37a9e | ||
|
|
8f6a151980 | ||
|
|
673b0bb083 | ||
|
|
dd34dfce22 | ||
|
|
bba9284c3e | ||
|
|
d1eae37577 | ||
|
|
727c3839d6 | ||
|
|
36cc6d8621 | ||
|
|
ac73be78b5 | ||
|
|
a252c7e469 | ||
|
|
8d3903cd37 | ||
|
|
e7189efcff | ||
|
|
53adb4e114 | ||
|
|
6567b23c85 | ||
|
|
30f088ad35 | ||
|
|
00ec95fc9f | ||
|
|
eb5e853f2d | ||
|
|
163cbc5052 | ||
|
|
3d01bf6505 | ||
|
|
f63aeb50a1 | ||
|
|
a58dbe7c66 | ||
|
|
db79f92b39 | ||
|
|
2ede405784 | ||
|
|
a309beddf5 | ||
|
|
4a495f61d5 | ||
|
|
7d8162309c | ||
|
|
17a36dbce3 | ||
|
|
956eab5ab5 | ||
|
|
08a986e433 | ||
|
|
1498d0f628 | ||
|
|
fbebf7deac | ||
|
|
c585dfda16 | ||
|
|
5ca7b5ffe8 | ||
|
|
8411e81e28 | ||
|
|
68cc1b5db4 | ||
|
|
410533be27 | ||
|
|
f85b8a5bdc | ||
|
|
8faa46de9b | ||
|
|
d3b8d49a3e | ||
|
|
cd4b7c1c36 | ||
|
|
aae2cea73a | ||
|
|
fbb9bb8941 | ||
|
|
3ffd59f18c | ||
|
|
da4e7536ca | ||
|
|
3cb97a62c8 | ||
|
|
b80e82e9c1 | ||
|
|
b71ddd1d26 | ||
|
|
f9d2976c1d | ||
|
|
dd6c5c056f | ||
|
|
9c95e40458 | ||
|
|
40c71a7b53 | ||
|
|
9a09ee23f1 | ||
|
|
deefff537d | ||
|
|
3b6035b585 | ||
|
|
912636b54c | ||
|
|
27ddf8630b | ||
|
|
50dc4c3790 | ||
|
|
841c5479ed | ||
|
|
68e1a0b114 | ||
|
|
1b533daeaa | ||
|
|
5db924627a | ||
|
|
29edecf05d | ||
|
|
ee0950449a | ||
|
|
18618b8e8b | ||
|
|
006cb4665e | ||
|
|
aff29ed197 | ||
|
|
ef1e5a9674 | ||
|
|
344669db46 | ||
|
|
10695a021a | ||
|
|
62618445e9 | ||
|
|
35b7a2ee96 | ||
|
|
85ed70f346 | ||
|
|
522ae8d538 | ||
|
|
7288520f38 | ||
|
|
f5a4701944 | ||
|
|
f0052cbd8e | ||
|
|
a51288da57 | ||
|
|
64b725a6f7 | ||
|
|
e1ecf9aad7 | ||
|
|
8dd5eb09f0 | ||
|
|
c621b66195 | ||
|
|
aea13bad72 | ||
|
|
825738ced9 | ||
|
|
a376bdcb60 | ||
|
|
9412013360 | ||
|
|
3c71164741 | ||
|
|
c784cab798 | ||
|
|
a14ab321bf | ||
|
|
310ff90d22 | ||
|
|
465dad522e | ||
|
|
a19e668d79 | ||
|
|
6260a1a156 | ||
|
|
956db2ec2f | ||
|
|
60a63d9eb9 | ||
|
|
43d4d6fec3 | ||
|
|
c76057fd37 | ||
|
|
36d4719a3b | ||
|
|
0b883df5ec | ||
|
|
cdb4c60c3c | ||
|
|
66b805829c | ||
|
|
f6b7e0392b | ||
|
|
b4bf0cf8d0 | ||
|
|
72a37516d3 | ||
|
|
180262d166 | ||
|
|
7723762dd1 | ||
|
|
df791068d6 | ||
|
|
f2f56aa310 | ||
|
|
d664549495 | ||
|
|
02a432102d | ||
|
|
f9671ee855 | ||
|
|
fa591c8c2f | ||
|
|
42cedf8ed0 | ||
|
|
4479497dfe | ||
|
|
9f384e016f | ||
|
|
d307d4f698 | ||
|
|
a2570f5568 | ||
|
|
9565152ced | ||
|
|
4dd919f640 | ||
|
|
ab5ddc263b | ||
|
|
bc915483ff | ||
|
|
a0ab934328 | ||
|
|
29c30a361b | ||
|
|
5011820f67 | ||
|
|
e315f5d2ac | ||
|
|
239813aec2 | ||
|
|
4041c4964c | ||
|
|
1615121b3e | ||
|
|
4befb19c35 | ||
|
|
842185e45d | ||
|
|
85cb520dd6 | ||
|
|
4dfafd1169 | ||
|
|
e1f9a8e0ce | ||
|
|
4cfc3b3a7f | ||
|
|
94676a6c16 | ||
|
|
a47bfe71bf | ||
|
|
de63618842 | ||
|
|
44ca7d9459 | ||
|
|
df37e1b7a9 | ||
|
|
d2798ea401 | ||
|
|
103469928e | ||
|
|
4b8287c097 | ||
|
|
8e99c44c59 | ||
|
|
32be6d6816 | ||
|
|
4b5d537eee | ||
|
|
41d31c28d0 | ||
|
|
b66f932b3b | ||
|
|
360a51ec20 | ||
|
|
e9239717d7 | ||
|
|
be642762b3 | ||
|
|
c074731615 | ||
|
|
0827d59f21 | ||
|
|
f95145b796 | ||
|
|
a8a23fb8eb | ||
|
|
bf34af86ac | ||
|
|
b2d884e329 | ||
|
|
cddacfbb78 | ||
|
|
1eea8c6d8b | ||
|
|
60127cc69d | ||
|
|
5dc8a2dee0 | ||
|
|
cbe7b35436 | ||
|
|
5ea19cd539 | ||
|
|
4d3ca5a064 | ||
|
|
8baa95db1c | ||
|
|
8a4fd3307d | ||
|
|
03f85c3b74 | ||
|
|
a97a476296 | ||
|
|
11f3474cb8 | ||
|
|
622c852367 | ||
|
|
e891424c17 | ||
|
|
256046cba7 | ||
|
|
629c1b1686 | ||
|
|
a450089fe7 | ||
|
|
bf119867e8 | ||
|
|
d34359fd49 | ||
|
|
1e92bda18b | ||
|
|
eaf59693b7 | ||
|
|
ae288ffc9b | ||
|
|
aff7c59097 | ||
|
|
983f885b3f | ||
|
|
d8717a4a16 | ||
|
|
24a140c3af | ||
|
|
7989518eec | ||
|
|
0049a76565 | ||
|
|
4f3188c63f | ||
|
|
d387f198db | ||
|
|
4efabe9370 | ||
|
|
cf5ecd5a62 | ||
|
|
67113971d9 | ||
|
|
4086ba93ad | ||
|
|
dad4e3a2f4 | ||
|
|
2bfbf4acaf | ||
|
|
049e3a2646 | ||
|
|
bd83da3331 | ||
|
|
a82c8e88c2 | ||
|
|
1b0abf4643 | ||
|
|
7433a260db | ||
|
|
0d29968169 | ||
|
|
80972aa4ce | ||
|
|
68d31d4ac7 | ||
|
|
6ae1a12219 | ||
|
|
afa508cbed | ||
|
|
60f2d10a50 | ||
|
|
4c5273b8b2 | ||
|
|
6d7b30d77e | ||
|
|
a0a0ef2386 | ||
|
|
1e5c94c2c6 | ||
|
|
6234ff20f4 | ||
|
|
db49e962f4 | ||
|
|
c23cca950a | ||
|
|
9274ec6880 | ||
|
|
323c0c5ab7 | ||
|
|
e46eeb3e16 | ||
|
|
2a96056b8d | ||
|
|
89b4d0026a | ||
|
|
572e5d5f93 | ||
|
|
569c5644fa | ||
|
|
b5a61d949b | ||
|
|
2469a42d1d | ||
|
|
e632731c4e | ||
|
|
a5e67db604 | ||
|
|
013f4d55c2 | ||
|
|
8e8801b11c | ||
|
|
7f256e5d06 | ||
|
|
08e44f0656 | ||
|
|
eb31bfb78e | ||
|
|
32f2333ab6 | ||
|
|
73bce1d7d0 | ||
|
|
9620dcbc07 | ||
|
|
c8b19bd84d | ||
|
|
c5bda4d2ff | ||
|
|
9fbf8d6aee | ||
|
|
ab2bb8176c | ||
|
|
719aa67afa | ||
|
|
4942ba9951 | ||
|
|
1f49985d26 | ||
|
|
2f444ead34 | ||
|
|
b6e7e84d50 | ||
|
|
e3b5919e40 | ||
|
|
34bd0fe93d | ||
|
|
bf064fa5c9 | ||
|
|
e2840a1c84 | ||
|
|
e2a63c10a1 | ||
|
|
889db82ad4 | ||
|
|
461c12c7fe | ||
|
|
c8b5301267 | ||
|
|
8c3cfa877b | ||
|
|
bde7fe21c4 | ||
|
|
cbeb4984ab | ||
|
|
8048d24857 | ||
|
|
3ed3ef9d71 | ||
|
|
04066af992 | ||
|
|
66893be752 | ||
|
|
9a1f54baf1 | ||
|
|
5fe2102e26 | ||
|
|
03f8e20dfd | ||
|
|
dfa35e0a0b | ||
|
|
2d6974da82 | ||
|
|
9195c813db | ||
|
|
d35b438255 | ||
|
|
a7e1e5f25f | ||
|
|
088fd89294 | ||
|
|
d1b3ca5178 | ||
|
|
f9a32f6011 | ||
|
|
a174c6aca6 | ||
|
|
81e1d2ac82 | ||
|
|
40be56b35f | ||
|
|
99b8769151 | ||
|
|
3d2ff6b587 | ||
|
|
f0bfdf3a1b | ||
|
|
455b9ca5df | ||
|
|
2eed73395a | ||
|
|
19123257fc | ||
|
|
6a4f3d6a6b | ||
|
|
120b788343 | ||
|
|
ce76fb58b1 | ||
|
|
f54aa1f4c2 | ||
|
|
c57da64feb | ||
|
|
c772844bcb | ||
|
|
5a2dc60420 | ||
|
|
61d6b435b3 | ||
|
|
60e924b62b | ||
|
|
fe4028f588 | ||
|
|
f6dc7b2068 | ||
|
|
c2e0ef3f98 | ||
|
|
a2b1941a47 | ||
|
|
61e5014ff0 | ||
|
|
080116cff8 | ||
|
|
641a4af8d4 | ||
|
|
c08f2e65e6 | ||
|
|
28252f693b | ||
|
|
0d0cacbf87 | ||
|
|
766cc7fcfa | ||
|
|
5ff8ac46aa | ||
|
|
868e487a59 | ||
|
|
28044662f4 | ||
|
|
8cd4060334 | ||
|
|
e9ef5b5eac | ||
|
|
027dafe906 | ||
|
|
e7c82a28dd | ||
|
|
305dafe2ad | ||
|
|
1fdaaeef71 | ||
|
|
d7f87051d6 | ||
|
|
8093f034c0 | ||
|
|
a3f14220d3 | ||
|
|
b1f3fdce7a | ||
|
|
f7904af0ed | ||
|
|
837422145d | ||
|
|
97df10e6b8 | ||
|
|
819ba45836 | ||
|
|
d1e9e7b396 | ||
|
|
e96c0b1127 | ||
|
|
f71eb5a169 | ||
|
|
5b90c433ab | ||
|
|
00d194d5d1 | ||
|
|
1fcc5eccb1 | ||
|
|
f3c183b563 | ||
|
|
ffe029932d | ||
|
|
f78cb09694 | ||
|
|
84f5842c81 | ||
|
|
e0f6b8c105 | ||
|
|
fb5d2fc25c | ||
|
|
fb68ed377e | ||
|
|
cecc6dada2 | ||
|
|
1c7019a04d | ||
|
|
939f974d19 | ||
|
|
019fa11616 | ||
|
|
b29e2c1808 | ||
|
|
90e9e5e7fd | ||
|
|
00973d4a14 | ||
|
|
c635846dba | ||
|
|
b65a8c86c5 | ||
|
|
f260836100 | ||
|
|
833e3f8ce1 | ||
|
|
f27ccf4212 | ||
|
|
51cbc653af | ||
|
|
e2555e2448 | ||
|
|
be3743e227 | ||
|
|
185b206076 | ||
|
|
f86a5b5fcd | ||
|
|
83071d85e3 | ||
|
|
65337bc9e9 | ||
|
|
e7b9ba70b6 | ||
|
|
dee4f5c517 | ||
|
|
788abad42e | ||
|
|
d0aedf33ba | ||
|
|
0316710ee4 | ||
|
|
e7e8c53d89 | ||
|
|
d64effc74f | ||
|
|
98bdcfe04e | ||
|
|
e9b595bdaa | ||
|
|
d163217d1f | ||
|
|
a1718e8a44 | ||
|
|
09e0e948b5 | ||
|
|
3f98cef633 | ||
|
|
a99ade13c0 | ||
|
|
742fc98cfc | ||
|
|
fe87b754c7 | ||
|
|
444c68e07e | ||
|
|
44e4f1e05b | ||
|
|
64ad2211c7 | ||
|
|
e4673aaa50 | ||
|
|
4e60db0598 | ||
|
|
143c53cb20 | ||
|
|
9c372d7335 | ||
|
|
a2b532ed22 | ||
|
|
5c5bdac3d9 | ||
|
|
0fdfdcf09c | ||
|
|
b6eef3a886 | ||
|
|
02ab2b8a63 | ||
|
|
e17188e52c | ||
|
|
99a6d7044c | ||
|
|
c4ad832d67 | ||
|
|
d625eb6508 | ||
|
|
bc66efa1a5 | ||
|
|
bbafadcbf6 | ||
|
|
aa5e9d7b2d | ||
|
|
9ae9213a97 | ||
|
|
9821cc5e18 | ||
|
|
bb64bcc1c1 | ||
|
|
597885028a | ||
|
|
5debe2c13c | ||
|
|
b6621f5e87 | ||
|
|
39fcbe4d76 | ||
|
|
4bc44183b6 | ||
|
|
ee2e8fcc23 | ||
|
|
99abb200d6 | ||
|
|
5542e5b04c | ||
|
|
29babb6015 | ||
|
|
367eb4f6d7 | ||
|
|
4210fdcb27 | ||
|
|
4d2e5c3e94 | ||
|
|
778751160b | ||
|
|
e9ef7c0ecd | ||
|
|
9ff918ac6b | ||
|
|
88a19f4bd3 | ||
|
|
6a44e82e33 | ||
|
|
9d9f96ac1e | ||
|
|
0a4b582dd9 | ||
|
|
4250234231 | ||
|
|
75c029c462 | ||
|
|
910c033d2a | ||
|
|
20bee94175 | ||
|
|
bf194c30e9 | ||
|
|
d5dd83ea91 | ||
|
|
62038bd2a9 | ||
|
|
0716c430d6 | ||
|
|
96bce27642 | ||
|
|
fd8d4f4a1b | ||
|
|
92778baf99 | ||
|
|
380a45f987 | ||
|
|
3ffc31ba58 | ||
|
|
f41d388bf5 | ||
|
|
8f3f00fe00 | ||
|
|
575b882b30 | ||
|
|
77fccc950a | ||
|
|
0733909d04 | ||
|
|
702710aab1 | ||
|
|
2fc22998cf | ||
|
|
f93099cbb6 | ||
|
|
608f1532a6 | ||
|
|
a247cc614f | ||
|
|
17d7bcca78 | ||
|
|
4100a3acdc | ||
|
|
25f46908b6 | ||
|
|
9cec0f5907 | ||
|
|
765dca4a3e | ||
|
|
5775bef5b5 | ||
|
|
7536818ec2 | ||
|
|
214c3e96a7 | ||
|
|
7982a49332 | ||
|
|
5d89feec61 | ||
|
|
b085b78d69 | ||
|
|
8ddbde62c1 | ||
|
|
9b2db20b3b | ||
|
|
22b00c019d | ||
|
|
d898ccd8e0 | ||
|
|
a0d1a12f1e | ||
|
|
6d82f04493 | ||
|
|
8209615609 | ||
|
|
5958577a6e | ||
|
|
de39663095 | ||
|
|
00eeebbf04 | ||
|
|
1313a30563 | ||
|
|
11caeaba11 | ||
|
|
36f59375da | ||
|
|
b4e0fe0735 | ||
|
|
c1f567a9ef | ||
|
|
f7db9f8ab2 | ||
|
|
b271a98867 | ||
|
|
0bb3aa6c9d | ||
|
|
49c0c2d996 | ||
|
|
3a2d6da052 | ||
|
|
643b2f1bb4 | ||
|
|
5a9525fb89 | ||
|
|
5e75fca33f | ||
|
|
187bf0165d | ||
|
|
f794eea2e6 | ||
|
|
81fc5ee6b3 | ||
|
|
d851733f6a | ||
|
|
57bbf346aa | ||
|
|
62f63923c1 | ||
|
|
01f4805bef | ||
|
|
00f3902cc4 | ||
|
|
7b38e8cdf6 | ||
|
|
737a0ab6d7 | ||
|
|
da9133ae85 | ||
|
|
4cee87273c | ||
|
|
676709e706 | ||
|
|
68df98eaaa | ||
|
|
77e8ffe500 | ||
|
|
5236b48f34 | ||
|
|
018e4fe54b | ||
|
|
120216f715 | ||
|
|
0c99ceda92 | ||
|
|
7a9b25889b | ||
|
|
4bb44ad315 | ||
|
|
b83a215885 | ||
|
|
6c10a75c6a | ||
|
|
b6321a8bed | ||
|
|
3e69d8203e | ||
|
|
73c144a75f | ||
|
|
052d35fe59 | ||
|
|
55985c47d4 | ||
|
|
ae2100244f | ||
|
|
207941558f | ||
|
|
3281b8bbd4 | ||
|
|
0bae88311e | ||
|
|
3da4b01f4e | ||
|
|
a55b149d6f | ||
|
|
a883d3728f | ||
|
|
2fbc40e966 | ||
|
|
5c6caf5603 | ||
|
|
2bb8854f61 | ||
|
|
1e0d8d2efb | ||
|
|
146793c42c | ||
|
|
91f16121d1 | ||
|
|
5d32ddb408 | ||
|
|
e5271dbda9 | ||
|
|
6c2fbec22b | ||
|
|
0c1222f0f5 | ||
|
|
fbbb150e86 | ||
|
|
2117fa53f0 | ||
|
|
1d3bd4e26a | ||
|
|
e0b9f57d85 | ||
|
|
f6c586986d | ||
|
|
757a3f8651 | ||
|
|
637a97c4de | ||
|
|
f8bd3299a3 | ||
|
|
6c1e2ae537 | ||
|
|
1b490233b6 | ||
|
|
d511c2d2ad | ||
|
|
65b4c9f931 | ||
|
|
b1197d581a | ||
|
|
c634d12ef4 | ||
|
|
b79fe964b8 | ||
|
|
d45464c6c8 | ||
|
|
6ac4d4f490 | ||
|
|
1fbcedec42 | ||
|
|
f1429e8155 | ||
|
|
7880adda3f | ||
|
|
c97fea37c0 | ||
|
|
09830aecb3 | ||
|
|
4150895401 | ||
|
|
7d5f68a7bf | ||
|
|
84da2eee8c | ||
|
|
eeb6ee0821 | ||
|
|
912933d4c8 | ||
|
|
c1df8cc5ea | ||
|
|
258ba6e7b4 | ||
|
|
df651c3c78 | ||
|
|
b176929557 | ||
|
|
fe0bd6a66e | ||
|
|
0471bf1c90 | ||
|
|
73d0f033b8 | ||
|
|
e5b9e261c5 | ||
|
|
6c81069b27 | ||
|
|
471f12b8ba | ||
|
|
5e1bb655dc | ||
|
|
69d42fc980 | ||
|
|
3f9a3f72c8 | ||
|
|
4a4cf77fd6 | ||
|
|
029fddceb0 | ||
|
|
8cc768cc50 | ||
|
|
85cd2c1472 | ||
|
|
b575df867e | ||
|
|
8c6ef5b26f | ||
|
|
895e12c29b | ||
|
|
4969ceadd4 | ||
|
|
7b0b0ef91a | ||
|
|
80ce2b2464 | ||
|
|
ef65503173 | ||
|
|
adf4465c27 | ||
|
|
3bbbca87d6 | ||
|
|
651ccef328 | ||
|
|
44cc33284f | ||
|
|
384543c553 | ||
|
|
69846749bf | ||
|
|
9524b06d26 | ||
|
|
0533339023 | ||
|
|
582b95915a | ||
|
|
46c91899e7 | ||
|
|
e850be40b2 | ||
|
|
54d14a5151 | ||
|
|
0159079608 | ||
|
|
c38f451c78 | ||
|
|
426f50c69c | ||
|
|
5e6b8ec506 | ||
|
|
5bde5bee16 | ||
|
|
aceb631aac | ||
|
|
a3b75a374b | ||
|
|
63a4853563 | ||
|
|
318bea760e | ||
|
|
eb818d15e0 | ||
|
|
6e4bad5969 | ||
|
|
7abb426e3f | ||
|
|
c2d3544c55 | ||
|
|
ba0be549e3 | ||
|
|
9caeb62695 | ||
|
|
f39ed010c7 | ||
|
|
792d22cf74 | ||
|
|
039dde600f | ||
|
|
0eb5a1c61f | ||
|
|
aee87e3d01 | ||
|
|
f3fac22bab | ||
|
|
b99f5b0663 | ||
|
|
8cd36b2721 | ||
|
|
1bd55c8d69 | ||
|
|
7c3732e277 | ||
|
|
5f0f136e6e | ||
|
|
5bd420a823 | ||
|
|
a5b31b1956 | ||
|
|
f7e5ccf296 | ||
|
|
7f236ed2bb | ||
|
|
80502ff937 | ||
|
|
2956d5f3eb | ||
|
|
398c3eec29 | ||
|
|
2d1cd7cb50 | ||
|
|
fea3580c1c | ||
|
|
a0910a7f58 | ||
|
|
f2af301f16 | ||
|
|
734561c6b6 | ||
|
|
a2778e410f | ||
|
|
76614d8659 | ||
|
|
87cc201811 | ||
|
|
e4d186b9a7 | ||
|
|
de0b88737e | ||
|
|
0ddd2f6c77 | ||
|
|
067858ae10 | ||
|
|
ec46fae228 | ||
|
|
4de148f783 | ||
|
|
8af0983274 | ||
|
|
6aee9e0ddb | ||
|
|
390453aa9b | ||
|
|
4d21323001 | ||
|
|
7167ea2a5f | ||
|
|
b69217bb39 | ||
|
|
6a1dcfa3c8 | ||
|
|
b217cf20d7 | ||
|
|
0389cc9ef2 | ||
|
|
9f008a18ea | ||
|
|
378d02f269 | ||
|
|
6fbbc2bea5 | ||
|
|
6c9ac29f37 | ||
|
|
e2f86aec62 | ||
|
|
144e723431 | ||
|
|
5cefda4ff2 | ||
|
|
e8b63f8c9d | ||
|
|
3166be9da0 | ||
|
|
6d9f43be5d | ||
|
|
fdc5fea887 | ||
|
|
92e3c79812 | ||
|
|
fb55f8cdd1 | ||
|
|
cde374b505 | ||
|
|
478a92a881 | ||
|
|
85ce646e0c | ||
|
|
ef4ffcf3a2 | ||
|
|
bf533be331 | ||
|
|
81aa611c74 | ||
|
|
66ffe200d8 | ||
|
|
c33a5580fe | ||
|
|
7ec9aef2ed | ||
|
|
5b240a867e | ||
|
|
304e72c7e2 | ||
|
|
8fe71531d6 | ||
|
|
2badad6da5 | ||
|
|
395beb39bc | ||
|
|
e5cae42356 | ||
|
|
fe82f05462 | ||
|
|
c9245748bf | ||
|
|
67c7250b3c | ||
|
|
29e5acb29e | ||
|
|
78f6748fb0 | ||
|
|
7c797515e6 | ||
|
|
044078f7e3 | ||
|
|
eb50137ee2 | ||
|
|
9b5db34669 | ||
|
|
9e427d7833 | ||
|
|
96134f2a5e | ||
|
|
544413dddf | ||
|
|
bf0b416183 | ||
|
|
fbcaa1f60a | ||
|
|
fd37df5f9c | ||
|
|
48ede78894 | ||
|
|
e9f75d32a1 | ||
|
|
2f08ec0926 | ||
|
|
01f77fa993 | ||
|
|
98747c949d | ||
|
|
e80e65946b | ||
|
|
89afad1a32 | ||
|
|
e31c7f7740 | ||
|
|
c42bf2d8da | ||
|
|
a983e792d8 | ||
|
|
215c02ac47 | ||
|
|
6e8a4cc97b | ||
|
|
360054f500 | ||
|
|
fd9d300654 | ||
|
|
ca318c1842 | ||
|
|
82141b7387 | ||
|
|
ffe525c5cc | ||
|
|
12b6e671b0 | ||
|
|
9985af45fb | ||
|
|
81ddc99f7b | ||
|
|
00a4929301 | ||
|
|
9ad270ec14 | ||
|
|
19eb6c7e50 | ||
|
|
e04b9fc136 | ||
|
|
ed79d4dffb | ||
|
|
6c9e805682 | ||
|
|
939b5a00f5 | ||
|
|
95d322dc36 | ||
|
|
192617c9ac | ||
|
|
5fb6d2fa6a | ||
|
|
fab87c7f00 | ||
|
|
0e2e7395a9 | ||
|
|
5fd0de842f | ||
|
|
6d5aec39fe | ||
|
|
b0d6e905f1 | ||
|
|
c20d46fef3 | ||
|
|
700d7bc6aa | ||
|
|
3b39b3a88d | ||
|
|
6c220467b8 | ||
|
|
9893bc166a | ||
|
|
a593d75404 | ||
|
|
d039671ffb | ||
|
|
776dc84475 | ||
|
|
fca8e0ebee | ||
|
|
c1c9ebd381 | ||
|
|
c9904f38d3 | ||
|
|
64926c7f4c | ||
|
|
4c3b477b44 | ||
|
|
d851115701 | ||
|
|
bdd67b0127 | ||
|
|
e7e1852b21 | ||
|
|
89f18f36b5 | ||
|
|
620d864d17 | ||
|
|
0b767c15a6 | ||
|
|
587a8b5b7b | ||
|
|
78cd9ea71a | ||
|
|
af00eeaf9a | ||
|
|
13fa712cc5 | ||
|
|
9fe4a295ff | ||
|
|
43b02c6f13 | ||
|
|
bb2b46cdc1 | ||
|
|
3df89e65ca | ||
|
|
90af882aa6 | ||
|
|
9c59deca1d | ||
|
|
d0c3f52ab6 | ||
|
|
1043cc4fc1 | ||
|
|
4a73b26821 | ||
|
|
f97280d071 | ||
|
|
d834091524 | ||
|
|
0e7f9cf2ad | ||
|
|
a4536d45a1 | ||
|
|
edbe0bd0e6 | ||
|
|
dab4c004d3 | ||
|
|
9f07cb0cc4 | ||
|
|
f388f3801a | ||
|
|
e416f9b017 | ||
|
|
ef144dbc24 | ||
|
|
5e5ca0ba16 | ||
|
|
d380cb8c47 | ||
|
|
5d6fff860e | ||
|
|
d28afc21a6 | ||
|
|
ce42915ce5 | ||
|
|
696c4bfde3 | ||
|
|
908ba51144 | ||
|
|
3da21ff2fc | ||
|
|
cdb54bcc72 | ||
|
|
832e42c881 | ||
|
|
3af85b7e82 | ||
|
|
98c8896588 | ||
|
|
f4c77a558f | ||
|
|
626e4b1b8a | ||
|
|
b72dcdeeb4 | ||
|
|
182db2b2e3 | ||
|
|
041066520f | ||
|
|
421db5b224 | ||
|
|
948049f944 | ||
|
|
2da7da0558 | ||
|
|
4260d01bb9 | ||
|
|
937674a373 | ||
|
|
183bb2e92d | ||
|
|
7a2fcf4165 | ||
|
|
8f67743834 | ||
|
|
717abccd10 | ||
|
|
eca5aa3c2b | ||
|
|
1af709ea64 | ||
|
|
0eb994e625 | ||
|
|
0123bba1fb | ||
|
|
4f2c46ac36 | ||
|
|
105e5fe71c | ||
|
|
20bbbfc493 | ||
|
|
640b5abe1e | ||
|
|
9fd869d917 | ||
|
|
554be364d6 | ||
|
|
70fe8cdd77 | ||
|
|
b98bbf482a | ||
|
|
efbe21d433 | ||
|
|
7d3101e077 | ||
|
|
6544ab4872 | ||
|
|
8b0aa634ec | ||
|
|
ae0919a3d5 | ||
|
|
f6868318e0 | ||
|
|
f0626d2ee4 | ||
|
|
59f33cbb51 | ||
|
|
6fc7f9b384 | ||
|
|
8688120df1 | ||
|
|
25fc9c2dc4 | ||
|
|
6938490328 | ||
|
|
53e6a3e312 | ||
|
|
381a26fa4c | ||
|
|
5ef11b44bf | ||
|
|
c5ee4d7eaf | ||
|
|
9806b395f1 | ||
|
|
75c4662c0b | ||
|
|
1d382cc542 | ||
|
|
b86f7689ec | ||
|
|
a43d03d8aa | ||
|
|
c4e5ff4c94 | ||
|
|
9cea1270c3 | ||
|
|
9269fb0dfe | ||
|
|
2cbd4d6cc5 | ||
|
|
36b45fd8f4 | ||
|
|
38b39a881e | ||
|
|
c2dcd507b8 | ||
|
|
7f0d4e6c36 | ||
|
|
1d7c25354a | ||
|
|
79b57cf696 | ||
|
|
022e992041 | ||
|
|
22038daa0e | ||
|
|
e6432df04b | ||
|
|
35fc397ecc | ||
|
|
e0267da7ac | ||
|
|
a7ef1367b8 | ||
|
|
75e80a210c | ||
|
|
704fd8693c | ||
|
|
184b1f2ddb | ||
|
|
ec5a0b8cde | ||
|
|
eb2f949175 | ||
|
|
ecf5ba26ca | ||
|
|
953d338966 | ||
|
|
7aeedb90bf | ||
|
|
ca6ee798d6 | ||
|
|
4bdad25a12 | ||
|
|
f10914156f | ||
|
|
1a1f33ef57 | ||
|
|
d6fa54bd1e | ||
|
|
a1194d91f3 | ||
|
|
4eedd443a5 | ||
|
|
20ca55f51e | ||
|
|
85b714857f | ||
|
|
a315f43039 | ||
|
|
07d65a3232 | ||
|
|
40781d45a0 | ||
|
|
31241f5d12 | ||
|
|
ce73b44ccf | ||
|
|
364da9d2f0 | ||
|
|
0124d2c121 | ||
|
|
3abd8bc565 | ||
|
|
34dda74176 | ||
|
|
6d235e7531 | ||
|
|
8b864324de | ||
|
|
319e5ac034 | ||
|
|
a9a1e849d3 | ||
|
|
71206a9886 | ||
|
|
0ce6deedbe | ||
|
|
526705d440 | ||
|
|
c8ac990436 | ||
|
|
668ebe4f41 | ||
|
|
21d40f4849 | ||
|
|
0d016dea64 | ||
|
|
48b3d82518 | ||
|
|
dc36f05ee7 | ||
|
|
b757951f9c | ||
|
|
b6495ff006 | ||
|
|
963ce8198e | ||
|
|
3f568f732e | ||
|
|
ef7f919fa1 | ||
|
|
947e4190d5 | ||
|
|
edb4ca6a63 | ||
|
|
e0a58a5c98 | ||
|
|
cd058633f5 | ||
|
|
016de02356 | ||
|
|
f494ec488f | ||
|
|
09e5e06614 | ||
|
|
910c37729d | ||
|
|
a698dfde8b | ||
|
|
19a5945730 | ||
|
|
16024e0020 | ||
|
|
10b0277120 | ||
|
|
f28248037c | ||
|
|
2ef98752dc | ||
|
|
101e6cb539 | ||
|
|
9c0da69dbb | ||
|
|
d865b5adee | ||
|
|
4809520462 | ||
|
|
3e23759e69 | ||
|
|
4cd0f4ea1e | ||
|
|
4822e326f2 | ||
|
|
b837fe7233 | ||
|
|
cb50220b60 | ||
|
|
98724c2acd | ||
|
|
a707fa0385 | ||
|
|
a059ec11e7 | ||
|
|
6d17d7f0f6 | ||
|
|
b41aaefc7e | ||
|
|
fd6a3ed30c | ||
|
|
87e3916568 | ||
|
|
3beacf0263 | ||
|
|
03fade9501 | ||
|
|
0cd1da0680 | ||
|
|
9fc800c45a | ||
|
|
0a9a84e357 | ||
|
|
6ac236ed22 | ||
|
|
3e34e42c93 | ||
|
|
aa2a6d7f11 | ||
|
|
0848815485 | ||
|
|
4f257f9e7e | ||
|
|
7132e3a7f6 | ||
|
|
ddd15921a3 | ||
|
|
256f83b7ea | ||
|
|
b19b9d450f | ||
|
|
6507bf9fad | ||
|
|
812cd9be76 | ||
|
|
9665d3a106 | ||
|
|
342c4a3253 | ||
|
|
5652e2d94f | ||
|
|
834e4e03e3 | ||
|
|
7d3829e35f | ||
|
|
7207d6ab7a | ||
|
|
ed81ff1263 | ||
|
|
f46327d206 | ||
|
|
cf1c9452c1 | ||
|
|
58ead53492 | ||
|
|
8450a0c213 | ||
|
|
15939bb155 | ||
|
|
14164e7a19 | ||
|
|
83f238867f | ||
|
|
119344031a | ||
|
|
3e8da28e70 | ||
|
|
28100545b5 | ||
|
|
fafef03512 | ||
|
|
801898a9e2 | ||
|
|
349833573f | ||
|
|
e4c1188498 | ||
|
|
33ab9ada96 | ||
|
|
0892669566 | ||
|
|
a9124e45b2 | ||
|
|
6ac2493445 | ||
|
|
30e18d43a2 | ||
|
|
b271a5349b | ||
|
|
f7b7a43f7d | ||
|
|
53d6961529 | ||
|
|
f01a473b4e | ||
|
|
000f377845 | ||
|
|
72068d5a93 | ||
|
|
c442a5bce2 | ||
|
|
61a852affd | ||
|
|
c22c28bc67 | ||
|
|
01fb4045bd | ||
|
|
1f17fb34e4 | ||
|
|
0895a25615 | ||
|
|
e23752d4f3 | ||
|
|
80e60d9d95 | ||
|
|
b4b3cc6e26 | ||
|
|
6918e8fdd7 | ||
|
|
e2444734fd | ||
|
|
ee80bf7ca7 | ||
|
|
d4d20961e3 | ||
|
|
e9c29a4812 | ||
|
|
9665ec09e7 | ||
|
|
80d57521aa | ||
|
|
5eaabe316c | ||
|
|
af1d1dd7a2 | ||
|
|
f18707c3a0 | ||
|
|
b7e9d4894e | ||
|
|
90e05fec87 | ||
|
|
0fd027b957 | ||
|
|
4acd4a03d3 | ||
|
|
a342a77caf | ||
|
|
50d20f7e84 | ||
|
|
e24d789510 | ||
|
|
fb3ba9a475 | ||
|
|
129c69ddac | ||
|
|
cb8688a8c3 | ||
|
|
eb0b33bfcd | ||
|
|
1d667dadad | ||
|
|
4604578864 | ||
|
|
4c310a4c36 | ||
|
|
2634c5ae19 | ||
|
|
97b261284f | ||
|
|
85bf2f77e0 | ||
|
|
2f2f96d2f3 | ||
|
|
bdb64031f3 | ||
|
|
a08c6f8235 | ||
|
|
51f2cb8a64 | ||
|
|
780134d18d | ||
|
|
0fcdc800e1 | ||
|
|
ecb8717672 | ||
|
|
858d1a7d66 | ||
|
|
c4496e6d43 | ||
|
|
4590c464ef | ||
|
|
477dbbb909 | ||
|
|
95c5d7eb39 | ||
|
|
39434c3fec | ||
|
|
d5d5c95507 | ||
|
|
713ca7d55a | ||
|
|
e8a8069a67 | ||
|
|
02f54eca65 | ||
|
|
27af4e3b7c | ||
|
|
5a7655ce06 | ||
|
|
5b1a512e1e | ||
|
|
3ad6ca0992 | ||
|
|
12ee240695 | ||
|
|
9113df9ba3 | ||
|
|
cc124bf211 | ||
|
|
c63feef171 | ||
|
|
a04a968def | ||
|
|
3b6098b5e8 | ||
|
|
5294f86595 | ||
|
|
8bc570b0a5 | ||
|
|
7105e6c3e8 | ||
|
|
ea544efba8 | ||
|
|
9e0e2f8e77 | ||
|
|
05c9640658 | ||
|
|
8ed9ed853d | ||
|
|
04b9311190 | ||
|
|
37530d30df | ||
|
|
ea51786192 | ||
|
|
c7780c5fe6 | ||
|
|
1362902082 | ||
|
|
e55faff0a1 | ||
|
|
a861b174a2 | ||
|
|
5c08e066aa | ||
|
|
34b3218d83 | ||
|
|
614985c837 | ||
|
|
ccabbc4fdf | ||
|
|
6df57b7975 | ||
|
|
d5028e601e | ||
|
|
fed981681a | ||
|
|
bcd05bf5a7 | ||
|
|
683a691bf9 | ||
|
|
06603e53ca | ||
|
|
eabc90ec49 | ||
|
|
db715f3e7d | ||
|
|
f63c740917 | ||
|
|
2c9b749f72 | ||
|
|
5ea0816540 | ||
|
|
5d16c39d5f | ||
|
|
8e1dc84a48 | ||
|
|
29d448c690 | ||
|
|
f0c61bc4a7 | ||
|
|
b6b2961d4e | ||
|
|
4b5747c730 | ||
|
|
1896193718 | ||
|
|
f4f178d3bf | ||
|
|
8639091d06 | ||
|
|
ae2826ad1f | ||
|
|
a847a37a83 | ||
|
|
78daf314bd | ||
|
|
ddcb68c944 | ||
|
|
c3d04848f4 | ||
|
|
c5c3b2ecf0 | ||
|
|
d0c8563fbf | ||
|
|
687ba70f77 | ||
|
|
0d26185c38 | ||
|
|
6b26e36c17 | ||
|
|
73bae25a3f | ||
|
|
26b6584a39 | ||
|
|
4e3bfc81b6 | ||
|
|
272c62e893 | ||
|
|
0ce1e5fa51 | ||
|
|
3bb836a101 | ||
|
|
646bbdf489 | ||
|
|
4ee3385f53 | ||
|
|
007ae1fcfc | ||
|
|
ebdef8afa7 | ||
|
|
2386381d03 | ||
|
|
d9c6f892c7 | ||
|
|
eb1fdcec73 | ||
|
|
c47e76fd50 | ||
|
|
432bfe96ac | ||
|
|
214018be4b | ||
|
|
b16053bbdf | ||
|
|
efa4824db6 | ||
|
|
e5a9195e66 | ||
|
|
9f5ae4d628 | ||
|
|
6cd1b94935 | ||
|
|
a0c0a7439e | ||
|
|
2e00fd5d4f | ||
|
|
a85a471d3b | ||
|
|
6600f08059 | ||
|
|
07b62cfbd3 | ||
|
|
505dd333c4 | ||
|
|
5900387237 | ||
|
|
8fe365d66e | ||
|
|
94ad09b53f | ||
|
|
f7da33a873 | ||
|
|
dab844308b | ||
|
|
b9a26efa80 | ||
|
|
dccb9b7291 | ||
|
|
0438e830b0 | ||
|
|
d5997d92c3 | ||
|
|
7b5cee8da9 | ||
|
|
d647bdaf9e | ||
|
|
67f38d0644 | ||
|
|
4bc022736c | ||
|
|
0129209eb0 | ||
|
|
39a39a8603 | ||
|
|
3ba8ee4ec8 | ||
|
|
b2023020e0 | ||
|
|
5e6c288c2b | ||
|
|
5a58312b53 | ||
|
|
bc786cdb86 | ||
|
|
0897cd0fe4 | ||
|
|
3fc2e70007 | ||
|
|
054516d5c5 | ||
|
|
7ea521665c | ||
|
|
e16cbe6a11 | ||
|
|
d1b5c69ba1 | ||
|
|
8fcb091f37 | ||
|
|
ce28dd8201 | ||
|
|
8da011ad78 | ||
|
|
1273f4c9b7 | ||
|
|
d2ec87de2c | ||
|
|
e4a4b0165f | ||
|
|
0a3a69b7dc | ||
|
|
69ed468ec3 | ||
|
|
b17f537a4c | ||
|
|
02b0ed118f | ||
|
|
594b66c18a | ||
|
|
bc7ec3854f | ||
|
|
63f95c0a5c | ||
|
|
77c72970e8 | ||
|
|
4ba8e725ea | ||
|
|
e54125a6f5 | ||
|
|
b1cb6ab9bc | ||
|
|
190d9684f0 | ||
|
|
1d4866c5b5 | ||
|
|
b9148d380b | ||
|
|
a87724ed1a | ||
|
|
3ee3954043 | ||
|
|
f6a0949d14 | ||
|
|
fe8295c2ec | ||
|
|
422cedf718 | ||
|
|
9b3e333134 | ||
|
|
dcdfcce3fe | ||
|
|
2d1741504a | ||
|
|
5fa7d34757 | ||
|
|
faaf233153 | ||
|
|
7591f68970 | ||
|
|
6c04550558 | ||
|
|
5666002ff2 | ||
|
|
cee77ef5a1 | ||
|
|
67d46e8d05 | ||
|
|
97a957e17a | ||
|
|
69066cfc5e | ||
|
|
1e95346205 | ||
|
|
574a993f59 | ||
|
|
5785212d28 | ||
|
|
63c02677a0 | ||
|
|
b96b2ea2fb | ||
|
|
3da3e2100c | ||
|
|
22f0468c8e | ||
|
|
691b9ef417 | ||
|
|
f4d916a4d2 | ||
|
|
c58dd9f741 | ||
|
|
fc3376cbe4 | ||
|
|
f0bce98d91 | ||
|
|
8c5948950d | ||
|
|
e6ccfb6dd7 | ||
|
|
177354407a | ||
|
|
b8f6393df6 | ||
|
|
2b3dc48bf1 | ||
|
|
2534e9ad8f | ||
|
|
087b7df52a | ||
|
|
6df1d35712 | ||
|
|
677121edca | ||
|
|
471cac8187 | ||
|
|
ce334b9518 | ||
|
|
8620b1f3a0 | ||
|
|
e5a63a5078 | ||
|
|
8ec8843a09 | ||
|
|
6cf3cdcadb | ||
|
|
4b50fe3f15 | ||
|
|
ce5c8d6817 | ||
|
|
3e2a7e2941 | ||
|
|
883207240c | ||
|
|
92783527c2 | ||
|
|
001e12b274 | ||
|
|
6039ee0b0f | ||
|
|
f50cd14bd8 | ||
|
|
7a2f90b5bf | ||
|
|
32a8232689 | ||
|
|
848c36aef4 | ||
|
|
938f8394e0 | ||
|
|
3f9bbd5fc7 | ||
|
|
583de3a505 | ||
|
|
60c05fad25 | ||
|
|
ad0e856e1b | ||
|
|
cdbfe4ae10 | ||
|
|
1974c422ce | ||
|
|
3da244b3dd | ||
|
|
f36fccfdea | ||
|
|
f54b6b2ebc | ||
|
|
47db8f5bf1 | ||
|
|
79eb351381 | ||
|
|
99b95cfb74 | ||
|
|
3ab275201a | ||
|
|
56282fb2fa | ||
|
|
951ed70fb0 | ||
|
|
889eaff418 | ||
|
|
6b5a0cbd4f | ||
|
|
6db0e3ac3c | ||
|
|
ccfd104545 | ||
|
|
4756b44cbe | ||
|
|
74969ed606 | ||
|
|
90dae04fb7 | ||
|
|
33152c7bb1 | ||
|
|
98cdb38c49 | ||
|
|
9a1ece5f13 | ||
|
|
aa96ede848 | ||
|
|
3a5db3e45f | ||
|
|
44970cc628 | ||
|
|
793fbff7f5 | ||
|
|
f6e7d00b1e | ||
|
|
8cacf91e09 | ||
|
|
25d9c8e501 | ||
|
|
6dade9e758 | ||
|
|
cab456aaff | ||
|
|
d97c3a09ca | ||
|
|
0389be522e | ||
|
|
55450a5ebc | ||
|
|
7f007c2ff4 | ||
|
|
8282a9769d | ||
|
|
a16cd37f1a | ||
|
|
c531213a5f | ||
|
|
82bea82d94 | ||
|
|
790276fced | ||
|
|
d78795e7bd | ||
|
|
dca0b211f6 | ||
|
|
bbeeb356ab | ||
|
|
3b7ac045d7 | ||
|
|
7f0507473d | ||
|
|
281eb2fafe | ||
|
|
a679f2f767 | ||
|
|
f904698d66 | ||
|
|
a320e220c3 | ||
|
|
57048400dc | ||
|
|
593c62116c | ||
|
|
f945ab79ec | ||
|
|
8307b2148f | ||
|
|
8e26f93211 | ||
|
|
60a4563908 | ||
|
|
78f65401e8 | ||
|
|
31bd5cbeda | ||
|
|
e25fbd1c2a | ||
|
|
b12fc12caa | ||
|
|
9ccffe9af2 | ||
|
|
6dcfbe2e17 | ||
|
|
51e6d024ab | ||
|
|
e6e126dff4 | ||
|
|
a373297a6b | ||
|
|
bc73b7bbf0 | ||
|
|
e44efb43d1 | ||
|
|
9d440552bd | ||
|
|
c85363f355 | ||
|
|
ddabb540c1 | ||
|
|
6a22768c9c | ||
|
|
035e72edf7 | ||
|
|
f15f8e7bf0 | ||
|
|
8513bb2dbe | ||
|
|
fd1068a600 | ||
|
|
7159f9c187 | ||
|
|
c6e614d896 | ||
|
|
064d538023 | ||
|
|
c30189f903 | ||
|
|
deb5164eb3 | ||
|
|
26a2569b8e | ||
|
|
c04734048f | ||
|
|
38ad6fe1cc | ||
|
|
10b8b4b4f6 | ||
|
|
292037f90b | ||
|
|
ec3c2628b1 | ||
|
|
9f3dd54198 | ||
|
|
e7298b13b0 | ||
|
|
625608804e | ||
|
|
3149a298a7 | ||
|
|
002b30d110 | ||
|
|
b434dfb6b8 | ||
|
|
756d0bbd5b | ||
|
|
fc38771d69 | ||
|
|
b1dfd94d49 | ||
|
|
65d09705ac | ||
|
|
55a2e52764 | ||
|
|
cb6a186c5d | ||
|
|
5e9acf59e4 | ||
|
|
b765ad603d | ||
|
|
4b01ff3f51 | ||
|
|
e0a1f4b8fe | ||
|
|
d4b270e527 | ||
|
|
0d3c1cba9a | ||
|
|
25590cc210 | ||
|
|
c3372e4a1f | ||
|
|
22f05b4cf8 | ||
|
|
4645fcd0c8 | ||
|
|
bd7395cb44 | ||
|
|
dabaa49f8d | ||
|
|
714bbc0af0 | ||
|
|
ea4b177b83 | ||
|
|
01a57d91ad | ||
|
|
7fb74027d4 | ||
|
|
11c8fccd88 | ||
|
|
531d57feff | ||
|
|
2d82eed4be | ||
|
|
b1348dbdd8 | ||
|
|
36903d6358 | ||
|
|
79560c13e6 | ||
|
|
e068bcdbc1 | ||
|
|
ee875a9320 | ||
|
|
94acd32d69 | ||
|
|
17e41eee1d | ||
|
|
a92f96bf80 | ||
|
|
1eee0baa28 | ||
|
|
6c971f6ece | ||
|
|
d1f6cb31d6 | ||
|
|
6fcd3757fe | ||
|
|
da73dc7fcb | ||
|
|
e780c90dd6 | ||
|
|
7784422f84 | ||
|
|
b24aec82e7 | ||
|
|
00441885d2 | ||
|
|
e989735e89 | ||
|
|
ad417731ce | ||
|
|
66ff297103 | ||
|
|
9881c4cc73 | ||
|
|
e09cb3a1cb | ||
|
|
464738274d | ||
|
|
cff0fd9476 | ||
|
|
0fd55cb13c | ||
|
|
3ceff5aa71 | ||
|
|
dd00c58b08 | ||
|
|
0304a57915 | ||
|
|
3fe22b7ed0 | ||
|
|
afa71bcaf3 | ||
|
|
228bedd6d0 | ||
|
|
b2b714f713 | ||
|
|
8841f8fe76 | ||
|
|
eb1476b28b | ||
|
|
fa165a735b | ||
|
|
0d57c67cc0 | ||
|
|
77958b465e | ||
|
|
6af467398b | ||
|
|
c5a8e470c5 | ||
|
|
2205ecd9c6 | ||
|
|
7952dac85e | ||
|
|
e70eb61717 | ||
|
|
2bde7aa02b | ||
|
|
146cb6a0d8 | ||
|
|
3890dea35b | ||
|
|
a5bc07c9e4 | ||
|
|
fe0b778fac | ||
|
|
f03510ef6b | ||
|
|
ca25d329ae | ||
|
|
c1cdad5566 | ||
|
|
926d1c6459 | ||
|
|
896f07fdd4 | ||
|
|
d4ba9441b6 | ||
|
|
0d81918685 | ||
|
|
7673fb44e5 | ||
|
|
dbd7afeda0 | ||
|
|
a904057a58 | ||
|
|
7895ac6c40 | ||
|
|
74a5bd2556 | ||
|
|
28d9e15980 | ||
|
|
30c157c23e | ||
|
|
e104f553cb | ||
|
|
f09cce93ee | ||
|
|
c9cae9479d | ||
|
|
2a750eaa73 | ||
|
|
61ab41bf86 | ||
|
|
278afd77c0 | ||
|
|
399df17a3d | ||
|
|
c1c3a7c7ae | ||
|
|
e4392bdbfb | ||
|
|
24875e352d | ||
|
|
cb6fc7f618 | ||
|
|
5f990ecb6d | ||
|
|
7019be987c | ||
|
|
9819088563 | ||
|
|
66539bbe3a | ||
|
|
27f69e3c09 | ||
|
|
f0d66985f7 | ||
|
|
753aebccab | ||
|
|
db9faebcc9 | ||
|
|
7c2ecaec4b | ||
|
|
e63e79d3f9 | ||
|
|
acd5dfc23f | ||
|
|
fdac3bfe4a | ||
|
|
3e43424129 | ||
|
|
b28d3ce302 | ||
|
|
4d482ebb81 | ||
|
|
bc9e928187 | ||
|
|
04844b180b | ||
|
|
c21b563614 | ||
|
|
70f0483f97 | ||
|
|
e425de9582 | ||
|
|
093a8be8a3 | ||
|
|
d1aa3fea4f | ||
|
|
a2af9fef2d | ||
|
|
ae5163c813 | ||
|
|
0abd8c4108 | ||
|
|
9fc2e4c3a5 | ||
|
|
5d99f7c3a6 | ||
|
|
cc9a166146 | ||
|
|
f52bfacf0e | ||
|
|
86ad8b7e98 | ||
|
|
837336e90f | ||
|
|
80ff661aeb | ||
|
|
73095a325e | ||
|
|
bb4390e089 | ||
|
|
4b062ca76d | ||
|
|
c0119e6a0d | ||
|
|
9cdb67a661 | ||
|
|
6b11fa26ce | ||
|
|
d4f02402bd | ||
|
|
fca655a48d | ||
|
|
0d97580361 | ||
|
|
e94c7eba83 | ||
|
|
7993e7e232 | ||
|
|
4b43847bd8 | ||
|
|
4729444622 | ||
|
|
a1e6f7bdf4 | ||
|
|
407c150631 | ||
|
|
9f3ebe9956 | ||
|
|
835ef0ffc1 | ||
|
|
a4c0d89d55 | ||
|
|
9c68cff317 | ||
|
|
1e404e379b | ||
|
|
88f6a67802 | ||
|
|
cc887beae9 | ||
|
|
2dc8fba40a | ||
|
|
163782d4cd | ||
|
|
f36f176849 | ||
|
|
3bc0aa8dca | ||
|
|
80dfa85c82 | ||
|
|
aacdf50f63 | ||
|
|
11ad9c8dd9 | ||
|
|
d2305a7900 | ||
|
|
4b0c707fd3 | ||
|
|
69a4f51d58 | ||
|
|
ccb8a1144c | ||
|
|
e0abc972ff | ||
|
|
7a1dfbfb8d | ||
|
|
1abd57567f | ||
|
|
4e7c976608 | ||
|
|
7ad7d1807e | ||
|
|
7d9f973c62 | ||
|
|
e658457b1b | ||
|
|
24609236b5 | ||
|
|
8056d02c0d | ||
|
|
f3b73f40f5 | ||
|
|
5d284b8c27 | ||
|
|
2814a6f680 | ||
|
|
3088535806 | ||
|
|
51cb03a93e | ||
|
|
b09aaabd14 | ||
|
|
1e96f8c3d9 | ||
|
|
e41de7575c | ||
|
|
32b1e0db51 | ||
|
|
381374209a | ||
|
|
ebb4d402b7 | ||
|
|
eaf9cd59e9 | ||
|
|
63977da7ee | ||
|
|
7626a0c121 | ||
|
|
24af94c569 | ||
|
|
de42895096 | ||
|
|
e3ec275a51 | ||
|
|
2202994060 | ||
|
|
44310876d2 | ||
|
|
f10cf58ec3 | ||
|
|
beaad6c49d | ||
|
|
923a741c5a | ||
|
|
3adca4588c | ||
|
|
17600deace | ||
|
|
1aa36b6b2c | ||
|
|
326220762e | ||
|
|
8f8f033d1a | ||
|
|
9a705ed44d | ||
|
|
5d84d6d43f | ||
|
|
bbf7f8d8c3 | ||
|
|
fd76332aca | ||
|
|
78b0b24c25 | ||
|
|
39231f8cf9 | ||
|
|
fb34ecfa7d | ||
|
|
6dd6436291 | ||
|
|
5b7b39a300 | ||
|
|
f672269a8c | ||
|
|
34fd6763e1 | ||
|
|
4fb743e934 | ||
|
|
94d9bb8a18 | ||
|
|
e59f00fcd0 | ||
|
|
078b1ec377 | ||
|
|
f52d28e75f | ||
|
|
9b85a89e71 | ||
|
|
9770f10a8c | ||
|
|
f091774afd | ||
|
|
9c82a842b6 | ||
|
|
590c1467bc | ||
|
|
e1cc6bc624 | ||
|
|
f1d186d6ae | ||
|
|
f7948285f7 | ||
|
|
8d1127182f | ||
|
|
f64c82a8cb | ||
|
|
f12ccfa02a | ||
|
|
a3a9def48d | ||
|
|
df0bdeb8a3 | ||
|
|
4f80bb87c2 | ||
|
|
bf2fa212aa | ||
|
|
28e7934aeb | ||
|
|
b53f185ec6 | ||
|
|
67d12d3120 | ||
|
|
564f5dbf64 | ||
|
|
77a28a2de7 | ||
|
|
edecd8dc22 | ||
|
|
813e9135c6 | ||
|
|
51afc8bdfb | ||
|
|
0dc485d8d7 | ||
|
|
72e524963c | ||
|
|
7723c633a0 | ||
|
|
5870e6367c | ||
|
|
246d9b94b7 | ||
|
|
c17918007d | ||
|
|
33970276b2 | ||
|
|
509f9083c9 | ||
|
|
6f74c565e8 | ||
|
|
794f07c3b0 | ||
|
|
66f518b531 | ||
|
|
c0d05132df | ||
|
|
a5aa20ac5c | ||
|
|
86f209800a | ||
|
|
7c34387d22 | ||
|
|
5e476a2e5a | ||
|
|
1210b4f59a | ||
|
|
6b618a5824 | ||
|
|
6824d23dfe | ||
|
|
bb46d950df | ||
|
|
5472898759 | ||
|
|
d5a1872f93 | ||
|
|
cb4a256d5f | ||
|
|
c0d8337417 | ||
|
|
9d02066615 | ||
|
|
104e6564f7 | ||
|
|
cabe989555 | ||
|
|
1109004727 | ||
|
|
f88a64df1c | ||
|
|
9b897629ba | ||
|
|
6fd01a6721 | ||
|
|
fbd5835bd9 | ||
|
|
95181546b4 | ||
|
|
857803e362 | ||
|
|
98e2564330 | ||
|
|
bd77354926 | ||
|
|
340cfb0104 | ||
|
|
be36b31c4e | ||
|
|
22c38a484f | ||
|
|
7d83f9d644 | ||
|
|
98fc8669e1 | ||
|
|
808319e781 | ||
|
|
2ed9199bf1 | ||
|
|
ab27e0a74d | ||
|
|
53b60c9244 | ||
|
|
613a0f8006 | ||
|
|
b36c24c508 | ||
|
|
74ae43b092 | ||
|
|
1da208ad10 | ||
|
|
25f176875d | ||
|
|
d641fbb635 | ||
|
|
8fdec117cd | ||
|
|
e26e2e44cc | ||
|
|
07ace68d75 | ||
|
|
36fd693728 | ||
|
|
9c260164ef | ||
|
|
5120158bdb | ||
|
|
e75dace600 | ||
|
|
cf86481d31 | ||
|
|
6a5de3e0b6 | ||
|
|
b007acfa95 | ||
|
|
9b9f2fe428 | ||
|
|
26634cdf0b | ||
|
|
bb4693ddfc | ||
|
|
00e64e3518 | ||
|
|
89b2234c17 | ||
|
|
d9745b3477 | ||
|
|
979768fc9f | ||
|
|
70d2ebf55b | ||
|
|
3ce8565513 | ||
|
|
734454ee85 | ||
|
|
ebb9af1eb2 | ||
|
|
8e24dbe22f | ||
|
|
91345e3f9b | ||
|
|
6b5a75ff84 | ||
|
|
5725fbef70 | ||
|
|
fb81627b83 | ||
|
|
aeaf6eee18 | ||
|
|
cfab9221f2 | ||
|
|
90478a04b6 | ||
|
|
a5bc0a61ca | ||
|
|
22c913f48a | ||
|
|
02a6282244 | ||
|
|
c0e3e94e57 | ||
|
|
662bfe0ab8 | ||
|
|
610ec1a6a4 | ||
|
|
c2f7cd6d19 | ||
|
|
c6f0b14e92 | ||
|
|
a8698b6065 | ||
|
|
c627226ac9 | ||
|
|
b2e659bd3f | ||
|
|
fe5d3a1892 | ||
|
|
89dd4be9f1 | ||
|
|
9e02c26908 | ||
|
|
237f5264d9 | ||
|
|
01fac30329 | ||
|
|
71cab7d453 | ||
|
|
d132202662 | ||
|
|
58ec3d912b | ||
|
|
02f57cd69d | ||
|
|
e3a8552774 | ||
|
|
af56db4048 | ||
|
|
2ca2f4e135 | ||
|
|
5296e61d35 | ||
|
|
630954c94e | ||
|
|
70758927cd | ||
|
|
8b23dcd69e | ||
|
|
ed712fb196 | ||
|
|
bdc8202422 | ||
|
|
68cc919aaa | ||
|
|
c6c5ce0619 | ||
|
|
5b4a3805bf | ||
|
|
51e3fc8cab | ||
|
|
a91584853c | ||
|
|
cec8e9188b | ||
|
|
ba239de8be | ||
|
|
1accc36d8c | ||
|
|
bfa4abbf87 | ||
|
|
9a488d1d8c | ||
|
|
7da5f3a04d | ||
|
|
a44e9f2c09 | ||
|
|
0cd1b232ed | ||
|
|
3ded17badf | ||
|
|
6d6a9cc72b | ||
|
|
4d9c37f2db | ||
|
|
45c46c1794 | ||
|
|
4be3f23757 | ||
|
|
62200eda9e | ||
|
|
e8a2e30bb5 | ||
|
|
48b2fb0b36 | ||
|
|
cc9dad6bb8 | ||
|
|
2a7b646c12 | ||
|
|
3c8acac758 | ||
|
|
737ebe408c | ||
|
|
c66a1991d8 | ||
|
|
792d657a4f | ||
|
|
560bd97fcd | ||
|
|
c1c325505b | ||
|
|
85f39ec0ef | ||
|
|
02074a93ef | ||
|
|
7717897357 | ||
|
|
3d25ef1be5 | ||
|
|
d688187e23 | ||
|
|
74a159ab99 | ||
|
|
3c7a7f0fa4 | ||
|
|
a1f9230169 | ||
|
|
be00b64b7e | ||
|
|
fc119ecd98 | ||
|
|
1abe9d4c01 | ||
|
|
9693c6e941 | ||
|
|
83c1129ed5 | ||
|
|
04a0216ebc | ||
|
|
930120fc4d | ||
|
|
160433ec9c | ||
|
|
693e18e5d9 | ||
|
|
b8d07cf43f | ||
|
|
cb87c248d5 | ||
|
|
8d4191b1a0 | ||
|
|
09b001e891 | ||
|
|
bdcacf0cfb | ||
|
|
d86e4ac0b6 | ||
|
|
d9104ceced | ||
|
|
7af92b3b30 | ||
|
|
1af95fcdaa | ||
|
|
f1a3af6fce | ||
|
|
26fad5e8fe | ||
|
|
23c3135051 | ||
|
|
d2b9102f84 | ||
|
|
5c6cb2a422 | ||
|
|
8a2f8ed6ee | ||
|
|
c104860448 | ||
|
|
5799bf2d62 | ||
|
|
f278517276 | ||
|
|
90cfac9b52 | ||
|
|
76f86c74ad | ||
|
|
1d7c25b561 | ||
|
|
bfe73ed9b4 | ||
|
|
856a53c33c | ||
|
|
770c08725f | ||
|
|
1234cc8152 | ||
|
|
47129afcfa | ||
|
|
072755ce7d | ||
|
|
0226478c63 | ||
|
|
3e17d9398b | ||
|
|
c18a386e09 | ||
|
|
057feaf5bd | ||
|
|
6fd7a841bd | ||
|
|
e86aab5e88 | ||
|
|
a5e6c04cb0 | ||
|
|
b5f977e46d | ||
|
|
0f3efde288 | ||
|
|
0d4c0d36af | ||
|
|
f4bbcc9fa3 | ||
|
|
6a4fee641d | ||
|
|
2d8abac0e3 | ||
|
|
f8721329ba | ||
|
|
13f915cc77 | ||
|
|
f0827fef38 | ||
|
|
cd9efec4d2 | ||
|
|
1753dbe8b0 | ||
|
|
a0591730b1 | ||
|
|
d05e1fdfb3 | ||
|
|
b5f5aff626 | ||
|
|
63371ce76e | ||
|
|
651dc651f5 | ||
|
|
c5993ba8b9 | ||
|
|
e666ae2b2e | ||
|
|
5647f957a7 | ||
|
|
73f675aebe | ||
|
|
cd5f3f2f2b | ||
|
|
b4e944e9ba | ||
|
|
741824ef87 | ||
|
|
15fbe86124 | ||
|
|
42d78f9a68 | ||
|
|
9fe28e830a | ||
|
|
4f218ea6d3 | ||
|
|
531247deb4 | ||
|
|
ac827f6861 | ||
|
|
e3cca44dce | ||
|
|
9002bbe77e | ||
|
|
6fc98eb1cb | ||
|
|
c566f1e7c9 | ||
|
|
5a15d29a22 | ||
|
|
d31c23d25a | ||
|
|
cb369dd326 | ||
|
|
91f1e89201 | ||
|
|
c7d6d38ab7 | ||
|
|
5424b85685 | ||
|
|
391d1127ca | ||
|
|
f9b8a0a661 | ||
|
|
25e893f712 | ||
|
|
8d83b6ada3 | ||
|
|
8aa7486e71 | ||
|
|
ebf3d813cb | ||
|
|
16fe9e241a | ||
|
|
b96904ec75 | ||
|
|
0b813ed159 | ||
|
|
4faa903fb3 | ||
|
|
2215e4db49 | ||
|
|
42e0895dad | ||
|
|
d2519c9a5b | ||
|
|
c6fc2a98ae | ||
|
|
a079284729 | ||
|
|
f0d4f43dba | ||
|
|
9e2e883c82 | ||
|
|
d9c7bbd3d2 | ||
|
|
96c822ffb3 | ||
|
|
a261dcd171 | ||
|
|
5db5f7217f | ||
|
|
f87d2ab89e | ||
|
|
6882ffaa00 | ||
|
|
603cb1db34 | ||
|
|
fe2fe1e08a | ||
|
|
97117f2fde | ||
|
|
27e54fae4c | ||
|
|
f1887449ab | ||
|
|
bf3c15708d | ||
|
|
d4da9d823b | ||
|
|
092e682591 | ||
|
|
af0c111d2e | ||
|
|
fb091f1b48 | ||
|
|
fe88ea4f9a | ||
|
|
a937cde7a3 | ||
|
|
c4d453b738 | ||
|
|
e17409e0b1 | ||
|
|
01106d801a | ||
|
|
db5d737235 | ||
|
|
82a240998b | ||
|
|
08ba652464 | ||
|
|
6a6c7d4b90 | ||
|
|
21a3037bdf | ||
|
|
59477a9fa9 | ||
|
|
dae148af76 | ||
|
|
b50863ff9f | ||
|
|
a61fd030ff | ||
|
|
cf369a9f12 | ||
|
|
3611f9df94 | ||
|
|
eb9ca5d7e3 | ||
|
|
bce998cb53 | ||
|
|
f26f2d9e9b | ||
|
|
99c1f14581 | ||
|
|
410597eca7 | ||
|
|
216486a4c6 | ||
|
|
8cae6bb627 | ||
|
|
ebac3f5547 | ||
|
|
c2b016cb5f | ||
|
|
7e470d2cb5 | ||
|
|
fa61dada55 | ||
|
|
fb0168ea50 | ||
|
|
1ddd34059b | ||
|
|
7affd0579e | ||
|
|
4bced29ec9 | ||
|
|
82e39ccf52 | ||
|
|
4ac204d230 | ||
|
|
9eb1c3a731 | ||
|
|
6e0b7cc650 | ||
|
|
2d7a2bb57f | ||
|
|
8a79748b6d | ||
|
|
75595dd07f | ||
|
|
bf050ece0f | ||
|
|
62ef532801 | ||
|
|
43660d9c8f | ||
|
|
f4c5b4c11d | ||
|
|
89f164bdd7 | ||
|
|
e2cf49aefd | ||
|
|
cb7bafbdc0 | ||
|
|
8317b167ce | ||
|
|
4b78169152 | ||
|
|
69a79f660f | ||
|
|
20f621d05c | ||
|
|
641740db77 | ||
|
|
686c42ae07 | ||
|
|
ac11617655 | ||
|
|
d7d3d6c808 | ||
|
|
a6a9ad8cc0 | ||
|
|
84a9dcf6cb | ||
|
|
01823d18c6 | ||
|
|
98330cdfa1 | ||
|
|
ff794dc215 | ||
|
|
1b1ba985a9 | ||
|
|
178414cef4 | ||
|
|
6ea4098679 | ||
|
|
e9ef94e8af | ||
|
|
c8196c41d4 | ||
|
|
ec39ff267c | ||
|
|
9818cfb572 | ||
|
|
f4c1a5ea35 | ||
|
|
e93c3483c8 | ||
|
|
6c653d8f2a | ||
|
|
93466d9a66 | ||
|
|
999962d161 | ||
|
|
9cd10686bb | ||
|
|
6fdf578919 | ||
|
|
dab0a8ec27 | ||
|
|
3f15035c1a | ||
|
|
1dc9f5e842 | ||
|
|
c92c2d7b75 | ||
|
|
6c21723ecb | ||
|
|
c96f1e76e2 | ||
|
|
78d02f50af | ||
|
|
0477551293 | ||
|
|
e01560ab89 | ||
|
|
5e4a422495 | ||
|
|
e8af744c97 | ||
|
|
08609af053 | ||
|
|
bed6eb70af | ||
|
|
62b1eba836 | ||
|
|
c6223fd6b0 | ||
|
|
73817a311b | ||
|
|
34794d2554 | ||
|
|
a0e056abc1 | ||
|
|
8ced769fae | ||
|
|
54b07a9446 | ||
|
|
d9f0ece812 | ||
|
|
8e49b8365c | ||
|
|
2650706be1 | ||
|
|
f62ad19ecd | ||
|
|
1f0b55bfed | ||
|
|
ed9d331f89 | ||
|
|
7b9fb32fae | ||
|
|
a0add9ec7a | ||
|
|
67fa3f728c | ||
|
|
dbb8e5421a | ||
|
|
9872c019b2 | ||
|
|
1ab86c9331 | ||
|
|
a9cfb06ee9 | ||
|
|
4ba641b13f | ||
|
|
4de0fd71e1 | ||
|
|
452a8512c4 | ||
|
|
31897bc7b5 | ||
|
|
28db040e57 | ||
|
|
9d9935a625 | ||
|
|
ce38e74ba2 | ||
|
|
4520114780 | ||
|
|
9a182d615a | ||
|
|
ce7d98ff1f | ||
|
|
51a6db995a | ||
|
|
66fcbee0df | ||
|
|
3df0ba27fd | ||
|
|
37835380e3 | ||
|
|
6db2ce2a40 | ||
|
|
bf9533007b | ||
|
|
8c825d477f | ||
|
|
13474985dc | ||
|
|
468692f5e4 | ||
|
|
cc93113934 | ||
|
|
1d38b2ce6a | ||
|
|
d6760bfbee | ||
|
|
45ad0f6bdf | ||
|
|
d0ec102c53 | ||
|
|
39d2f6b981 | ||
|
|
feadcb24df | ||
|
|
5511a93e9c | ||
|
|
a587547e2c | ||
|
|
ba65b5a58f | ||
|
|
1eb7b4724c | ||
|
|
a4588cc848 | ||
|
|
e3fee55e3d | ||
|
|
7b95c46cf5 | ||
|
|
bd550f8a1e | ||
|
|
fcc4ed396a | ||
|
|
ba40ac4e90 | ||
|
|
15b5df10a4 | ||
|
|
7e3c6b4944 | ||
|
|
e922c9bc72 | ||
|
|
1382fa8901 | ||
|
|
a0df8e7989 | ||
|
|
13bcb062b2 | ||
|
|
977932250d | ||
|
|
f555568d50 | ||
|
|
7ea8c3d173 | ||
|
|
467d8b2cd2 | ||
|
|
e3a3ca4c0d | ||
|
|
b40826f4ee | ||
|
|
62e1f8e96d | ||
|
|
04eeb28c62 | ||
|
|
85256f545c | ||
|
|
e9bbcc9135 | ||
|
|
c823ca7ac6 | ||
|
|
f85497a421 | ||
|
|
1e95279fc0 | ||
|
|
d92328dbb4 | ||
|
|
a4eafa5c66 | ||
|
|
11ea71f7b9 | ||
|
|
723c6f5436 | ||
|
|
f5514c4fea | ||
|
|
3c808ca895 | ||
|
|
96c6c0ff00 | ||
|
|
ae80607248 | ||
|
|
d09bf306ce | ||
|
|
0bf39bd152 | ||
|
|
f3d8a7338a | ||
|
|
4da9273b28 | ||
|
|
8d99860c55 | ||
|
|
8d253ed5ff | ||
|
|
1c619fd4bc | ||
|
|
3f51595d25 | ||
|
|
4bc645074a | ||
|
|
ef3496d55b | ||
|
|
dbccf39a9f | ||
|
|
ba0d60aa35 | ||
|
|
9e2cc6bb21 | ||
|
|
73bc2036e3 | ||
|
|
d96ce25b1c | ||
|
|
0eb14052ec | ||
|
|
e5f07dcae8 | ||
|
|
0df77a5a65 | ||
|
|
5b73561aba | ||
|
|
3cc23c8e5b | ||
|
|
95fc47048a | ||
|
|
16312c921d | ||
|
|
b58c04c28b | ||
|
|
901170ab41 | ||
|
|
185624fda1 | ||
|
|
fd79922284 | ||
|
|
3f09575740 | ||
|
|
4241cbc3a0 | ||
|
|
d69e77d572 | ||
|
|
966765dac7 | ||
|
|
16ed2c1572 | ||
|
|
0a926337cb | ||
|
|
f214ead6f6 | ||
|
|
61cba414bf | ||
|
|
509057f84c | ||
|
|
64050337cf | ||
|
|
8d37a0a39e | ||
|
|
e9bacfe58a | ||
|
|
02907f12f1 | ||
|
|
9f45bce55f | ||
|
|
80258efc7f | ||
|
|
e28ebcb564 | ||
|
|
40022decb4 | ||
|
|
d121ebe078 | ||
|
|
70a7446ad4 | ||
|
|
b168dde502 | ||
|
|
24d0d84bd1 | ||
|
|
2c69dbcc22 | ||
|
|
427b25dfb7 | ||
|
|
0653d99e4d | ||
|
|
4ab7308e77 | ||
|
|
6b089d35a9 | ||
|
|
6b1b8bb522 | ||
|
|
ea68810f58 | ||
|
|
ddc85579c9 | ||
|
|
f8649a1a8d | ||
|
|
1899b3ccd2 | ||
|
|
1c90edf206 | ||
|
|
51fa558325 | ||
|
|
de5ccf9c47 | ||
|
|
1a4375ed73 | ||
|
|
3a963fab0b | ||
|
|
3623d41fd3 | ||
|
|
5f773c30cb | ||
|
|
ac184a6fe3 | ||
|
|
4d729b9d39 | ||
|
|
dea9aef965 | ||
|
|
6c76f94a1a | ||
|
|
928b5a04ca | ||
|
|
15427e0aa1 | ||
|
|
183a18f618 | ||
|
|
58f9ccb023 | ||
|
|
169fb4d6f4 | ||
|
|
e67029829a | ||
|
|
09d3159439 | ||
|
|
11ff88e44f | ||
|
|
b4b76b72ff | ||
|
|
8320fc0686 | ||
|
|
9035db1137 | ||
|
|
2630aa6ac8 | ||
|
|
c05c759eac | ||
|
|
2d0bc9c193 | ||
|
|
3d5c8b0053 | ||
|
|
1ca9e7ab3e | ||
|
|
5e4dc6ad13 | ||
|
|
37c832134b | ||
|
|
d3565b673e | ||
|
|
4cf5797827 | ||
|
|
ca62d67d37 | ||
|
|
f41acc2990 | ||
|
|
a9612fc5cc | ||
|
|
310409355a | ||
|
|
8eef613d4b | ||
|
|
0c38f1db08 | ||
|
|
f1e8753f16 | ||
|
|
74a53af316 | ||
|
|
404ed4cb1f | ||
|
|
604396a029 | ||
|
|
35c546782f | ||
|
|
9333efd3c3 | ||
|
|
47c8088fb7 | ||
|
|
e3bed0c636 | ||
|
|
88064cdbaf | ||
|
|
2b8e992646 | ||
|
|
41dfd84543 | ||
|
|
f37bddbc6d | ||
|
|
b8bd81e232 | ||
|
|
adbb1a1d79 | ||
|
|
03a2a00b30 | ||
|
|
3530d3dfe0 | ||
|
|
73f3d5dde8 | ||
|
|
9461a70784 | ||
|
|
13199a3013 | ||
|
|
31081b01cb | ||
|
|
b6cc0471c5 | ||
|
|
0555912a13 | ||
|
|
ada00948c5 | ||
|
|
0c7caf80b3 | ||
|
|
410d9b0f0c | ||
|
|
b3600ae576 | ||
|
|
1dc557042f | ||
|
|
f9d62599a4 | ||
|
|
dbd941f464 | ||
|
|
ff8e730b8a | ||
|
|
9fe6c0a3c0 | ||
|
|
e405e35736 | ||
|
|
af167a58a8 | ||
|
|
eed9999acc | ||
|
|
fb5805a43c | ||
|
|
6a333e79d2 | ||
|
|
d0a138e718 | ||
|
|
8b0b5f4026 | ||
|
|
88a77127ea | ||
|
|
16851be3e4 | ||
|
|
79b3cef729 | ||
|
|
f05bd8b0f6 | ||
|
|
0076cdccc9 | ||
|
|
e1df9faad7 | ||
|
|
bb51393332 | ||
|
|
52f5d7d346 | ||
|
|
a874a25b69 | ||
|
|
efbde82ca2 | ||
|
|
4f64c72d0d | ||
|
|
bded3da5df | ||
|
|
195e276e4b | ||
|
|
39e6607afd | ||
|
|
1b74473421 | ||
|
|
97250d8193 | ||
|
|
0816dd15f9 | ||
|
|
427c13a104 | ||
|
|
5604a6837f | ||
|
|
5f0032bb7f | ||
|
|
0b7b5496f3 | ||
|
|
221e7b59c4 | ||
|
|
13ff5ca45e | ||
|
|
4f5c7ddada | ||
|
|
4ee7f80433 | ||
|
|
ded46774bc | ||
|
|
c154ea3fc6 | ||
|
|
8d4817b710 | ||
|
|
3e534404c3 | ||
|
|
0fbfabe5d3 | ||
|
|
30054d177b | ||
|
|
e17ab5f455 | ||
|
|
c94871b5ef | ||
|
|
9a8ef2a30d | ||
|
|
45ac40cc85 | ||
|
|
76b65c5dea | ||
|
|
814298c549 | ||
|
|
3896cd3d39 | ||
|
|
7ab26e5b0d | ||
|
|
4ca6750309 | ||
|
|
d862de8e7f | ||
|
|
cdd7569e86 | ||
|
|
a656699fa7 | ||
|
|
d93ac31b4e | ||
|
|
92c8e2c3a1 | ||
|
|
306f89ff94 | ||
|
|
83dfbf377d | ||
|
|
0f2cb5ed0d | ||
|
|
d7a1b7b3fc | ||
|
|
ac8e47ac2d | ||
|
|
4732127777 | ||
|
|
3f2037a906 | ||
|
|
eeed6d6038 | ||
|
|
bf284f4fde | ||
|
|
d0874e7612 | ||
|
|
9909493bfc | ||
|
|
a97df22da4 | ||
|
|
8360fccd06 | ||
|
|
fc39281bdb | ||
|
|
2a9983032d | ||
|
|
f676a0bf9e | ||
|
|
313fd5582a | ||
|
|
f63c1c95fe | ||
|
|
f384a3352b | ||
|
|
9e29d5f22d | ||
|
|
d28131469d | ||
|
|
ace6badfed | ||
|
|
78cd903543 | ||
|
|
10a5c24a63 | ||
|
|
7259f21cf1 | ||
|
|
7e08575245 | ||
|
|
40b2bd2595 | ||
|
|
68616957ab | ||
|
|
d96be650e4 | ||
|
|
122a59c723 | ||
|
|
3ee7ccff48 | ||
|
|
9f5fec7135 | ||
|
|
648b6f2038 | ||
|
|
163a013e1f | ||
|
|
2652fbc1cb | ||
|
|
2760f40a7c | ||
|
|
abc6f7a29f | ||
|
|
9d1bd61720 | ||
|
|
5d7e7a48ad | ||
|
|
bddd477bd7 | ||
|
|
f53b5121af | ||
|
|
4befae3a8d | ||
|
|
1616f15a40 | ||
|
|
de5d0eec06 | ||
|
|
8a9a5bec1e | ||
|
|
4d60904cff | ||
|
|
c15a90f709 | ||
|
|
aae7ec6146 | ||
|
|
1c5e84324b | ||
|
|
428adcf47f | ||
|
|
59627ea9ef | ||
|
|
020bb160b9 | ||
|
|
4016a37573 | ||
|
|
817524de4b | ||
|
|
ca3da4adf5 | ||
|
|
4adc0eb563 | ||
|
|
a54151fb86 | ||
|
|
5d70a4749a | ||
|
|
2d8ddfedfa | ||
|
|
16c1b0bc2a | ||
|
|
0c46022f1b | ||
|
|
6d85772bd1 | ||
|
|
c20d05ba45 | ||
|
|
dea874e36e | ||
|
|
8fa227f3da | ||
|
|
76921ced71 | ||
|
|
da71aec774 | ||
|
|
a589681c3d | ||
|
|
5b3c4921d6 | ||
|
|
3666d0ab73 | ||
|
|
b45256c31b | ||
|
|
bc33356a57 | ||
|
|
9968bebed2 | ||
|
|
43e9f9998f | ||
|
|
eeca43014a | ||
|
|
b5b5abdc82 | ||
|
|
5eab0d47e4 | ||
|
|
0249966741 | ||
|
|
6437ba8c6a | ||
|
|
b2b2534dcc | ||
|
|
c06957b2f0 | ||
|
|
00b22d50c7 | ||
|
|
a0cfc2110a | ||
|
|
0615c6624e | ||
|
|
d2bf2f8f5a | ||
|
|
7a486e1158 | ||
|
|
10ca7de6ba | ||
|
|
e0f2d59864 | ||
|
|
05f8352837 | ||
|
|
ae32d89a0f | ||
|
|
79dc3ab3fa | ||
|
|
29436c1e5f | ||
|
|
b5e8273839 | ||
|
|
8762bcba90 | ||
|
|
5e02355215 | ||
|
|
a193f96b8d | ||
|
|
380dbb32e9 | ||
|
|
d13afe0b2e | ||
|
|
32399ff52c | ||
|
|
4ec637f889 | ||
|
|
23c52da9f8 | ||
|
|
d0f81c9621 | ||
|
|
8a0f2010a2 | ||
|
|
f8c6050abe | ||
|
|
dea98d5e87 | ||
|
|
f8e85e3208 | ||
|
|
b01f4ed802 | ||
|
|
d7244bd9ba | ||
|
|
039ad5727f | ||
|
|
6d55083ef6 | ||
|
|
a02800695a | ||
|
|
6683e5f8ec | ||
|
|
8d2ec59f71 | ||
|
|
c8e0bb04b6 | ||
|
|
4d9bacbecc | ||
|
|
326992f3ba | ||
|
|
9bdd1087ba | ||
|
|
806a67b1b0 | ||
|
|
fdfda47f0c | ||
|
|
2065a0b349 | ||
|
|
2272ff5ed7 | ||
|
|
92a43a5930 | ||
|
|
67807f8c6f | ||
|
|
b81978c67c | ||
|
|
5d3c5a151f | ||
|
|
937240f244 | ||
|
|
81265ab458 | ||
|
|
8bbe5abd72 | ||
|
|
ee4aff0ca4 | ||
|
|
39233cfe1c | ||
|
|
99262e28fe | ||
|
|
ce3180433d | ||
|
|
5ac634b68d | ||
|
|
61035a98a6 | ||
|
|
96e86f6e4e | ||
|
|
bc098d73fa | ||
|
|
58ffde5326 | ||
|
|
4422613248 | ||
|
|
4abd4a6882 | ||
|
|
ae4cb685c0 | ||
|
|
acba505144 | ||
|
|
858dab7814 | ||
|
|
e034b4a331 | ||
|
|
2630d0b56c | ||
|
|
9f364bb9dd | ||
|
|
c1b9a9477e | ||
|
|
203b3058a2 | ||
|
|
a9a91603d1 | ||
|
|
27333035c0 | ||
|
|
0c2c030b71 | ||
|
|
56a21b3968 | ||
|
|
c5b0a40279 | ||
|
|
716c62ba8f | ||
|
|
e4144f2c64 | ||
|
|
ee6abd45db | ||
|
|
804c2dc3ae | ||
|
|
79cbdb537e | ||
|
|
903ca342cb | ||
|
|
8628105eef | ||
|
|
f4d74c4935 | ||
|
|
9486a6b2a3 | ||
|
|
10884f2371 | ||
|
|
608ad18bb9 | ||
|
|
cf1fe63a61 | ||
|
|
4a0042f3d6 | ||
|
|
c22ffc1dfe | ||
|
|
ac10ed31ac | ||
|
|
7b547456fe | ||
|
|
fd4e741c84 | ||
|
|
14dda66c03 | ||
|
|
c6553aef3d | ||
|
|
65011d1af7 | ||
|
|
7b429bdcd7 | ||
|
|
9623974ed5 | ||
|
|
7b5a35cbc6 | ||
|
|
e26dc9a2b7 | ||
|
|
d5e8b23577 | ||
|
|
69a036b2b7 | ||
|
|
af518cf1f4 | ||
|
|
73a19614e1 | ||
|
|
bafc8f829a | ||
|
|
0d5550b043 | ||
|
|
6c6f7fb3bb | ||
|
|
cff5bab403 | ||
|
|
6e8b11113e | ||
|
|
9d973d9252 | ||
|
|
d405f74c54 | ||
|
|
ab06570a50 | ||
|
|
6d46332f96 | ||
|
|
e07e5b5cf8 | ||
|
|
d877795561 | ||
|
|
5ae2546339 | ||
|
|
4ba33daf46 | ||
|
|
fb53acbf2a | ||
|
|
4acd56e661 | ||
|
|
bdd65ed448 | ||
|
|
845e32e5e1 | ||
|
|
1d797dacc6 | ||
|
|
7c8cc39d07 | ||
|
|
f8d3bd7da3 | ||
|
|
ac90f8fddc | ||
|
|
9d134a949f | ||
|
|
b61764693f | ||
|
|
ee9e204410 | ||
|
|
1c1442a4fc | ||
|
|
477bc95a00 | ||
|
|
02f45abcb9 | ||
|
|
fead0f89b9 | ||
|
|
2b3ba0370b | ||
|
|
61913f9e46 | ||
|
|
65f82b47fe | ||
|
|
be0fd7d1f4 | ||
|
|
f85169452a | ||
|
|
e36192388d | ||
|
|
8db6148297 | ||
|
|
2eb74d2898 | ||
|
|
46d4525c45 | ||
|
|
abdb27ea29 | ||
|
|
4ffca32653 | ||
|
|
e81ea8126a | ||
|
|
2a263b0e82 | ||
|
|
1a2392274a | ||
|
|
7a58814c61 | ||
|
|
3e1a92f486 | ||
|
|
5668ad01d5 | ||
|
|
056e0d79ed | ||
|
|
defd527625 | ||
|
|
ec1da1f540 | ||
|
|
ed71b9f31d | ||
|
|
1b9f6ba58e | ||
|
|
c72f8f6499 | ||
|
|
79936b88d0 | ||
|
|
71c8c8a72e | ||
|
|
25aca3c4d3 | ||
|
|
01016b0444 | ||
|
|
6a90c7c653 | ||
|
|
11420fa84f | ||
|
|
b09e5aac8a | ||
|
|
d019937e4f | ||
|
|
09d30ec249 | ||
|
|
4367d1bafc | ||
|
|
32e530e757 | ||
|
|
77941bcf73 | ||
|
|
5a10a9494f | ||
|
|
06d145777e | ||
|
|
9ceeb3411b | ||
|
|
5337a721b0 | ||
|
|
bd9cb92035 | ||
|
|
5ea41a0253 | ||
|
|
a117e7c1f4 | ||
|
|
d6f48261d5 | ||
|
|
1bbfe9dbf7 | ||
|
|
d9a88515bc | ||
|
|
a5836ecc60 | ||
|
|
09c89ecd12 | ||
|
|
e865f77a6b | ||
|
|
87d85d14d4 | ||
|
|
f14c8ad131 | ||
|
|
62ec121a5e | ||
|
|
11909a25cb | ||
|
|
6c6261ac23 | ||
|
|
c6426d29e2 | ||
|
|
0aaee54a63 | ||
|
|
797bdda405 | ||
|
|
df36580657 | ||
|
|
3ee68aba81 | ||
|
|
21d5309644 | ||
|
|
0bcfee0449 | ||
|
|
882f149f6b | ||
|
|
21ee8282a5 | ||
|
|
e9144fa2fc | ||
|
|
697bb569ef | ||
|
|
0aaa5779ab | ||
|
|
476a73d1b1 | ||
|
|
f0cd4437d8 | ||
|
|
33ef75865a | ||
|
|
64ee483665 | ||
|
|
47a3541787 | ||
|
|
0cab9c0082 | ||
|
|
6f76fb3613 | ||
|
|
37438a51b2 | ||
|
|
001e69ab80 | ||
|
|
bdaf9bcdb8 | ||
|
|
a90700fc6b | ||
|
|
401e0047ee | ||
|
|
9b0b6a44af | ||
|
|
7cb40f3c77 | ||
|
|
0d94e4c9ce | ||
|
|
666f1fbfb1 | ||
|
|
bee6a7a2b3 | ||
|
|
0c55a60897 | ||
|
|
72e3106e0d | ||
|
|
c3ab01afd4 | ||
|
|
2d6dcf976a | ||
|
|
e2d21d7d6e | ||
|
|
f70e247906 | ||
|
|
deb1c95c47 | ||
|
|
b0df349a23 | ||
|
|
ba620b28a3 | ||
|
|
4e26ec98b6 | ||
|
|
af599ceba8 | ||
|
|
55e73c7c67 | ||
|
|
521eb439b0 | ||
|
|
814bb5c6b6 | ||
|
|
7c45ca4d0f | ||
|
|
55c8caf0cc | ||
|
|
71155a0bf3 | ||
|
|
bc1b62f7da | ||
|
|
7689f7ef6f | ||
|
|
9d2aad1134 | ||
|
|
d94d5d53bf | ||
|
|
1055a00090 | ||
|
|
f7c1a6c4a6 | ||
|
|
cdefbef05c | ||
|
|
54e672498e | ||
|
|
9deaf335cf | ||
|
|
4180784033 | ||
|
|
40d8760dd5 | ||
|
|
f02b73c109 | ||
|
|
4f00b30fca | ||
|
|
5b555c8aac | ||
|
|
7edbb428d0 | ||
|
|
6138e4af5d | ||
|
|
4020c4413d | ||
|
|
248f19f61e | ||
|
|
85873c9efb | ||
|
|
21dd2935dd | ||
|
|
b78b71b260 | ||
|
|
71a0cb59b5 | ||
|
|
87333e2eb0 | ||
|
|
f31080f52c | ||
|
|
4b216fccb9 | ||
|
|
fba936c2dc | ||
|
|
c00f20f212 | ||
|
|
79c5683574 | ||
|
|
0c497f2600 | ||
|
|
b60627e06d | ||
|
|
af2895dd9b | ||
|
|
2ee5762bc7 | ||
|
|
e3824170b1 | ||
|
|
d07fff5c3e | ||
|
|
ab44874626 | ||
|
|
383edd1fe1 | ||
|
|
3f3858f0b3 | ||
|
|
5b680d28d0 | ||
|
|
5127602c9e | ||
|
|
dbb49ac7bf | ||
|
|
5b1f973b45 | ||
|
|
1057d7e3aa | ||
|
|
e79343bae7 | ||
|
|
4a2b620611 | ||
|
|
601576a9e3 | ||
|
|
5010317d8b | ||
|
|
b4ff4aa123 | ||
|
|
d4a77504c0 | ||
|
|
9250a735d9 | ||
|
|
58241d6fe8 | ||
|
|
f81e69d7d6 | ||
|
|
d95a1b277f | ||
|
|
af128ede7c | ||
|
|
47642f63ff | ||
|
|
d1fd776076 | ||
|
|
a1d877b726 | ||
|
|
af98bb9c4a | ||
|
|
40b04a3721 | ||
|
|
b1521a2b33 | ||
|
|
75fec18356 | ||
|
|
10449822ce | ||
|
|
831429f00c | ||
|
|
29266ce83e | ||
|
|
c75d6b7b69 | ||
|
|
196c763421 | ||
|
|
4a34971043 | ||
|
|
13c16dc18c | ||
|
|
eb85ff8e1e | ||
|
|
c2ee34584e | ||
|
|
d9c0d1d556 | ||
|
|
8ee96c575e | ||
|
|
ede4fa8f69 | ||
|
|
48ba76f365 | ||
|
|
69e7c2fed9 | ||
|
|
48c610348e | ||
|
|
7d66e995c3 | ||
|
|
5d5a7b88e0 | ||
|
|
886b0a6373 | ||
|
|
d3e8e5ff21 | ||
|
|
7a128fb7cc | ||
|
|
4ff32612c8 | ||
|
|
a32f341f31 | ||
|
|
2add8f3601 | ||
|
|
859444703f | ||
|
|
c1ef83eec7 | ||
|
|
1a81f10d89 | ||
|
|
82f368f0d9 | ||
|
|
a50f099ba3 | ||
|
|
8141043946 | ||
|
|
b7b63b1b2d | ||
|
|
e419e4bc1c | ||
|
|
20e006ad73 | ||
|
|
457ab656e7 | ||
|
|
b47e57b1a0 | ||
|
|
fc41c2c33d | ||
|
|
9f6eb9ba67 | ||
|
|
925162fc4b | ||
|
|
08da222c46 | ||
|
|
053440c81e | ||
|
|
22558f9e8f | ||
|
|
bbee14862b | ||
|
|
207c06e912 | ||
|
|
8c9d6edff2 | ||
|
|
772f110eee | ||
|
|
455ba3ff18 | ||
|
|
99ade8ec13 | ||
|
|
bf57280503 | ||
|
|
28337dc1cc | ||
|
|
60b863311b | ||
|
|
4c49fb1b05 | ||
|
|
15558f997d | ||
|
|
96bb4613e4 | ||
|
|
b1c58e2fc4 | ||
|
|
d0c3111844 | ||
|
|
b3f96c6b4c | ||
|
|
7319a1eeb9 | ||
|
|
2eb024200e | ||
|
|
99d0074280 | ||
|
|
f1a7e9092c | ||
|
|
2823c95929 | ||
|
|
2b9f974125 | ||
|
|
3b0884e628 | ||
|
|
4eae1f6f02 | ||
|
|
293b1b3c8a | ||
|
|
42d6194ed9 | ||
|
|
b3a0df734e | ||
|
|
f11e431341 | ||
|
|
f378bcf48a | ||
|
|
bb050e75d3 | ||
|
|
b7674e3278 | ||
|
|
1349a1a8b5 | ||
|
|
e9529bb93a | ||
|
|
945e5ea9ac | ||
|
|
a4097081ab | ||
|
|
8458438465 | ||
|
|
1364d89cae | ||
|
|
81e61c9cc9 | ||
|
|
981e6527c3 | ||
|
|
63fa19b73a | ||
|
|
f07b8ca296 | ||
|
|
437cff3efb | ||
|
|
77d0f2b37a | ||
|
|
a050b7ebb8 |
@@ -56,8 +56,8 @@ _APP_SMTP_PORT=1025
|
||||
_APP_SMTP_SECURE=
|
||||
_APP_SMTP_USERNAME=
|
||||
_APP_SMTP_PASSWORD=
|
||||
_APP_PHONE_PROVIDER=phone://mock
|
||||
_APP_PHONE_FROM=+123456789
|
||||
_APP_SMS_PROVIDER=sms://username:password@mock
|
||||
_APP_SMS_FROM=+123456789
|
||||
_APP_STORAGE_LIMIT=30000000
|
||||
_APP_STORAGE_PREVIEW_LIMIT=20000000
|
||||
_APP_FUNCTIONS_SIZE_LIMIT=30000000
|
||||
@@ -72,13 +72,18 @@ OPEN_RUNTIMES_NETWORK=appwrite_runtimes
|
||||
_APP_EXECUTOR_SECRET=your-secret-key
|
||||
_APP_EXECUTOR_HOST=http://appwrite-executor/v1
|
||||
_APP_MAINTENANCE_INTERVAL=86400
|
||||
_APP_MAINTENANCE_RETENTION_CACHE=2592000
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE=86400
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
|
||||
_APP_USAGE_AGGREGATION_INTERVAL=30
|
||||
_APP_USAGE_AGGREGATION_INTERVAL=5
|
||||
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
|
||||
_APP_USAGE_STATS=enabled
|
||||
_APP_LOGGING_PROVIDER=
|
||||
_APP_LOGGING_CONFIG=
|
||||
_APP_GRAPHQL_MAX_BATCH_SIZE=10
|
||||
_APP_GRAPHQL_MAX_COMPLEXITY=250
|
||||
_APP_GRAPHQL_MAX_DEPTH=3
|
||||
DOCKERHUB_PULL_USERNAME=
|
||||
DOCKERHUB_PULL_PASSWORD=
|
||||
DOCKERHUB_PULL_EMAIL=
|
||||
+7
-24
@@ -1,24 +1,7 @@
|
||||
app/config/* linguist-detectable=false
|
||||
app/config/*/* linguist-detectable=false
|
||||
app/config/*/*/* linguist-detectable=false
|
||||
app/config/*/*/*/* linguist-detectable=false
|
||||
app/views/* linguist-detectable=false
|
||||
app/views/*/* linguist-detectable=false
|
||||
app/views/*/*/* linguist-detectable=false
|
||||
app/views/*/*/*/* linguist-detectable=false
|
||||
app/controllers/* linguist-detectable=false
|
||||
app/controllers/*/* linguist-detectable=false
|
||||
app/controllers/*/*/* linguist-detectable=false
|
||||
app/controllers/*/*/*/* linguist-detectable=false
|
||||
app/controllers/*/*/*/*/* linguist-detectable=false
|
||||
src/* linguist-detectable=false
|
||||
src/*/* linguist-detectable=false
|
||||
src/*/*/* linguist-detectable=false
|
||||
src/*/*/*/* linguist-detectable=false
|
||||
src/*/*/*/*/* linguist-detectable=false
|
||||
tests/* linguist-detectable=false
|
||||
tests/*/* linguist-detectable=false
|
||||
tests/*/*/* linguist-detectable=false
|
||||
tests/*/*/*/* linguist-detectable=false
|
||||
tests/*/*/*/*/* linguist-detectable=false
|
||||
tests/*/*/*/*/*/* linguist-detectable=false
|
||||
app/config/** linguist-detectable=false
|
||||
app/views/** linguist-detectable=false
|
||||
app/controllers/** linguist-detectable=false
|
||||
src/** linguist-detectable=false
|
||||
tests/** linguist-detectable=false
|
||||
public/scripts/** linguist-detectable=false
|
||||
public/dist/scripts/** linguist-detectable=false
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
name: "🐛 Bug Report"
|
||||
description: "Submit a bug report to help us improve"
|
||||
title: "🐛 Bug Report: "
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out our bug report form 🙏
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "👟 Reproduction steps"
|
||||
description: "How do you trigger this bug? Please walk us through it step by step."
|
||||
placeholder: "When I ..."
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "👍 Expected behavior"
|
||||
description: "What did you think would happen?"
|
||||
placeholder: "It should ..."
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "👎 Actual Behavior"
|
||||
description: "What did actually happen? Add screenshots, if applicable."
|
||||
placeholder: "It actually ..."
|
||||
- type: dropdown
|
||||
id: appwrite-version
|
||||
attributes:
|
||||
label: "🎲 Appwrite version"
|
||||
description: "What version of Appwrite are you running?"
|
||||
options:
|
||||
- Version 0.15.x
|
||||
- Version 0.14.x
|
||||
- Version 0.13.x
|
||||
- Version 0.12.x
|
||||
- Version 0.11.x
|
||||
- Version 0.10.x
|
||||
- Version 0.9.x
|
||||
- Version 0.8.x
|
||||
- Version 0.7.x
|
||||
- Version 0.6.x
|
||||
- Different version (specify in environment)
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: "💻 Operating system"
|
||||
description: "What OS is your server / device running on?"
|
||||
options:
|
||||
- Linux
|
||||
- MacOS
|
||||
- Windows
|
||||
- Something else
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "🧱 Your Environment"
|
||||
description: "Is your environment customized in any way?"
|
||||
placeholder: "I use Cloudflare for ..."
|
||||
- type: checkboxes
|
||||
id: no-duplicate-issues
|
||||
attributes:
|
||||
label: "👀 Have you spent some time to check if this issue has been raised before?"
|
||||
description: "Have you Googled for a similar issue or checked our older issues for a similar bug?"
|
||||
options:
|
||||
- label: "I checked and didn't find similar issue"
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: read-code-of-conduct
|
||||
attributes:
|
||||
label: "🏢 Have you read the Code of Conduct?"
|
||||
options:
|
||||
- label: "I have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/HEAD/CODE_OF_CONDUCT.md)"
|
||||
required: true
|
||||
@@ -1,32 +0,0 @@
|
||||
name: "📚 Documentation"
|
||||
description: "Report an issue related to documentation"
|
||||
title: "📚 Documentation: "
|
||||
labels: [documentation]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to make our documentation better 🙏
|
||||
- type: textarea
|
||||
id: issue-description
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "💭 Description"
|
||||
description: "A clear and concise description of what the issue is."
|
||||
placeholder: "Documentation should not ..."
|
||||
- type: checkboxes
|
||||
id: no-duplicate-issues
|
||||
attributes:
|
||||
label: "👀 Have you spent some time to check if this issue has been raised before?"
|
||||
description: "Have you Googled for a similar issue or checked our older issues for a similar bug?"
|
||||
options:
|
||||
- label: "I checked and didn't find similar issue"
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: read-code-of-conduct
|
||||
attributes:
|
||||
label: "🏢 Have you read the Code of Conduct?"
|
||||
options:
|
||||
- label: "I have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/HEAD/CODE_OF_CONDUCT.md)"
|
||||
required: true
|
||||
@@ -1,40 +0,0 @@
|
||||
name: 🚀 Feature
|
||||
description: "Submit a proposal for a new feature"
|
||||
title: "🚀 Feature: "
|
||||
labels: [feature]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out our feature request form 🙏
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "🔖 Feature description"
|
||||
description: "A clear and concise description of what the feature is."
|
||||
placeholder: "You should add ..."
|
||||
- type: textarea
|
||||
id: pitch
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "🎤 Pitch"
|
||||
description: "Please explain why this feature should be implemented and how it would be used. Add examples, if applicable."
|
||||
placeholder: "In my use-case, ..."
|
||||
- type: checkboxes
|
||||
id: no-duplicate-issues
|
||||
attributes:
|
||||
label: "👀 Have you spent some time to check if this issue has been raised before?"
|
||||
description: "Have you Googled for a similar issue or checked our older issues for a similar bug?"
|
||||
options:
|
||||
- label: "I checked and didn't find similar issue"
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: read-code-of-conduct
|
||||
attributes:
|
||||
label: "🏢 Have you read the Code of Conduct?"
|
||||
options:
|
||||
- label: "I have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/HEAD/CODE_OF_CONDUCT.md)"
|
||||
required: true
|
||||
@@ -11,16 +11,17 @@ Happy contributing!
|
||||
|
||||
## What does this PR do?
|
||||
|
||||
(Provide a description of what this PR does.)
|
||||
(Provide a description of what this PR does and why it's needed.)
|
||||
|
||||
## Test Plan
|
||||
|
||||
(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)
|
||||
(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Screenshots may also be helpful.)
|
||||
|
||||
## Related PRs and Issues
|
||||
|
||||
(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)
|
||||
- (Related PR or issue)
|
||||
|
||||
### Have you read the [Contributing Guidelines on issues](https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md)?
|
||||
## Checklist
|
||||
|
||||
(Write your answer here.)
|
||||
- [ ] Have you read the [Contributing Guidelines on issues](https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md)?
|
||||
- [ ] If the PR includes a change to an API's metadata (desc, label, params, etc.), does it also include updated API specs and example docs?
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
name: "CodeQL"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
@@ -25,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@@ -38,14 +42,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -59,4 +63,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
@@ -1,34 +1,24 @@
|
||||
name: "Linter"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
tests:
|
||||
lint:
|
||||
name: Linter
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.0'
|
||||
|
||||
- name: Install dependencies
|
||||
uses: php-actions/composer@v6
|
||||
with:
|
||||
php_version: '8.0'
|
||||
args: --profile --ignore-platform-reqs
|
||||
|
||||
- name: Run Linter
|
||||
run: ./vendor/bin/phpcs -p
|
||||
run: |
|
||||
docker run --rm -v $PWD:/app composer sh -c \
|
||||
"composer install --profile --ignore-platform-reqs && composer lint"
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
name: "Release"
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Unit & E2E
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
# Fetch submodules
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: appwrite/appwrite
|
||||
tags: |
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}.{{minor}}.{{patch}}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
VERSION=${{ steps.meta.outputs.version }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
+29
-17
@@ -1,34 +1,53 @@
|
||||
name: "Tests"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
tests:
|
||||
name: Unit & E2E
|
||||
runs-on: self-hosted
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
# Fetch submodules
|
||||
submodules: recursive
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# This is a separate action that sets up buildx runner
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build Appwrite
|
||||
# Upstream bug causes buildkit pulls to fail so prefetch base images
|
||||
# https://github.com/moby/moby/issues/41864
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: appwrite-dev
|
||||
load: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
DEBUG=false
|
||||
TESTING=true
|
||||
VERSION=dev
|
||||
|
||||
- name: Start Appwrite
|
||||
run: |
|
||||
echo "_APP_FUNCTIONS_RUNTIMES=php-8.0" >> .env
|
||||
docker pull composer:2.0
|
||||
docker pull php:8.0-cli-alpine
|
||||
docker compose build --progress=plain
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
sleep 30
|
||||
|
||||
- name: Doctor
|
||||
run: docker compose exec -T appwrite doctor
|
||||
|
||||
@@ -36,11 +55,4 @@ jobs:
|
||||
run: docker compose exec -T appwrite vars
|
||||
|
||||
- name: Run Tests
|
||||
run: docker compose exec -T appwrite test --debug
|
||||
|
||||
- name: Teardown
|
||||
if: always()
|
||||
run: |
|
||||
docker ps -aq | xargs docker rm --force || true
|
||||
docker volume prune --force || true
|
||||
docker network prune --force || true
|
||||
run: docker compose exec -T appwrite test --debug
|
||||
@@ -5,8 +5,11 @@
|
||||
/tests/resources/functions/**/code.tar.gz
|
||||
/app/sdks/*
|
||||
/.idea/
|
||||
!/.idea/workspace.xml
|
||||
!/.idea/php.xml
|
||||
.DS_Store
|
||||
.php_cs.cache
|
||||
debug/
|
||||
app/sdks
|
||||
dev/yasd_init.php
|
||||
.phpunit.result.cache
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[submodule "app/console"]
|
||||
path = app/console
|
||||
url = https://github.com/appwrite/console
|
||||
branch = 2.3.3
|
||||
+3
-1
@@ -1,8 +1,9 @@
|
||||
tasks:
|
||||
- name: Run Appwrite Docker Stack
|
||||
init: |
|
||||
docker compose pull
|
||||
git submodule update --init
|
||||
docker compose build
|
||||
docker compose pull
|
||||
docker pull composer
|
||||
command: |
|
||||
docker run --rm --interactive --tty \
|
||||
@@ -22,6 +23,7 @@ ports:
|
||||
vscode:
|
||||
extensions:
|
||||
- ms-azuretools.vscode-docker
|
||||
- zobo.php-intellisense
|
||||
|
||||
github:
|
||||
# https://www.gitpod.io/docs/prebuilds#github-specific-configuration
|
||||
|
||||
+172
-2
@@ -1,3 +1,173 @@
|
||||
# Version 1.3.1
|
||||
|
||||
## Bugs
|
||||
- Fixed Migration issue regarding 500 error [4906](https://github.com/appwrite/appwrite/pull/5356)
|
||||
|
||||
# Version 1.3.0
|
||||
|
||||
## Features
|
||||
- Password dictionary setting allows to compare user's password against command password database [4906](https://github.com/appwrite/appwrite/pull/4906)
|
||||
- Password history setting allows to save user's last used password so that it may not be used again. Maximum number of history saved is 20, which can be configured. Minimum is 0 which means disabled. [#4866](https://github.com/appwrite/appwrite/pull/4866)
|
||||
- Update APIs to check X-Appwrite-Timestamp header [#5024](https://github.com/appwrite/appwrite/pull/5024)
|
||||
- Database relationships [#5238](https://github.com/appwrite/appwrite/pull/5238)
|
||||
- New query operators [#5238](https://github.com/appwrite/appwrite/pull/5238)
|
||||
- Team preferences [#5196](https://github.com/appwrite/appwrite/pull/5196)
|
||||
- Update attribute metadata [#5164](https://github.com/appwrite/appwrite/pull/5164)
|
||||
|
||||
## Bugs
|
||||
- Fix not storing function's response on response codes 5xx [#4610](https://github.com/appwrite/appwrite/pull/4610)
|
||||
- Fix expire to formatTz in create account session [#4985](https://github.com/appwrite/appwrite/pull/4985)
|
||||
- Fix deleting projects when organization is deleted [#5335](https://github.com/appwrite/appwrite/pull/5335)
|
||||
- Fix deleting collections from a project [#4983](https://github.com/appwrite/appwrite/pull/4983)
|
||||
- Fix cleaning up project databases [#4984](https://github.com/appwrite/appwrite/pull/4984)
|
||||
- Fix creating documents with attributes with special characters [#246](https://github.com/utopia-php/database/pull/246)
|
||||
- Fix deleting attribute not deleting metadata index [#246](https://github.com/utopia-php/database/pull/246)
|
||||
- Fix create attribute event payload [#246](https://github.com/utopia-php/database/pull/246)
|
||||
|
||||
# Version 1.2.1
|
||||
## Changes
|
||||
- Upgrade Console to [2.2.0](https://github.com/appwrite/console/releases/tag/2.2.0)
|
||||
- Update DBIP Database [#5049](https://github.com/appwrite/appwrite/pull/5049)
|
||||
|
||||
## Bugs
|
||||
- Fix a few null safety warnings [#4654](https://github.com/appwrite/appwrite/pull/4654)
|
||||
- Fix timestamp format in Realtime response [#4515](https://github.com/appwrite/appwrite/pull/4515)
|
||||
- Add flutter-web as a platform type [#4992](https://github.com/appwrite/appwrite/pull/4992)
|
||||
- Fix typo in Model/Locale.php [#4669](https://github.com/appwrite/appwrite/pull/4669)
|
||||
- Fix deletes worker not deleting project database tables [#4984](https://github.com/appwrite/appwrite/pull/4984)
|
||||
- Fix deletes worker not deleting database collections [#4983](https://github.com/appwrite/appwrite/pull/4983)
|
||||
- Fix restart policy for worker-messaging container [#4994](https://github.com/appwrite/appwrite/pull/4994)
|
||||
- Fix validating origin for apple platforms [#5089](https://github.com/appwrite/appwrite/pull/5089)
|
||||
|
||||
# Version 1.2.0
|
||||
## Features
|
||||
- Added GraphQL API [#974](https://github.com/appwrite/appwrite/pull/974)
|
||||
- Added GraphQL Explorer [#974](https://github.com/appwrite/appwrite/pull/974)
|
||||
- Added ability to set max sessions per user per project [#4831](https://github.com/appwrite/appwrite/pull/4831)
|
||||
|
||||
## Changes
|
||||
- Get default region from environment on project create [#4780](https://github.com/appwrite/appwrite/pull/4780)
|
||||
- Fix french translation [#4782](https://github.com/appwrite/appwrite/pull/4782)
|
||||
- Fix max mimetype size [#4814](https://github.com/appwrite/appwrite/pull/4814)
|
||||
|
||||
## Bugs
|
||||
- Fix invited account verified status [#4776](https://github.com/appwrite/appwrite/pull/4776)
|
||||
|
||||
# Version 1.1.2
|
||||
## Changes
|
||||
- Released `appwrite/console` [2.0.2](https://github.com/appwrite/console/releases/tag/2.0.2)
|
||||
- Make `region` parameter optional with default for project create [#4763](https://github.com/appwrite/appwrite/pull/4763)
|
||||
|
||||
## Bugs
|
||||
- Fix default oauth paths [#4725](https://github.com/appwrite/appwrite/pull/4725)
|
||||
- Fix session expiration, and expired session deletion [#4739](https://github.com/appwrite/appwrite/pull/4739)
|
||||
- Fix processing status on sync executions [#4737](https://github.com/appwrite/appwrite/pull/4737)
|
||||
- Fix Locale API returning Unknown continent [#4761](https://github.com/appwrite/appwrite/pull/4761)
|
||||
|
||||
# Version 1.1.1
|
||||
## Bugs
|
||||
- Fix Deletes worker using incorrect device for file deletion [#4662](https://github.com/appwrite/appwrite/pull/4662)
|
||||
- Fix Migration for Stats adding the region attribute [#4704](https://github.com/appwrite/appwrite/pull/4704)
|
||||
- Fix Migration stopping scheduled functions [#4704](https://github.com/appwrite/appwrite/pull/4704)
|
||||
- Fix Migration enabling OAuth providers with data by default [#4704](https://github.com/appwrite/appwrite/pull/4704)
|
||||
- Fix Error pages when OAuth providers are disabled [#4704](https://github.com/appwrite/appwrite/pull/4704)
|
||||
|
||||
# Version 1.1.0
|
||||
## Features
|
||||
- Added support for the new Appwrite Console [#4655](https://github.com/appwrite/appwrite/pull/4655)
|
||||
- Added new property to projects configuration: `authDuration` which allows you to alter the duration of signed in sessions for your project. [#4618](https://github.com/appwrite/appwrite/pull/4618)
|
||||
|
||||
## Bugs
|
||||
- Fix license detection for Flutter and Dart SDKs [#4435](https://github.com/appwrite/appwrite/pull/4435)
|
||||
- Fix missing realtime event for create function deployment [#4574](https://github.com/appwrite/appwrite/pull/4574)
|
||||
- Fix missing `status`, `buildStderr` and `buildStderr` from get deployment response [#4611](https://github.com/appwrite/appwrite/pull/4611)
|
||||
- Fix project pagination in DB usage aggregation [#4517](https://github.com/appwrite/appwrite/pull/4517)
|
||||
- Fix missing file permissions due to cache [#4661](https://github.com/appwrite/appwrite/pull/4661)
|
||||
- Fix usage stats for async function executions [#4674](https://github.com/appwrite/appwrite/pull/4674)
|
||||
|
||||
# Version 1.0.3
|
||||
## Bugs
|
||||
- Fix document audit deletion [#4429](https://github.com/appwrite/appwrite/pull/4429)
|
||||
- Fix attribute and index deletion when deleting a collection [#4429](https://github.com/appwrite/appwrite/pull/4429)
|
||||
|
||||
# Version 1.0.2
|
||||
## Bugs
|
||||
- Fixed nullable values in functions variables [#3885](https://github.com/appwrite/appwrite/pull/3885)
|
||||
- Fixed migration for audit by migrating the `time` attribute [#4038](https://github.com/appwrite/appwrite/pull/4038)
|
||||
- Fixed default value for creating Boolean Attribute [#4040](https://github.com/appwrite/appwrite/pull/4040)
|
||||
- Fixed phone authentication code to be hashed in the internal database [#3906](https://github.com/appwrite/appwrite/pull/3906)
|
||||
- Fixed `/v1/teams/:teamId/memberships/:membershipId` response [#3883](https://github.com/appwrite/appwrite/pull/3883)
|
||||
- Fixed removing variables when function is deleted [#3884](https://github.com/appwrite/appwrite/pull/3884)
|
||||
- Fixed scheduled function not being triggered [#3908](https://github.com/appwrite/appwrite/pull/3908)
|
||||
- Fixed Phone Provider configuration [#3862](https://github.com/appwrite/appwrite/pull/3883)
|
||||
- Fixed Queries with `0` values [utopia-php/database#194](https://github.com/utopia-php/database/pull/194)
|
||||
|
||||
# Version 1.0.1
|
||||
## Bugs
|
||||
- Fixed migration for abuse by migrating the `time` attribute [#3839](https://github.com/appwrite/appwrite/pull/3839)
|
||||
|
||||
# Version 1.0.0
|
||||
## BREAKING CHANGES
|
||||
- All Date values are now stored as ISO-8601 instead of UNIX timestamps [#3516](https://github.com/appwrite/appwrite/pull/3516)
|
||||
- Permission levels and syntax have been reworked. See the Permissions V2 section in the document for more information [#3700](https://github.com/appwrite/appwrite/pull/3700)
|
||||
- Function Variables are now stored in a separate collection with their own API endpoints [#3634](https://github.com/appwrite/appwrite/pull/3634)
|
||||
- Resources that are computed asynchronously, such as function deployments, will now return a `202 Accepted` status code instead of `200 OK` [#3547](https://github.com/appwrite/appwrite/pull/3547)
|
||||
- Queries have been improved to allow even more flexibility, and introduced to new endpoints. See the Queries V2 section in the document for more information [#3702](https://github.com/appwrite/appwrite/pull/3702)
|
||||
- Compound indexes are now more flexible [#151](https://github.com/utopia-php/database/pull/151)
|
||||
- `createExecution` parameter `async` default value was changed from `true` to `false` [#3781](https://github.com/appwrite/appwrite/pull/3781)
|
||||
- String attribute `status` has been refactored to a Boolean attribute `enabled` in the functions collection [#3798](https://github.com/appwrite/appwrite/pull/3798)
|
||||
- `time` attribute in Execution response model has been renamed to `duration` to be more consistent with other response models. [#3801](https://github.com/appwrite/appwrite/pull/3801)
|
||||
- Renamed the following list endpoints to stay consistent with other endpoints [#3825](https://github.com/appwrite/appwrite/pull/3825)
|
||||
- `getMemberships` to `listMemberships` in Teams API
|
||||
- `getMemberships` to `listMemberships` in Users API
|
||||
- `getLogs` to `listLogs` in Users API
|
||||
- `getLogs` to `listLogs` in Accounts API
|
||||
- `getSessions` to `listSessions` in Accounts API
|
||||
- `getSessions` to `listSessions` in Users API
|
||||
- `getCountries` to `listCountries` in Locale API
|
||||
- `getCountriesEU` to `listCountriesEU` in Locale API
|
||||
- `getCountriesPhones` to `listCountriesPhones` in Locale API
|
||||
- `getContinents` to `listContinents` in Locale API
|
||||
- `getCurrencies` to `listCurrencies` in Locale API
|
||||
- `getLanguages` to `listLanguages` in Locale API
|
||||
|
||||
## Features
|
||||
- Added the UI to see the Parent ID of all resources within the UI. [#3653](https://github.com/appwrite/appwrite/pull/3653)
|
||||
- Added automatic cache cleaning for internal Appwrite services [#3491](https://github.com/appwrite/appwrite/pull/3491)
|
||||
- Added the ability for Appwrite to handle importing hashed passwords, this can be leveraged to import existing user data from other systems. More information can be found in the document linked above. [#2747](https://github.com/appwrite/appwrite/pull/2747)
|
||||
- `Users` has now been renamed to `Authentication` within the Appwrite console [#3664](https://github.com/appwrite/appwrite/pull/3664)
|
||||
- More endpoints were made public (for guests) with proper rate limits [#3741](https://github.com/appwrite/appwrite/pull/3741)
|
||||
- Added Disqus, Podio, and Etsy OAuth providers [#3526](https://github.com/appwrite/appwrite/pull/3526), [#3488](https://github.com/appwrite/appwrite/pull/3488), [#3522](https://github.com/appwrite/appwrite/pull/3522)
|
||||
- Function logs now capture stdout [#3656](https://github.com/appwrite/appwrite/pull/3656)
|
||||
- Added the ability to grant guests write permissions for documents, files and executions [#3727](https://github.com/appwrite/appwrite/pull/3727)
|
||||
|
||||
## Bugs
|
||||
- Fixed an issue where after resetting your password in the Appwrite console, you would not be redirected to the login page. [#3654](https://github.com/appwrite/appwrite/pull/3654)
|
||||
- Fixed an issue where invalid data could be loaded into the Appwrite console. [#3660](https://github.com/appwrite/appwrite/pull/3660)
|
||||
- Fixed an issue where users using the MySQL adapter for Appwrite would run into an issue with full text indexes [#154](https://github.com/utopia-php/database/pull/154)
|
||||
- Fix teams being created with no owners [#3558](https://github.com/appwrite/appwrite/pull/3558)
|
||||
- Fixed a bug where you could not search users by phone [#3619](https://github.com/appwrite/appwrite/pull/3619)
|
||||
- Fixed a bug where unaccepted invitations would grant access to projects [#3738](https://github.com/appwrite/appwrite/pull/3738)
|
||||
|
||||
# Version 0.15.3
|
||||
## Features
|
||||
- Added hint during Installation for DNS Configuration by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/2450
|
||||
## Bugs
|
||||
- Fixed Migration for Attributes and Indexes by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3568
|
||||
- Fixed Closed Icon in the alerts to be centered by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3594
|
||||
- Fixed Response Model for Get and Update Database Endpoint by @ishanvyas22 in https://github.com/appwrite/appwrite/pull/3553
|
||||
- Fixed Missing Usage on Functions exection by @Meldiron in https://github.com/appwrite/appwrite/pull/3543
|
||||
- Fixed Validation for Permissions to only accept a maximum of 100 Permissions for all endpoints by @Meldiron in https://github.com/appwrite/appwrite/pull/3532
|
||||
- Fixed backwards compatibility for Create Email Session Endpoint by @stnguyen90 in https://github.com/appwrite/appwrite/pull/3517
|
||||
|
||||
# Version 0.15.2
|
||||
## Bugs
|
||||
- Fixed Realtime Authentication for the Console by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3506
|
||||
- Fixed Collection Usage by @stnguyen90 in https://github.com/appwrite/appwrite/pull/3505
|
||||
- Fixed `$createdAt` after updating document by @Meldiron in https://github.com/appwrite/appwrite/pull/3498
|
||||
- Fixed Redirect after deleting Collection in Console @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3476
|
||||
- Fixed broken Link for Documents under Collections by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3469
|
||||
|
||||
# Version 0.15.1
|
||||
## Bugs
|
||||
- Fixed SMS for `createVerification` by @christyjacob4 in https://github.com/appwrite/appwrite/pull/3454
|
||||
@@ -26,8 +196,8 @@
|
||||
- `collections.[COLLECTION_ID]` is now `databases.[DATABASE_ID].collections.[COLLECTION_ID]`
|
||||
- `collections.[COLLECTION_ID].documents.[DOCUMENT_ID]` is now `databases.[DATABASE_ID].collections.[COLLECTION_ID].documents.[DOCUMENT_ID]`
|
||||
- Following Realtime Channels are changed:
|
||||
- `collections.[COLLECTION_ID]` is now `databases.[DATABASE_ID].ollections.[COLLECTION_ID]`
|
||||
- `collections.[COLLECTION_ID].documents` is now `databases.[DATABASE_ID].ollections.[COLLECTION_ID].documents`
|
||||
- `collections.[COLLECTION_ID]` is now `databases.[DATABASE_ID].collections.[COLLECTION_ID]`
|
||||
- `collections.[COLLECTION_ID].documents` is now `databases.[DATABASE_ID].collections.[COLLECTION_ID].documents`
|
||||
- After Migration a Database called `default` is created for all your existing Database Collections
|
||||
|
||||
## Features
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity, expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at team@appwrite.io. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
+95
-79
@@ -1,14 +1,14 @@
|
||||
# Contributing
|
||||
|
||||
We would ❤️ for you to contribute to Appwrite and help make it better! We want contributing to Appwrite to be fun, enjoyable, and educational for anyone and everyone. All contributions are welcome, including issues, new docs as well as updates and tweaks, blog posts, workshops, and more.
|
||||
We would ❤️ you to contribute to Appwrite and help make it better! We want contributing to Appwrite to be fun, enjoyable, and educational for anyone and everyone. All contributions are welcome, including issues, and new docs, as well as updates and tweaks, blog posts, workshops, and more.
|
||||
|
||||
## How to Start?
|
||||
|
||||
If you are worried or don’t know where to start, check out our next section explaining what kind of help we could use and where can you get involved. You can reach out with questions to [Eldad Fux (@eldadfux)](https://twitter.com/eldadfux) or [@appwrite](https://twitter.com/appwrite) on Twitter, and anyone from the [Appwrite team on Discord](https://discord.gg/GSeTUeA). You can also submit an issue, and a maintainer can guide you!
|
||||
If you are worried or don’t know where to start, check out the next section that explains what kind of help we could use and where you can get involved. You can send your questions to [@appwrite](https://twitter.com/appwrite) on Twitter or to anyone from the [Appwrite team on Discord](https://appwrite.io/discord). You can also submit an issue, and a maintainer can guide you!
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Help us keep Appwrite open and inclusive. Please read and follow our [Code of Conduct](/CODE_OF_CONDUCT.md).
|
||||
Help us keep Appwrite open and inclusive. Please read and follow our [Code of Conduct](https://github.com/appwrite/.github/blob/main/CODE_OF_CONDUCT.md).
|
||||
|
||||
## Submit a Pull Request 🚀
|
||||
|
||||
@@ -24,13 +24,13 @@ doc-548-submit-a-pull-request-section-to-contribution-guide
|
||||
|
||||
When `TYPE` can be:
|
||||
|
||||
- **feat** - is a new feature
|
||||
- **feat** - a new feature
|
||||
- **doc** - documentation only changes
|
||||
- **cicd** - changes related to CI/CD system
|
||||
- **fix** - a bug fix
|
||||
- **refactor** - code change that neither fixes a bug nor adds a feature
|
||||
|
||||
**All PRs must include a commit message with the changes description!**
|
||||
**All PRs must include a commit message with the description of the changes made!**
|
||||
|
||||
For the initial start, fork the project and use git clone command to download the repository to your computer. A standard procedure for working on an issue would be to:
|
||||
|
||||
@@ -40,33 +40,33 @@ For the initial start, fork the project and use git clone command to do
|
||||
$ git pull
|
||||
```
|
||||
|
||||
2. Create new branch from `master` like: `doc-548-submit-a-pull-request-section-to-contribution-guide`<br/>
|
||||
2. Create a new branch from `master` like: `doc-548-submit-a-pull-request-section-to-contribution-guide`.<br/>
|
||||
|
||||
```
|
||||
$ git checkout -b [name_of_your_new_branch]
|
||||
```
|
||||
|
||||
3. Work - commit - repeat ( be sure to be in your branch )
|
||||
3. Work - commit - repeat (make sure you're on the correct branch!)
|
||||
|
||||
4. Before you push your changes, make sure your code follows the `PSR12` coding standards , which is the standard Appwrite follows currently. You can easily do this by running the formatter.
|
||||
4. Before you push your changes, make sure your code follows the `PSR12` coding standards, which is the standard that Appwrite currently follows. You can easily do this by running the formatter.
|
||||
|
||||
```bash
|
||||
./vendor/bin/phpcbf <your file path>
|
||||
composer format <your file path>
|
||||
```
|
||||
|
||||
Now, go a step further by running the linter by the following command to manually fix the issues the formatter wasn't able to fix.
|
||||
Now, go a step further by running the linter using the following command to manually fix the issues the formatter wasn't able to.
|
||||
|
||||
```bash
|
||||
./vendor/bin/phpcs <your file path>
|
||||
composer lint <your file path>
|
||||
```
|
||||
|
||||
This will give you a list of errors for you to rectify , if there is an instance you need more information on the errors being displayed you can pass in additional command line arguments. More list of available arguments can be found [here](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage). A very useful command line argument is `--report=diff`. This will give you the expected changes by the linter for easy fixing of formatting issues.
|
||||
This will give you a list of errors to rectify. If you need more information on the errors, you can pass in additional command line arguments to get more verbose information. More lists of available arguments can be found [here](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage). A very useful command line argument is `--report=diff`. This will give you the expected changes by the linter for easy fixing of formatting issues.
|
||||
|
||||
```bash
|
||||
./vendor/bin/phpcs --report=diff <your file path>
|
||||
composer lint --report=diff <your file path>
|
||||
```
|
||||
|
||||
5. Push changes to GitHub
|
||||
5. Push changes to GitHub.
|
||||
|
||||
```
|
||||
$ git push origin [name_of_your_new_branch]
|
||||
@@ -76,23 +76,24 @@ $ git push origin [name_of_your_new_branch]
|
||||
If you go to your repository on GitHub, you'll see a `Compare & pull request` button. Click on that button.
|
||||
7. Start a Pull Request
|
||||
Now submit the pull request and click on `Create pull request`.
|
||||
8. Get a code review approval/reject
|
||||
9. After approval, merge your PR
|
||||
8. Get a code review approval/reject.
|
||||
9. After approval, merge your PR.
|
||||
10. GitHub will automatically delete the branch after the merge is done. (they can still be restored).
|
||||
|
||||
## Setup From Source
|
||||
|
||||
To set up a working **development environment**, just fork the project git repository and install the backend and frontend dependencies using the proper package manager and create run the docker-compose stack.
|
||||
|
||||
> If you just want to install Appwrite for day-to-day usage and not as a code maintainer use this [installation guide](https://github.com/appwrite/appwrite#installation).
|
||||
|
||||
Please note that these instructions are for setting a functional dev environment. If you want to set up an Appwrite instance to integrate into your app, you should probably try and install Appwrite by using the instructions in the [getting started guide](https://appwrite.io/docs/getting-started-for-web) or in the main [README](README.md) file.
|
||||
> If you just want to install Appwrite for day-to-day use and not as a contributor, you can reference the [installation guide](https://github.com/appwrite/appwrite#installation), the [getting started guide](https://appwrite.io/docs/getting-started-for-web), or the main [README](README.md) file.
|
||||
|
||||
```bash
|
||||
git clone git@github.com:[YOUR_FORK_HERE]/appwrite.git
|
||||
|
||||
cd appwrite
|
||||
|
||||
git submodule update --init
|
||||
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
@@ -116,7 +117,7 @@ After finishing the installation process, you can start writing and editing code
|
||||
|
||||
#### Advanced Topics
|
||||
|
||||
We love to create issues that are good for beginners and label them as `good first issue` or `hacktoberfest`, but some more advanced topics might require extra knowledge. Below is a list of links you can use to learn more about some of the more advance topics that will help you master the Appwrite codebase.
|
||||
We love to create issues that are good for beginners and label them as `good first issue` or `hacktoberfest`, but some more advanced topics might require extra knowledge. Below is a list of links you can use to learn about the more advanced topics that will help you master the Appwrite codebase.
|
||||
|
||||
##### Tools and Libs
|
||||
|
||||
@@ -142,14 +143,14 @@ Learn more at our [Technology Stack](#technology-stack) section.
|
||||
|
||||
##### Security
|
||||
|
||||
- [Appwrite Auth and ACL](https://github.com/appwrite/appwrite/blob/0.7.x/docs/specs/authentication.drawio.svg)
|
||||
- [Appwrite Auth and ACL](https://github.com/appwrite/appwrite/blob/master/docs/specs/authentication.drawio.svg)
|
||||
- [OAuth](https://en.wikipedia.org/wiki/OAuth)
|
||||
- [Encryption](https://medium.com/searchencrypt/what-is-encryption-how-does-it-work-e8f20e340537#:~:text=Encryption%20is%20a%20process%20that,%2C%20or%20decrypt%2C%20the%20information.)
|
||||
- [Hashing](https://searchsqlserver.techtarget.com/definition/hashing#:~:text=Hashing%20is%20the%20transformation%20of,it%20using%20the%20original%20value.)
|
||||
|
||||
## Architecture
|
||||
|
||||
Appwrite's current structure is a combination of both [Monolithic](https://en.wikipedia.org/wiki/Monolithic_application) and [Microservice](https://en.wikipedia.org/wiki/Microservices) architectures, but our final goal, as we grow, is to be using only microservices.
|
||||
Appwrite's current structure is a combination of both [Monolithic](https://en.wikipedia.org/wiki/Monolithic_application) and [Microservice](https://en.wikipedia.org/wiki/Microservices) architectures.
|
||||
|
||||
---
|
||||
|
||||
@@ -187,20 +188,23 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
|
||||
├── src # Supporting libraries (each lib has one role, common libs are released as individual projects)
|
||||
│ └── Appwrite
|
||||
│ ├── Auth
|
||||
│ ├── Database
|
||||
│ ├── Detector
|
||||
│ ├── Docker
|
||||
| ├── DSN
|
||||
│ ├── Event
|
||||
│ ├── Extend
|
||||
│ ├── GraphQL
|
||||
│ ├── Messaging
|
||||
│ ├── Migration
|
||||
│ ├── Network
|
||||
│ ├── OpenSSL
|
||||
│ ├── Realtime
|
||||
│ ├── Promises
|
||||
│ ├── Resque
|
||||
│ ├── Specification
|
||||
│ ├── Task
|
||||
│ ├── Template
|
||||
│ ├── URL
|
||||
│ ├── Usage
|
||||
│ └── Utopia
|
||||
└── tests # End to end & unit tests
|
||||
├── e2e
|
||||
@@ -212,13 +216,13 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
|
||||
|
||||
Appwrite's main API container is designed as a monolithic app. This is a decision we made to allow us to develop the project faster while still being a very small team.
|
||||
|
||||
Although the Appwrite API is a monolithic app, it has a very clear separation of concern as each internal service or worker is separated by its container, which will allow us as we grow to start breaking services for better maintenance and scalability.
|
||||
Although the Appwrite API is a monolithic app, it has a very clear separation of concern as each internal service or worker is separated by its container, which allows us to start breaking services for better maintenance and scalability as we grow.
|
||||
|
||||
### The Microservice Part
|
||||
|
||||
Each container in Appwrite is a microservice on its own. Each service is an independent process that can scale without regard to any of the other services.
|
||||
|
||||
Currently, all of the Appwrite microservices are intended to communicate using the TCP protocol over a private network. You should be aware to not expose any of the services to the public-facing network, besides the public port 80 and 443, who, by default, are used to expose the Appwrite HTTP API.
|
||||
Currently, all the Appwrite microservices are intended to communicate using the TCP protocol over a private network. With the exception of the public-facing port 80 and 443, which by default are used to expose the Appwrite HTTP API, you should **avoid exposing any other services' ports**.
|
||||
|
||||
## Ports
|
||||
|
||||
@@ -226,43 +230,43 @@ Appwrite dev version uses ports 80 and 443 as an entry point to the Appwrite API
|
||||
|
||||
## Technology Stack
|
||||
|
||||
To start helping us to improve the Appwrite server by submitting code, prior knowledge of Appwrite's technology stack can help you with getting started.
|
||||
To start helping us to improve the Appwrite server by submitting code, prior knowledge of Appwrite's technology stack can help you get started.
|
||||
|
||||
Appwrite stack is combined from a variety of open-source technologies and tools. Appwrite backend API is written primarily with PHP version 7 and above on top of the [Utopia PHP framework](https://github.com/utopia-php/framework). The Appwrite frontend is built with tools like gulp, less, and [litespeed.js](https://github.com/litespeed-js). We use Docker as the container technology to package the Appwrite server for easy integration on-cloud, on-premise, or on-localhosts.
|
||||
Appwrite stack is a combination of a variety of open-source technologies and tools. Appwrite backend API is written primarily with PHP version 7 and above, on top of the [Utopia PHP framework](https://github.com/utopia-php/framework). The Appwrite frontend is built with tools like gulp, less, and [litespeed.js](https://github.com/litespeed-js). We use Docker as the container technology to package the Appwrite server for easy on-cloud, on-premise, or on-localhost integration.
|
||||
|
||||
### Other Technologies
|
||||
|
||||
- Redis - for managing cache and in-memory data (currently, we do not use Redis for persistent data)
|
||||
- MariaDB - for database storage and queries
|
||||
- InfluxDB - for managing stats and time-series based data
|
||||
- Statsd - for sending data over UDP protocol (using Telegraf)
|
||||
- ClamAV - for validating and scanning storage files
|
||||
- Redis - for managing cache and in-memory data (currently, we do not use Redis for persistent data).
|
||||
- MariaDB - for database storage and queries.
|
||||
- InfluxDB - for managing stats and time-series based data.
|
||||
- Statsd - for sending data over UDP protocol (using Telegraf).
|
||||
- ClamAV - for validating and scanning storage files.
|
||||
- Imagemagick - for manipulating and managing image media files.
|
||||
- Webp - for better compression of images on supporting clients
|
||||
- SMTP - for sending email messages and alerts
|
||||
- Resque - for managing data queues and scheduled tasks over a Redis server
|
||||
- Webp - for better compression of images on supporting clients.
|
||||
- SMTP - for sending email messages and alerts.
|
||||
- Resque - for managing data queues and scheduled tasks over a Redis server.
|
||||
|
||||
## Package Managers
|
||||
|
||||
Appwrite uses a package manager for managing code dependencies for both backend and frontend development. We try our best to avoid creating any unnecessary, and any new dependency to the project is subjected to a lead developer review and approval.
|
||||
Appwrite uses a package manager for managing code dependencies for both backend and frontend development. We try our best to avoid creating any unnecessary dependencies. New dependency to the project is subjected to a lead developer's review and approval.
|
||||
|
||||
Many of Appwrite's internal modules are also used as dependencies to allow other Appwrite's projects to reuse them and as a way to contribute them back to the community.
|
||||
Many of Appwrite's internal modules are also used as dependencies to allow other Appwrite projects to reuse them and as a way to contribute back to the community.
|
||||
|
||||
Appwrite uses [PHP's Composer](https://getcomposer.org/) for managing dependencies on the server-side and [JS NPM](https://www.npmjs.com/) for managing dependencies on the frontend side.
|
||||
|
||||
## Coding Standards
|
||||
|
||||
Appwrite is following the [PHP-FIG standards](https://www.php-fig.org/). Currently, we are using both PSR-0 and PSR-12 for coding standards and autoloading standards.
|
||||
Appwrite follows the [PHP-FIG standards](https://www.php-fig.org/). Currently, we use both PSR-0 and PSR-12 for coding standards and autoloading standards.
|
||||
|
||||
We use prettier for our JS coding standards and auto-formatting our code.
|
||||
We use prettier for our JS coding standards and auto-formatting for our code.
|
||||
|
||||
## Scalability, Speed, and Performance
|
||||
|
||||
Appwrite is built to scale. Please keep in mind that the Appwrite stack can run in different environments and different scales.
|
||||
|
||||
We wish Appwrite will be as easy to set up and in a single, localhost, and easy to grow to a large environment with dozens and even hundreds of instances.
|
||||
We intend Appwrite to be as easy to set up as possible in a single localhost, and to grow easily into a large environment with dozens and even hundreds of instances.
|
||||
|
||||
When contributing code, please take into account the following considerations:
|
||||
When contributing code, please take into account the following:
|
||||
|
||||
- Response Time
|
||||
- Throughput
|
||||
@@ -273,25 +277,25 @@ When contributing code, please take into account the following considerations:
|
||||
- Background Jobs
|
||||
- Task Execution Time
|
||||
|
||||
## Security & Privacy
|
||||
## Security and Privacy
|
||||
|
||||
Security and privacy are extremely important to Appwrite, developers, and users alike. Make sure to follow the best industry standards and practices.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Please avoid introducing new dependencies to Appwrite without consulting the team. New dependencies can be very helpful but also introduce new security and privacy issues, complexity, and impact total docker image size.
|
||||
Please avoid introducing new dependencies to Appwrite without consulting the team. New dependencies can be very helpful, but they also introduce new security and privacy risks, add complexity, and impact the total docker image size.
|
||||
|
||||
Adding a new dependency should have vital value on the product with minimum possible risk.
|
||||
Adding a new dependency should have vital value for the product with minimum possible risk.
|
||||
|
||||
## Introducing New Features
|
||||
|
||||
We would 💖 you to contribute to Appwrite, but we would also like to make sure Appwrite is as great as possible and loyal to its vision and mission statement 🙏.
|
||||
We would 💖 you to contribute to Appwrite, but we also want to ensure Appwrite is loyal to its vision and mission statement 🙏.
|
||||
|
||||
For us to find the right balance, please open an issue explaining your ideas before introducing a new pull request.
|
||||
|
||||
This will allow the Appwrite community to have sufficient discussion about the new feature value and how it fits in the product roadmap and vision.
|
||||
This will allow the Appwrite community to sufficiently discuss the new feature value and how it fits within the product roadmap and vision.
|
||||
|
||||
This is also important for the Appwrite lead developers to be able to give technical input and different emphasis regarding the feature design and architecture. Some bigger features might need to go through our [RFC process](https://github.com/appwrite/rfc).
|
||||
This is also important for the Appwrite lead developers to be able to provide technical input and potentially a different emphasis regarding the feature design and architecture. Some bigger features might need to go through our [RFC process](https://github.com/appwrite/rfc).
|
||||
|
||||
## Build
|
||||
|
||||
@@ -315,30 +319,34 @@ The Runtimes for all supported cloud functions (multicore builds) can be found a
|
||||
|
||||
## Generate SDK
|
||||
|
||||
For generating a new console SDK follow the next steps:
|
||||
The following steps are used to generate a new console SDK:
|
||||
|
||||
1. Update the console spec file located at `app/config/specs/swagger2-0.12.x.console.json` from the dynamic version located at `https://localhost/specs/swagger2?platform=console`
|
||||
2. Generate a new SDK using the command `php app/cli.php sdks`
|
||||
3. Change your working dir using `cd app/sdks/console-web`
|
||||
4. Build the new SDK `npm run build`
|
||||
5. Copy `iife/sdk.js` to `appwrite.js`
|
||||
6. Go back to the root of the project `run npm run build`
|
||||
1. Update the console spec file located at `app/config/specs/swagger2-<version-number>.console.json` using Appwrite Tasks. Run the `php app/cli.php specs <version-number> normal` command in a running `appwrite/appwrite` container.
|
||||
2. Generate a new SDK using the command `php app/cli.php sdks`.
|
||||
3. Change your working dir using `cd app/sdks/console-web`.
|
||||
4. Build the new SDK `npm run build`.
|
||||
5. Copy `iife/sdk.js` to `appwrite.js`.
|
||||
6. Go back to the root of the project `run npm run build`.
|
||||
|
||||
## Checklist for Releasing SDKs
|
||||
|
||||
Things to remember when releasing SDKs
|
||||
Things to remember when releasing SDKs:
|
||||
|
||||
- Update the Changelogs in **docs/sdks** (right now only Dart and Flutter are using these)
|
||||
- Update **GETTING_STARTED.md** in **docs/sdks** for each SDKs if any changes in the related APIs in there
|
||||
- Update SDK versions as required on **app/config/platforms.php**
|
||||
- Generate SDKs using the command `php app/cli.php sdks` and follow the instructions
|
||||
- Release new tags on GitHub repository for each SDKs
|
||||
- Update the Changelogs in **docs/sdks** (right now only Dart and Flutter are using these).
|
||||
- Update **GETTING_STARTED.md** in **docs/sdks** for each SDKs if any changes in the related APIs are in there.
|
||||
- Update SDK versions as required on **app/config/platforms.php**.
|
||||
- Generate SDKs using the command `php app/cli.php sdks` and follow the instructions.
|
||||
- Release new tags on GitHub repository for each SDK.
|
||||
|
||||
## Debug
|
||||
|
||||
Appwrite uses [yasd](https://github.com/swoole/yasd) debugger, which can be made available during build of Appwrite. You can connect to the debugger using VS Code [PHP Debug](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug) extension or if you are in PHP Storm you don't need any plugin. Below are the settings required for remote debugger connection.
|
||||
Appwrite uses [yasd](https://github.com/swoole/yasd) debugger, which can be made available during build of Appwrite. You can connect to the debugger using VS Code's [PHP Debug](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug) extension.
|
||||
|
||||
First, you need to create an init file. Duplicate **dev/yasd_init.php.stub** file and name it **dev/yasd_init.php** and there change the IP address to your development machine's IP. Without the proper IP address debugger wont connect. And you also need to set **DEBUG** build arg in **appwrite** service in **docker-compose.yml** file.
|
||||
If you are in PHP Storm you don't need any plugin. Below are the settings required for remote debugger connection:
|
||||
|
||||
1. Create an init file.
|
||||
2. Duplicate **dev/yasd_init.php.stub** file and name it **dev/yasd_init.php**.
|
||||
3. Set **DEBUG** build arg in **appwrite** service in **docker-compose.yml** file.
|
||||
|
||||
### VS Code Launch Configuration
|
||||
|
||||
@@ -356,7 +364,7 @@ First, you need to create an init file. Duplicate **dev/yasd_init.php.stub** fil
|
||||
|
||||
### PHPStorm Setup
|
||||
|
||||
In settings, go to **Languages & Frameworks** > **PHP** > **Debug**, there under **Xdebug** set the debug port to **9005** and enable **can accept external connections** checkbox.
|
||||
In settings, go to **Languages & Frameworks** > **PHP** > **Debug**, under **Xdebug** set the debug port to **9005** and enable the **can accept external connections** checkbox.
|
||||
|
||||
## Tests
|
||||
|
||||
@@ -378,7 +386,7 @@ To run end-2-end tests use:
|
||||
docker compose exec appwrite test /usr/src/code/tests/e2e
|
||||
```
|
||||
|
||||
To run end-2-end tests for a spcific service use:
|
||||
To run end-2-end tests for a specific service use:
|
||||
|
||||
```bash
|
||||
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName]
|
||||
@@ -413,18 +421,26 @@ We use some automation tools to help us keep a healthy codebase.
|
||||
|
||||
```bash
|
||||
# Run on all files
|
||||
./vendor/bin/phpcbf
|
||||
composer format
|
||||
# Run on single file or folder
|
||||
./vendor/bin/phpcbf <your file path>
|
||||
composer format <your file path>
|
||||
```
|
||||
|
||||
**Run Linter:**
|
||||
|
||||
```bash
|
||||
# Run on all files
|
||||
./vendor/bin/phpcs
|
||||
composer lint
|
||||
# Run on single file or folder
|
||||
./vendor/bin/phpcs <your file path>
|
||||
composer lint <your file path>
|
||||
```
|
||||
|
||||
## Clearing the Cache
|
||||
|
||||
If you need to clear the cache, you can do so by running the following command:
|
||||
|
||||
```bash
|
||||
docker compose exec redis redis-cli FLUSHALL
|
||||
```
|
||||
|
||||
## Tutorials
|
||||
@@ -432,34 +448,34 @@ We use some automation tools to help us keep a healthy codebase.
|
||||
From time to time, our team will add tutorials that will help contributors find their way in the Appwrite source code. Below is a list of currently available tutorials:
|
||||
|
||||
- [Adding Support for a New OAuth2 Provider](./docs/tutorials/add-oauth2-provider.md)
|
||||
- [Appwrite Environment Variables](./docs/tutorials/environment-variables.md)
|
||||
- [Running in Production](./docs/tutorials/running-in-production.md)
|
||||
- [Appwrite Environment Variables](./docs/tutorials/add-environment-variable.md)
|
||||
- [Running in Production](https://appwrite.io/docs/production)
|
||||
- [Adding Storage Adapter](./docs/tutorials/add-storage-adapter.md)
|
||||
|
||||
## Other Ways to Help
|
||||
|
||||
Pull requests are great, but there are many other areas where you can help Appwrite.
|
||||
Pull requests are great, but there are many other ways you can help Appwrite.
|
||||
|
||||
### Blogging & Speaking
|
||||
|
||||
Blogging, speaking about, or creating tutorials about one of Appwrite’s many features. Mention [@appwrite](https://twitter.com/appwrite) on Twitter and/or [email team@appwrite.io](mailto:team@appwrite.io) so we can give pointers and tips and help you spread the word by promoting your content on the different Appwrite communication channels. Please add your blog posts and videos of talks to our [Awesome Appwrite](https://github.com/appwrite/awesome-appwrite) repo on GitHub.
|
||||
Blogging, speaking about, or creating tutorials about one of Appwrite’s many features are great ways to get the word out about Appwrite. Mention [@appwrite](https://twitter.com/appwrite) on Twitter and/or [email team@appwrite.io](mailto:team@appwrite.io) so we can give pointers and tips and help you spread the word by promoting your content on the different Appwrite communication channels. Please add your blog posts and videos of talks to our [Awesome Appwrite](https://github.com/appwrite/awesome-appwrite) repo on GitHub.
|
||||
|
||||
### Presenting at Meetups
|
||||
|
||||
Presenting at meetups and conferences about your Appwrite projects. Your unique challenges and successes in building things with Appwrite can provide great speaking material. We’d love to review your talk abstract/CFP, so get in touch with us if you’d like some help!
|
||||
We encourage our contributors to present at meetups and conferences about your Appwrite projects. Your unique challenges and successes in building things with Appwrite can provide great speaking material. We’d love to review your talk abstract/CFP, so get in touch with us if you’d like some help!
|
||||
|
||||
### Sending Feedbacks & Reporting Bugs
|
||||
### Sending Feedbacks and Reporting Bugs
|
||||
|
||||
Sending feedback is a great way for us to understand your different use cases of Appwrite better. If you had any issues, bugs, or want to share about your experience, feel free to do so on our GitHub issues page or at our [Discord channel](https://discord.gg/GSeTUeA).
|
||||
Sending feedback is a great way for us to understand your different use cases of Appwrite better. If you had any issues, bugs, or want to share your experience, feel free to do so on our GitHub issues page or at our [Discord channel](https://discord.gg/GSeTUeA).
|
||||
|
||||
### Submitting New Ideas
|
||||
|
||||
If you think Appwrite could use a new feature, please open an issue on our GitHub repository, stating as much information as you can think about your new idea and it's implications. We would also use this issue to gather more information, get more feedback from the community, and have a proper discussion about the new feature.
|
||||
If you think Appwrite could use a new feature, please open an issue on our GitHub repository, stating as much information as you have about your new idea and its implications. We would also use this issue to gather more information, get more feedback from the community, and have a proper discussion about the new feature.
|
||||
|
||||
### Improving Documentation
|
||||
|
||||
Submitting documentation updates, enhancements, designs, or bug fixes. Spelling or grammar fixes will be very much appreciated.
|
||||
Submitting documentation updates, enhancements, designs, or bug fixes, as well as spelling or grammar fixes is much appreciated.
|
||||
|
||||
### Helping Someone
|
||||
|
||||
Searching for Appwrite on Discord, GitHub, or StackOverflow and helping someone else who needs help. You can also help by teaching others how to contribute to Appwrite's repo!
|
||||
Consider searching for Appwrite on Discord, GitHub, or StackOverflow to help someone who needs help. You can also help by teaching others how to contribute to Appwrite's repo!
|
||||
|
||||
+19
-156
@@ -12,118 +12,24 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
|
||||
--no-plugins --no-scripts --prefer-dist \
|
||||
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
|
||||
|
||||
FROM node:16.14.2-alpine3.15 as node
|
||||
FROM --platform=$BUILDPLATFORM node:16.14.2-alpine3.15 as node
|
||||
|
||||
WORKDIR /usr/local/src/
|
||||
COPY app/console /usr/local/src/console
|
||||
|
||||
COPY package-lock.json /usr/local/src/
|
||||
COPY package.json /usr/local/src/
|
||||
COPY gulpfile.js /usr/local/src/
|
||||
COPY public /usr/local/src/public
|
||||
WORKDIR /usr/local/src/console
|
||||
|
||||
ARG VITE_GA_PROJECT
|
||||
ARG VITE_CONSOLE_MODE
|
||||
ARG VITE_APPWRITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
|
||||
|
||||
ENV VITE_GA_PROJECT=$VITE_GA_PROJECT
|
||||
ENV VITE_CONSOLE_MODE=$VITE_CONSOLE_MODE
|
||||
ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT
|
||||
|
||||
RUN npm ci
|
||||
RUN npm run build
|
||||
|
||||
FROM php:8.0.18-cli-alpine3.15 as compile
|
||||
|
||||
ARG DEBUG=false
|
||||
ENV DEBUG=$DEBUG
|
||||
|
||||
ENV PHP_REDIS_VERSION=5.3.7 \
|
||||
PHP_MONGODB_VERSION=1.13.0 \
|
||||
PHP_SWOOLE_VERSION=v4.8.10 \
|
||||
PHP_IMAGICK_VERSION=3.7.0 \
|
||||
PHP_YAML_VERSION=2.2.2 \
|
||||
PHP_MAXMINDDB_VERSION=v1.11.0
|
||||
|
||||
RUN \
|
||||
apk add --no-cache --virtual .deps \
|
||||
make \
|
||||
automake \
|
||||
autoconf \
|
||||
gcc \
|
||||
g++ \
|
||||
git \
|
||||
zlib-dev \
|
||||
brotli-dev \
|
||||
openssl-dev \
|
||||
yaml-dev \
|
||||
imagemagick \
|
||||
imagemagick-dev \
|
||||
libmaxminddb-dev
|
||||
|
||||
RUN docker-php-ext-install sockets
|
||||
|
||||
FROM compile AS redis
|
||||
RUN \
|
||||
# Redis Extension
|
||||
git clone --depth 1 --branch $PHP_REDIS_VERSION https://github.com/phpredis/phpredis.git && \
|
||||
cd phpredis && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install
|
||||
|
||||
## Swoole Extension
|
||||
FROM compile AS swoole
|
||||
RUN \
|
||||
git clone --depth 1 --branch $PHP_SWOOLE_VERSION https://github.com/swoole/swoole-src.git && \
|
||||
cd swoole-src && \
|
||||
phpize && \
|
||||
./configure --enable-sockets --enable-http2 --enable-openssl && \
|
||||
make && make install && \
|
||||
cd ..
|
||||
|
||||
## Swoole Debugger setup
|
||||
RUN if [ "$DEBUG" == "true" ]; then \
|
||||
cd /tmp && \
|
||||
apk add boost-dev && \
|
||||
git clone --depth 1 https://github.com/swoole/yasd && \
|
||||
cd yasd && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install && \
|
||||
cd ..;\
|
||||
fi
|
||||
|
||||
## Imagick Extension
|
||||
FROM compile AS imagick
|
||||
RUN \
|
||||
git clone --depth 1 --branch $PHP_IMAGICK_VERSION https://github.com/imagick/imagick && \
|
||||
cd imagick && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install
|
||||
|
||||
## YAML Extension
|
||||
FROM compile AS yaml
|
||||
RUN \
|
||||
git clone --depth 1 --branch $PHP_YAML_VERSION https://github.com/php/pecl-file_formats-yaml && \
|
||||
cd pecl-file_formats-yaml && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install
|
||||
|
||||
## Maxminddb extension
|
||||
FROM compile AS maxmind
|
||||
RUN \
|
||||
git clone --depth 1 --branch $PHP_MAXMINDDB_VERSION https://github.com/maxmind/MaxMind-DB-Reader-php.git && \
|
||||
cd MaxMind-DB-Reader-php && \
|
||||
cd ext && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install
|
||||
|
||||
# Mongodb Extension
|
||||
FROM compile as mongodb
|
||||
RUN \
|
||||
git clone --depth 1 --branch $PHP_MONGODB_VERSION https://github.com/mongodb/mongo-php-driver.git && \
|
||||
cd mongo-php-driver && \
|
||||
git submodule update --init && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install
|
||||
|
||||
FROM php:8.0.18-cli-alpine3.15 as final
|
||||
FROM appwrite/base:0.2.2 as final
|
||||
|
||||
LABEL maintainer="team@appwrite.io"
|
||||
|
||||
@@ -193,8 +99,8 @@ ENV _APP_SERVER=swoole \
|
||||
_APP_SMTP_SECURE= \
|
||||
_APP_SMTP_USERNAME= \
|
||||
_APP_SMTP_PASSWORD= \
|
||||
_APP_PHONE_PROVIDER= \
|
||||
_APP_PHONE_FROM= \
|
||||
_APP_SMS_PROVIDER= \
|
||||
_APP_SMS_FROM= \
|
||||
_APP_FUNCTIONS_SIZE_LIMIT=30000000 \
|
||||
_APP_FUNCTIONS_TIMEOUT=900 \
|
||||
_APP_FUNCTIONS_CONTAINERS=10 \
|
||||
@@ -207,47 +113,17 @@ ENV _APP_SERVER=swoole \
|
||||
_APP_SETUP=self-hosted \
|
||||
_APP_VERSION=$VERSION \
|
||||
_APP_USAGE_STATS=enabled \
|
||||
_APP_USAGE_AGGREGATION_INTERVAL=30 \
|
||||
# 14 Days = 1209600 s
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600 \
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT=1209600 \
|
||||
# 1 Day = 86400 s
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE=86400 \
|
||||
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000 \
|
||||
_APP_MAINTENANCE_INTERVAL=86400 \
|
||||
_APP_LOGGING_PROVIDER= \
|
||||
_APP_LOGGING_CONFIG=
|
||||
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
RUN \
|
||||
apk update \
|
||||
&& apk add --no-cache --virtual .deps \
|
||||
make \
|
||||
automake \
|
||||
autoconf \
|
||||
gcc \
|
||||
g++ \
|
||||
curl-dev \
|
||||
&& apk add --no-cache \
|
||||
libstdc++ \
|
||||
certbot \
|
||||
brotli-dev \
|
||||
yaml-dev \
|
||||
imagemagick \
|
||||
imagemagick-dev \
|
||||
libmaxminddb-dev \
|
||||
certbot \
|
||||
docker-cli \
|
||||
libgomp \
|
||||
&& docker-php-ext-install sockets opcache pdo_mysql \
|
||||
&& apk del .deps \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
RUN \
|
||||
mkdir -p $DOCKER_CONFIG/cli-plugins \
|
||||
&& ARCH=$(uname -m) && if [ $ARCH == "armv7l" ]; then ARCH="armv7"; fi \
|
||||
&& curl -SL https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-linux-$ARCH -o $DOCKER_CONFIG/cli-plugins/docker-compose \
|
||||
&& chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
|
||||
RUN \
|
||||
if [ "$DEBUG" == "true" ]; then \
|
||||
apk add boost boost-dev; \
|
||||
@@ -256,20 +132,12 @@ RUN \
|
||||
WORKDIR /usr/src/code
|
||||
|
||||
COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor
|
||||
COPY --from=node /usr/local/src/public/dist /usr/src/code/public/dist
|
||||
COPY --from=swoole /usr/local/lib/php/extensions/no-debug-non-zts-20200930/swoole.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/yasd.so* /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=redis /usr/local/lib/php/extensions/no-debug-non-zts-20200930/redis.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=imagick /usr/local/lib/php/extensions/no-debug-non-zts-20200930/imagick.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=yaml /usr/local/lib/php/extensions/no-debug-non-zts-20200930/yaml.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=maxmind /usr/local/lib/php/extensions/no-debug-non-zts-20200930/maxminddb.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=mongodb /usr/local/lib/php/extensions/no-debug-non-zts-20200930/mongodb.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=node /usr/local/src/console/build /usr/src/code/console
|
||||
|
||||
# Add Source Code
|
||||
COPY ./app /usr/src/code/app
|
||||
COPY ./bin /usr/local/bin
|
||||
COPY ./docs /usr/src/code/docs
|
||||
COPY ./public/fonts /usr/src/code/public/fonts
|
||||
COPY ./public/images /usr/src/code/public/images
|
||||
COPY ./src /usr/src/code/src
|
||||
|
||||
# Set Volumes
|
||||
@@ -314,12 +182,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
||||
|
||||
# Enable Extensions
|
||||
RUN echo extension=swoole.so >> /usr/local/etc/php/conf.d/swoole.ini
|
||||
RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini
|
||||
RUN echo extension=imagick.so >> /usr/local/etc/php/conf.d/imagick.ini
|
||||
RUN echo extension=yaml.so >> /usr/local/etc/php/conf.d/yaml.ini
|
||||
RUN echo extension=maxminddb.so >> /usr/local/etc/php/conf.d/maxminddb.ini
|
||||
RUN if [ "$DEBUG" == "true" ]; then printf "zend_extension=yasd \nyasd.debug_mode=remote \nyasd.init_file=/usr/local/dev/yasd_init.php \nyasd.remote_port=9005 \nyasd.log_level=-1" >> /usr/local/etc/php/conf.d/yasd.ini; fi
|
||||
RUN if [ "$DEBUG" == "true" ]; then printf "zend_extension=yasd \nyasd.debug_mode=remote \nyasd.init_file=/usr/src/code/dev/yasd_init.php \nyasd.remote_port=9005 \nyasd.log_level=-1" >> /usr/local/etc/php/conf.d/yasd.ini; fi
|
||||
|
||||
RUN if [ "$DEBUG" == "true" ]; then echo "opcache.enable=0" >> /usr/local/etc/php/conf.d/appwrite.ini; fi
|
||||
RUN echo "opcache.preload_user=www-data" >> /usr/local/etc/php/conf.d/appwrite.ini
|
||||
@@ -331,4 +194,4 @@ RUN echo "opcache.jit=1235" >> /usr/local/etc/php/conf.d/appwrite.ini
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD [ "php", "app/http.php", "-dopcache.preload=opcache.preload=/usr/src/code/app/preload.php" ]
|
||||
CMD [ "php", "app/http.php", "-dopcache.preload=opcache.preload=/usr/src/code/app/preload.php" ]
|
||||
+26
-18
@@ -1,3 +1,6 @@
|
||||
> 准备好迎接狂风暴'云'了吗? 🌩 ☂️
|
||||
> Appwrite Cloud即将到来!你能够通过https://appwrite.io/cloud了解更多的资讯, 注册即可领取试用额度哦
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://appwrite.io" target="_blank"><img width="260" height="39" src="https://appwrite.io/images/appwrite.svg" alt="Appwrite Logo"></a>
|
||||
@@ -8,17 +11,22 @@
|
||||
<br />
|
||||
</p>
|
||||
|
||||
<!-- [](https://hacktoberfest.appwrite.io) -->
|
||||
<!-- [](https://travis-ci.com/appwrite/appwrite) -->
|
||||
[](https://appwrite.io/company/careers)
|
||||
[](https://hacktoberfest.appwrite.io)
|
||||
[](https://appwrite.io/discord?r=Github)
|
||||
[](https://hub.docker.com/r/appwrite/appwrite)
|
||||
[](https://travis-ci.com/appwrite/appwrite)
|
||||
[](https://github.com/appwrite/appwrite/actions)
|
||||
[](https://twitter.com/appwrite)
|
||||
[](docs/tutorials/add-translations.md)
|
||||
[](https://store.appwrite.io)
|
||||
|
||||
<!-- [](https://hub.docker.com/r/appwrite/appwrite) -->
|
||||
<!-- [](docs/tutorials/add-translations.md) -->
|
||||
<!-- [](https://store.appwrite.io) -->
|
||||
|
||||
[English](README.md) | 简体中文
|
||||
|
||||
Appwrite是一个基于Docker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面极简了从零编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
|
||||
[**宣布带有数据库关系的 Appwrite 1.3!了解最新消息!**](https://dev.to/appwrite/join-celebrations-appwrite-13-ships-relationships-57fc)
|
||||
|
||||
Appwrite是一个基于Docker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面简化了从零开始编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
|
||||
|
||||
Appwrite 可以提供给开发者用户验证,外部授权,用户数据读写检索,文件储存,图像处理,云函数计算,[等多种服务](https://appwrite.io/docs).
|
||||
|
||||
@@ -50,7 +58,7 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
|
||||
|
||||
Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 Kubernetes、Docker Swarm 或 Rancher)上运行 Appwrite。
|
||||
|
||||
开始运行 Appwrite 服务器的最简单方法是运行我们的 docker-compose 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://dockerdocs.cn/get-docker/index.html):
|
||||
启动 Appwrite 服务器的最简单方法是运行我们的 docker-compose 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://dockerdocs.cn/get-docker/index.html):
|
||||
|
||||
### Unix
|
||||
|
||||
@@ -59,7 +67,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:0.15.1
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -71,23 +79,23 @@ 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:0.15.1
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
|
||||
```powershell
|
||||
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:0.15.1
|
||||
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.3.1
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
|
||||
|
||||
需要自定义容器构架,请查看我们的 Docker [环境变量](https://appwrite.io/docs/environment-variables) 文档。您还可以参考我们的 [docker-compose.yml](https://gist.github.com/eldadfux/977869ff6bdd7312adfd4e629ee15cc5#file-docker-compose-yml) 文件手动设置环境。
|
||||
需要自定义容器构架,请查看我们的 Docker [环境变量](https://appwrite.io/docs/environment-variables) 文档。您还可以参考我们的 [docker-compose.yml](https://appwrite.io/install/compose) 和 [.env](https://appwrite.io/install/env) 文件手动设置环境。
|
||||
|
||||
### 从旧版本升级
|
||||
|
||||
@@ -128,7 +136,7 @@ docker run -it --rm ,
|
||||
|
||||
#### 服务器
|
||||
* ✅ [NodeJS](https://github.com/appwrite/sdk-for-node) (由 Appwrite 团队维护)
|
||||
* ✅ [PHP](https://github.com/appwrite/sdk-for-php) (由 Appwr实验 团队维护)
|
||||
* ✅ [PHP](https://github.com/appwrite/sdk-for-php) (由 Appwrite 团队维护)
|
||||
* ✅ [Dart](https://github.com/appwrite/sdk-for-dart) - (由 Appwrite 团队维护)
|
||||
* ✅ [Deno](https://github.com/appwrite/sdk-for-deno) - **公测** (由 Appwrite 团队维护)
|
||||
* ✅ [Ruby](https://github.com/appwrite/sdk-for-ruby) (由 Appwrite 团队维护)
|
||||
@@ -154,7 +162,7 @@ Appwrite API 界面层利用后台缓存和任务委派来提供极速的响应
|
||||
|
||||
## 贡献代码
|
||||
|
||||
所有代码贡献 - 包括来自具有直接提交更改权限的贡献者 - 都必须提交PR请求并在合并分支之前得到核心开发人员的批准。这是为了确保正确审查所有代码。
|
||||
为了确保正确审查,所有代码贡献 - 包括来自具有直接提交更改权限的贡献者 - 都必须提交PR请求并在合并分支之前得到核心开发人员的批准。
|
||||
|
||||
我们欢迎所有人提交PR!如果您愿意提供帮助,可以在 [贡献指南](CONTRIBUTING.md) 中了解有关如何为项目做出贡献的更多信息。
|
||||
|
||||
@@ -164,7 +172,7 @@ Appwrite API 界面层利用后台缓存和任务委派来提供极速的响应
|
||||
|
||||
## 订阅我们
|
||||
|
||||
加入我们在世界各地不断发展的社区!请参阅我们的官方 [博客](https://medium.com/appwrite-io)。在 [Twitter](https://twitter.com/appwrite)、[Facebook 页面](https://www.facebook.com/appwrite.io)、[Facebook 群组](https://www.facebook)、[开发者社区](https://dev.to/appwrite) 等平台订阅我们或加入我们的 [Discord 社区](https://discord.gg/GSeTUeA) 以获得更多帮助,想法和讨论。
|
||||
加入我们在世界各地不断发展的社区!请参阅我们的官方 [博客](https://medium.com/appwrite-io)。在 [Twitter](https://twitter.com/appwrite)、[Facebook 页面](https://www.facebook.com/appwrite.io)、[Facebook 群组](https://www.facebook.com/appwrite.io/groups/)、[开发者社区](https://dev.to/appwrite) 等平台订阅我们或加入我们的 [Discord 社区](https://discord.gg/GSeTUeA) 以获得更多帮助,想法和讨论。
|
||||
|
||||
## 版权说明
|
||||
|
||||
|
||||
@@ -11,22 +11,33 @@
|
||||
<br />
|
||||
</p>
|
||||
|
||||
<!-- [](https://hacktoberfest.appwrite.io) -->
|
||||
|
||||
<!-- [](https://travis-ci.com/appwrite/appwrite) -->
|
||||
|
||||
[](https://appwrite.io/company/careers)
|
||||
[](https://hacktoberfest.appwrite.io)
|
||||
[](https://appwrite.io/discord?r=Github)
|
||||
[](https://hub.docker.com/r/appwrite/appwrite)
|
||||
[](https://github.com/appwrite/appwrite/actions)
|
||||
[](https://github.com/appwrite/appwrite/actions)
|
||||
[](https://twitter.com/appwrite)
|
||||
[](docs/tutorials/add-translations.md)
|
||||
[](https://store.appwrite.io)
|
||||
|
||||
<!-- [](https://hub.docker.com/r/appwrite/appwrite) -->
|
||||
<!-- [](docs/tutorials/add-translations.md) -->
|
||||
<!-- [](https://store.appwrite.io) -->
|
||||
|
||||
English | [简体中文](README-CN.md)
|
||||
|
||||
[**Appwrite 0.15 has been released! Learn what's new!**](https://dev.to/appwrite/announcing-appwrite-015-with-phone-authentication-more-5cjj)
|
||||
[**Announcing Appwrite 1.3 with Database Relationships! Learn what's new!**](https://dev.to/appwrite/join-celebrations-appwrite-13-ships-relationships-57fc)
|
||||
|
||||
Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps packaged as a set of Docker<nobr> microservices. Appwrite abstracts the complexity and repetitiveness required to build a modern backend API from scratch and allows you to build secure apps faster.
|
||||
|
||||
Using Appwrite, you can easily integrate your app with user authentication & multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, and [more services](https://appwrite.io/docs).
|
||||
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, 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>
|
||||
|
||||

|
||||
|
||||
@@ -54,7 +65,7 @@ Table of Contents:
|
||||
|
||||
## Installation
|
||||
|
||||
Appwrite backend server is designed to run in a container environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool like Kubernetes, Docker Swarm, or Rancher.
|
||||
Appwrite is designed to run in a containerized environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool, such as Kubernetes, Docker Swarm, or Rancher.
|
||||
|
||||
The easiest way to start running your Appwrite server is by running our docker-compose file. Before running the installation command, make sure you have [Docker](https://www.docker.com/products/docker-desktop) installed on your machine:
|
||||
|
||||
@@ -65,7 +76,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:0.15.1
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -77,91 +88,116 @@ 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:0.15.1
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
|
||||
```powershell
|
||||
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:0.15.1
|
||||
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.3.1
|
||||
```
|
||||
|
||||
Once the Docker installation completes, 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 installation completes.
|
||||
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.
|
||||
|
||||
|
||||
For advanced production and custom installation, check out our Docker [environment variables](https://appwrite.io/docs/environment-variables) docs. You can also use our public [docker-compose.yml](https://gist.github.com/eldadfux/977869ff6bdd7312adfd4e629ee15cc5#file-docker-compose-yml) file to manually set up an environment.
|
||||
For advanced production and custom installation, check out our Docker [environment variables](https://appwrite.io/docs/environment-variables) docs. You can also use our public [docker-compose.yml](https://appwrite.io/install/compose) and [.env](https://appwrite.io/install/env) files to manually set up an environment.
|
||||
|
||||
### Upgrade from an Older Version
|
||||
|
||||
If you are upgrading your Appwrite server from an older version, you should use the Appwrite migration tool once your setup is completed. For more information regarding this, check out the [Installation Docs](https://appwrite.io/docs/installation).
|
||||
|
||||
## One-Click Setups
|
||||
|
||||
In addition to running Appwrite locally, you can also launch Appwrite using a pre-configured setup. This allows you to get up and running quickly with Appwrite without installing Docker on your local machine.
|
||||
|
||||
Choose from one of the providers below:
|
||||
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://marketplace.digitalocean.com/apps/appwrite">
|
||||
<img width="50" height="39" src="public/images/integrations/digitalocean-logo.svg" alt="DigitalOcean Logo" />
|
||||
<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>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Getting Started
|
||||
|
||||
Getting started with Appwrite is as easy as creating a new project, choosing your platform, and integrating its SDK into your code. You can easily get started with your platform of choice by reading one of our Getting Started tutorials.
|
||||
|
||||
* [Getting Started for Web](https://appwrite.io/docs/getting-started-for-web)
|
||||
* [Getting Started for Flutter](https://appwrite.io/docs/getting-started-for-flutter)
|
||||
* [Getting Started for Apple](https://appwrite.io/docs/getting-started-for-apple)
|
||||
* [Getting Started for Android](https://appwrite.io/docs/getting-started-for-android)
|
||||
* [Getting Started for Server](https://appwrite.io/docs/getting-started-for-server)
|
||||
* [Getting Started for CLI](https://appwrite.io/docs/command-line)
|
||||
- [Getting Started for Web](https://appwrite.io/docs/getting-started-for-web)
|
||||
- [Getting Started for Flutter](https://appwrite.io/docs/getting-started-for-flutter)
|
||||
- [Getting Started for Apple](https://appwrite.io/docs/getting-started-for-apple)
|
||||
- [Getting Started for Android](https://appwrite.io/docs/getting-started-for-android)
|
||||
- [Getting Started for Server](https://appwrite.io/docs/getting-started-for-server)
|
||||
- [Getting Started for CLI](https://appwrite.io/docs/command-line)
|
||||
|
||||
### Services
|
||||
|
||||
* [**Account**](https://appwrite.io/docs/client/account) - Manage current user authentication and account. Track and manage the user sessions, devices, sign-in methods, and security logs.
|
||||
* [**Users**](https://appwrite.io/docs/server/users) - Manage and list all project users when in admin mode.
|
||||
* [**Teams**](https://appwrite.io/docs/client/teams) - Manage and group users in teams. Manage memberships, invites, and user roles within a team.
|
||||
* [**Databases**](https://appwrite.io/docs/client/databases) - Manage databases, collections and documents. Read, create, update, and delete documents and filter lists of document collections using advanced filters.
|
||||
* [**Storage**](https://appwrite.io/docs/client/storage) - Manage storage files. Read, create, delete, and preview files. Manipulate the preview of your files to fit your app perfectly. All files are scanned by ClamAV and stored in a secure and encrypted way.
|
||||
* [**Functions**](https://appwrite.io/docs/server/functions) - Customize your Appwrite server by executing your custom code in a secure, isolated environment. You can trigger your code on any Appwrite system event, manually or using a CRON schedule.
|
||||
* [**Locale**](https://appwrite.io/docs/client/locale) - Track your user's location, and manage your app locale-based data.
|
||||
* [**Avatars**](https://appwrite.io/docs/client/avatars) - Manage your users' avatars, countries' flags, browser icons, credit card symbols, and generate QR codes.
|
||||
- [**Account**](https://appwrite.io/docs/client/account) - Manage current user authentication and account. Track and manage the user sessions, devices, sign-in methods, and security logs.
|
||||
- [**Users**](https://appwrite.io/docs/server/users) - Manage and list all project users when building backend integrations with Server SDKs.
|
||||
- [**Teams**](https://appwrite.io/docs/client/teams) - Manage and group users in teams. Manage memberships, invites, and user roles within a team.
|
||||
- [**Databases**](https://appwrite.io/docs/client/databases) - Manage databases, collections, and documents. Read, create, update, and delete documents and filter lists of document collections using advanced filters.
|
||||
- [**Storage**](https://appwrite.io/docs/client/storage) - Manage storage files. Read, create, delete, and preview files. Manipulate the preview of your files to perfectly fit your app. All files are scanned by ClamAV and stored in a secure and encrypted way.
|
||||
- [**Functions**](https://appwrite.io/docs/server/functions) - Customize your Appwrite server by executing your custom code in a secure, isolated environment. You can trigger your code on any Appwrite system event either manually or using a CRON schedule.
|
||||
- [**Realtime**](https://appwrite.io/docs/realtime) - Listen to real-time events for any of your Appwrite services including users, storage, functions, databases, and more.
|
||||
- [**Locale**](https://appwrite.io/docs/client/locale) - Track your user's location and manage your app locale-based data.
|
||||
- [**Avatars**](https://appwrite.io/docs/client/avatars) - Manage your users' avatars, countries' flags, browser icons, and credit card symbols. Generate QR codes from links or plaintext strings.
|
||||
|
||||
For the complete API documentation, visit [https://appwrite.io/docs](https://appwrite.io/docs). For more tutorials, news and announcements check out our [blog](https://medium.com/appwrite-io) and [Discord Server](https://discord.gg/GSeTUeA).
|
||||
|
||||
### SDKs
|
||||
|
||||
Below is a list of currently supported platforms and languages. If you wish to help us add support to your platform of choice, you can go over to our [SDK Generator](https://github.com/appwrite/sdk-generator) project and view our [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md).
|
||||
Below is a list of currently supported platforms and languages. If you would like to help us add support to your platform of choice, you can go over to our [SDK Generator](https://github.com/appwrite/sdk-generator) project and view our [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md).
|
||||
|
||||
#### Client
|
||||
* ✅ [Web](https://github.com/appwrite/sdk-for-web) (Maintained by the Appwrite Team)
|
||||
* ✅ [Flutter](https://github.com/appwrite/sdk-for-flutter) (Maintained by the Appwrite Team)
|
||||
* ✅ [Apple](https://github.com/appwrite/sdk-for-apple) - **Beta** (Maintained by the Appwrite Team)
|
||||
* ✅ [Android](https://github.com/appwrite/sdk-for-android) (Maintained by the Appwrite Team)
|
||||
|
||||
- ✅ [Web](https://github.com/appwrite/sdk-for-web) (Maintained by the Appwrite Team)
|
||||
- ✅ [Flutter](https://github.com/appwrite/sdk-for-flutter) (Maintained by the Appwrite Team)
|
||||
- ✅ [Apple](https://github.com/appwrite/sdk-for-apple) - **Beta** (Maintained by the Appwrite Team)
|
||||
- ✅ [Android](https://github.com/appwrite/sdk-for-android) (Maintained by the Appwrite Team)
|
||||
|
||||
#### Server
|
||||
* ✅ [NodeJS](https://github.com/appwrite/sdk-for-node) (Maintained by the Appwrite Team)
|
||||
* ✅ [PHP](https://github.com/appwrite/sdk-for-php) (Maintained by the Appwrite Team)
|
||||
* ✅ [Dart](https://github.com/appwrite/sdk-for-dart) - (Maintained by the Appwrite Team)
|
||||
* ✅ [Deno](https://github.com/appwrite/sdk-for-deno) - **Beta** (Maintained by the Appwrite Team)
|
||||
* ✅ [Ruby](https://github.com/appwrite/sdk-for-ruby) (Maintained by the Appwrite Team)
|
||||
* ✅ [Python](https://github.com/appwrite/sdk-for-python) (Maintained by the Appwrite Team)
|
||||
* ✅ [Kotlin](https://github.com/appwrite/sdk-for-kotlin) - **Beta** (Maintained by the Appwrite Team)
|
||||
* ✅ [Apple](https://github.com/appwrite/sdk-for-apple) - **Beta** (Maintained by the Appwrite Team)
|
||||
* ✅ [.NET](https://github.com/appwrite/sdk-for-dotnet) - **Experimental** (Maintained by the Appwrite Team)
|
||||
|
||||
- ✅ [NodeJS](https://github.com/appwrite/sdk-for-node) (Maintained by the Appwrite Team)
|
||||
- ✅ [PHP](https://github.com/appwrite/sdk-for-php) (Maintained by the Appwrite Team)
|
||||
- ✅ [Dart](https://github.com/appwrite/sdk-for-dart) - (Maintained by the Appwrite Team)
|
||||
- ✅ [Deno](https://github.com/appwrite/sdk-for-deno) - **Beta** (Maintained by the Appwrite Team)
|
||||
- ✅ [Ruby](https://github.com/appwrite/sdk-for-ruby) (Maintained by the Appwrite Team)
|
||||
- ✅ [Python](https://github.com/appwrite/sdk-for-python) (Maintained by the Appwrite Team)
|
||||
- ✅ [Kotlin](https://github.com/appwrite/sdk-for-kotlin) - **Beta** (Maintained by the Appwrite Team)
|
||||
- ✅ [Apple](https://github.com/appwrite/sdk-for-apple) - **Beta** (Maintained by the Appwrite Team)
|
||||
- ✅ [.NET](https://github.com/appwrite/sdk-for-dotnet) - **Experimental** (Maintained by the Appwrite Team)
|
||||
|
||||
#### Community
|
||||
* ✅ [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (Maintained by [Michael Gangolf](https://github.com/m1ga/))
|
||||
* ✅ [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (Maintained by [fenix-hub @GodotNuts](https://github.com/fenix-hub))
|
||||
|
||||
- ✅ [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (Maintained by [Michael Gangolf](https://github.com/m1ga/))
|
||||
- ✅ [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (Maintained by [fenix-hub @GodotNuts](https://github.com/fenix-hub))
|
||||
|
||||
Looking for more SDKs? - Help us by contributing a pull request to our [SDK Generator](https://github.com/appwrite/sdk-generator)!
|
||||
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
Appwrite uses a microservices architecture that was designed for easy scaling and delegation of responsibilities. In addition, Appwrite supports multiple APIs (REST, WebSocket, and GraphQL-soon) to allow you to interact with your resources leveraging your existing knowledge and protocols of choice.
|
||||
Appwrite uses a microservices architecture that was designed for easy scaling and delegation of responsibilities. In addition, Appwrite supports multiple APIs, such as REST, WebSocket, and GraphQL to allow you to interact with your resources by leveraging your existing knowledge and protocols of choice.
|
||||
|
||||
The Appwrite API layer was designed to be extremely fast by leveraging in-memory caching and delegating any heavy-lifting tasks to the Appwrite background workers. The background workers also allow you to precisely control your compute capacity and costs using a message queue to handle the load. You can learn more about our architecture in the [contribution guide](CONTRIBUTING.md#architecture-1).
|
||||
|
||||
## Contributing
|
||||
|
||||
All code contributions - including those of people having commit access - must go through a pull request and be approved by a core developer before being merged. This is to ensure a proper review of all the code.
|
||||
All code contributions, including those of people having commit access, must go through a pull request and be approved by a core developer before being merged. This is to ensure a proper review of all the code.
|
||||
|
||||
We truly ❤️ pull requests! If you wish to help, you can learn more about how you can contribute to this project in the [contribution guide](CONTRIBUTING.md).
|
||||
|
||||
@@ -171,7 +207,7 @@ For security issues, kindly email us at [security@appwrite.io](mailto:security@a
|
||||
|
||||
## Follow Us
|
||||
|
||||
Join our growing community around the world! See our official [Blog](https://medium.com/appwrite-io). Follow us on [Twitter](https://twitter.com/appwrite), [Facebook Page](https://www.facebook.com/appwrite.io), [Facebook Group](https://www.facebook.com/groups/appwrite.developers/) , [Dev Community](https://dev.to/appwrite) or join our live [Discord server](https://discord.gg/GSeTUeA) for more help, ideas, and discussions.
|
||||
Join our growing community around the world! Check out our official [Blog](https://medium.com/appwrite-io). Follow us on [Twitter](https://twitter.com/appwrite), [Facebook Page](https://www.facebook.com/appwrite.io), [Facebook Group](https://www.facebook.com/groups/appwrite.developers/), [Dev Community](https://dev.to/appwrite) or join our live [Discord server](https://discord.gg/GSeTUeA) for more help, ideas, and discussions.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+7
-5
@@ -2,11 +2,13 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| <= 0.10 | :x: |
|
||||
| 0.11.x | :white_check_mark: |
|
||||
| 0.12.x | :white_check_mark: |
|
||||
| Version | Supported |
|
||||
| --------- | ------------------ |
|
||||
| <= 0.15.x | :x: |
|
||||
| 1.0.x | :white_check_mark: |
|
||||
| 1.1.x | :white_check_mark: |
|
||||
| 1.2.x | :white_check_mark: |
|
||||
| 1.3.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
+2865
-2259
File diff suppressed because it is too large
Load Diff
+87
-20
@@ -50,7 +50,7 @@ return [
|
||||
],
|
||||
Exception::GENERAL_PHONE_DISABLED => [
|
||||
'name' => Exception::GENERAL_PHONE_DISABLED,
|
||||
'description' => 'Phone provider is not configured. Please check the _APP_PHONE_PROVIDER environment variable of your Appwrite server.',
|
||||
'description' => 'Phone provider is not configured. Please check the _APP_SMS_PROVIDER environment variable of your Appwrite server.',
|
||||
'code' => 503,
|
||||
],
|
||||
Exception::GENERAL_ARGUMENT_INVALID => [
|
||||
@@ -70,7 +70,7 @@ return [
|
||||
],
|
||||
Exception::GENERAL_ROUTE_NOT_FOUND => [
|
||||
'name' => Exception::GENERAL_ROUTE_NOT_FOUND,
|
||||
'description' => 'The requested route was not found. Please refer to the docs and try again.',
|
||||
'description' => 'The requested route was not found. Please refer to the API docs and try again.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::GENERAL_CURSOR_NOT_FOUND => [
|
||||
@@ -102,7 +102,7 @@ return [
|
||||
],
|
||||
Exception::USER_ALREADY_EXISTS => [
|
||||
'name' => Exception::USER_ALREADY_EXISTS,
|
||||
'description' => 'A user with the same email ID already exists in your project.',
|
||||
'description' => 'A user with the same email already exists in your project.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::USER_BLOCKED => [
|
||||
@@ -122,12 +122,12 @@ return [
|
||||
],
|
||||
Exception::USER_EMAIL_NOT_WHITELISTED => [
|
||||
'name' => Exception::USER_EMAIL_NOT_WHITELISTED,
|
||||
'description' => 'The user\'s email is not part of the whitelist. Please check the _APP_CONSOLE_WHITELIST_EMAILS environment variable of your Appwrite server.',
|
||||
'description' => 'Console registration is restricted to specific emails. Contact your administrator for more information.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_IP_NOT_WHITELISTED => [
|
||||
'name' => Exception::USER_IP_NOT_WHITELISTED,
|
||||
'description' => 'The user\'s IP address is not part of the whitelist. Please check the _APP_CONSOLE_WHITELIST_IPS environment variable of your Appwrite server.',
|
||||
'description' => 'Console registration is restricted to specific IPs. Contact your administrator for more information.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_INVALID_CREDENTIALS => [
|
||||
@@ -152,7 +152,7 @@ return [
|
||||
],
|
||||
Exception::USER_EMAIL_ALREADY_EXISTS => [
|
||||
'name' => Exception::USER_EMAIL_ALREADY_EXISTS,
|
||||
'description' => 'Another user with the same email already exists in the current project.',
|
||||
'description' => 'A user with the same email already exists in the current project.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::USER_PASSWORD_MISMATCH => [
|
||||
@@ -185,6 +185,11 @@ return [
|
||||
'description' => 'The current user does not have a phone number associated with their account.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::USER_MISSING_ID => [
|
||||
'name' => Exception::USER_MISSING_ID,
|
||||
'description' => 'Missing ID from OAuth2 provider.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Teams */
|
||||
Exception::TEAM_NOT_FOUND => [
|
||||
@@ -194,7 +199,7 @@ return [
|
||||
],
|
||||
Exception::TEAM_INVITE_ALREADY_EXISTS => [
|
||||
'name' => Exception::TEAM_INVITE_ALREADY_EXISTS,
|
||||
'description' => 'The current user has already received an invitation to join the team.',
|
||||
'description' => 'User has already been invited or is already a member of this team',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::TEAM_INVITE_NOT_FOUND => [
|
||||
@@ -218,13 +223,17 @@ return [
|
||||
'code' => 401,
|
||||
],
|
||||
|
||||
|
||||
/** Membership */
|
||||
Exception::MEMBERSHIP_NOT_FOUND => [
|
||||
'name' => Exception::MEMBERSHIP_NOT_FOUND,
|
||||
'description' => 'Membership with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::MEMBERSHIP_ALREADY_CONFIRMED => [
|
||||
'name' => Exception::MEMBERSHIP_ALREADY_CONFIRMED,
|
||||
'description' => 'Membership already confirmed',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Avatars */
|
||||
Exception::AVATAR_SET_NOT_FOUND => [
|
||||
@@ -271,7 +280,7 @@ return [
|
||||
],
|
||||
Exception::STORAGE_FILE_TYPE_UNSUPPORTED => [
|
||||
'name' => Exception::STORAGE_FILE_TYPE_UNSUPPORTED,
|
||||
'description' => 'The file type is not supported.',
|
||||
'description' => 'The given file extension is not supported.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::STORAGE_INVALID_FILE_SIZE => [
|
||||
@@ -325,7 +334,7 @@ return [
|
||||
],
|
||||
Exception::BUILD_NOT_READY => [
|
||||
'name' => Exception::BUILD_NOT_READY,
|
||||
'description' => 'Build with the requested ID is builing and not ready for execution.',
|
||||
'description' => 'Build with the requested ID is building and not ready for execution.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::BUILD_IN_PROGRESS => [
|
||||
@@ -348,6 +357,19 @@ return [
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
/** Databases */
|
||||
Exception::DATABASE_NOT_FOUND => [
|
||||
'name' => Exception::DATABASE_NOT_FOUND,
|
||||
'description' => 'Database not found',
|
||||
'code' => 404
|
||||
],
|
||||
|
||||
Exception::DATABASE_ALREADY_EXISTS => [
|
||||
'name' => Exception::DATABASE_ALREADY_EXISTS,
|
||||
'description' => 'Database already exists',
|
||||
'code' => 409
|
||||
],
|
||||
|
||||
/** Collections */
|
||||
Exception::COLLECTION_NOT_FOUND => [
|
||||
'name' => Exception::COLLECTION_NOT_FOUND,
|
||||
@@ -386,6 +408,16 @@ return [
|
||||
'description' => 'Document with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::DOCUMENT_UPDATE_CONFLICT => [
|
||||
'name' => Exception::DOCUMENT_UPDATE_CONFLICT,
|
||||
'description' => 'Remote document is newer than local.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::DOCUMENT_DELETE_RESTRICTED => [
|
||||
'name' => Exception::DOCUMENT_DELETE_RESTRICTED,
|
||||
'description' => 'Document cannot be deleted because it is referenced by another document.',
|
||||
'code' => 403,
|
||||
],
|
||||
|
||||
/** Attributes */
|
||||
Exception::ATTRIBUTE_NOT_FOUND => [
|
||||
@@ -395,12 +427,12 @@ return [
|
||||
],
|
||||
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 <span class="tag">available</span> state.',
|
||||
'description' => 'The attribute 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 <span class="tag">available</span>. Please try again later.',
|
||||
'description' => 'The requested attribute is not yet available. Please try again later.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_FORMAT_UNSUPPORTED => [
|
||||
@@ -410,7 +442,7 @@ return [
|
||||
],
|
||||
Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED => [
|
||||
'name' => Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED,
|
||||
'description' => 'Default values cannot be set for <span class="tag">array</span> and <span class="tag">required</span> attributes.',
|
||||
'description' => 'Default values cannot be set for array or required attributes.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_ALREADY_EXISTS => [
|
||||
@@ -428,6 +460,11 @@ return [
|
||||
'description' => 'The attribute value is invalid. Please check the type, range and value of the attribute.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_TYPE_INVALID => [
|
||||
'name' => Exception::ATTRIBUTE_TYPE_INVALID,
|
||||
'description' => 'The attribute type is invalid.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Indexes */
|
||||
Exception::INDEX_NOT_FOUND => [
|
||||
@@ -464,24 +501,29 @@ return [
|
||||
],
|
||||
Exception::PROJECT_PROVIDER_UNSUPPORTED => [
|
||||
'name' => Exception::PROJECT_PROVIDER_UNSUPPORTED,
|
||||
'description' => 'The chosen OAuth provider is unsupported. Please check <a href="/docs/client/account?sdk=web-default#accountCreateOAuth2Session"> the docs</a> for the complete list of supported OAuth providers.',
|
||||
'description' => 'The chosen OAuth provider is unsupported. Please check the <a href="/docs/client/account?sdk=web-default#accountCreateOAuth2Session">Create OAuth2 Session docs</a> for the complete list of supported OAuth providers.',
|
||||
'code' => 501,
|
||||
],
|
||||
Exception::PROJECT_INVALID_SUCCESS_URL => [
|
||||
'name' => Exception::PROJECT_INVALID_SUCCESS_URL,
|
||||
'description' => 'Invalid URL received for OAuth success redirect.',
|
||||
'description' => 'Invalid redirect URL for OAuth success.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::PROJECT_INVALID_FAILURE_URL => [
|
||||
'name' => Exception::PROJECT_INVALID_FAILURE_URL,
|
||||
'description' => 'Invalid URL received for OAuth failure redirect.',
|
||||
'description' => 'Invalid redirect URL for OAuth failure.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::PROJECT_MISSING_USER_ID => [
|
||||
'name' => Exception::PROJECT_MISSING_USER_ID,
|
||||
'description' => 'Failed to obtain user ID from the OAuth provider.',
|
||||
Exception::PROJECT_RESERVED_PROJECT => [
|
||||
'name' => Exception::PROJECT_RESERVED_PROJECT,
|
||||
'description' => 'The project ID is reserved. Please choose another project ID.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::PROJECT_KEY_EXPIRED => [
|
||||
'name' => Exception::PROJECT_KEY_EXPIRED,
|
||||
'description' => 'The project key has expired. Please generate a new key using the Appwrite console.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::WEBHOOK_NOT_FOUND => [
|
||||
'name' => Exception::WEBHOOK_NOT_FOUND,
|
||||
'description' => 'Webhook with the requested ID could not be found.',
|
||||
@@ -507,9 +549,34 @@ return [
|
||||
'description' => 'A Domain with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::VARIABLE_NOT_FOUND => [
|
||||
'name' => Exception::VARIABLE_NOT_FOUND,
|
||||
'description' => 'Variable with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::VARIABLE_ALREADY_EXISTS => [
|
||||
'name' => Exception::VARIABLE_ALREADY_EXISTS,
|
||||
'description' => 'Variable with the same ID already exists in your project.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::DOMAIN_VERIFICATION_FAILED => [
|
||||
'name' => Exception::DOMAIN_VERIFICATION_FAILED,
|
||||
'description' => 'Domain verification for the requested domain has failed.',
|
||||
'code' => 401,
|
||||
]
|
||||
],
|
||||
Exception::DOMAIN_TARGET_INVALID => [
|
||||
'name' => Exception::DOMAIN_TARGET_INVALID,
|
||||
'description' => 'Your Appwrite instance is not publicly accessible. Please check the _APP_DOMAIN_TARGET environment variable of your Appwrite server.',
|
||||
'code' => 501,
|
||||
],
|
||||
Exception::GRAPHQL_NO_QUERY => [
|
||||
'name' => Exception::GRAPHQL_NO_QUERY,
|
||||
'description' => 'Param "query" is not optional.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::GRAPHQL_TOO_MANY_QUERIES => [
|
||||
'name' => Exception::GRAPHQL_TOO_MANY_QUERIES,
|
||||
'description' => 'Too many queries.',
|
||||
'code' => 400,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* List of publicly accessiable system events
|
||||
* List of publicly accessible system events
|
||||
*/
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
@@ -183,13 +183,16 @@ return [
|
||||
],
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a bucket is created.'
|
||||
'$description' => 'This event triggers when a team is created.'
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a bucket is deleted.',
|
||||
'$description' => 'This event triggers when a team is deleted.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a bucket is updated.',
|
||||
'$description' => 'This event triggers when a team is updated.',
|
||||
'prefs' => [
|
||||
'$description' => 'This event triggers when a team\'s preferences are updated.',
|
||||
],
|
||||
]
|
||||
],
|
||||
'functions' => [
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"emails.magicSession.signature": "L'équipe {{project}}",
|
||||
"emails.recovery.subject": "Réinitialisation du mot de passe",
|
||||
"emails.recovery.hello": "Bonjour {{name}}",
|
||||
"emails.recovery.body": "Suivez ce lien pour réinitialiser votre mot de passe pour {{projet}}.",
|
||||
"emails.recovery.body": "Suivez ce lien pour réinitialiser votre mot de passe pour {{project}}.",
|
||||
"emails.recovery.footer": "Si vous n'avez pas demandé à réinitialiser votre mot de passe, vous pouvez ignorer ce message.",
|
||||
"emails.recovery.thanks": "Merci",
|
||||
"emails.recovery.signature": "L'équipe {{project}}",
|
||||
@@ -29,7 +29,7 @@
|
||||
"emails.invitation.signature": "L'équipe {{project}}",
|
||||
"emails.certificate.subject": "Échec du certificat pour %s",
|
||||
"emails.certificate.hello": "Bonjour",
|
||||
"emails.certificate.body": "Le certificate pour votre domaine '{{domain}}' n'a pas pu être généré. Ceci est la tentative {{tentative}} et l'échec a été causé par : {{erreur}}",
|
||||
"emails.certificate.body": "Le certificate pour votre domaine '{{domain}}' n'a pas pu être généré. Ceci est la tentative {{attempt}} et l'échec a été causé par : {{error}}",
|
||||
"emails.certificate.footer": "Votre certificat précédent sera valide pendant 30 jours à compter de la première défaillance. Nous vous recommandons fortement d'enquêter sur ce cas, sinon votre domaine se retrouvera sans communication SSL valide.",
|
||||
"emails.certificate.thanks": "Merci",
|
||||
"emails.certificate.signature": "L'équipe {{project}}",
|
||||
|
||||
@@ -6,25 +6,25 @@
|
||||
"emails.verification.subject": "अकाउंट वेरिफिकेशन ",
|
||||
"emails.verification.hello": "नमस्ते {{name}}",
|
||||
"emails.verification.body": "इस लिंक के माध्यम से अपने ईमेल को सत्यापित कीजिये।",
|
||||
"emails.verification.footer": "यदि आपने इस पते को सत्यापित नहीं करना चाहते है, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.verification.footer": "यदि आप इस पते को सत्यापित नहीं करना चाहते हैं, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.verification.thanks": "धन्यवाद",
|
||||
"emails.verification.signature": "{{project}} टीम",
|
||||
"emails.magicSession.subject": "लॉग इन",
|
||||
"emails.magicSession.hello": "नमस्ते,",
|
||||
"emails.magicSession.body": "इस लिंक के माध्यम से लॉग-इन करें।",
|
||||
"emails.magicSession.footer": "यदि आप इस ईमेल द्वारा लॉगिन नहीं करना चाहते है, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.magicSession.footer": "यदि आप इस ईमेल द्वारा लॉगिन नहीं करना चाहते हैं, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.magicSession.thanks": "धन्यवाद",
|
||||
"emails.magicSession.signature": "{{project}} टीम",
|
||||
"emails.recovery.subject": "पासवर्ड रीसेट",
|
||||
"emails.recovery.hello": "नमस्ते {{name}}",
|
||||
"emails.recovery.body": "इस लिंक के माध्यम से अपना {{project}} पासवर्ड रीसेट करें।",
|
||||
"emails.recovery.footer": "यदि आप अपना पासवर्ड रिसेट नहीं करना चाहते है, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.recovery.footer": "यदि आप अपना पासवर्ड रीसेट नहीं करना चाहते हैं, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.recovery.thanks": "धन्यवाद",
|
||||
"emails.recovery.signature": "{{project}} टीम",
|
||||
"emails.invitation.subject": "%s टीम का यहाँ %s पर आमंत्रण",
|
||||
"emails.invitation.hello": "नमस्ते",
|
||||
"emails.invitation.body": "यह मेल आपको इसलिए भेजा गया था क्योंकि {{owner}} आपको {{team}} टीम का सदस्य बनाना चाहते थे, जो {{project}} से जुड़ा हुआ है।",
|
||||
"emails.invitation.footer": "यदि आप इसमे रूचि नहीं रखते, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.invitation.body": "यह मेल आपको इसलिए भेजा गया है क्योंकि {{owner}} आपको {{team}} टीम का सदस्य बनाना चाहते है, जो {{project}} से जुड़ा हुआ है।",
|
||||
"emails.invitation.footer": "यदि आप इसमें रूचि नहीं रखते, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.invitation.thanks": "धन्यवाद",
|
||||
"emails.invitation.signature": "{{project}} टीम",
|
||||
"locale.country.unknown": "अज्ञात",
|
||||
@@ -35,7 +35,7 @@
|
||||
"countries.ae": "संयुक्त अरब अमीरात",
|
||||
"countries.ar": "अर्जेंटीना",
|
||||
"countries.am": "आर्मीनिया",
|
||||
"countries.ag": "अंटीगुआ और बारबूडा",
|
||||
"countries.ag": "एंटीगुआ और बारबूडा",
|
||||
"countries.au": "ऑस्ट्रेलिया",
|
||||
"countries.at": "ऑस्ट्रिया",
|
||||
"countries.az": "अज़रबैजान",
|
||||
@@ -46,7 +46,7 @@
|
||||
"countries.bd": "बांग्लादेश",
|
||||
"countries.bg": "बुल्गारिया",
|
||||
"countries.bh": "बहरीन",
|
||||
"countries.bs": "बहामास",
|
||||
"countries.bs": "द बाहामास",
|
||||
"countries.ba": "बॉस्निया और हर्ज़ेगोविना",
|
||||
"countries.by": "बेलारूस",
|
||||
"countries.bz": "बेलीज़",
|
||||
|
||||
@@ -5,57 +5,63 @@
|
||||
"emails.sender": "%s 小組",
|
||||
"emails.verification.subject": "帳戶驗證",
|
||||
"emails.verification.hello": "嗨 {{name}}",
|
||||
"emails.verification.body": "按照此鏈接驗證您的電子郵件地址。",
|
||||
"emails.verification.body": "按照此連結驗證您的電子郵件地址。",
|
||||
"emails.verification.footer": "如果您沒有要求驗證此地址,則可以忽略此消息。",
|
||||
"emails.verification.thanks": "謝謝",
|
||||
"emails.verification.signature": "{{project}} 團隊",
|
||||
"emails.magicSession.subject": "登錄",
|
||||
"emails.magicSession.hello": "嗨",
|
||||
"emails.magicSession.body": "點此鏈接登錄。",
|
||||
"emails.magicSession.footer": "如果您沒有要求使用此電子郵件登錄,則可以忽略此消息。",
|
||||
"emails.magicSession.subject": "登入",
|
||||
"emails.magicSession.hello": "嗨,",
|
||||
"emails.magicSession.body": "點此連結登入。",
|
||||
"emails.magicSession.footer": "如果您沒有要求使用此電子郵件登入,則可以忽略此消息。",
|
||||
"emails.magicSession.thanks": "謝謝",
|
||||
"emails.magicSession.signature": "{{project}} 團隊",
|
||||
"emails.recovery.subject": "重設密碼",
|
||||
"emails.recovery.hello": "你好 {{name}}",
|
||||
"emails.recovery.body": "按照此鏈接重置您的 {{project}} 密碼.",
|
||||
"emails.recovery.hello": "您好 {{name}}",
|
||||
"emails.recovery.body": "按照此連結重置您的 {{project}} 密碼。",
|
||||
"emails.recovery.footer": "如果您沒有要求重置密碼,則可以忽略此消息。",
|
||||
"emails.recovery.thanks": "謝謝",
|
||||
"emails.recovery.signature": "{{project}} 團隊",
|
||||
"emails.invitation.subject": "邀請 %s 團隊在 %s",
|
||||
"emails.invitation.hello": "你好",
|
||||
"emails.invitation.body": "這封郵件發送給您是因為 {{owner}} 想邀請您成為 {{team}} 團隊在 {{project}}.",
|
||||
"emails.invitation.hello": "您好",
|
||||
"emails.invitation.body": "發送這封郵件給您是因為 {{owner}} 想邀請您成為 {{team}} 團隊在 {{project}}。",
|
||||
"emails.invitation.footer": "如果您不感興趣,可以忽略此消息。",
|
||||
"emails.invitation.thanks": "謝謝",
|
||||
"emails.invitation.signature": "{{project}} 團隊",
|
||||
"emails.certificate.subject": "%s 的憑證更新失敗",
|
||||
"emails.certificate.hello": "您好",
|
||||
"emails.certificate.body": "無法產生您的域名 '{{domain}}' 的憑證。這是第 {{attempt}} 次嘗試,失敗原因是:{{error}}",
|
||||
"emails.certificate.footer": "您的上一個憑證將會在第一次失敗的 30 天後失效。我們強烈建議您調查這件事情,否則您的域名將會無法進行有效的 SSL 通訊。",
|
||||
"emails.certificate.thanks": "謝謝",
|
||||
"emails.certificate.signature": "{{project}} 團隊",
|
||||
"locale.country.unknown": "未知",
|
||||
"countries.af": "阿富汗",
|
||||
"countries.ao": "安哥拉",
|
||||
"countries.al": "阿爾巴尼亞",
|
||||
"countries.ad": "安道爾",
|
||||
"countries.ae": "阿拉伯聯合酋長國",
|
||||
"countries.ae": "阿拉伯聯合大公國",
|
||||
"countries.ar": "阿根廷",
|
||||
"countries.am": "亞美尼亞",
|
||||
"countries.ag": "安提瓜和巴布達",
|
||||
"countries.au": "澳大利亞",
|
||||
"countries.ag": "安地卡及巴布達",
|
||||
"countries.au": "澳洲",
|
||||
"countries.at": "奧地利",
|
||||
"countries.az": "阿塞拜疆",
|
||||
"countries.bi": "布隆迪",
|
||||
"countries.az": "亞塞拜然",
|
||||
"countries.bi": "蒲隆地",
|
||||
"countries.be": "比利時",
|
||||
"countries.bj": "貝甯",
|
||||
"countries.bf": "布基納法索",
|
||||
"countries.bd": "孟加拉國",
|
||||
"countries.bj": "貝南",
|
||||
"countries.bf": "布吉納法索",
|
||||
"countries.bd": "孟加拉",
|
||||
"countries.bg": "保加利亞",
|
||||
"countries.bh": "巴林",
|
||||
"countries.bs": "巴哈馬",
|
||||
"countries.ba": "波黑",
|
||||
"countries.by": "白羅斯",
|
||||
"countries.bz": "伯利茲",
|
||||
"countries.ba": "波士尼亞與赫塞哥維納",
|
||||
"countries.by": "白俄羅斯",
|
||||
"countries.bz": "貝里斯",
|
||||
"countries.bo": "玻利維亞",
|
||||
"countries.br": "巴西",
|
||||
"countries.bb": "巴巴多斯",
|
||||
"countries.bn": "文萊",
|
||||
"countries.bb": "巴貝多",
|
||||
"countries.bn": "汶萊",
|
||||
"countries.bt": "不丹",
|
||||
"countries.bw": "博茨瓦納",
|
||||
"countries.bw": "波札那",
|
||||
"countries.cf": "中非共和國",
|
||||
"countries.ca": "加拿大",
|
||||
"countries.ch": "瑞士",
|
||||
@@ -66,42 +72,42 @@
|
||||
"countries.cd": "剛果民主共和國",
|
||||
"countries.cg": "剛果共和國",
|
||||
"countries.co": "哥倫比亞",
|
||||
"countries.km": "科摩",
|
||||
"countries.cv": "佛得角",
|
||||
"countries.cr": "哥斯達黎加",
|
||||
"countries.km": "葛摩",
|
||||
"countries.cv": "維德角",
|
||||
"countries.cr": "哥斯大黎加",
|
||||
"countries.cu": "古巴",
|
||||
"countries.cy": "塞浦路斯",
|
||||
"countries.cy": "賽普勒斯",
|
||||
"countries.cz": "捷克",
|
||||
"countries.de": "德國",
|
||||
"countries.dj": "吉布提",
|
||||
"countries.dj": "吉布地",
|
||||
"countries.dm": "多米尼克",
|
||||
"countries.dk": "丹麥",
|
||||
"countries.do": "多明尼加共和國",
|
||||
"countries.dz": "阿爾及利亞",
|
||||
"countries.ec": "厄瓜多爾",
|
||||
"countries.ec": "厄瓜多",
|
||||
"countries.eg": "埃及",
|
||||
"countries.er": "厄立特裏亞",
|
||||
"countries.er": "厄利垂亞",
|
||||
"countries.es": "西班牙",
|
||||
"countries.ee": "愛沙尼亞",
|
||||
"countries.et": "埃塞俄比亞",
|
||||
"countries.et": "衣索比亞",
|
||||
"countries.fi": "芬蘭",
|
||||
"countries.fj": "斐濟",
|
||||
"countries.fr": "法國",
|
||||
"countries.fm": "密克羅尼西亞",
|
||||
"countries.ga": "加蓬",
|
||||
"countries.ga": "加彭",
|
||||
"countries.gb": "英國",
|
||||
"countries.ge": "格魯吉亞",
|
||||
"countries.gh": "加納",
|
||||
"countries.ge": "喬治亞",
|
||||
"countries.gh": "迦納",
|
||||
"countries.gn": "幾內亞",
|
||||
"countries.gm": "岡比亞",
|
||||
"countries.gw": "幾內亞比紹",
|
||||
"countries.gm": "甘比亞",
|
||||
"countries.gw": "幾內亞比索",
|
||||
"countries.gq": "赤道幾內亞",
|
||||
"countries.gr": "希臘",
|
||||
"countries.gd": "格林納達",
|
||||
"countries.gt": "危地馬拉",
|
||||
"countries.gy": "圭亞那",
|
||||
"countries.hn": "洪都拉斯",
|
||||
"countries.hr": "克羅地亞",
|
||||
"countries.gd": "格瑞那達",
|
||||
"countries.gt": "瓜地馬拉",
|
||||
"countries.gy": "蓋亞那",
|
||||
"countries.hn": "宏都拉斯",
|
||||
"countries.hr": "克羅埃西亞",
|
||||
"countries.ht": "海地",
|
||||
"countries.hu": "匈牙利",
|
||||
"countries.id": "印度尼西亞",
|
||||
@@ -111,26 +117,26 @@
|
||||
"countries.iq": "伊拉克",
|
||||
"countries.is": "冰島",
|
||||
"countries.il": "以色列",
|
||||
"countries.it": "意大利",
|
||||
"countries.it": "義大利",
|
||||
"countries.jm": "牙買加",
|
||||
"countries.jo": "約旦",
|
||||
"countries.jp": "日本",
|
||||
"countries.kz": "哈薩克斯坦",
|
||||
"countries.ke": "肯尼亞",
|
||||
"countries.kg": "吉爾吉斯斯坦",
|
||||
"countries.kz": "哈薩克",
|
||||
"countries.ke": "肯亞",
|
||||
"countries.kg": "吉爾吉斯",
|
||||
"countries.kh": "柬埔寨",
|
||||
"countries.ki": "基裏巴斯",
|
||||
"countries.kn": "聖基茨和尼維斯",
|
||||
"countries.ki": "吉里巴斯",
|
||||
"countries.kn": "聖克里斯多福及尼維斯",
|
||||
"countries.kr": "韓國",
|
||||
"countries.kw": "科威特",
|
||||
"countries.la": "老撾",
|
||||
"countries.la": "寮國",
|
||||
"countries.lb": "黎巴嫩",
|
||||
"countries.lr": "利比裏亞",
|
||||
"countries.lr": "賴比瑞亞",
|
||||
"countries.ly": "利比亞",
|
||||
"countries.lc": "聖盧西亞",
|
||||
"countries.li": "列支敦士登",
|
||||
"countries.lk": "斯裏蘭卡",
|
||||
"countries.ls": "萊索托",
|
||||
"countries.lc": "聖露西亞",
|
||||
"countries.li": "列支敦斯登",
|
||||
"countries.lk": "斯里蘭卡",
|
||||
"countries.ls": "賴索托",
|
||||
"countries.lt": "立陶宛",
|
||||
"countries.lu": "盧森堡",
|
||||
"countries.lv": "拉脫維亞",
|
||||
@@ -138,90 +144,90 @@
|
||||
"countries.mc": "摩納哥",
|
||||
"countries.md": "摩爾多瓦",
|
||||
"countries.mg": "馬達加斯加",
|
||||
"countries.mv": "馬爾代夫",
|
||||
"countries.mv": "馬爾地夫",
|
||||
"countries.mx": "墨西哥",
|
||||
"countries.mh": "馬紹爾群島",
|
||||
"countries.mk": "馬其頓共和國",
|
||||
"countries.ml": "馬裏",
|
||||
"countries.mt": "馬耳他",
|
||||
"countries.ml": "馬利",
|
||||
"countries.mt": "馬爾他",
|
||||
"countries.mm": "緬甸",
|
||||
"countries.me": "黑山",
|
||||
"countries.me": "蒙特內哥羅",
|
||||
"countries.mn": "蒙古",
|
||||
"countries.mz": "莫桑比克",
|
||||
"countries.mr": "毛裏塔尼亞",
|
||||
"countries.mu": "毛裏求斯",
|
||||
"countries.mw": "馬拉維",
|
||||
"countries.mz": "莫三比克",
|
||||
"countries.mr": "茅利塔尼亞",
|
||||
"countries.mu": "模里西斯",
|
||||
"countries.mw": "馬拉威",
|
||||
"countries.my": "馬來西亞",
|
||||
"countries.na": "納米比亞",
|
||||
"countries.ne": "尼日爾",
|
||||
"countries.ng": "尼日利亞",
|
||||
"countries.ne": "尼日",
|
||||
"countries.ng": "奈及利亞",
|
||||
"countries.ni": "尼加拉瓜",
|
||||
"countries.nl": "荷蘭",
|
||||
"countries.no": "挪威",
|
||||
"countries.np": "尼泊爾",
|
||||
"countries.nr": "瑙魯",
|
||||
"countries.nz": "新西蘭",
|
||||
"countries.nr": "諾魯",
|
||||
"countries.nz": "紐西蘭",
|
||||
"countries.om": "阿曼",
|
||||
"countries.pk": "巴基斯坦",
|
||||
"countries.pa": "巴拿馬",
|
||||
"countries.pe": "秘魯",
|
||||
"countries.ph": "菲律賓",
|
||||
"countries.pw": "帕勞",
|
||||
"countries.pg": "巴布亞新幾內亞",
|
||||
"countries.pw": "帛琉",
|
||||
"countries.pg": "巴布亞紐幾內亞",
|
||||
"countries.pl": "波蘭",
|
||||
"countries.kp": "北朝鮮",
|
||||
"countries.kp": "北韓",
|
||||
"countries.pt": "葡萄牙",
|
||||
"countries.py": "巴拉圭",
|
||||
"countries.qa": "卡塔爾",
|
||||
"countries.qa": "卡達",
|
||||
"countries.ro": "羅馬尼亞",
|
||||
"countries.ru": "俄羅斯",
|
||||
"countries.rw": "盧旺達",
|
||||
"countries.sa": "沙特阿拉伯",
|
||||
"countries.rw": "盧安達",
|
||||
"countries.sa": "沙烏地阿拉伯",
|
||||
"countries.sd": "蘇丹",
|
||||
"countries.sn": "塞內加爾",
|
||||
"countries.sg": "新加坡",
|
||||
"countries.sb": "所羅門群島",
|
||||
"countries.sl": "塞拉利昂",
|
||||
"countries.sb": "索羅門群島",
|
||||
"countries.sl": "獅子山",
|
||||
"countries.sv": "薩爾瓦多",
|
||||
"countries.sm": "聖馬力諾",
|
||||
"countries.so": "索馬裏",
|
||||
"countries.sm": "聖馬利諾",
|
||||
"countries.so": "索馬利亞",
|
||||
"countries.rs": "塞爾維亞",
|
||||
"countries.ss": "南蘇丹",
|
||||
"countries.st": "聖多美和普林西比",
|
||||
"countries.sr": "蘇裏南",
|
||||
"countries.st": "聖多美普林西比",
|
||||
"countries.sr": "蘇利南",
|
||||
"countries.sk": "斯洛伐克",
|
||||
"countries.si": "斯洛文尼亞",
|
||||
"countries.si": "斯洛維尼亞",
|
||||
"countries.se": "瑞典",
|
||||
"countries.sz": "斯威士蘭",
|
||||
"countries.sc": "塞舌爾",
|
||||
"countries.sz": "史瓦帝尼",
|
||||
"countries.sc": "塞席爾",
|
||||
"countries.sy": "敘利亞",
|
||||
"countries.td": "乍得",
|
||||
"countries.td": "查德",
|
||||
"countries.tg": "多哥",
|
||||
"countries.th": "泰國",
|
||||
"countries.tj": "塔吉克斯坦",
|
||||
"countries.tm": "土庫曼斯坦",
|
||||
"countries.tj": "塔吉克",
|
||||
"countries.tm": "土庫曼",
|
||||
"countries.tl": "東帝汶",
|
||||
"countries.to": "湯加",
|
||||
"countries.tt": "特立尼達和多巴哥",
|
||||
"countries.tn": "突尼斯",
|
||||
"countries.to": "東加",
|
||||
"countries.tt": "千里達及托巴哥",
|
||||
"countries.tn": "突尼西亞",
|
||||
"countries.tr": "土耳其",
|
||||
"countries.tv": "圖瓦盧",
|
||||
"countries.tz": "坦桑尼亞",
|
||||
"countries.ug": "烏幹達",
|
||||
"countries.tv": "吐瓦魯",
|
||||
"countries.tz": "坦尚尼亞",
|
||||
"countries.ug": "烏干達",
|
||||
"countries.ua": "烏克蘭",
|
||||
"countries.uy": "烏拉圭",
|
||||
"countries.us": "美國",
|
||||
"countries.uz": "烏茲別克斯",
|
||||
"countries.uz": "烏茲別克",
|
||||
"countries.va": "梵蒂岡",
|
||||
"countries.vc": "聖文森特和格林納丁斯",
|
||||
"countries.vc": "聖文森及格瑞那丁",
|
||||
"countries.ve": "委內瑞拉",
|
||||
"countries.vn": "越南",
|
||||
"countries.vu": "瓦努阿圖",
|
||||
"countries.vu": "萬那杜",
|
||||
"countries.ws": "薩摩亞",
|
||||
"countries.ye": "也門",
|
||||
"countries.ye": "葉門",
|
||||
"countries.za": "南非",
|
||||
"countries.zm": "贊比亞",
|
||||
"countries.zw": "津巴布韋",
|
||||
"countries.zm": "尚比亞",
|
||||
"countries.zw": "辛巴威",
|
||||
"continents.af": "非洲",
|
||||
"continents.an": "南極洲",
|
||||
"continents.as": "亞洲",
|
||||
@@ -229,4 +235,4 @@
|
||||
"continents.na": "北美洲",
|
||||
"continents.oc": "大洋洲",
|
||||
"continents.sa": "南美洲"
|
||||
}
|
||||
}
|
||||
|
||||
+103
-42
@@ -11,11 +11,11 @@ return [
|
||||
'description' => 'Client libraries for integrating with Appwrite to build client-based applications and websites. Read the [getting started for web](/docs/getting-started-for-web) or [getting started for Flutter](/docs/getting-started-for-flutter) tutorials to start building your first application.',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'languages' => [ // TODO change key to 'sdks'
|
||||
'sdks' => [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '9.0.0',
|
||||
'version' => '11.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-web',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -63,7 +63,7 @@ return [
|
||||
[
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'version' => '6.0.0',
|
||||
'version' => '9.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-flutter',
|
||||
'package' => 'https://pub.dev/packages/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -81,11 +81,11 @@ return [
|
||||
[
|
||||
'key' => 'apple',
|
||||
'name' => 'Apple',
|
||||
'version' => '0.6.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
@@ -116,11 +116,11 @@ return [
|
||||
[
|
||||
'key' => 'android',
|
||||
'name' => 'Android',
|
||||
'version' => '0.7.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-android',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
@@ -135,21 +135,44 @@ return [
|
||||
'Java' => 'java',
|
||||
],
|
||||
],
|
||||
// [
|
||||
// 'key' => 'java',
|
||||
// 'name' => 'Java',
|
||||
// 'url' => '',
|
||||
// 'enabled' => false,
|
||||
// 'beta' => false,
|
||||
// 'dev' => false,
|
||||
// 'hidden' => false,
|
||||
// 'family' => APP_PLATFORM_CLIENT,
|
||||
// 'prism' => 'java',
|
||||
// 'source' => false,
|
||||
// 'gitUrl' => 'git@github.com:appwrite/sdk-for-java.git',
|
||||
// 'gitRepoName' => 'sdk-for-java',
|
||||
// 'gitUserName' => 'appwrite',
|
||||
// ],
|
||||
[
|
||||
'key' => 'graphql',
|
||||
'name' => 'GraphQL',
|
||||
'version' => 'October 2021',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'prism' => 'graphql',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-graphql'),
|
||||
'gitUrl' => '',
|
||||
'gitRepoName' => '',
|
||||
'gitUserName' => '',
|
||||
'gitBranch' => '',
|
||||
'isSDK' => false,
|
||||
],
|
||||
[
|
||||
'key' => 'rest',
|
||||
'name' => 'REST',
|
||||
'version' => '',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'prism' => 'http',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-rest'),
|
||||
'gitUrl' => '',
|
||||
'gitRepoName' => '',
|
||||
'gitUserName' => '',
|
||||
'gitBranch' => '',
|
||||
'isSDK' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -158,11 +181,11 @@ return [
|
||||
'name' => 'Console',
|
||||
'enabled' => false,
|
||||
'beta' => false,
|
||||
'languages' => [
|
||||
'sdks' => [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Console',
|
||||
'version' => '6.0.0',
|
||||
'version' => '0.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-console',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
@@ -170,17 +193,17 @@ return [
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CONSOLE,
|
||||
'prism' => 'console',
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
|
||||
'gitUrl' => null,
|
||||
'gitBranch' => null,
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-console.git',
|
||||
'gitBranch' => 'main',
|
||||
'gitRepoName' => 'sdk-for-console',
|
||||
'gitUserName' => 'appwrite',
|
||||
],
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '0.18.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
@@ -204,11 +227,11 @@ return [
|
||||
'description' => 'Libraries for integrating with Appwrite to build server side integrations. Read the [getting started for server](/docs/getting-started-for-server) tutorial to start building your first server integration.',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'languages' => [
|
||||
'sdks' => [
|
||||
[
|
||||
'key' => 'nodejs',
|
||||
'name' => 'Node.js',
|
||||
'version' => '7.0.1',
|
||||
'version' => '9.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-node',
|
||||
'package' => 'https://www.npmjs.com/package/node-appwrite',
|
||||
'enabled' => true,
|
||||
@@ -226,7 +249,7 @@ return [
|
||||
[
|
||||
'key' => 'deno',
|
||||
'name' => 'Deno',
|
||||
'version' => '5.0.0',
|
||||
'version' => '7.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-deno',
|
||||
'package' => 'https://deno.land/x/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -244,7 +267,7 @@ return [
|
||||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '6.0.0',
|
||||
'version' => '8.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -262,7 +285,7 @@ return [
|
||||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '0.10.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
@@ -280,7 +303,7 @@ return [
|
||||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '6.0.0',
|
||||
'version' => '8.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -298,7 +321,7 @@ return [
|
||||
[
|
||||
'key' => 'go',
|
||||
'name' => 'Go',
|
||||
'version' => '0.1.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'package' => '',
|
||||
'enabled' => false,
|
||||
@@ -316,7 +339,7 @@ return [
|
||||
[
|
||||
'key' => 'java',
|
||||
'name' => 'Java',
|
||||
'version' => '0.0.2',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-java',
|
||||
'package' => '',
|
||||
'enabled' => false,
|
||||
@@ -334,7 +357,7 @@ return [
|
||||
[
|
||||
'key' => 'dotnet',
|
||||
'name' => '.NET',
|
||||
'version' => '0.4.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
|
||||
'package' => 'https://www.nuget.org/packages/Appwrite',
|
||||
'enabled' => false,
|
||||
@@ -352,7 +375,7 @@ return [
|
||||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '6.0.0',
|
||||
'version' => '8.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_appwrite',
|
||||
'enabled' => true,
|
||||
@@ -370,11 +393,11 @@ return [
|
||||
[
|
||||
'key' => 'kotlin',
|
||||
'name' => 'Kotlin',
|
||||
'version' => '0.6.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
@@ -392,11 +415,11 @@ return [
|
||||
[
|
||||
'key' => 'swift',
|
||||
'name' => 'Swift',
|
||||
'version' => '0.6.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
@@ -407,6 +430,44 @@ return [
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'main',
|
||||
],
|
||||
[
|
||||
'key' => 'graphql',
|
||||
'name' => 'GraphQL',
|
||||
'version' => 'October 2021',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'prism' => 'graphql',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-graphql'),
|
||||
'gitUrl' => '',
|
||||
'gitRepoName' => '',
|
||||
'gitUserName' => '',
|
||||
'gitBranch' => '',
|
||||
'isSDK' => false,
|
||||
],
|
||||
[
|
||||
'key' => 'rest',
|
||||
'name' => 'REST',
|
||||
'version' => '',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'prism' => 'http',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-rest'),
|
||||
'gitUrl' => '',
|
||||
'gitRepoName' => '',
|
||||
'gitUserName' => '',
|
||||
'gitBranch' => '',
|
||||
'isSDK' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -17,7 +17,7 @@ return [ // Ordered by ABC.
|
||||
'icon' => 'icon-apple',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => 'apple.phtml', // Perperation for adding ability to customized OAuth UI forms, currently handled hardcoded.
|
||||
'form' => 'apple.phtml', // Preparation for adding ability to customized OAuth UI forms, currently handled hardcoded.
|
||||
'beta' => true,
|
||||
'mock' => false,
|
||||
],
|
||||
@@ -31,6 +31,16 @@ return [ // Ordered by ABC.
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'authentik' => [
|
||||
'name' => 'Authentik',
|
||||
'developers' => 'https://goauthentik.io/docs/',
|
||||
'icon' => 'icon-authentik',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => 'authentik.phtml',
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'autodesk' => [
|
||||
'name' => 'Autodesk',
|
||||
'developers' => 'https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/overview/',
|
||||
@@ -91,6 +101,16 @@ return [ // Ordered by ABC.
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'disqus' => [
|
||||
'name' => 'Disqus',
|
||||
'developers' => 'https://disqus.com/api/docs/auth/',
|
||||
'icon' => 'icon-disqus',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'dropbox' => [
|
||||
'name' => 'Dropbox',
|
||||
'developers' => 'https://www.dropbox.com/developers/documentation',
|
||||
@@ -101,6 +121,16 @@ return [ // Ordered by ABC.
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'etsy' => [
|
||||
'name' => 'Etsy',
|
||||
'developers' => 'https://developers.etsy.com/',
|
||||
'icon' => 'icon-etsy',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'facebook' => [
|
||||
'name' => 'Facebook',
|
||||
'developers' => 'https://developers.facebook.com/',
|
||||
@@ -201,6 +231,16 @@ return [ // Ordered by ABC.
|
||||
'beta' => false,
|
||||
'mock' => false
|
||||
],
|
||||
'podio' => [
|
||||
'name' => 'Podio',
|
||||
'developers' => 'https://developers.podio.com/doc/oauth-authorization',
|
||||
'icon' => 'icon-podio',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'salesforce' => [
|
||||
'name' => 'Salesforce',
|
||||
'developers' => 'https://developer.salesforce.com/docs/',
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'name' => 'Default',
|
||||
'default' => true,
|
||||
'disabled' => false,
|
||||
]
|
||||
];
|
||||
+13
-7
@@ -6,6 +6,7 @@ $member = [
|
||||
'public',
|
||||
'home',
|
||||
'console',
|
||||
'graphql',
|
||||
'account',
|
||||
'teams.read',
|
||||
'teams.write',
|
||||
@@ -22,6 +23,7 @@ $member = [
|
||||
];
|
||||
|
||||
$admins = [
|
||||
'graphql',
|
||||
'teams.read',
|
||||
'teams.write',
|
||||
'documents.read',
|
||||
@@ -52,20 +54,24 @@ $admins = [
|
||||
];
|
||||
|
||||
return [
|
||||
Auth::USER_ROLE_GUEST => [
|
||||
'label' => 'Guest',
|
||||
Auth::USER_ROLE_GUESTS => [
|
||||
'label' => 'Guests',
|
||||
'scopes' => [
|
||||
'public',
|
||||
'home',
|
||||
'console',
|
||||
'graphql',
|
||||
'documents.read',
|
||||
'documents.write',
|
||||
'files.read',
|
||||
'files.write',
|
||||
'locale.read',
|
||||
'avatars.read',
|
||||
'execution.write',
|
||||
],
|
||||
],
|
||||
Auth::USER_ROLE_MEMBER => [
|
||||
'label' => 'Member',
|
||||
Auth::USER_ROLE_USERS => [
|
||||
'label' => 'Users',
|
||||
'scopes' => \array_merge($member, []),
|
||||
],
|
||||
Auth::USER_ROLE_ADMIN => [
|
||||
@@ -80,8 +86,8 @@ return [
|
||||
'label' => 'Owner',
|
||||
'scopes' => \array_merge($member, $admins, []),
|
||||
],
|
||||
Auth::USER_ROLE_APP => [
|
||||
'label' => 'Application',
|
||||
'scopes' => ['health.read'],
|
||||
Auth::USER_ROLE_APPS => [
|
||||
'label' => 'Applications',
|
||||
'scopes' => ['health.read', 'graphql'],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use Utopia\App;
|
||||
use Appwrite\Runtimes\Runtimes;
|
||||
|
||||
$runtimes = new Runtimes('v1');
|
||||
$runtimes = new Runtimes('v2');
|
||||
|
||||
$allowList = empty(App::getEnv('_APP_FUNCTIONS_RUNTIMES')) ? [] : \explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES'));
|
||||
|
||||
|
||||
+23
-10
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'/' => [
|
||||
'key' => 'homepage',
|
||||
'name' => 'Homepage',
|
||||
'web/home' => [
|
||||
'key' => 'web/home',
|
||||
'name' => 'Home',
|
||||
'subtitle' => '',
|
||||
'description' => '',
|
||||
'controller' => 'web/home.php',
|
||||
@@ -14,8 +14,8 @@ return [
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
'console' => [
|
||||
'key' => 'console',
|
||||
'web/console' => [
|
||||
'key' => 'web/console',
|
||||
'name' => 'Console',
|
||||
'subtitle' => '',
|
||||
'description' => '',
|
||||
@@ -176,13 +176,26 @@ return [
|
||||
'graphql' => [
|
||||
'key' => 'graphql',
|
||||
'name' => 'GraphQL',
|
||||
'subtitle' => 'Appwrite\'s GraphQL Endpoint',
|
||||
'description' => 'GraphQL Endpoint',
|
||||
'subtitle' => 'The GraphQL API allows you to query and mutate your Appwrite server using GraphQL.',
|
||||
'description' => '/docs/services/graphql.md',
|
||||
'controller' => 'api/graphql.php',
|
||||
'sdk' => false,
|
||||
'docs' => false,
|
||||
'docsUrl' => '',
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/graphql',
|
||||
'tests' => true,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/graphql.png',
|
||||
],
|
||||
'console' => [
|
||||
'key' => 'console',
|
||||
'name' => 'Console',
|
||||
'subtitle' => 'The Console service allows you to interact with console relevant informations.',
|
||||
'description' => '',
|
||||
'controller' => 'api/console.php',
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => '',
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+80
-11
@@ -68,7 +68,7 @@ return [
|
||||
'default' => 'localhost',
|
||||
'required' => true,
|
||||
'question' => 'Enter a DNS A record hostname to serve as a CNAME for your custom domains.' . PHP_EOL . 'You can use the same value as used for the Appwrite hostname.',
|
||||
'filter' => ''
|
||||
'filter' => 'domainTarget'
|
||||
],
|
||||
[
|
||||
'name' => '_APP_CONSOLE_WHITELIST_ROOT',
|
||||
@@ -170,13 +170,31 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_USAGE_AGGREGATION_INTERVAL',
|
||||
'description' => 'Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats and syncing it to mariadb from InfluxDB. The default value is 30 seconds.',
|
||||
'introduction' => '0.10.0',
|
||||
'description' => 'Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats and syncing it to Database from TimeSeries data. The default value is 30 seconds. Reintroduced in 1.1.0.',
|
||||
'introduction' => '1.1.0',
|
||||
'default' => '30',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_USAGE_TIMESERIES_INTERVAL',
|
||||
'description' => 'Deprecated since 1.1.0 use _APP_USAGE_AGGREGATION_INTERVAL instead.',
|
||||
'introduction' => '1.0.0',
|
||||
'default' => '30',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_USAGE_DATABASE_INTERVAL',
|
||||
'description' => 'Deprecated since 1.1.0 use _APP_USAGE_AGGREGATION_INTERVAL instead.',
|
||||
'introduction' => '1.0.0',
|
||||
'default' => '900',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_WORKER_PER_CORE',
|
||||
'description' => 'Internal Worker per core for the API, Realtime and Executor containers. Can be configured to optimize performance.',
|
||||
@@ -394,8 +412,8 @@ return [
|
||||
'description' => '',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_PHONE_PROVIDER',
|
||||
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'phone://[USER]:[SECRET]@[PROVIDER]'. \n\nAvailable providers are twilio, text-magic and telesign.",
|
||||
'name' => '_APP_SMS_PROVIDER',
|
||||
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'. \n\nAvailable providers are twilio, text-magic, telesign, msg91, and vonage.",
|
||||
'introduction' => '0.15.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
@@ -403,7 +421,7 @@ return [
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_PHONE_FROM',
|
||||
'name' => '_APP_SMS_FROM',
|
||||
'description' => 'Phone number used for sending out messages. Must start with a leading \'+\' and maximum of 15 digits without spaces (+123456789).',
|
||||
'introduction' => '0.15.0',
|
||||
'default' => '',
|
||||
@@ -464,9 +482,9 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_DEVICE',
|
||||
'description' => 'Select default storage device. The default value is \'Local\'. List of supported adapters are \'Local\', \'S3\', \'DOSpaces\', \'Backblaze\', \'Linode\' and \'Wasabi\'.',
|
||||
'description' => 'Select default storage device. The default value is \'local\'. List of supported adapters are \'local\', \'s3\', \'dospaces\', \'backblaze\', \'linode\' and \'wasabi\'.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => 'Local',
|
||||
'default' => 'local',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
@@ -647,7 +665,7 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_TIMEOUT',
|
||||
'description' => 'The maximum number of seconds allowed as a timeout value when creating a new function. The default value is 900 seconds.',
|
||||
'description' => 'The maximum number of seconds allowed as a timeout value when creating a new function. The default value is 900 seconds. This is the global limit, timeout for individual functions are configured in the function\'s settings or in appwrite.json.',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '900',
|
||||
'required' => false,
|
||||
@@ -746,7 +764,7 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_INACTIVE_THRESHOLD',
|
||||
'description' => 'The minimum time a function can be inactive before it\'s container is shutdown and put to sleep. The default value is 60 seconds',
|
||||
'description' => 'The minimum time a function must be inactive before it can be shut down and cleaned up. This feature is intended to clean up unused containers. Containers may remain active for longer than the interval before being shut down, as Appwrite only cleans up unused containers every hour. If no value is provided, the default is 60 seconds.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '60',
|
||||
'required' => false,
|
||||
@@ -804,6 +822,15 @@ return [
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_CACHE',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain cached files. The default value is 2592000 seconds (30 days).',
|
||||
'introduction' => '1.0.0',
|
||||
'default' => '2592000',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_EXECUTION',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain execution logs. The default value is 1209600 seconds (14 days).',
|
||||
@@ -830,7 +857,49 @@ return [
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_USAGE_HOURLY',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain hourly usage metrics. The default value is 8640000 seconds (100 days).',
|
||||
'introduction' => '',
|
||||
'default' => '8640000',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'category' => 'GraphQL',
|
||||
'description' => '',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_GRAPHQL_MAX_BATCH_SIZE',
|
||||
'description' => 'Maximum number of batched queries per request. The default value is 10.',
|
||||
'introduction' => '1.2.0',
|
||||
'default' => '10',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_GRAPHQL_MAX_COMPLEXITY',
|
||||
'description' => 'Maximum complexity of a GraphQL query. One field adds one to query complexity. Lists multiply the complexity by the number of items requested. The default value is 250.',
|
||||
'introduction' => '1.2.0',
|
||||
'default' => '250',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_GRAPHQL_MAX_DEPTH',
|
||||
'description' => 'Maximum depth of a GraphQL query. One nested field level adds one to query depth. The default value is 3.',
|
||||
'introduction' => '1.2.0',
|
||||
'default' => '3',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
Submodule
+1
Submodule app/console added at ac4181aea4
+639
-499
File diff suppressed because it is too large
Load Diff
+63
-111
@@ -1,14 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Utopia\Validator\URL;
|
||||
use Appwrite\URL\URL as URLParse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Image\Image;
|
||||
@@ -25,56 +23,34 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
|
||||
$set = Config::getParam('avatar-' . $type, []);
|
||||
|
||||
if (empty($set)) {
|
||||
throw new Exception('Avatar set not found', 404, Exception::AVATAR_SET_NOT_FOUND);
|
||||
throw new Exception(Exception::AVATAR_SET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!\array_key_exists($code, $set)) {
|
||||
throw new Exception('Avatar not found', 404, Exception::AVATAR_NOT_FOUND);
|
||||
throw new Exception(Exception::AVATAR_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
$output = 'png';
|
||||
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
|
||||
$key = \md5('/v1/avatars/' . $type . '/:code-' . $code . $width . $height . $quality . $output);
|
||||
$path = $set[$code];
|
||||
$type = 'png';
|
||||
|
||||
if (!\is_readable($path)) {
|
||||
throw new Exception('File not readable in ' . $path, 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-0')); // Limit file number or size
|
||||
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3/* 3 months */);
|
||||
|
||||
if ($data) {
|
||||
//$output = (empty($output)) ? $type : $output;
|
||||
|
||||
return $response
|
||||
->setContentType('image/png')
|
||||
->addHeader('Expires', $date)
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->send($data);
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'File not readable in ' . $path);
|
||||
}
|
||||
|
||||
$image = new Image(\file_get_contents($path));
|
||||
|
||||
$image->crop((int) $width, (int) $height);
|
||||
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
$cache->save($key, $data);
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->setContentType('image/png')
|
||||
->addHeader('Expires', $date)
|
||||
->addHeader('X-Appwrite-Cache', 'miss')
|
||||
->send($data, null);
|
||||
|
||||
->file($data)
|
||||
;
|
||||
unset($image);
|
||||
};
|
||||
|
||||
@@ -82,6 +58,8 @@ App::get('/v1/avatars/credit-cards/:code')
|
||||
->desc('Get Credit Card Icon')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/credit-card')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'avatars')
|
||||
->label('sdk.method', 'getCreditCard')
|
||||
@@ -100,6 +78,8 @@ App::get('/v1/avatars/browsers/:code')
|
||||
->desc('Get Browser Icon')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/browser')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'avatars')
|
||||
->label('sdk.method', 'getBrowser')
|
||||
@@ -118,6 +98,8 @@ App::get('/v1/avatars/flags/:code')
|
||||
->desc('Get Country Flag')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/flag')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'avatars')
|
||||
->label('sdk.method', 'getFlag')
|
||||
@@ -136,6 +118,8 @@ App::get('/v1/avatars/image')
|
||||
->desc('Get Image from URL')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/image')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'avatars')
|
||||
->label('sdk.method', 'getImage')
|
||||
@@ -151,50 +135,33 @@ App::get('/v1/avatars/image')
|
||||
|
||||
$quality = 80;
|
||||
$output = 'png';
|
||||
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
|
||||
$key = \md5('/v2/avatars/images-' . $url . '-' . $width . '/' . $height . '/' . $quality);
|
||||
$type = 'png';
|
||||
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-0')); // Limit file number or size
|
||||
$data = $cache->load($key, 60 * 60 * 24 * 7/* 1 week */);
|
||||
|
||||
if ($data) {
|
||||
return $response
|
||||
->setContentType('image/png')
|
||||
->addHeader('Expires', $date)
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->send($data);
|
||||
}
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
$fetch = @\file_get_contents($url, false);
|
||||
|
||||
if (!$fetch) {
|
||||
throw new Exception('Image not found', 404, Exception::AVATAR_IMAGE_NOT_FOUND);
|
||||
throw new Exception(Exception::AVATAR_IMAGE_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$image = new Image($fetch);
|
||||
} catch (\Exception $exception) {
|
||||
throw new Exception('Unable to parse image', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unable to parse image');
|
||||
}
|
||||
|
||||
$image->crop((int) $width, (int) $height);
|
||||
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
$cache->save($key, $data);
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->setContentType('image/png')
|
||||
->addHeader('Expires', $date)
|
||||
->addHeader('X-Appwrite-Cache', 'miss')
|
||||
->send($data);
|
||||
|
||||
->file($data)
|
||||
;
|
||||
unset($image);
|
||||
});
|
||||
|
||||
@@ -202,6 +169,8 @@ App::get('/v1/avatars/favicon')
|
||||
->desc('Get Favicon')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/favicon')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'avatars')
|
||||
->label('sdk.method', 'getFavicon')
|
||||
@@ -217,22 +186,10 @@ App::get('/v1/avatars/favicon')
|
||||
$height = 56;
|
||||
$quality = 80;
|
||||
$output = 'png';
|
||||
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
|
||||
$key = \md5('/v2/avatars/favicon-' . $url);
|
||||
$type = 'png';
|
||||
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-0')); // Limit file number or size
|
||||
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3/* 3 months */);
|
||||
|
||||
if ($data) {
|
||||
return $response
|
||||
->setContentType('image/png')
|
||||
->addHeader('Expires', $date)
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->send($data);
|
||||
}
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
$curl = \curl_init();
|
||||
@@ -254,7 +211,7 @@ App::get('/v1/avatars/favicon')
|
||||
\curl_close($curl);
|
||||
|
||||
if (!$html) {
|
||||
throw new Exception('Failed to fetch remote URL', 404, Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
@@ -285,8 +242,8 @@ App::get('/v1/avatars/favicon')
|
||||
case 'jpeg':
|
||||
$size = \explode('x', \strtolower($sizes));
|
||||
|
||||
$sizeWidth = (int) $size[0] ?? 0;
|
||||
$sizeHeight = (int) $size[1] ?? 0;
|
||||
$sizeWidth = (int) ($size[0] ?? 0);
|
||||
$sizeHeight = (int) ($size[1] ?? 0);
|
||||
|
||||
if (($sizeWidth * $sizeHeight) >= $space) {
|
||||
$space = $sizeWidth * $sizeHeight;
|
||||
@@ -312,40 +269,31 @@ App::get('/v1/avatars/favicon')
|
||||
$data = @\file_get_contents($outputHref, false);
|
||||
|
||||
if (empty($data) || (\mb_substr($data, 0, 5) === '<html') || \mb_substr($data, 0, 5) === '<!doc') {
|
||||
throw new Exception('Favicon not found', 404, Exception::AVATAR_ICON_NOT_FOUND);
|
||||
throw new Exception(Exception::AVATAR_ICON_NOT_FOUND, 'Favicon not found');
|
||||
}
|
||||
|
||||
$cache->save($key, $data);
|
||||
|
||||
return $response
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->setContentType('image/x-icon')
|
||||
->addHeader('Expires', $date)
|
||||
->addHeader('X-Appwrite-Cache', 'miss')
|
||||
->send($data);
|
||||
->file($data)
|
||||
;
|
||||
}
|
||||
|
||||
$fetch = @\file_get_contents($outputHref, false);
|
||||
|
||||
if (!$fetch) {
|
||||
throw new Exception('Icon not found', 404, Exception::AVATAR_ICON_NOT_FOUND);
|
||||
throw new Exception(Exception::AVATAR_ICON_NOT_FOUND);
|
||||
}
|
||||
|
||||
$image = new Image($fetch);
|
||||
|
||||
$image->crop((int) $width, (int) $height);
|
||||
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
$cache->save($key, $data);
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->setContentType('image/png')
|
||||
->addHeader('Expires', $date)
|
||||
->addHeader('X-Appwrite-Cache', 'miss')
|
||||
->send($data);
|
||||
|
||||
->file($data)
|
||||
;
|
||||
unset($image);
|
||||
});
|
||||
|
||||
@@ -381,19 +329,20 @@ App::get('/v1/avatars/qr')
|
||||
}
|
||||
|
||||
$image = new Image($qrcode->render($text));
|
||||
|
||||
$image->crop((int) $size, (int) $size);
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->setContentType('image/png')
|
||||
->send($image->output('png', 9));
|
||||
->send($image->output('png', 9))
|
||||
;
|
||||
});
|
||||
|
||||
App::get('/v1/avatars/initials')
|
||||
->desc('Get User Initials')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache.resource', 'avatar/initials')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'avatars')
|
||||
->label('sdk.method', 'getInitials')
|
||||
@@ -404,27 +353,20 @@ App::get('/v1/avatars/initials')
|
||||
->param('name', '', new Text(128), 'Full Name. When empty, current user name or email will be used. Max length: 128 chars.', true)
|
||||
->param('width', 500, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 500, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('color', '', new HexColor(), 'Changes text color. By default a random color will be picked and stay will persistent to the given name.', true)
|
||||
->param('background', '', new HexColor(), 'Changes background color. By default a random color will be picked and stay will persistent to the given name.', true)
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->action(function (string $name, int $width, int $height, string $color, string $background, Response $response, Document $user) {
|
||||
->action(function (string $name, int $width, int $height, string $background, Response $response, Document $user) {
|
||||
|
||||
$themes = [
|
||||
['color' => '#27005e', 'background' => '#e1d2f6'], // VIOLET
|
||||
['color' => '#5e2700', 'background' => '#f3d9c6'], // ORANGE
|
||||
['color' => '#006128', 'background' => '#c9f3c6'], // GREEN
|
||||
['color' => '#580061', 'background' => '#f2d1f5'], // FUSCHIA
|
||||
['color' => '#00365d', 'background' => '#c6e1f3'], // BLUE
|
||||
['color' => '#00075c', 'background' => '#d2d5f6'], // INDIGO
|
||||
['color' => '#610038', 'background' => '#f5d1e6'], // PINK
|
||||
['color' => '#386100', 'background' => '#dcf1bd'], // LIME
|
||||
['color' => '#615800', 'background' => '#f1ecba'], // YELLOW
|
||||
['color' => '#610008', 'background' => '#f6d2d5'], // RED
|
||||
['background' => '#FFA1CE'], // Default (Pink)
|
||||
['background' => '#FDC584'], // Orange
|
||||
['background' => '#94DBD1'], // Green
|
||||
['background' => '#A1C4FF'], // Blue
|
||||
['background' => '#FFA1CE'], // Pink
|
||||
['background' => '#CBB1FC'] // Purple
|
||||
];
|
||||
|
||||
$rand = \rand(0, \count($themes) - 1);
|
||||
|
||||
$name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', ''));
|
||||
$words = \explode(' ', \strtoupper($name));
|
||||
// if there is no space, try to split by `_` underscore
|
||||
@@ -443,30 +385,40 @@ App::get('/v1/avatars/initials')
|
||||
}
|
||||
|
||||
$rand = \substr($code, -1);
|
||||
|
||||
// Wrap rand value to avoid out of range
|
||||
$rand = ($rand > \count($themes) - 1) ? $rand % \count($themes) : $rand;
|
||||
|
||||
$background = (!empty($background)) ? '#' . $background : $themes[$rand]['background'];
|
||||
$color = (!empty($color)) ? '#' . $color : $themes[$rand]['color'];
|
||||
|
||||
$image = new \Imagick();
|
||||
$punch = new \Imagick();
|
||||
$draw = new \ImagickDraw();
|
||||
$fontSize = \min($width, $height) / 2;
|
||||
|
||||
$draw->setFont(__DIR__ . "/../../../public/fonts/poppins-v9-latin-500.ttf");
|
||||
$image->setFont(__DIR__ . "/../../../public/fonts/poppins-v9-latin-500.ttf");
|
||||
$punch->newImage($width, $height, 'transparent');
|
||||
|
||||
$draw->setFillColor(new \ImagickPixel($color));
|
||||
$draw->setFont(__DIR__ . "/../../assets/fonts/poppins-v9-latin-500.ttf");
|
||||
$image->setFont(__DIR__ . "/../../assets/fonts/poppins-v9-latin-500.ttf");
|
||||
|
||||
$draw->setFillColor(new ImagickPixel('black'));
|
||||
$draw->setFontSize($fontSize);
|
||||
|
||||
$draw->setTextAlignment(\Imagick::ALIGN_CENTER);
|
||||
$draw->annotation($width / 1.97, ($height / 2) + ($fontSize / 3), $initials);
|
||||
|
||||
$punch->drawImage($draw);
|
||||
$punch->negateImage(true, Imagick::CHANNEL_ALPHA);
|
||||
|
||||
$image->newImage($width, $height, $background);
|
||||
$image->setImageFormat("png");
|
||||
$image->drawImage($draw);
|
||||
$image->compositeImage($punch, Imagick::COMPOSITE_COPYOPACITY, 0, 0);
|
||||
|
||||
//$image->setImageCompressionQuality(9 - round(($quality / 100) * 9));
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->setContentType('image/png')
|
||||
->send($image->getImageBlob());
|
||||
->file($image->getImageBlob())
|
||||
;
|
||||
});
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
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.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'console')
|
||||
->label('sdk.method', 'variables')
|
||||
->label('sdk.description', '/docs/references/console/variables.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_CONSOLE_VARIABLES)
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
$variables = new Document([
|
||||
'_APP_DOMAIN_TARGET' => App::getEnv('_APP_DOMAIN_TARGET'),
|
||||
'_APP_STORAGE_LIMIT' => +App::getEnv('_APP_STORAGE_LIMIT'),
|
||||
'_APP_FUNCTIONS_SIZE_LIMIT' => +App::getEnv('_APP_FUNCTIONS_SIZE_LIMIT'),
|
||||
'_APP_USAGE_STATS' => App::getEnv('_APP_USAGE_STATS'),
|
||||
]);
|
||||
|
||||
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
|
||||
});
|
||||
+1923
-773
File diff suppressed because it is too large
Load Diff
+670
-240
File diff suppressed because it is too large
Load Diff
+288
-15
@@ -1,24 +1,297 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* 1. Map all objects, object-params, object-fields
|
||||
* 2. Parse GraphQL request payload (use: https://github.com/webonyx/graphql-php)
|
||||
* 3. Route request to relevant controllers (of REST API?) / resolvers and aggergate data
|
||||
* 4. Handle errors if any
|
||||
* 5. Returen JSON response
|
||||
* 6. Write tests!
|
||||
*/
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\GraphQL\Promises\Adapter;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use GraphQL\Error\DebugFlag;
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Type\Schema as GQLSchema;
|
||||
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\Validator\JSON;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
App::get('/v1/graphql')
|
||||
->desc('GraphQL Endpoint')
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'graphql')
|
||||
->label('sdk.hide', true)
|
||||
->label('sdk.description', '/docs/references/graphql/get.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->param('query', '', new Text(0, 0), 'The query to execute.')
|
||||
->param('operationName', '', new Text(256), 'The name of the operation to execute.', true)
|
||||
->param('variables', '', new Text(0), 'The JSON encoded variables to use in the query.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('schema')
|
||||
->inject('promiseAdapter')
|
||||
->action(function (string $query, string $operationName, string $variables, Request $request, Response $response, GQLSchema $schema, Adapter $promiseAdapter) {
|
||||
$query = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
if (!empty($operationName)) {
|
||||
$query['operationName'] = $operationName;
|
||||
}
|
||||
|
||||
if (!empty($variables)) {
|
||||
$query['variables'] = \json_decode($variables, true);
|
||||
}
|
||||
|
||||
$output = execute($schema, $promiseAdapter, $query);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->json($output);
|
||||
});
|
||||
|
||||
App::post('/v1/graphql/mutation')
|
||||
->desc('GraphQL Endpoint')
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'graphql')
|
||||
->label('sdk.method', 'mutation')
|
||||
->label('sdk.methodType', 'graphql')
|
||||
->label('sdk.description', '/docs/references/graphql/post.md')
|
||||
->label('sdk.parameters', [
|
||||
'query' => ['default' => [], 'validator' => new JSON(), 'description' => 'The query or queries to execute.', 'optional' => false],
|
||||
])
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('schema')
|
||||
->inject('promiseAdapter')
|
||||
->action(function (Request $request, Response $response, GQLSchema $schema, Adapter $promiseAdapter) {
|
||||
$query = $request->getParams();
|
||||
|
||||
if ($request->getHeader('x-sdk-graphql') == 'true') {
|
||||
$query = $query['query'];
|
||||
}
|
||||
|
||||
$type = $request->getHeader('content-type');
|
||||
|
||||
if (\str_starts_with($type, 'application/graphql')) {
|
||||
$query = parseGraphql($request);
|
||||
}
|
||||
|
||||
if (\str_starts_with($type, 'multipart/form-data')) {
|
||||
$query = parseMultipart($query, $request);
|
||||
}
|
||||
|
||||
$output = execute($schema, $promiseAdapter, $query);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->json($output);
|
||||
});
|
||||
|
||||
App::post('/v1/graphql')
|
||||
->desc('GraphQL Endpoint')
|
||||
->groups(['api', 'graphql'])
|
||||
->label('scope', 'public')
|
||||
->action(
|
||||
function () {
|
||||
throw new Exception('GraphQL support is coming soon!', 502, Exception::GENERAL_SERVER_ERROR);
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'graphql')
|
||||
->label('sdk.method', 'query')
|
||||
->label('sdk.methodType', 'graphql')
|
||||
->label('sdk.description', '/docs/references/graphql/post.md')
|
||||
->label('sdk.parameters', [
|
||||
'query' => ['default' => [], 'validator' => new JSON(), 'description' => 'The query or queries to execute.', 'optional' => false],
|
||||
])
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('schema')
|
||||
->inject('promiseAdapter')
|
||||
->action(function (Request $request, Response $response, GQLSchema $schema, Adapter $promiseAdapter) {
|
||||
$query = $request->getParams();
|
||||
|
||||
if ($request->getHeader('x-sdk-graphql') == 'true') {
|
||||
$query = $query['query'];
|
||||
}
|
||||
|
||||
$type = $request->getHeader('content-type');
|
||||
|
||||
if (\str_starts_with($type, 'application/graphql')) {
|
||||
$query = parseGraphql($request);
|
||||
}
|
||||
|
||||
if (\str_starts_with($type, 'multipart/form-data')) {
|
||||
$query = parseMultipart($query, $request);
|
||||
}
|
||||
|
||||
$output = execute($schema, $promiseAdapter, $query);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->json($output);
|
||||
});
|
||||
|
||||
/**
|
||||
* Execute a GraphQL request
|
||||
*
|
||||
* @param GQLSchema $schema
|
||||
* @param Adapter $promiseAdapter
|
||||
* @param array $query
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
function execute(
|
||||
GQLSchema $schema,
|
||||
Adapter $promiseAdapter,
|
||||
array $query
|
||||
): array {
|
||||
$maxBatchSize = App::getEnv('_APP_GRAPHQL_MAX_BATCH_SIZE', 10);
|
||||
$maxComplexity = App::getEnv('_APP_GRAPHQL_MAX_COMPLEXITY', 250);
|
||||
$maxDepth = App::getEnv('_APP_GRAPHQL_MAX_DEPTH', 3);
|
||||
|
||||
if (!empty($query) && !isset($query[0])) {
|
||||
$query = [$query];
|
||||
}
|
||||
if (empty($query)) {
|
||||
throw new Exception(Exception::GRAPHQL_NO_QUERY);
|
||||
}
|
||||
if (\count($query) > $maxBatchSize) {
|
||||
throw new Exception(Exception::GRAPHQL_TOO_MANY_QUERIES);
|
||||
}
|
||||
foreach ($query as $item) {
|
||||
if (empty($item['query'])) {
|
||||
throw new Exception(Exception::GRAPHQL_NO_QUERY);
|
||||
}
|
||||
}
|
||||
|
||||
$flags = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
|
||||
$validations = GraphQL::getStandardValidationRules();
|
||||
|
||||
if (App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
|
||||
$validations[] = new DisableIntrospection();
|
||||
$validations[] = new QueryComplexity($maxComplexity);
|
||||
$validations[] = new QueryDepth($maxDepth);
|
||||
}
|
||||
if (App::getMode() === App::MODE_TYPE_PRODUCTION) {
|
||||
$flags = DebugFlag::NONE;
|
||||
}
|
||||
|
||||
$promises = [];
|
||||
foreach ($query as $indexed) {
|
||||
$promises[] = GraphQL::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$indexed['query'],
|
||||
variableValues: $indexed['variables'] ?? null,
|
||||
operationName: $indexed['operationName'] ?? null,
|
||||
validationRules: $validations
|
||||
);
|
||||
}
|
||||
|
||||
$output = [];
|
||||
$wg = new WaitGroup();
|
||||
$wg->add();
|
||||
$promiseAdapter->all($promises)->then(
|
||||
function (array $results) use (&$output, &$wg, $flags) {
|
||||
try {
|
||||
$output = processResult($results, $flags);
|
||||
} finally {
|
||||
$wg->done();
|
||||
}
|
||||
}
|
||||
);
|
||||
$wg->wait();
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an "application/graphql" type request
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
function parseGraphql(Request $request): array
|
||||
{
|
||||
return ['query' => $request->getRawPayload()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an "multipart/form-data" type request
|
||||
*
|
||||
* @param array $query
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
function parseMultipart(array $query, Request $request): array
|
||||
{
|
||||
$operations = \json_decode($query['operations'], true);
|
||||
$map = \json_decode($query['map'], true);
|
||||
|
||||
foreach ($map as $fileKey => $locations) {
|
||||
foreach ($locations as $location) {
|
||||
$items = &$operations;
|
||||
foreach (\explode('.', $location) as $key) {
|
||||
if (!isset($items[$key]) || !\is_array($items[$key])) {
|
||||
$items[$key] = [];
|
||||
}
|
||||
$items = &$items[$key];
|
||||
}
|
||||
$items = $request->getFiles($fileKey);
|
||||
}
|
||||
}
|
||||
|
||||
$query['query'] = $operations['query'];
|
||||
$query['variables'] = $operations['variables'];
|
||||
|
||||
unset($query['operations']);
|
||||
unset($query['map']);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an array of results for output.
|
||||
*
|
||||
* @param $result
|
||||
* @param $debugFlags
|
||||
* @return array
|
||||
*/
|
||||
function processResult($result, $debugFlags): array
|
||||
{
|
||||
// Only one query, return the result
|
||||
if (!isset($result[1])) {
|
||||
return $result[0]->toArray($debugFlags);
|
||||
}
|
||||
|
||||
// Batched queries, return an array of results
|
||||
return \array_map(
|
||||
static function ($item) use ($debugFlags) {
|
||||
return $item->toArray($debugFlags);
|
||||
},
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
App::shutdown()
|
||||
->groups(['schema'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
Schema::setDirty($project->getId());
|
||||
});
|
||||
|
||||
@@ -73,7 +73,7 @@ App::get('/v1/health/db')
|
||||
|
||||
$statement->execute();
|
||||
} catch (Exception $_e) {
|
||||
throw new Exception('Database is not available', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Database is not available');
|
||||
}
|
||||
|
||||
$output = [
|
||||
@@ -104,7 +104,7 @@ App::get('/v1/health/cache')
|
||||
$redis = $utopia->getResource('cache');
|
||||
|
||||
if (!$redis->ping(true)) {
|
||||
throw new Exception('Cache is not available', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Cache is not available');
|
||||
}
|
||||
|
||||
$output = [
|
||||
@@ -160,7 +160,7 @@ App::get('/v1/health/time')
|
||||
$diff = ($timestamp - \time());
|
||||
|
||||
if ($diff > $gap || $diff < ($gap * -1)) {
|
||||
throw new Exception('Server time gaps detected', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Server time gaps detected');
|
||||
}
|
||||
|
||||
$output = [
|
||||
@@ -267,11 +267,11 @@ App::get('/v1/health/storage/local')
|
||||
$device = new Local($volume);
|
||||
|
||||
if (!\is_readable($device->getRoot())) {
|
||||
throw new Exception('Device ' . $key . ' dir is not readable', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Device ' . $key . ' dir is not readable');
|
||||
}
|
||||
|
||||
if (!\is_writable($device->getRoot())) {
|
||||
throw new Exception('Device ' . $key . ' dir is not writable', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Device ' . $key . ' dir is not writable');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ App::get('/v1/health/anti-virus')
|
||||
$output['version'] = @$antivirus->version();
|
||||
$output['status'] = (@$antivirus->ping()) ? 'pass' : 'fail';
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception('Antivirus is not available', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Antivirus is not available');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ App::get('/v1/locale')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LOCALE)
|
||||
->label('sdk.offline.model', '/locale')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
@@ -40,7 +42,6 @@ App::get('/v1/locale')
|
||||
$output['countryCode'] = $record['country']['iso_code'];
|
||||
$output['country'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
|
||||
$output['continent'] = $locale->getText('continents.' . strtolower($record['continent']['code']), $locale->getText('locale.country.unknown'));
|
||||
$output['continent'] = (isset($continents[$record['continent']['code']])) ? $continents[$record['continent']['code']] : $locale->getText('locale.country.unknown');
|
||||
$output['continentCode'] = $record['continent']['code'];
|
||||
$output['eu'] = (\in_array($record['country']['iso_code'], $eu)) ? true : false;
|
||||
|
||||
@@ -73,11 +74,13 @@ App::get('/v1/locale/countries')
|
||||
->label('scope', 'locale.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'locale')
|
||||
->label('sdk.method', 'getCountries')
|
||||
->label('sdk.description', '/docs/references/locale/get-countries.md')
|
||||
->label('sdk.method', 'listCountries')
|
||||
->label('sdk.description', '/docs/references/locale/list-countries.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_COUNTRY_LIST)
|
||||
->label('sdk.offline.model', '/locale/countries')
|
||||
->label('sdk.offline.response.key', 'code')
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
@@ -104,11 +107,13 @@ App::get('/v1/locale/countries/eu')
|
||||
->label('scope', 'locale.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'locale')
|
||||
->label('sdk.method', 'getCountriesEU')
|
||||
->label('sdk.description', '/docs/references/locale/get-countries-eu.md')
|
||||
->label('sdk.method', 'listCountriesEU')
|
||||
->label('sdk.description', '/docs/references/locale/list-countries-eu.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_COUNTRY_LIST)
|
||||
->label('sdk.offline.model', '/locale/countries/eu')
|
||||
->label('sdk.offline.response.key', 'code')
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
@@ -137,11 +142,13 @@ App::get('/v1/locale/countries/phones')
|
||||
->label('scope', 'locale.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'locale')
|
||||
->label('sdk.method', 'getCountriesPhones')
|
||||
->label('sdk.description', '/docs/references/locale/get-countries-phones.md')
|
||||
->label('sdk.method', 'listCountriesPhones')
|
||||
->label('sdk.description', '/docs/references/locale/list-countries-phones.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PHONE_LIST)
|
||||
->label('sdk.offline.model', '/locale/countries/phones')
|
||||
->label('sdk.offline.response.key', 'countryCode')
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
@@ -169,11 +176,13 @@ App::get('/v1/locale/continents')
|
||||
->label('scope', 'locale.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'locale')
|
||||
->label('sdk.method', 'getContinents')
|
||||
->label('sdk.description', '/docs/references/locale/get-continents.md')
|
||||
->label('sdk.method', 'listContinents')
|
||||
->label('sdk.description', '/docs/references/locale/list-continents.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_CONTINENT_LIST)
|
||||
->label('sdk.offline.model', '/locale/continents')
|
||||
->label('sdk.offline.response.key', 'code')
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
@@ -199,11 +208,13 @@ App::get('/v1/locale/currencies')
|
||||
->label('scope', 'locale.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'locale')
|
||||
->label('sdk.method', 'getCurrencies')
|
||||
->label('sdk.description', '/docs/references/locale/get-currencies.md')
|
||||
->label('sdk.method', 'listCurrencies')
|
||||
->label('sdk.description', '/docs/references/locale/list-currencies.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_CURRENCY_LIST)
|
||||
->label('sdk.offline.model', '/locale/currencies')
|
||||
->label('sdk.offline.response.key', 'code')
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$list = Config::getParam('locale-currencies');
|
||||
@@ -220,11 +231,13 @@ App::get('/v1/locale/languages')
|
||||
->label('scope', 'locale.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'locale')
|
||||
->label('sdk.method', 'getLanguages')
|
||||
->label('sdk.description', '/docs/references/locale/get-languages.md')
|
||||
->label('sdk.method', 'listLanguages')
|
||||
->label('sdk.description', '/docs/references/locale/list-languages.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LANGUAGE_LIST)
|
||||
->label('sdk.offline.model', '/locale/languages')
|
||||
->label('sdk.offline.response.key', 'code')
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$list = Config::getParam('locale-languages');
|
||||
|
||||
+389
-215
File diff suppressed because it is too large
Load Diff
+526
-543
File diff suppressed because it is too large
Load Diff
+374
-208
File diff suppressed because it is too large
Load Diff
+524
-241
File diff suppressed because it is too large
Load Diff
+488
-466
File diff suppressed because it is too large
Load Diff
+121
-38
@@ -4,15 +4,18 @@ global $utopia, $request, $response;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Database\Document;
|
||||
use Appwrite\Network\Validator\Host;
|
||||
use Utopia\Validator\Host;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\App;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Storage\Validator\File;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
App::get('/v1/mock/tests/foo')
|
||||
->desc('Get Foo')
|
||||
@@ -133,6 +136,8 @@ App::post('/v1/mock/tests/bar')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.offline.model', '/mock/tests/bar')
|
||||
->label('sdk.offline.key', '{required}')
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
@@ -194,6 +199,35 @@ App::delete('/v1/mock/tests/bar')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
/** Endpoint to test if required headers are sent from the SDK */
|
||||
App::get('/v1/mock/tests/general/headers')
|
||||
->desc('Get headers')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'headers')
|
||||
->label('sdk.description', 'Return headers from the request')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
$res = [
|
||||
'x-sdk-name' => $request->getHeader('x-sdk-name'),
|
||||
'x-sdk-platform' => $request->getHeader('x-sdk-platform'),
|
||||
'x-sdk-language' => $request->getHeader('x-sdk-language'),
|
||||
'x-sdk-version' => $request->getHeader('x-sdk-version'),
|
||||
];
|
||||
$res = array_map(function ($key, $value) {
|
||||
return $key . ': ' . $value;
|
||||
}, array_keys($res), $res);
|
||||
$res = implode("; ", $res);
|
||||
|
||||
$response->dynamic(new Document(['result' => $res]), Response::MODEL_MOCK);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/download')
|
||||
->desc('Download File')
|
||||
->groups(['mock'])
|
||||
@@ -214,7 +248,7 @@ App::get('/v1/mock/tests/general/download')
|
||||
->addHeader('Content-Disposition', 'attachment; filename="test.txt"')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->send("Download test passed.")
|
||||
->send("GET:/v1/mock/tests/general/download:passed")
|
||||
;
|
||||
});
|
||||
|
||||
@@ -234,7 +268,7 @@ App::post('/v1/mock/tests/general/upload')
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->param('file', [], new File(), 'Sample file param', false)
|
||||
->param('file', [], new File(), 'Sample file param', skipValidation: true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (string $x, int $y, array $z, mixed $file, Request $request, Response $response) {
|
||||
@@ -253,36 +287,36 @@ App::post('/v1/mock/tests/general/upload')
|
||||
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
|
||||
|
||||
if (is_null($start) || is_null($end) || is_null($size)) {
|
||||
throw new Exception('Invalid content-range header', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
|
||||
}
|
||||
|
||||
if ($start > $end || $end > $size) {
|
||||
throw new Exception('Invalid content-range header', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
|
||||
}
|
||||
|
||||
if ($start === 0 && !empty($id)) {
|
||||
throw new Exception('First chunked request cannot have id header', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'First chunked request cannot have id header');
|
||||
}
|
||||
|
||||
if ($start !== 0 && $id !== 'newfileid') {
|
||||
throw new Exception('All chunked request must have id header (except first)', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'All chunked request must have id header (except first)');
|
||||
}
|
||||
|
||||
if ($end !== $size && $end - $start + 1 !== $chunkSize) {
|
||||
throw new Exception('Chunk size must be 5MB (except last chunk)', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB (except last chunk)');
|
||||
}
|
||||
|
||||
if ($end !== $size && $file['size'] !== $chunkSize) {
|
||||
throw new Exception('Wrong chunk size', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Wrong chunk size');
|
||||
}
|
||||
|
||||
if ($file['size'] > $chunkSize) {
|
||||
throw new Exception('Chunk size must be 5MB or less', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB or less');
|
||||
}
|
||||
|
||||
if ($end !== $size) {
|
||||
$response->json([
|
||||
'$id' => 'newfileid',
|
||||
'$id' => ID::custom('newfileid'),
|
||||
'chunksTotal' => $file['size'] / $chunkSize,
|
||||
'chunksUploaded' => $start / $chunkSize
|
||||
]);
|
||||
@@ -293,15 +327,15 @@ App::post('/v1/mock/tests/general/upload')
|
||||
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
|
||||
|
||||
if ($file['name'] !== 'file.png') {
|
||||
throw new Exception('Wrong file name', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file name');
|
||||
}
|
||||
|
||||
if ($file['size'] !== 38756) {
|
||||
throw new Exception('Wrong file size', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file size');
|
||||
}
|
||||
|
||||
if (\md5(\file_get_contents($file['tmp_name'])) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
|
||||
throw new Exception('Wrong file uploaded', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file uploaded');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -374,7 +408,7 @@ App::get('/v1/mock/tests/general/get-cookie')
|
||||
->action(function (Request $request) {
|
||||
|
||||
if ($request->getCookie('cookieName', '') !== 'cookieValue') {
|
||||
throw new Exception('Missing cookie value', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Missing cookie value');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -395,6 +429,50 @@ App::get('/v1/mock/tests/general/empty')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/general/nullable')
|
||||
->desc('Nullable Test')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'nullable')
|
||||
->label('sdk.description', 'Mock a nullable parameter.')
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('nullable', '', new Nullable(new Text(100)), 'Sample string param')
|
||||
->param('optional', '', new Text(100), 'Sample string param', true)
|
||||
->action(function (string $required, string $nullable, ?string $optional) {
|
||||
});
|
||||
|
||||
/** Endpoint to test if required headers are sent from the SDK */
|
||||
App::get('/v1/mock/tests/general/headers')
|
||||
->desc('Get headers')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'headers')
|
||||
->label('sdk.description', 'Return headers from the request')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
$res = [
|
||||
'x-sdk-name' => $request->getHeader('x-sdk-name'),
|
||||
'x-sdk-platform' => $request->getHeader('x-sdk-platform'),
|
||||
'x-sdk-language' => $request->getHeader('x-sdk-language'),
|
||||
'x-sdk-version' => $request->getHeader('x-sdk-version'),
|
||||
];
|
||||
$res = array_map(function ($key, $value) {
|
||||
return $key . ': ' . $value;
|
||||
}, array_keys($res), $res);
|
||||
$res = implode("; ", $res);
|
||||
|
||||
$response->dynamic(new Document(['result' => $res]), Response::MODEL_MOCK);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/400-error')
|
||||
->desc('400 Error')
|
||||
->groups(['mock'])
|
||||
@@ -408,7 +486,7 @@ App::get('/v1/mock/tests/general/400-error')
|
||||
->label('sdk.response.model', Response::MODEL_ERROR)
|
||||
->label('sdk.mock', true)
|
||||
->action(function () {
|
||||
throw new Exception('Mock 400 error', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Mock 400 error');
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/500-error')
|
||||
@@ -424,7 +502,7 @@ App::get('/v1/mock/tests/general/500-error')
|
||||
->label('sdk.response.model', Response::MODEL_ERROR)
|
||||
->label('sdk.mock', true)
|
||||
->action(function () {
|
||||
throw new Exception('Mock 500 error', 500, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Mock 500 error', 500);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/502-error')
|
||||
@@ -480,11 +558,11 @@ App::get('/v1/mock/tests/general/oauth2/token')
|
||||
->action(function (string $client_id, string $client_secret, string $grantType, string $redirectURI, string $code, string $refreshToken, Response $response) {
|
||||
|
||||
if ($client_id != '1') {
|
||||
throw new Exception('Invalid client ID', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid client ID');
|
||||
}
|
||||
|
||||
if ($client_secret != '123456') {
|
||||
throw new Exception('Invalid client secret', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid client secret');
|
||||
}
|
||||
|
||||
$responseJson = [
|
||||
@@ -495,18 +573,18 @@ App::get('/v1/mock/tests/general/oauth2/token')
|
||||
|
||||
if ($grantType === 'authorization_code') {
|
||||
if ($code !== 'abcdef') {
|
||||
throw new Exception('Invalid token', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid token');
|
||||
}
|
||||
|
||||
$response->json($responseJson);
|
||||
} elseif ($grantType === 'refresh_token') {
|
||||
if ($refreshToken !== 'tuvwxyz') {
|
||||
throw new Exception('Invalid refresh token', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid refresh token');
|
||||
}
|
||||
|
||||
$response->json($responseJson);
|
||||
} else {
|
||||
throw new Exception('Invalid grant type', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid grant type');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -520,7 +598,7 @@ App::get('/v1/mock/tests/general/oauth2/user')
|
||||
->action(function (string $token, Response $response) {
|
||||
|
||||
if ($token != '123456') {
|
||||
throw new Exception('Invalid token', 400, Exception::GENERAL_MOCK);
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid token');
|
||||
}
|
||||
|
||||
$response->json([
|
||||
@@ -558,24 +636,29 @@ App::get('/v1/mock/tests/general/oauth2/failure')
|
||||
]);
|
||||
});
|
||||
|
||||
App::shutdown(function (App $utopia, Response $response, Request $request) {
|
||||
App::shutdown()
|
||||
->groups(['mock'])
|
||||
->inject('utopia')
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->action(function (App $utopia, Response $response, Request $request) {
|
||||
|
||||
$result = [];
|
||||
$route = $utopia->match($request);
|
||||
$path = APP_STORAGE_CACHE . '/tests.json';
|
||||
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
|
||||
$result = [];
|
||||
$route = $utopia->match($request);
|
||||
$path = APP_STORAGE_CACHE . '/tests.json';
|
||||
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
|
||||
|
||||
if (!\is_array($tests)) {
|
||||
throw new Exception('Failed to read results', 500, Exception::GENERAL_MOCK);
|
||||
}
|
||||
if (!\is_array($tests)) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Failed to read results', 500);
|
||||
}
|
||||
|
||||
$result[$route->getMethod() . ':' . $route->getPath()] = true;
|
||||
$result[$route->getMethod() . ':' . $route->getPath()] = true;
|
||||
|
||||
$tests = \array_merge($tests, $result);
|
||||
$tests = \array_merge($tests, $result);
|
||||
|
||||
if (!\file_put_contents($path, \json_encode($tests), LOCK_EX)) {
|
||||
throw new Exception('Failed to save results', 500, Exception::GENERAL_MOCK);
|
||||
}
|
||||
if (!\file_put_contents($path, \json_encode($tests), LOCK_EX)) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Failed to save results', 500);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']), Response::MODEL_MOCK);
|
||||
}, ['utopia', 'response', 'request'], 'mock');
|
||||
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']), Response::MODEL_MOCK);
|
||||
});
|
||||
|
||||
+515
-213
@@ -5,248 +5,550 @@ use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Stats\Stats;
|
||||
use Appwrite\Usage\Stats;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Utopia\App;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Registry\Registry;
|
||||
|
||||
App::init(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) {
|
||||
$parseLabel = function (string $label, array $responsePayload, array $requestParams, Document $user) {
|
||||
preg_match_all('/{(.*?)}/', $label, $matches);
|
||||
foreach ($matches[1] ?? [] as $pos => $match) {
|
||||
$find = $matches[0][$pos];
|
||||
$parts = explode('.', $match);
|
||||
|
||||
$route = $utopia->match($request);
|
||||
if (count($parts) !== 2) {
|
||||
throw new Exception('Too less or too many parts', 400, Exception::GENERAL_ARGUMENT_INVALID);
|
||||
}
|
||||
|
||||
if ($project->isEmpty() && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope
|
||||
throw new Exception('Missing or unknown project ID', 400, Exception::PROJECT_UNKNOWN);
|
||||
$namespace = $parts[0] ?? '';
|
||||
$replace = $parts[1] ?? '';
|
||||
|
||||
$params = match ($namespace) {
|
||||
'user' => (array)$user,
|
||||
'request' => $requestParams,
|
||||
default => $responsePayload,
|
||||
};
|
||||
|
||||
if (array_key_exists($replace, $params)) {
|
||||
$label = \str_replace($find, $params[$replace], $label);
|
||||
}
|
||||
}
|
||||
return $label;
|
||||
};
|
||||
|
||||
$databaseListener = function (string $event, Document $document, Stats $usage) {
|
||||
$multiplier = 1;
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$multiplier = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
*/
|
||||
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
|
||||
$timeLimitArray = [];
|
||||
$collection = $document->getCollection();
|
||||
switch ($collection) {
|
||||
case 'users':
|
||||
$usage->setParam('users.{scope}.count.total', 1 * $multiplier);
|
||||
break;
|
||||
case 'databases':
|
||||
$usage->setParam('databases.{scope}.count.total', 1 * $multiplier);
|
||||
break;
|
||||
case 'buckets':
|
||||
$usage->setParam('buckets.{scope}.count.total', 1 * $multiplier);
|
||||
break;
|
||||
case 'deployments':
|
||||
$usage->setParam('deployments.{scope}.storage.size', $document->getAttribute('size') * $multiplier);
|
||||
break;
|
||||
default:
|
||||
if (strpos($collection, 'bucket_') === 0) {
|
||||
$usage
|
||||
->setParam('bucketId', $document->getAttribute('bucketId'))
|
||||
->setParam('files.{scope}.storage.size', $document->getAttribute('sizeOriginal') * $multiplier)
|
||||
->setParam('files.{scope}.count.total', 1 * $multiplier);
|
||||
} elseif (strpos($collection, 'database_') === 0) {
|
||||
$usage
|
||||
->setParam('databaseId', $document->getAttribute('databaseId'));
|
||||
if (strpos($collection, '_collection_') !== false) {
|
||||
$usage
|
||||
->setParam('collectionId', $document->getAttribute('$collectionId'))
|
||||
->setParam('documents.{scope}.count.total', 1 * $multiplier);
|
||||
} else {
|
||||
$usage->setParam('collections.{scope}.count.total', 1 * $multiplier);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
|
||||
App::init()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('events')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
->inject('deletes')
|
||||
->inject('database')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) use ($databaseListener) {
|
||||
|
||||
foreach ($abuseKeyLabel as $abuseKey) {
|
||||
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
|
||||
$timeLimit
|
||||
$route = $utopia->match($request);
|
||||
|
||||
if ($project->isEmpty() && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope
|
||||
throw new Exception(Exception::PROJECT_UNKNOWN);
|
||||
}
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
*/
|
||||
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
|
||||
$timeLimitArray = [];
|
||||
|
||||
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
|
||||
|
||||
foreach ($abuseKeyLabel as $abuseKey) {
|
||||
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
|
||||
$timeLimit
|
||||
->setParam('{userId}', $user->getId())
|
||||
->setParam('{userAgent}', $request->getUserAgent(''))
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getHostname() . $route->getPath());
|
||||
$timeLimitArray[] = $timeLimit;
|
||||
}
|
||||
->setParam('{url}', $request->getHostname() . $route->getPath())
|
||||
->setParam('{method}', $request->getMethod());
|
||||
$timeLimitArray[] = $timeLimit;
|
||||
}
|
||||
|
||||
$closestLimit = null;
|
||||
$closestLimit = null;
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
foreach ($timeLimitArray as $timeLimit) {
|
||||
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
|
||||
if (!empty($value)) {
|
||||
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
|
||||
foreach ($timeLimitArray as $timeLimit) {
|
||||
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
|
||||
if (!empty($value)) {
|
||||
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
|
||||
}
|
||||
}
|
||||
|
||||
$abuse = new Abuse($timeLimit);
|
||||
$remaining = $timeLimit->remaining();
|
||||
$limit = $timeLimit->limit();
|
||||
$time = (new \DateTime($timeLimit->time()))->getTimestamp() + $route->getLabel('abuse-time', 3600);
|
||||
|
||||
if ($limit && ($remaining < $closestLimit || is_null($closestLimit))) {
|
||||
$closestLimit = $remaining;
|
||||
$response
|
||||
->addHeader('X-RateLimit-Limit', $limit)
|
||||
->addHeader('X-RateLimit-Remaining', $remaining)
|
||||
->addHeader('X-RateLimit-Reset', $time)
|
||||
;
|
||||
}
|
||||
|
||||
$enabled = App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled';
|
||||
|
||||
if (
|
||||
$enabled // Abuse is enabled
|
||||
&& !$isAppUser // User is not API key
|
||||
&& !$isPrivilegedUser // User is not an admin
|
||||
&& $abuse->check() // Route is rate-limited
|
||||
) {
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
$abuse = new Abuse($timeLimit);
|
||||
|
||||
if ($timeLimit->limit() && ($timeLimit->remaining() < $closestLimit || is_null($closestLimit))) {
|
||||
$closestLimit = $timeLimit->remaining();
|
||||
$response
|
||||
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
|
||||
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
|
||||
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600))
|
||||
;
|
||||
}
|
||||
|
||||
if (
|
||||
(App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled' // Route is rate-limited
|
||||
&& $abuse->check()) // Abuse is not disabled
|
||||
&& (!$isAppUser && !$isPrivilegedUser)
|
||||
) { // User is not an admin or API key
|
||||
throw new Exception('Too many requests', 429, Exception::GENERAL_RATE_LIMIT_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
$events
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$mails
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$audits
|
||||
->setMode($mode)
|
||||
->setUserAgent($request->getUserAgent(''))
|
||||
->setIP($request->getIP())
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$usage
|
||||
->setParam('projectId', $project->getId())
|
||||
->setParam('httpRequest', 1)
|
||||
->setParam('httpUrl', $request->getHostname() . $request->getURI())
|
||||
->setParam('httpMethod', $request->getMethod())
|
||||
->setParam('httpPath', $route->getPath())
|
||||
->setParam('networkRequestSize', 0)
|
||||
->setParam('networkResponseSize', 0)
|
||||
->setParam('storage', 0)
|
||||
;
|
||||
|
||||
$deletes->setProject($project);
|
||||
$database->setProject($project);
|
||||
}, ['utopia', 'request', 'response', 'project', 'user', 'events', 'audits', 'mails', 'usage', 'deletes', 'database', 'dbForProject', 'mode'], 'api');
|
||||
|
||||
App::init(function (App $utopia, Request $request, Document $project) {
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||
return;
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
switch ($route->getLabel('auth.type', '')) {
|
||||
case 'emailPassword':
|
||||
if (($auths['emailPassword'] ?? true) === false) {
|
||||
throw new Exception('Email / Password authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'magic-url':
|
||||
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
|
||||
throw new Exception('Magic URL authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'anonymous':
|
||||
if (($auths['anonymous'] ?? true) === false) {
|
||||
throw new Exception('Anonymous authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'invites':
|
||||
if (($auths['invites'] ?? true) === false) {
|
||||
throw new Exception('Invites authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'jwt':
|
||||
if (($auths['JWT'] ?? true) === false) {
|
||||
throw new Exception('JWT authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Unsupported authentication route', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
break;
|
||||
}
|
||||
}, ['utopia', 'request', 'project'], 'auth');
|
||||
|
||||
App::shutdown(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject) {
|
||||
|
||||
if (!empty($events->getEvent())) {
|
||||
if (empty($events->getPayload())) {
|
||||
$events->setPayload($response->getPayload());
|
||||
}
|
||||
/**
|
||||
* Trigger functions.
|
||||
*/
|
||||
$events
|
||||
->setClass(Event::FUNCTIONS_CLASS_NAME)
|
||||
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
|
||||
->trigger();
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user);
|
||||
|
||||
/**
|
||||
* Trigger webhooks.
|
||||
*/
|
||||
$events
|
||||
->setClass(Event::WEBHOOK_CLASS_NAME)
|
||||
->setQueue(Event::WEBHOOK_QUEUE_NAME)
|
||||
->trigger();
|
||||
$audits
|
||||
->setMode($mode)
|
||||
->setUserAgent($request->getUserAgent(''))
|
||||
->setIP($request->getIP())
|
||||
->setEvent($route->getLabel('audits.event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user);
|
||||
|
||||
/**
|
||||
* Trigger realtime.
|
||||
*/
|
||||
if ($project->getId() !== 'console') {
|
||||
$allEvents = Event::generateEvents($events->getEvent(), $events->getParams());
|
||||
$payload = new Document($events->getPayload());
|
||||
|
||||
$db = $events->getContext('database');
|
||||
$collection = $events->getContext('collection');
|
||||
$bucket = $events->getContext('bucket');
|
||||
|
||||
$target = Realtime::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $payload,
|
||||
project: $project,
|
||||
database: $db,
|
||||
collection: $collection,
|
||||
bucket: $bucket,
|
||||
);
|
||||
|
||||
Realtime::send(
|
||||
projectId: $target['projectId'] ?? $project->getId(),
|
||||
payload: $events->getPayload(),
|
||||
events: $allEvents,
|
||||
channels: $target['channels'],
|
||||
roles: $target['roles'],
|
||||
options: [
|
||||
'permissionsChanged' => $target['permissionsChanged'],
|
||||
'userId' => $events->getParam('userId')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($audits->getResource())) {
|
||||
foreach ($events->getParams() as $key => $value) {
|
||||
$audits->setParam($key, $value);
|
||||
}
|
||||
$audits->trigger();
|
||||
}
|
||||
|
||||
if (!empty($deletes->getType())) {
|
||||
$deletes->trigger();
|
||||
}
|
||||
|
||||
if (!empty($database->getType())) {
|
||||
$database->trigger();
|
||||
}
|
||||
|
||||
$route = $utopia->match($request);
|
||||
if (
|
||||
App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
|
||||
&& $project->getId()
|
||||
&& $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin
|
||||
&& !empty($route->getLabel('sdk.namespace', null))
|
||||
) { // Don't calculate console usage on admin mode
|
||||
$usage
|
||||
->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage'))
|
||||
->setParam('networkResponseSize', $response->getSize())
|
||||
->submit();
|
||||
}
|
||||
}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode', 'dbForProject'], 'api');
|
||||
->setParam('projectInternalId', $project->getInternalId())
|
||||
->setParam('projectId', $project->getId())
|
||||
->setParam('project.{scope}.network.requests', 1)
|
||||
->setParam('httpMethod', $request->getMethod())
|
||||
->setParam('project.{scope}.network.inbound', 0)
|
||||
->setParam('project.{scope}.network.outbound', 0);
|
||||
|
||||
$deletes->setProject($project);
|
||||
$database->setProject($project);
|
||||
|
||||
$dbForProject->on(Database::EVENT_DOCUMENT_CREATE, fn ($event, Document $document) => $databaseListener($event, $document, $usage));
|
||||
|
||||
$dbForProject->on(Database::EVENT_DOCUMENT_DELETE, fn ($event, Document $document) => $databaseListener($event, $document, $usage));
|
||||
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
|
||||
if ($useCache) {
|
||||
$key = md5($request->getURI() . implode('*', $request->getParams())) . '*' . APP_CACHE_BUSTER;
|
||||
$cache = new Cache(
|
||||
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
|
||||
);
|
||||
$timestamp = 60 * 60 * 24 * 30;
|
||||
$data = $cache->load($key, $timestamp);
|
||||
|
||||
if (!empty($data)) {
|
||||
$data = json_decode($data, true);
|
||||
$parts = explode('/', $data['resourceType']);
|
||||
$type = $parts[0] ?? null;
|
||||
|
||||
if ($type === 'bucket') {
|
||||
$bucketId = $parts[1] ?? null;
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$parts = explode('/', $data['resource']);
|
||||
$fileId = $parts[1] ?? null;
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $timestamp) . ' GMT')
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->setContentType($data['contentType'])
|
||||
->send(base64_decode($data['payload']))
|
||||
;
|
||||
|
||||
$route->setIsActive(false);
|
||||
} else {
|
||||
$response->addHeader('X-Appwrite-Cache', 'miss');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
App::init()
|
||||
->groups(['auth'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('project')
|
||||
->action(function (App $utopia, Request $request, Document $project) {
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||
return;
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
switch ($route->getLabel('auth.type', '')) {
|
||||
case 'emailPassword':
|
||||
if (($auths['emailPassword'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Email / Password authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'magic-url':
|
||||
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Magic URL authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'anonymous':
|
||||
if (($auths['anonymous'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Anonymous authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'invites':
|
||||
if (($auths['invites'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Invites authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'jwt':
|
||||
if (($auths['JWT'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'JWT authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication route');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Limit user session
|
||||
*
|
||||
* Delete older sessions if the number of sessions have crossed
|
||||
* the session limit set for the project
|
||||
*/
|
||||
App::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) {
|
||||
$sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT;
|
||||
$session = $response->getPayload();
|
||||
$userId = $session['userId'] ?? '';
|
||||
if (empty($userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
if ($user->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
$count = \count($sessions);
|
||||
if ($count <= $sessionLimit) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < ($count - $sessionLimit); $i++) {
|
||||
$session = array_shift($sessions);
|
||||
$dbForProject->deleteDocument('sessions', $session->getId());
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $userId);
|
||||
});
|
||||
|
||||
App::shutdown()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('events')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
->inject('deletes')
|
||||
->inject('database')
|
||||
->inject('mode')
|
||||
->inject('dbForProject')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject) use ($parseLabel) {
|
||||
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
if (!empty($events->getEvent())) {
|
||||
if (empty($events->getPayload())) {
|
||||
$events->setPayload($responsePayload);
|
||||
}
|
||||
/**
|
||||
* Trigger functions.
|
||||
*/
|
||||
$events
|
||||
->setClass(Event::FUNCTIONS_CLASS_NAME)
|
||||
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
|
||||
->trigger();
|
||||
|
||||
/**
|
||||
* Trigger webhooks.
|
||||
*/
|
||||
$events
|
||||
->setClass(Event::WEBHOOK_CLASS_NAME)
|
||||
->setQueue(Event::WEBHOOK_QUEUE_NAME)
|
||||
->trigger();
|
||||
|
||||
/**
|
||||
* Trigger realtime.
|
||||
*/
|
||||
if ($project->getId() !== 'console') {
|
||||
$allEvents = Event::generateEvents($events->getEvent(), $events->getParams());
|
||||
$payload = new Document($events->getPayload());
|
||||
|
||||
$db = $events->getContext('database');
|
||||
$collection = $events->getContext('collection');
|
||||
$bucket = $events->getContext('bucket');
|
||||
|
||||
$target = Realtime::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $payload,
|
||||
project: $project,
|
||||
database: $db,
|
||||
collection: $collection,
|
||||
bucket: $bucket,
|
||||
);
|
||||
|
||||
Realtime::send(
|
||||
projectId: $target['projectId'] ?? $project->getId(),
|
||||
payload: $events->getPayload(),
|
||||
events: $allEvents,
|
||||
channels: $target['channels'],
|
||||
roles: $target['roles'],
|
||||
options: [
|
||||
'permissionsChanged' => $target['permissionsChanged'],
|
||||
'userId' => $events->getParam('userId')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$route = $utopia->match($request);
|
||||
$requestParams = $route->getParamsValues();
|
||||
$user = $audits->getUser();
|
||||
|
||||
/**
|
||||
* Audit labels
|
||||
*/
|
||||
$pattern = $route->getLabel('audits.resource', null);
|
||||
if (!empty($pattern)) {
|
||||
$resource = $parseLabel($pattern, $responsePayload, $requestParams, $user);
|
||||
if (!empty($resource) && $resource !== $pattern) {
|
||||
$audits->setResource($resource);
|
||||
}
|
||||
}
|
||||
|
||||
$pattern = $route->getLabel('audits.userId', null);
|
||||
if (!empty($pattern)) {
|
||||
$userId = $parseLabel($pattern, $responsePayload, $requestParams, $user);
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
$audits->setUser($user);
|
||||
}
|
||||
|
||||
if (!empty($audits->getResource()) && !empty($audits->getUser()->getId())) {
|
||||
/**
|
||||
* audits.payload is switched to default true
|
||||
* in order to auto audit payload for all endpoints
|
||||
*/
|
||||
$pattern = $route->getLabel('audits.payload', true);
|
||||
if (!empty($pattern)) {
|
||||
$audits->setPayload($responsePayload);
|
||||
}
|
||||
|
||||
foreach ($events->getParams() as $key => $value) {
|
||||
$audits->setParam($key, $value);
|
||||
}
|
||||
$audits->trigger();
|
||||
}
|
||||
|
||||
if (!empty($deletes->getType())) {
|
||||
$deletes->trigger();
|
||||
}
|
||||
|
||||
if (!empty($database->getType())) {
|
||||
$database->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache label
|
||||
*/
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
if ($useCache) {
|
||||
$resource = $resourceType = null;
|
||||
$data = $response->getPayload();
|
||||
|
||||
if (!empty($data['payload'])) {
|
||||
$pattern = $route->getLabel('cache.resource', null);
|
||||
if (!empty($pattern)) {
|
||||
$resource = $parseLabel($pattern, $responsePayload, $requestParams, $user);
|
||||
}
|
||||
|
||||
$pattern = $route->getLabel('cache.resourceType', null);
|
||||
if (!empty($pattern)) {
|
||||
$resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user);
|
||||
}
|
||||
|
||||
$key = md5($request->getURI() . implode('*', $request->getParams())) . '*' . APP_CACHE_BUSTER;
|
||||
$data = json_encode([
|
||||
'resourceType' => $resourceType,
|
||||
'resource' => $resource,
|
||||
'contentType' => $response->getContentType(),
|
||||
'payload' => base64_encode($data['payload']),
|
||||
]) ;
|
||||
|
||||
$signature = md5($data);
|
||||
$cacheLog = $dbForProject->getDocument('cache', $key);
|
||||
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
|
||||
$now = DateTime::now();
|
||||
if ($cacheLog->isEmpty()) {
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
|
||||
'$id' => $key,
|
||||
'resource' => $resource,
|
||||
'accessedAt' => $now,
|
||||
'signature' => $signature,
|
||||
])));
|
||||
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
|
||||
$cacheLog->setAttribute('accessedAt', $now);
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
|
||||
}
|
||||
|
||||
if ($signature !== $cacheLog->getAttribute('signature')) {
|
||||
$cache = new Cache(
|
||||
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
|
||||
);
|
||||
$cache->save($key, $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
|
||||
&& $project->getId()
|
||||
&& !empty($route->getLabel('sdk.namespace', null))
|
||||
) { // Don't calculate console usage on admin mode
|
||||
$metric = $route->getLabel('usage.metric', '');
|
||||
$usageParams = $route->getLabel('usage.params', []);
|
||||
|
||||
if (!empty($metric)) {
|
||||
$usage->setParam($metric, 1);
|
||||
foreach ($usageParams as $param) {
|
||||
$param = $parseLabel($param, $responsePayload, $requestParams, $user);
|
||||
$parts = explode(':', $param);
|
||||
if (count($parts) != 2) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Usage params not properly set');
|
||||
}
|
||||
$usage->setParam($parts[0], $parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
if (!empty($file)) {
|
||||
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
|
||||
}
|
||||
|
||||
$usage
|
||||
->setParam('project.{scope}.network.inbound', $request->getSize() + $fileSize)
|
||||
->setParam('project.{scope}.network.outbound', $response->getSize())
|
||||
->submit();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\View;
|
||||
|
||||
App::init(function (App $utopia, Request $request, Response $response, View $layout) {
|
||||
|
||||
/* AJAX check */
|
||||
if (!empty($request->getQuery('version', ''))) {
|
||||
$layout->setPath(__DIR__ . '/../../views/layouts/empty.phtml');
|
||||
}
|
||||
|
||||
$port = $request->getPort();
|
||||
$protocol = $request->getProtocol();
|
||||
$domain = $request->getHostname();
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
->setParam('protocol', $protocol)
|
||||
->setParam('domain', $domain)
|
||||
->setParam('endpoint', $protocol . '://' . $domain . ($port != 80 && $port != 443 ? ':' . $port : ''))
|
||||
->setParam('home', App::getEnv('_APP_HOME'))
|
||||
->setParam('setup', App::getEnv('_APP_SETUP'))
|
||||
->setParam('class', 'unknown')
|
||||
->setParam('icon', '/images/favicon.png')
|
||||
->setParam('roles', [
|
||||
['type' => 'owner', 'label' => 'Owner'],
|
||||
['type' => 'developer', 'label' => 'Developer'],
|
||||
['type' => 'admin', 'label' => 'Admin'],
|
||||
])
|
||||
->setParam('runtimes', Config::getParam('runtimes'))
|
||||
->setParam('mode', App::getMode())
|
||||
;
|
||||
|
||||
$time = (60 * 60 * 24 * 45); // 45 days cache
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'public, max-age=' . $time)
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
|
||||
->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
|
||||
;
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$route->label('error', __DIR__ . '/../../views/general/error.phtml');
|
||||
|
||||
$scope = $route->getLabel('scope', '');
|
||||
|
||||
$layout
|
||||
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
|
||||
->setParam('isDev', App::isDevelopment())
|
||||
->setParam('class', $scope)
|
||||
;
|
||||
}, ['utopia', 'request', 'response', 'layout'], 'web');
|
||||
+10
-506
@@ -1,515 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\View;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Storage\Storage;
|
||||
|
||||
App::init(function (View $layout) {
|
||||
|
||||
$layout
|
||||
->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.')
|
||||
->setParam('analytics', 'UA-26264668-5')
|
||||
;
|
||||
}, ['layout'], 'console');
|
||||
|
||||
App::shutdown(function (Response $response, View $layout) {
|
||||
|
||||
$header = new View(__DIR__ . '/../../views/console/comps/header.phtml');
|
||||
$footer = new View(__DIR__ . '/../../views/console/comps/footer.phtml');
|
||||
|
||||
$footer
|
||||
->setParam('home', App::getEnv('_APP_HOME', ''))
|
||||
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('header', [$header])
|
||||
->setParam('footer', [$footer])
|
||||
;
|
||||
|
||||
$response->html($layout->render());
|
||||
}, ['response', 'layout'], 'console');
|
||||
|
||||
App::get('/error/:code')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
|
||||
->inject('layout')
|
||||
->action(function (int $code, View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/error.phtml');
|
||||
|
||||
$page
|
||||
->setParam('code', $code)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Error')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console')
|
||||
->groups(['web', 'console'])
|
||||
->alias('/')
|
||||
->alias('/invite')
|
||||
->alias('/login')
|
||||
->alias('/recover')
|
||||
->alias('/register')
|
||||
->groups(['web'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/index.phtml');
|
||||
|
||||
$page
|
||||
->setParam('home', App::getEnv('_APP_HOME', ''))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Console')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/account')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/account/index.phtml');
|
||||
|
||||
$cc = new View(__DIR__ . '/../../views/console/forms/credit-card.phtml');
|
||||
|
||||
$page
|
||||
->setParam('cc', $cc)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Account - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/notifications')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/v1/console/notifications/index.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Notifications')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/home')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/home/index.phtml');
|
||||
$page
|
||||
->setParam('usageStatsEnabled', App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled');
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Console')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/settings')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/settings/index.phtml');
|
||||
|
||||
$page->setParam('services', array_filter(Config::getParam('services'), fn($element) => $element['optional']))
|
||||
->setParam('customDomainsEnabled', ($target->isKnown() && !$target->isTest()))
|
||||
->setParam('customDomainsTarget', $target->get())
|
||||
->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST'))))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Settings')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/webhooks')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/webhooks/index.phtml');
|
||||
|
||||
$page->setParam('events', Config::getParam('events', []));
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Webhooks')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/webhooks/webhook')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->param('id', '', new UID(), 'Webhook unique ID.')
|
||||
->inject('layout')
|
||||
->action(function (string $id, View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/webhooks/webhook.phtml');
|
||||
|
||||
$page
|
||||
->setParam('events', Config::getParam('events', []))
|
||||
->setParam('new', false)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Webhooks')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/webhooks/webhook/new')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/webhooks/webhook.phtml');
|
||||
|
||||
$page
|
||||
->setParam('events', Config::getParam('events', []))
|
||||
->setParam('new', true)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Webhooks')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/keys')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$scopes = array_keys(Config::getParam('scopes'));
|
||||
$page = new View(__DIR__ . '/../../views/console/keys/index.phtml');
|
||||
|
||||
$page->setParam('scopes', $scopes);
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - API Keys')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/databases')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/index.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Database')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/databases/database')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->param('id', '', new UID(), 'Database unique ID.')
|
||||
->label('scope', 'home')
|
||||
->inject('response')
|
||||
->inject('layout')
|
||||
->action(function (string $id, Response $response, View $layout) {
|
||||
|
||||
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
|
||||
|
||||
$logs
|
||||
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
|
||||
->setParam('method', 'database.listLogs')
|
||||
->setParam('params', [
|
||||
'database-id' => '{{router.params.id}}',
|
||||
])
|
||||
;
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/database.phtml');
|
||||
|
||||
$page->setParam('logs', $logs);
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Database')
|
||||
->setParam('body', $page)
|
||||
;
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Expires', 0)
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
;
|
||||
});
|
||||
|
||||
App::get('/console/databases/collection')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->param('id', '', new UID(), 'Collection unique ID.')
|
||||
->inject('response')
|
||||
->inject('layout')
|
||||
->action(function (string $id, Response $response, View $layout) {
|
||||
|
||||
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
|
||||
|
||||
$logs
|
||||
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
|
||||
->setParam('method', 'databases.listCollectionLogs')
|
||||
->setParam('params', [
|
||||
'collection-id' => '{{router.params.id}}',
|
||||
'database-id' => '{{router.params.databaseId}}'
|
||||
])
|
||||
;
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/collection.phtml');
|
||||
|
||||
$page->setParam('logs', $logs);
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Database Collection')
|
||||
->setParam('body', $page)
|
||||
;
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Expires', 0)
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
;
|
||||
});
|
||||
|
||||
App::get('/console/databases/document')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->param('databaseId', '', new UID(), 'Database unique ID.')
|
||||
->param('collection', '', new UID(), 'Collection unique ID.')
|
||||
->inject('layout')
|
||||
->action(function (string $databaseId, string $collection, View $layout) {
|
||||
|
||||
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
|
||||
|
||||
$logs
|
||||
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
|
||||
->setParam('method', 'databases.listDocumentLogs')
|
||||
->setParam('params', [
|
||||
'database-id' => '{{router.params.databaseId}}',
|
||||
'collection-id' => '{{router.params.collection}}',
|
||||
'document-id' => '{{router.params.id}}',
|
||||
])
|
||||
;
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/document.phtml');
|
||||
|
||||
$page
|
||||
->setParam('new', false)
|
||||
->setParam('database', $databaseId)
|
||||
->setParam('collection', $collection)
|
||||
->setParam('logs', $logs)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Database Document')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/databases/document/new')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->param('databaseId', '', new UID(), 'Database unique ID.')
|
||||
->param('collection', '', new UID(), 'Collection unique ID.')
|
||||
->inject('layout')
|
||||
->action(function (string $databaseId, string $collection, View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/document.phtml');
|
||||
|
||||
$page
|
||||
->setParam('new', true)
|
||||
->setParam('database', $databaseId)
|
||||
->setParam('collection', $collection)
|
||||
->setParam('logs', new View())
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Database Document')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/storage')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/storage/index.phtml');
|
||||
|
||||
$page
|
||||
->setParam('home', App::getEnv('_APP_HOME', 0))
|
||||
->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0))
|
||||
->setParam('fileLimitHuman', Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0)))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Storage')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/storage/bucket')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->param('id', '', new UID(), 'Bucket unique ID.')
|
||||
->inject('response')
|
||||
->inject('layout')
|
||||
->action(function (string $id, Response $response, View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/storage/bucket.phtml');
|
||||
$page
|
||||
->setParam('home', App::getEnv('_APP_HOME', 0))
|
||||
->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0))
|
||||
->setParam('fileLimitHuman', Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0)))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Storage Buckets')
|
||||
->setParam('body', $page)
|
||||
;
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Expires', 0)
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
;
|
||||
});
|
||||
|
||||
App::get('/console/users')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/users/index.phtml');
|
||||
|
||||
$page
|
||||
->setParam('auth', Config::getParam('auth'))
|
||||
->setParam('providers', Config::getParam('providers'))
|
||||
->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST'))))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Users')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/users/user')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/users/user.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - User')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/users/teams/team')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/users/team.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Team')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/functions')
|
||||
->groups(['web', 'console'])
|
||||
->desc('Platform console project functions')
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
$page = new View(__DIR__ . '/../../views/console/functions/index.phtml');
|
||||
|
||||
$page
|
||||
->setParam('runtimes', Config::getParam('runtimes'))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Functions')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/functions/function')
|
||||
->groups(['web', 'console'])
|
||||
->desc('Platform console project function')
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
$page = new View(__DIR__ . '/../../views/console/functions/function.phtml');
|
||||
|
||||
$page
|
||||
->setParam('events', Config::getParam('events', []))
|
||||
->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0))
|
||||
->setParam('fileLimitHuman', Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0)))
|
||||
->setParam('timeout', (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900))
|
||||
->setParam('usageStatsEnabled', App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled');
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Function')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/version')
|
||||
->groups(['web', 'console'])
|
||||
->desc('Check for new version')
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
try {
|
||||
$version = \json_decode(@\file_get_contents(App::getEnv('_APP_HOME', 'http://localhost') . '/v1/health/version'), true);
|
||||
|
||||
if ($version && isset($version['version'])) {
|
||||
return $response->json(['version' => $version['version']]);
|
||||
} else {
|
||||
throw new Exception('Failed to check for a newer version', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
throw new Exception('Failed to check for a newer version', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
->action(function (Response $response) {
|
||||
$fallback = file_get_contents(__DIR__ . '/../../../console/index.html');
|
||||
$response->html($fallback);
|
||||
});
|
||||
|
||||
@@ -1,231 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Utopia\View;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
App::init(function (View $layout) {
|
||||
|
||||
$header = new View(__DIR__ . '/../../views/home/comps/header.phtml');
|
||||
$footer = new View(__DIR__ . '/../../views/home/comps/footer.phtml');
|
||||
|
||||
$footer
|
||||
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
->setParam('description', '')
|
||||
->setParam('class', 'home')
|
||||
->setParam('platforms', Config::getParam('platforms'))
|
||||
->setParam('header', [$header])
|
||||
->setParam('footer', [$footer])
|
||||
;
|
||||
}, ['layout'], 'home');
|
||||
|
||||
App::shutdown(function (Response $response, View $layout) {
|
||||
|
||||
$response->html($layout->render());
|
||||
}, ['response', 'layout'], 'home');
|
||||
|
||||
App::get('/')
|
||||
->groups(['web', 'home'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('project')
|
||||
->action(function (Response $response, Database $dbForConsole, Document $project) {
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Expires', 0)
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
;
|
||||
|
||||
if ('console' === $project->getId() || $project->isEmpty()) {
|
||||
$whitelistRoot = App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled');
|
||||
|
||||
if ($whitelistRoot !== 'disabled') {
|
||||
$count = $dbForConsole->count('users', [], 1);
|
||||
|
||||
if ($count !== 0) {
|
||||
return $response->redirect('/auth/signin');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response->redirect('/auth/signup');
|
||||
});
|
||||
|
||||
App::get('/auth/signin')
|
||||
->groups(['web', 'home'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/signin.phtml');
|
||||
|
||||
$page
|
||||
->setParam('root', App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled'))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Sign In - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/auth/signup')
|
||||
->groups(['web', 'home'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/signup.phtml');
|
||||
|
||||
$page
|
||||
->setParam('root', App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled'))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Sign Up - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/auth/recovery')
|
||||
->groups(['web', 'home'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/recovery.phtml');
|
||||
|
||||
$page
|
||||
->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST'))))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Password Recovery - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/auth/confirm')
|
||||
->groups(['web', 'home'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/confirm.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Account Confirmation - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/auth/join')
|
||||
->groups(['web', 'home'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/join.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Invitation - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/auth/recovery/reset')
|
||||
->groups(['web', 'home'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/recovery/reset.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Password Reset - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/auth/oauth2/success')
|
||||
->groups(['web', 'home'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/oauth2.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
->setParam('body', $page)
|
||||
->setParam('header', [])
|
||||
->setParam('footer', [])
|
||||
;
|
||||
});
|
||||
|
||||
App::get('/auth/magic-url')
|
||||
->groups(['web', 'home'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/magicURL.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
->setParam('body', $page)
|
||||
->setParam('header', [])
|
||||
->setParam('footer', [])
|
||||
;
|
||||
});
|
||||
|
||||
App::get('/auth/oauth2/failure')
|
||||
->groups(['web', 'home'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/oauth2.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
->setParam('body', $page)
|
||||
->setParam('header', [])
|
||||
->setParam('footer', [])
|
||||
;
|
||||
});
|
||||
|
||||
App::get('/error/:code')
|
||||
->groups(['web', 'home'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
|
||||
->inject('layout')
|
||||
->action(function (int $code, View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/error.phtml');
|
||||
|
||||
$page
|
||||
->setParam('code', $code)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Error' . ' - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/versions')
|
||||
->desc('Get Version')
|
||||
@@ -233,7 +10,6 @@ App::get('/versions')
|
||||
->label('scope', 'public')
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
$platforms = Config::getParam('platforms');
|
||||
|
||||
$versions = [
|
||||
@@ -241,7 +17,7 @@ App::get('/versions')
|
||||
];
|
||||
|
||||
foreach ($platforms as $platform) {
|
||||
$languages = $platform['languages'] ?? [];
|
||||
$languages = $platform['sdks'] ?? [];
|
||||
|
||||
foreach ($languages as $key => $language) {
|
||||
if (isset($language['dev']) && $language['dev']) {
|
||||
|
||||
Binary file not shown.
+93
-67
@@ -12,6 +12,7 @@ use Swoole\Runtime;
|
||||
use Swoole\Timer;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Orchestration\Adapter\DockerCLI;
|
||||
@@ -118,7 +119,7 @@ function logError(Throwable $error, string $action, Utopia\Route $route = null)
|
||||
|
||||
function getStorageDevice($root): Device
|
||||
{
|
||||
switch (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) {
|
||||
switch (strtolower(App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL))) {
|
||||
case Storage::DEVICE_LOCAL:
|
||||
default:
|
||||
return new Local($root);
|
||||
@@ -164,14 +165,14 @@ App::post('/v1/runtimes')
|
||||
->desc("Create a new runtime server")
|
||||
->param('runtimeId', '', new Text(64), 'Unique runtime ID.')
|
||||
->param('source', '', new Text(0), 'Path to source files.')
|
||||
->param('destination', '', new Text(0), 'Destination folder to store build files into.', true)
|
||||
->param('destination', '', new Text(0, 0), 'Destination folder to store build files into.', true)
|
||||
->param('vars', [], new Assoc(), 'Environment Variables required for the build.')
|
||||
->param('commands', [], new ArrayList(new Text(1024), 100), 'Commands required to build the container. Maximum of 100 commands are allowed, each 1024 characters long.')
|
||||
->param('runtime', '', new Text(128), 'Runtime for the cloud function.')
|
||||
->param('baseImage', '', new Text(128), 'Base image name of the runtime.')
|
||||
->param('entrypoint', '', new Text(256), 'Entrypoint of the code file.', true)
|
||||
->param('remove', false, new Boolean(), 'Remove a runtime after execution.')
|
||||
->param('workdir', '', new Text(256), 'Working directory.', true)
|
||||
->param('workdir', '', new Text(256, 0), 'Working directory.', true)
|
||||
->inject('orchestrationPool')
|
||||
->inject('activeRuntimes')
|
||||
->inject('response')
|
||||
@@ -188,8 +189,9 @@ App::post('/v1/runtimes')
|
||||
$containerId = '';
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$startTime = \time();
|
||||
$endTime = 0;
|
||||
$startTime = DateTime::now();
|
||||
$startTimeUnix = (new \DateTime($startTime))->getTimestamp();
|
||||
$endTimeUnix = 0;
|
||||
$orchestration = $orchestrationPool->get();
|
||||
|
||||
$secret = \bin2hex(\random_bytes(16));
|
||||
@@ -198,8 +200,8 @@ App::post('/v1/runtimes')
|
||||
$activeRuntimes->set($runtimeId, [
|
||||
'id' => $containerId,
|
||||
'name' => $runtimeId,
|
||||
'created' => $startTime,
|
||||
'updated' => $endTime,
|
||||
'created' => $startTimeUnix,
|
||||
'updated' => $endTimeUnix,
|
||||
'status' => 'pending',
|
||||
'key' => $secret,
|
||||
]);
|
||||
@@ -262,7 +264,7 @@ App::post('/v1/runtimes')
|
||||
labels: [
|
||||
'openruntimes-id' => $runtimeId,
|
||||
'openruntimes-type' => 'runtime',
|
||||
'openruntimes-created' => strval($startTime),
|
||||
'openruntimes-created' => strval($startTimeUnix),
|
||||
'openruntimes-runtime' => $runtime,
|
||||
],
|
||||
workdir: $workdir,
|
||||
@@ -319,28 +321,32 @@ App::post('/v1/runtimes')
|
||||
$stdout = 'Build Successful!';
|
||||
}
|
||||
|
||||
$endTime = \time();
|
||||
$endTime = DateTime::now();
|
||||
$endTimeUnix = (new \DateTime($endTime))->getTimestamp();
|
||||
$duration = $endTimeUnix - $startTimeUnix;
|
||||
|
||||
$container = array_merge($container, [
|
||||
'status' => 'ready',
|
||||
'response' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
|
||||
'stderr' => \mb_strcut($stderr, 0, 1000000), // Limit to 1MB
|
||||
'startTime' => $startTime,
|
||||
'endTime' => $endTime,
|
||||
'duration' => $endTime - $startTime,
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
|
||||
if (!$remove) {
|
||||
$activeRuntimes->set($runtimeId, [
|
||||
'id' => $containerId,
|
||||
'name' => $runtimeId,
|
||||
'created' => $startTime,
|
||||
'updated' => $endTime,
|
||||
'status' => 'Up ' . \round($endTime - $startTime, 2) . 's',
|
||||
'created' => $startTimeUnix,
|
||||
'updated' => $endTimeUnix,
|
||||
'status' => 'Up ' . \round($duration, 2) . 's',
|
||||
'key' => $secret,
|
||||
]);
|
||||
}
|
||||
|
||||
Console::success('Build Stage completed in ' . ($endTime - $startTime) . ' seconds');
|
||||
Console::success('Build Stage completed in ' . ($duration) . ' seconds');
|
||||
} catch (Throwable $th) {
|
||||
Console::error('Build failed: ' . $th->getMessage() . $stdout);
|
||||
|
||||
@@ -453,7 +459,7 @@ App::post('/v1/execution')
|
||||
->desc('Create an execution')
|
||||
->param('runtimeId', '', new Text(64), 'The runtimeID to execute.')
|
||||
->param('vars', [], new Assoc(), 'Environment variables required for the build.')
|
||||
->param('data', '', new Text(8192), 'Data to be forwarded to the function, this is user specified.', true)
|
||||
->param('data', '', new Text(8192, 0), 'Data to be forwarded to the function, this is user specified.', true)
|
||||
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.')
|
||||
->inject('activeRuntimes')
|
||||
->inject('response')
|
||||
@@ -488,6 +494,7 @@ App::post('/v1/execution')
|
||||
$executionStart = \microtime(true);
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$res = '';
|
||||
$statusCode = 0;
|
||||
$errNo = -1;
|
||||
$executorResponse = '';
|
||||
@@ -496,7 +503,7 @@ App::post('/v1/execution')
|
||||
|
||||
$ch = \curl_init();
|
||||
$body = \json_encode([
|
||||
'env' => $vars,
|
||||
'variables' => $vars,
|
||||
'payload' => $data,
|
||||
'timeout' => $timeout
|
||||
]);
|
||||
@@ -515,6 +522,7 @@ App::post('/v1/execution')
|
||||
]);
|
||||
|
||||
$executorResponse = \curl_exec($ch);
|
||||
$executorResponse = json_decode($executorResponse, true);
|
||||
|
||||
$statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
@@ -538,13 +546,23 @@ App::post('/v1/execution')
|
||||
|
||||
switch (true) {
|
||||
case $statusCode >= 500:
|
||||
$stderr = $executorResponse ?? 'Internal Runtime error.';
|
||||
$stderr = ($executorResponse ?? [])['stderr'] ?? 'Internal Runtime error.';
|
||||
$stdout = ($executorResponse ?? [])['stdout'] ?? 'Internal Runtime error.';
|
||||
$res = ($executorResponse ?? [])['response'] ?? '';
|
||||
if (is_array($res)) {
|
||||
$res = json_encode($res, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
break;
|
||||
case $statusCode >= 100:
|
||||
$stdout = $executorResponse;
|
||||
$stdout = $executorResponse['stdout'];
|
||||
$res = $executorResponse['response'];
|
||||
if (is_array($res)) {
|
||||
$res = json_encode($res, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$stderr = $executorResponse ?? 'Execution failed.';
|
||||
$stderr = ($executorResponse ?? [])['stderr'] ?? 'Execution failed.';
|
||||
$stdout = ($executorResponse ?? [])['stdout'] ?? '';
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -557,9 +575,10 @@ App::post('/v1/execution')
|
||||
$execution = [
|
||||
'status' => $functionStatus,
|
||||
'statusCode' => $statusCode,
|
||||
'response' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
|
||||
'response' => \mb_strcut($res, 0, 1000000), // Limit to 1MB
|
||||
'stdout' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
|
||||
'stderr' => \mb_strcut($stderr, 0, 1000000), // Limit to 1MB
|
||||
'time' => $executionTime,
|
||||
'duration' => $executionTime,
|
||||
];
|
||||
|
||||
/** Update swoole table */
|
||||
@@ -581,57 +600,64 @@ App::setResource('orchestrationPool', fn() => $orchestrationPool);
|
||||
App::setResource('activeRuntimes', fn() => $activeRuntimes);
|
||||
|
||||
/** Set callbacks */
|
||||
App::error(function ($utopia, $error, $request, $response) {
|
||||
$route = $utopia->match($request);
|
||||
logError($error, "httpError", $route);
|
||||
App::error()
|
||||
->inject('utopia')
|
||||
->inject('error')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (App $utopia, throwable $error, Request $request, Response $response) {
|
||||
$route = $utopia->match($request);
|
||||
logError($error, "httpError", $route);
|
||||
|
||||
switch ($error->getCode()) {
|
||||
case 400: // Error allowed publicly
|
||||
case 401: // Error allowed publicly
|
||||
case 402: // Error allowed publicly
|
||||
case 403: // Error allowed publicly
|
||||
case 404: // Error allowed publicly
|
||||
case 406: // Error allowed publicly
|
||||
case 409: // Error allowed publicly
|
||||
case 412: // Error allowed publicly
|
||||
case 425: // Error allowed publicly
|
||||
case 429: // Error allowed publicly
|
||||
case 501: // Error allowed publicly
|
||||
case 503: // Error allowed publicly
|
||||
$code = $error->getCode();
|
||||
break;
|
||||
default:
|
||||
$code = 500; // All other errors get the generic 500 server error status code
|
||||
}
|
||||
switch ($error->getCode()) {
|
||||
case 400: // Error allowed publicly
|
||||
case 401: // Error allowed publicly
|
||||
case 402: // Error allowed publicly
|
||||
case 403: // Error allowed publicly
|
||||
case 404: // Error allowed publicly
|
||||
case 406: // Error allowed publicly
|
||||
case 409: // Error allowed publicly
|
||||
case 412: // Error allowed publicly
|
||||
case 425: // Error allowed publicly
|
||||
case 429: // Error allowed publicly
|
||||
case 501: // Error allowed publicly
|
||||
case 503: // Error allowed publicly
|
||||
$code = $error->getCode();
|
||||
break;
|
||||
default:
|
||||
$code = 500; // All other errors get the generic 500 server error status code
|
||||
}
|
||||
|
||||
$output = [
|
||||
'message' => $error->getMessage(),
|
||||
'code' => $error->getCode(),
|
||||
'file' => $error->getFile(),
|
||||
'line' => $error->getLine(),
|
||||
'trace' => $error->getTrace(),
|
||||
'version' => App::getEnv('_APP_VERSION', 'UNKNOWN')
|
||||
];
|
||||
$output = [
|
||||
'message' => $error->getMessage(),
|
||||
'code' => $error->getCode(),
|
||||
'file' => $error->getFile(),
|
||||
'line' => $error->getLine(),
|
||||
'trace' => $error->getTrace(),
|
||||
'version' => App::getEnv('_APP_VERSION', 'UNKNOWN')
|
||||
];
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
->addHeader('Expires', '0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->setStatusCode($code);
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
->addHeader('Expires', '0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->setStatusCode($code);
|
||||
|
||||
$response->json($output);
|
||||
}, ['utopia', 'error', 'request', 'response']);
|
||||
$response->json($output);
|
||||
});
|
||||
|
||||
App::init(function ($request, $response) {
|
||||
$secretKey = $request->getHeader('x-appwrite-executor-key', '');
|
||||
if (empty($secretKey)) {
|
||||
throw new Exception('Missing executor key', 401);
|
||||
}
|
||||
App::init()
|
||||
->inject('request')
|
||||
->action(function (Request $request) {
|
||||
$secretKey = $request->getHeader('x-appwrite-executor-key', '');
|
||||
if (empty($secretKey)) {
|
||||
throw new Exception('Missing executor key', 401);
|
||||
}
|
||||
|
||||
if ($secretKey !== App::getEnv('_APP_EXECUTOR_SECRET', '')) {
|
||||
throw new Exception('Missing executor key', 401);
|
||||
}
|
||||
}, ['request', 'response']);
|
||||
if ($secretKey !== App::getEnv('_APP_EXECUTOR_SECRET', '')) {
|
||||
throw new Exception('Missing executor key', 401);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$http->on('start', function ($http) {
|
||||
@@ -641,7 +667,7 @@ $http->on('start', function ($http) {
|
||||
/**
|
||||
* Warmup: make sure images are ready to run fast 🚀
|
||||
*/
|
||||
$runtimes = new Runtimes('v1');
|
||||
$runtimes = new Runtimes('v2');
|
||||
$allowList = empty(App::getEnv('_APP_FUNCTIONS_RUNTIMES')) ? [] : \explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES'));
|
||||
$runtimes = $runtimes->getAll(true, $allowList);
|
||||
foreach ($runtimes as $runtime) {
|
||||
|
||||
+36
-42
@@ -10,6 +10,9 @@ use Swoole\Http\Response as SwooleResponse;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
@@ -35,8 +38,7 @@ $http
|
||||
'http_compression_level' => 6,
|
||||
'package_max_length' => $payloadSize,
|
||||
'buffer_output_size' => $payloadSize,
|
||||
])
|
||||
;
|
||||
]);
|
||||
|
||||
$http->on('WorkerStart', function ($server, $workerId) {
|
||||
Console::success('Worker ' . ++$workerId . ' started successfully');
|
||||
@@ -50,7 +52,7 @@ $http->on('AfterReload', function ($server, $workerId) {
|
||||
Console::success('Reload completed...');
|
||||
});
|
||||
|
||||
Files::load(__DIR__ . '/../public');
|
||||
Files::load(__DIR__ . '/../console');
|
||||
|
||||
include __DIR__ . '/controllers/general.php';
|
||||
|
||||
@@ -78,26 +80,21 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
}
|
||||
} while ($attempts < $max);
|
||||
|
||||
App::setResource('db', fn() => $db);
|
||||
App::setResource('cache', fn() => $redis);
|
||||
App::setResource('db', fn () => $db);
|
||||
App::setResource('cache', fn () => $redis);
|
||||
|
||||
$dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
$dbForConsole = $app->getResource('dbForConsole');
|
||||
|
||||
Console::success('[Setup] - Server database init started...');
|
||||
$collections = Config::getParam('collections', []); /** @var array $collections */
|
||||
|
||||
if (!$dbForConsole->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'))) {
|
||||
$redis->flushAll();
|
||||
|
||||
Console::success('[Setup] - Creating database: appwrite...');
|
||||
|
||||
$dbForConsole->create(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
}
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
|
||||
try {
|
||||
Console::success('[Setup] - Creating metadata table: appwrite...');
|
||||
$dbForConsole->createMetadata();
|
||||
} catch (\Throwable $th) {
|
||||
Console::success('[Setup] - Creating database: appwrite...');
|
||||
$dbForConsole->create();
|
||||
} catch (\Exception $e) {
|
||||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
}
|
||||
|
||||
@@ -119,9 +116,9 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* Skip to prevent 0.15 migration issues.
|
||||
* Skip to prevent 0.16 migration issues.
|
||||
*/
|
||||
if ($key === 'databases' && $dbForConsole->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), 'collections')) {
|
||||
if (in_array($key, ['cache', 'variables']) && $dbForConsole->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), 'bucket_1')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -132,7 +129,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
|
||||
foreach ($collection['attributes'] as $attribute) {
|
||||
$attributes[] = new Document([
|
||||
'$id' => $attribute['$id'],
|
||||
'$id' => ID::custom($attribute['$id']),
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
@@ -146,7 +143,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
|
||||
foreach ($collection['indexes'] as $index) {
|
||||
$indexes[] = new Document([
|
||||
'$id' => $index['$id'],
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
@@ -157,20 +154,25 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
$dbForConsole->createCollection($key, $attributes, $indexes);
|
||||
}
|
||||
|
||||
if ($dbForConsole->getDocument('buckets', 'default')->isEmpty()) {
|
||||
if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), 'bucket_1')) {
|
||||
Console::success('[Setup] - Creating default bucket...');
|
||||
$dbForConsole->createDocument('buckets', new Document([
|
||||
'$id' => 'default',
|
||||
'$collection' => 'buckets',
|
||||
'$id' => ID::custom('default'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'name' => 'Default',
|
||||
'permission' => 'file',
|
||||
'maximumFileSize' => (int) App::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB
|
||||
'allowedFileExtensions' => [],
|
||||
'enabled' => true,
|
||||
'compression' => 'gzip',
|
||||
'encryption' => true,
|
||||
'antivirus' => true,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'fileSecurity' => true,
|
||||
'$permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'search' => 'buckets Default',
|
||||
]));
|
||||
|
||||
@@ -187,7 +189,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
|
||||
foreach ($files['attributes'] as $attribute) {
|
||||
$attributes[] = new Document([
|
||||
'$id' => $attribute['$id'],
|
||||
'$id' => ID::custom($attribute['$id']),
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
@@ -201,7 +203,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
|
||||
foreach ($files['indexes'] as $index) {
|
||||
$indexes[] = new Document([
|
||||
'$id' => $index['$id'],
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
@@ -236,8 +238,7 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
||||
->setContentType(Files::getFileMimeType($request->getURI()))
|
||||
->addHeader('Cache-Control', 'public, max-age=' . $time)
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
|
||||
->send(Files::getFileContents($request->getURI()))
|
||||
;
|
||||
->send(Files::getFileContents($request->getURI()));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -247,12 +248,12 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
||||
$db = $register->get('dbPool')->get();
|
||||
$redis = $register->get('redisPool')->get();
|
||||
|
||||
App::setResource('db', fn() => $db);
|
||||
App::setResource('cache', fn() => $redis);
|
||||
App::setResource('db', fn () => $db);
|
||||
App::setResource('cache', fn () => $redis);
|
||||
|
||||
try {
|
||||
Authorization::cleanRoles();
|
||||
Authorization::setRole('role:all');
|
||||
Authorization::setRole(Role::any()->toString());
|
||||
|
||||
$app->run($request, $response);
|
||||
} catch (\Throwable $th) {
|
||||
@@ -294,7 +295,7 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
||||
$log->addExtra('line', $th->getLine());
|
||||
$log->addExtra('trace', $th->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $th->getTrace());
|
||||
$log->addExtra('roles', Authorization::$roles);
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
$log->setAction($action);
|
||||
@@ -315,13 +316,6 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
||||
Console::error('[Error] File: ' . $th->getFile());
|
||||
Console::error('[Error] Line: ' . $th->getLine());
|
||||
|
||||
/**
|
||||
* Reset Database connection if PDOException was thrown.
|
||||
*/
|
||||
if ($th instanceof PDOException) {
|
||||
$db = null;
|
||||
}
|
||||
|
||||
$swooleResponse->setStatusCode(500);
|
||||
|
||||
$output = ((App::isDevelopment())) ? [
|
||||
|
||||
+265
-78
@@ -18,15 +18,9 @@ ini_set('display_startup_errors', 1);
|
||||
ini_set('default_socket_timeout', -1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
use Appwrite\Extend\PDO;
|
||||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Phone\Mock;
|
||||
use Appwrite\Auth\Phone\Telesign;
|
||||
use Appwrite\Auth\Phone\TextMagic;
|
||||
use Appwrite\Auth\Phone\Twilio;
|
||||
use Appwrite\DSN\DSN;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
@@ -34,41 +28,53 @@ use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\PDO;
|
||||
use Appwrite\GraphQL\Promises\Adapter\Swoole;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\IP;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Stats\Stats;
|
||||
use Appwrite\Utopia\View;
|
||||
use Utopia\App;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Registry\Registry;
|
||||
use Appwrite\Usage\Stats;
|
||||
use MaxMind\Db\Reader;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Swoole\Database\PDOConfig;
|
||||
use Swoole\Database\PDOPool;
|
||||
use Swoole\Database\RedisConfig;
|
||||
use Swoole\Database\RedisPool;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Messaging\Adapters\SMS\Mock;
|
||||
use Utopia\Messaging\Adapters\SMS\Msg91;
|
||||
use Utopia\Messaging\Adapters\SMS\Telesign;
|
||||
use Utopia\Messaging\Adapters\SMS\TextMagic;
|
||||
use Utopia\Messaging\Adapters\SMS\Twilio;
|
||||
use Utopia\Messaging\Adapters\SMS\Vonage;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Storage\Device\Backblaze;
|
||||
use Utopia\Storage\Device\DOSpaces;
|
||||
use Utopia\Storage\Device\Linode;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Device\S3;
|
||||
use Utopia\Storage\Device\Linode;
|
||||
use Utopia\Storage\Device\Wasabi;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
const APP_NAME = 'Appwrite';
|
||||
const APP_DOMAIN = 'appwrite.io';
|
||||
@@ -80,17 +86,26 @@ const APP_MODE_ADMIN = 'admin';
|
||||
const APP_PAGING_LIMIT = 12;
|
||||
const APP_LIMIT_COUNT = 5000;
|
||||
const APP_LIMIT_USERS = 10000;
|
||||
const APP_LIMIT_USER_PASSWORD_HISTORY = 20;
|
||||
const APP_LIMIT_USER_SESSIONS_MAX = 100;
|
||||
const APP_LIMIT_USER_SESSIONS_DEFAULT = 10;
|
||||
const APP_LIMIT_ANTIVIRUS = 20000000; //20MB
|
||||
const APP_LIMIT_ENCRYPTION = 20000000; //20MB
|
||||
const APP_LIMIT_COMPRESSION = 20000000; //20MB
|
||||
const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
|
||||
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
|
||||
const APP_LIMIT_SUBQUERY = 1000;
|
||||
const APP_CACHE_BUSTER = 401;
|
||||
const APP_VERSION_STABLE = '0.15.1';
|
||||
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
|
||||
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
|
||||
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
|
||||
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 503;
|
||||
const APP_VERSION_STABLE = '1.3.1';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
const APP_DATABASE_ATTRIBUTE_DATETIME = 'datetime';
|
||||
const APP_DATABASE_ATTRIBUTE_URL = 'url';
|
||||
const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange';
|
||||
const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange';
|
||||
@@ -141,6 +156,12 @@ const DELETE_TYPE_USAGE = 'usage';
|
||||
const DELETE_TYPE_REALTIME = 'realtime';
|
||||
const DELETE_TYPE_BUCKETS = 'buckets';
|
||||
const DELETE_TYPE_SESSIONS = 'sessions';
|
||||
const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';
|
||||
const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource';
|
||||
// Compression type
|
||||
const COMPRESSION_TYPE_NONE = 'none';
|
||||
const COMPRESSION_TYPE_GZIP = 'gzip';
|
||||
const COMPRESSION_TYPE_ZSTD = 'zstd';
|
||||
// Mail Types
|
||||
const MAIL_TYPE_VERIFICATION = 'verification';
|
||||
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
|
||||
@@ -173,6 +194,7 @@ Config::load('roles', __DIR__ . '/config/roles.php'); // User roles and scopes
|
||||
Config::load('scopes', __DIR__ . '/config/scopes.php'); // User roles and scopes
|
||||
Config::load('services', __DIR__ . '/config/services.php'); // List of services
|
||||
Config::load('variables', __DIR__ . '/config/variables.php'); // List of env variables
|
||||
Config::load('regions', __DIR__ . '/config/regions.php'); // List of available regions
|
||||
Config::load('avatar-browsers', __DIR__ . '/config/avatars/browsers.php');
|
||||
Config::load('avatar-credit-cards', __DIR__ . '/config/avatars/credit-cards.php');
|
||||
Config::load('avatar-flags', __DIR__ . '/config/avatars/flags.php');
|
||||
@@ -263,11 +285,23 @@ Database::addFilter(
|
||||
return null;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('attributes', [
|
||||
new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]),
|
||||
new Query('databaseInternalId', Query::TYPE_EQUAL, [$document->getAttribute('databaseInternalId')])
|
||||
], $database->getAttributeLimit(), 0, []);
|
||||
$attributes = $database->find('attributes', [
|
||||
Query::equal('collectionInternalId', [$document->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit($database->getLimitForAttributes()),
|
||||
]);
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
|
||||
$options = $attribute->getAttribute('options');
|
||||
foreach ($options as $key => $value) {
|
||||
$attribute->setAttribute($key, $value);
|
||||
}
|
||||
$attribute->removeAttribute('options');
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -279,9 +313,10 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('indexes', [
|
||||
new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]),
|
||||
new Query('databaseInternalId', Query::TYPE_EQUAL, [$document->getAttribute('databaseInternalId')])
|
||||
], 64);
|
||||
Query::equal('collectionInternalId', [$document->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit(64),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -293,8 +328,9 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('platforms', [
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -306,8 +342,9 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('domains', [
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -319,8 +356,9 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('keys', [
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -332,8 +370,9 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('webhooks', [
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -344,8 +383,9 @@ Database::addFilter(
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database->find('sessions', [
|
||||
new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY));
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -357,8 +397,9 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn() => $database
|
||||
->find('tokens', [
|
||||
new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY));
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -370,8 +411,23 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn() => $database
|
||||
->find('memberships', [
|
||||
new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY));
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryVariables',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('variables', [
|
||||
Query::equal('functionInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -408,6 +464,10 @@ Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function () {
|
||||
return new Email();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_DATETIME, function () {
|
||||
return new DatetimeValidator();
|
||||
}, Database::VAR_DATETIME);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) {
|
||||
$elements = $attribute['formatOptions']['elements'];
|
||||
return new WhiteList($elements, true);
|
||||
@@ -446,7 +506,7 @@ $register->set('logger', function () {
|
||||
}
|
||||
|
||||
if (!Logger::hasProvider($providerName)) {
|
||||
throw new Exception("Logging provider not supported. Logging disabled.", 500, Exception::GENERAL_SERVER_ERROR);
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled");
|
||||
}
|
||||
|
||||
$classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName);
|
||||
@@ -557,7 +617,13 @@ $register->set('smtp', function () {
|
||||
return $mail;
|
||||
});
|
||||
$register->set('geodb', function () {
|
||||
return new Reader(__DIR__ . '/db/DBIP/dbip-country-lite-2022-06.mmdb');
|
||||
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2023-01.mmdb');
|
||||
});
|
||||
$register->set('passwordsDictionary', function () {
|
||||
$content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords');
|
||||
$content = explode("\n", $content);
|
||||
$content = array_flip($content);
|
||||
return $content;
|
||||
});
|
||||
$register->set('db', function () {
|
||||
// This is usually for our workers or CLI commands scope
|
||||
@@ -586,6 +652,9 @@ $register->set('cache', function () {
|
||||
|
||||
return $redis;
|
||||
});
|
||||
$register->set('promiseAdapter', function () {
|
||||
return new Swoole();
|
||||
});
|
||||
|
||||
/*
|
||||
* Localization
|
||||
@@ -687,13 +756,6 @@ App::setResource('loggerBreadcrumbs', function () {
|
||||
|
||||
App::setResource('register', fn() => $register);
|
||||
|
||||
App::setResource('layout', function ($locale) {
|
||||
$layout = new View(__DIR__ . '/views/layouts/default.phtml');
|
||||
$layout->setParam('locale', $locale);
|
||||
|
||||
return $layout;
|
||||
}, ['locale']);
|
||||
|
||||
App::setResource('locale', fn() => new Locale(App::getEnv('_APP_LOCALE', 'en')));
|
||||
|
||||
// Queues
|
||||
@@ -709,9 +771,9 @@ App::setResource('usage', function ($register) {
|
||||
|
||||
App::setResource('clients', function ($request, $console, $project) {
|
||||
$console->setAttribute('platforms', [ // Always allow current host
|
||||
'$collection' => 'platforms',
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Current Host',
|
||||
'type' => 'web',
|
||||
'type' => Origin::CLIENT_TYPE_WEB,
|
||||
'hostname' => $request->getHostname(),
|
||||
], Document::SET_TYPE_APPEND);
|
||||
|
||||
@@ -723,7 +785,7 @@ App::setResource('clients', function ($request, $console, $project) {
|
||||
fn ($node) => $node['hostname'],
|
||||
\array_filter(
|
||||
$console->getAttribute('platforms', []),
|
||||
fn ($node) => (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname']))
|
||||
fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && isset($node['hostname']) && !empty($node['hostname']))
|
||||
)
|
||||
);
|
||||
|
||||
@@ -734,7 +796,7 @@ App::setResource('clients', function ($request, $console, $project) {
|
||||
fn ($node) => $node['hostname'],
|
||||
\array_filter(
|
||||
$project->getAttribute('platforms', []),
|
||||
fn ($node) => (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname']))
|
||||
fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB || $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) && isset($node['hostname']) && !empty($node['hostname']))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -754,9 +816,11 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
Authorization::setDefaultStatus(true);
|
||||
|
||||
Auth::setCookieName('a_session_' . $project->getId());
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
|
||||
if (APP_MODE_ADMIN === $mode) {
|
||||
Auth::setCookieName('a_session_' . $console->getId());
|
||||
$authDuration = Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
}
|
||||
|
||||
$session = Auth::decodeSession(
|
||||
@@ -785,7 +849,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
|
||||
if (APP_MODE_ADMIN !== $mode) {
|
||||
if ($project->isEmpty()) {
|
||||
$user = new Document(['$id' => '', '$collection' => 'users']);
|
||||
$user = new Document(['$id' => ID::custom(''), '$collection' => 'users']);
|
||||
} else {
|
||||
$user = $dbForProject->getDocument('users', Auth::$unique);
|
||||
}
|
||||
@@ -795,16 +859,16 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
|
||||
if (
|
||||
$user->isEmpty() // Check a document has been found in the DB
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret, $authDuration)
|
||||
) { // Validate user has valid login token
|
||||
$user = new Document(['$id' => '', '$collection' => 'users']);
|
||||
$user = new Document(['$id' => ID::custom(''), '$collection' => 'users']);
|
||||
}
|
||||
|
||||
if (APP_MODE_ADMIN === $mode) {
|
||||
if ($user->find('teamId', $project->getAttribute('teamId'), 'memberships')) {
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
} else {
|
||||
$user = new Document(['$id' => '', '$collection' => 'users']);
|
||||
$user = new Document(['$id' => ID::custom(''), '$collection' => 'users']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,7 +880,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
try {
|
||||
$payload = $jwt->decode($authJWT);
|
||||
} catch (JWTException $error) {
|
||||
throw new Exception('Failed to verify JWT. ' . $error->getMessage(), 401, Exception::USER_JWT_INVALID);
|
||||
throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
|
||||
}
|
||||
|
||||
$jwtUserId = $payload['userId'] ?? '';
|
||||
@@ -827,7 +891,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
}
|
||||
|
||||
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
|
||||
$user = new Document(['$id' => '', '$collection' => 'users']);
|
||||
$user = new Document(['$id' => ID::custom(''), '$collection' => 'users']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -852,10 +916,10 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
|
||||
|
||||
App::setResource('console', function () {
|
||||
return new Document([
|
||||
'$id' => 'console',
|
||||
'$internalId' => 'console',
|
||||
'$id' => ID::custom('console'),
|
||||
'$internalId' => ID::custom('console'),
|
||||
'name' => 'Appwrite',
|
||||
'$collection' => 'projects',
|
||||
'$collection' => ID::custom('projects'),
|
||||
'description' => 'Appwrite core engine',
|
||||
'logo' => '',
|
||||
'teamId' => -1,
|
||||
@@ -863,9 +927,9 @@ App::setResource('console', function () {
|
||||
'keys' => [],
|
||||
'platforms' => [
|
||||
[
|
||||
'$collection' => 'platforms',
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Localhost',
|
||||
'type' => 'web',
|
||||
'type' => Origin::CLIENT_TYPE_WEB,
|
||||
'hostname' => 'localhost',
|
||||
], // Current host is added on app init
|
||||
],
|
||||
@@ -877,6 +941,7 @@ App::setResource('console', function () {
|
||||
'legalTaxId' => '',
|
||||
'auths' => [
|
||||
'limit' => (App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
|
||||
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
|
||||
],
|
||||
'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
|
||||
'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
|
||||
@@ -922,7 +987,7 @@ App::setResource('deviceBuilds', function ($project) {
|
||||
|
||||
function getDevice($root): Device
|
||||
{
|
||||
switch (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) {
|
||||
switch (strtolower(App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL))) {
|
||||
case Storage::DEVICE_LOCAL:
|
||||
default:
|
||||
return new Local($root);
|
||||
@@ -980,16 +1045,138 @@ App::setResource('geodb', function ($register) {
|
||||
return $register->get('geodb');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('phone', function () {
|
||||
$dsn = new DSN(App::getEnv('_APP_PHONE_PROVIDER'));
|
||||
App::setResource('passwordsDictionary', function ($register) {
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
return $register->get('passwordsDictionary');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('sms', function () {
|
||||
$dsn = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
|
||||
$user = $dsn->getUser();
|
||||
$secret = $dsn->getPassword();
|
||||
|
||||
return match ($dsn->getHost()) {
|
||||
'mock' => new Mock('', ''), // used for tests
|
||||
'mock' => new Mock($user, $secret), // used for tests
|
||||
'twilio' => new Twilio($user, $secret),
|
||||
'text-magic' => new TextMagic($user, $secret),
|
||||
'telesign' => new Telesign($user, $secret),
|
||||
'msg91' => new Msg91($user, $secret),
|
||||
'vonage' => new Vonage($user, $secret),
|
||||
default => null
|
||||
};
|
||||
});
|
||||
|
||||
App::setResource('servers', function () {
|
||||
$platforms = Config::getParam('platforms');
|
||||
$server = $platforms[APP_PLATFORM_SERVER];
|
||||
|
||||
$languages = array_map(function ($language) {
|
||||
return strtolower($language['name']);
|
||||
}, $server['sdks']);
|
||||
|
||||
return $languages;
|
||||
});
|
||||
|
||||
App::setResource('promiseAdapter', function ($register) {
|
||||
return $register->get('promiseAdapter');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('schema', function ($utopia, $dbForProject) {
|
||||
|
||||
$complexity = function (int $complexity, array $args) {
|
||||
$queries = Query::parseQueries($args['queries'] ?? []);
|
||||
$query = Query::getByType($queries, Query::TYPE_LIMIT)[0] ?? null;
|
||||
$limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT;
|
||||
|
||||
return $complexity * $limit;
|
||||
};
|
||||
|
||||
$attributes = function (int $limit, int $offset) use ($dbForProject) {
|
||||
$attrs = Authorization::skip(fn() => $dbForProject->find('attributes', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
]));
|
||||
|
||||
return \array_map(function ($attr) {
|
||||
return $attr->getArrayCopy();
|
||||
}, $attrs);
|
||||
};
|
||||
|
||||
$urls = [
|
||||
'list' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents";
|
||||
},
|
||||
'create' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents";
|
||||
},
|
||||
'read' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
|
||||
},
|
||||
'update' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
|
||||
},
|
||||
'delete' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
|
||||
},
|
||||
];
|
||||
|
||||
$params = [
|
||||
'list' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return [ 'queries' => $args['queries']];
|
||||
},
|
||||
'create' => function (string $databaseId, string $collectionId, array $args) {
|
||||
$id = $args['id'] ?? 'unique()';
|
||||
$permissions = $args['permissions'] ?? null;
|
||||
|
||||
unset($args['id']);
|
||||
unset($args['permissions']);
|
||||
|
||||
// Order must be the same as the route params
|
||||
return [
|
||||
'databaseId' => $databaseId,
|
||||
'documentId' => $id,
|
||||
'collectionId' => $collectionId,
|
||||
'data' => $args,
|
||||
'permissions' => $permissions,
|
||||
];
|
||||
},
|
||||
'update' => function (string $databaseId, string $collectionId, array $args) {
|
||||
$documentId = $args['id'];
|
||||
$permissions = $args['permissions'] ?? null;
|
||||
|
||||
unset($args['id']);
|
||||
unset($args['permissions']);
|
||||
|
||||
// Order must be the same as the route params
|
||||
return [
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
'documentId' => $documentId,
|
||||
'data' => $args,
|
||||
'permissions' => $permissions,
|
||||
];
|
||||
},
|
||||
];
|
||||
|
||||
return Schema::build(
|
||||
$utopia,
|
||||
$complexity,
|
||||
$attributes,
|
||||
$urls,
|
||||
$params,
|
||||
);
|
||||
}, ['utopia', 'dbForProject']);
|
||||
|
||||
App::setResource('requestTimestamp', function ($request) {
|
||||
//TODO: Move this to the Request class itself
|
||||
$timestampHeader = $request->getHeader('x-appwrite-timestamp');
|
||||
$requestTimestamp = null;
|
||||
if (!empty($timestampHeader)) {
|
||||
try {
|
||||
$requestTimestamp = new \DateTime($timestampHeader);
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value');
|
||||
}
|
||||
}
|
||||
return $requestTimestamp;
|
||||
}, ['request']);
|
||||
|
||||
@@ -35,6 +35,7 @@ foreach (
|
||||
realpath(__DIR__ . '/../vendor/symfony'),
|
||||
realpath(__DIR__ . '/../vendor/mongodb'),
|
||||
realpath(__DIR__ . '/../vendor/utopia-php/websocket'), // TODO: remove workerman autoload
|
||||
realpath(__DIR__ . '/../vendor/utopia-php/cache'), // TODO: remove memcache autoload
|
||||
] as $key => $value
|
||||
) {
|
||||
if ($value !== false) {
|
||||
|
||||
+23
-20
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Utopia\Response;
|
||||
@@ -14,8 +13,11 @@ use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
@@ -134,7 +136,7 @@ function getDatabase(Registry &$register, string $namespace)
|
||||
|
||||
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) {
|
||||
sleep(5); // wait for the initial database schema to be ready
|
||||
Console::success('Server started succefully');
|
||||
Console::success('Server started successfully');
|
||||
|
||||
/**
|
||||
* Create document for this worker to share stats across Containers.
|
||||
@@ -146,12 +148,11 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
try {
|
||||
$attempts++;
|
||||
$document = new Document([
|
||||
'$id' => $database->getId(),
|
||||
'$collection' => 'realtime',
|
||||
'$read' => [],
|
||||
'$write' => [],
|
||||
'$id' => ID::unique(),
|
||||
'$collection' => ID::custom('realtime'),
|
||||
'$permissions' => [],
|
||||
'container' => $containerId,
|
||||
'timestamp' => time(),
|
||||
'timestamp' => DateTime::now(),
|
||||
'value' => '{}'
|
||||
]);
|
||||
|
||||
@@ -181,7 +182,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
[$database, $returnDatabase] = getDatabase($register, '_console');
|
||||
|
||||
$statsDocument
|
||||
->setAttribute('timestamp', time())
|
||||
->setAttribute('timestamp', DateTime::now())
|
||||
->setAttribute('value', json_encode($payload));
|
||||
|
||||
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
@@ -203,13 +204,13 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
/**
|
||||
* Sending current connections to project channels on the console project every 5 seconds.
|
||||
*/
|
||||
if ($realtime->hasSubscriber('console', 'role:member', 'project')) {
|
||||
if ($realtime->hasSubscriber('console', Role::users()->toString(), 'project')) {
|
||||
[$database, $returnDatabase] = getDatabase($register, '_console');
|
||||
|
||||
$payload = [];
|
||||
|
||||
$list = Authorization::skip(fn () => $database->find('realtime', [
|
||||
new Query('timestamp', Query::TYPE_GREATER, [(time() - 15)])
|
||||
Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)),
|
||||
]));
|
||||
|
||||
/**
|
||||
@@ -236,7 +237,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
'data' => [
|
||||
'events' => ['stats.connections'],
|
||||
'channels' => ['project'],
|
||||
'timestamp' => time(),
|
||||
'timestamp' => DateTime::formatTz(DateTime::now()),
|
||||
'payload' => [
|
||||
$projectId => $payload[$projectId]
|
||||
]
|
||||
@@ -254,16 +255,16 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
/**
|
||||
* Sending test message for SDK E2E tests every 5 seconds.
|
||||
*/
|
||||
if ($realtime->hasSubscriber('console', 'role:guest', 'tests')) {
|
||||
if ($realtime->hasSubscriber('console', Role::guests()->toString(), 'tests')) {
|
||||
$payload = ['response' => 'WS:/v1/realtime:passed'];
|
||||
|
||||
$event = [
|
||||
'project' => 'console',
|
||||
'roles' => ['role:guest'],
|
||||
'roles' => [Role::guests()->toString()],
|
||||
'data' => [
|
||||
'events' => ['test.event'],
|
||||
'channels' => ['tests'],
|
||||
'timestamp' => time(),
|
||||
'timestamp' => DateTime::formatTz(DateTime::now()),
|
||||
'payload' => $payload
|
||||
]
|
||||
];
|
||||
@@ -305,7 +306,7 @@ $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, $returnConsoleDatabase] = getDatabase($register, '_console');
|
||||
$project = Authorization::skip(fn() => $consoleDatabase->getDocument('projects', $projectId));
|
||||
$project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
|
||||
[$database, $returnDatabase] = getDatabase($register, "_{$project->getInternalId()}");
|
||||
|
||||
$user = $database->getDocument('users', $userId);
|
||||
@@ -433,7 +434,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
|
||||
$realtime->subscribe($project->getId(), $connection, $roles, $channels);
|
||||
|
||||
$user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_USER);
|
||||
$user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_ACCOUNT);
|
||||
|
||||
$server->send([$connection], json_encode([
|
||||
'type' => 'connected',
|
||||
@@ -483,6 +484,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
|
||||
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
|
||||
try {
|
||||
$app = new App('UTC');
|
||||
$response = new Response(new SwooleResponse());
|
||||
$db = $register->get('dbPool')->get();
|
||||
$redis = $register->get('redisPool')->get();
|
||||
@@ -491,9 +493,9 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
$database = new Database(new MariaDB($db), $cache);
|
||||
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$database->setNamespace("_console");
|
||||
$project = Authorization::skip(fn() => $database->getDocument('projects', $realtime->connections[$connection]['projectId']));
|
||||
$projectId = $realtime->connections[$connection]['projectId'];
|
||||
$project = $projectId === 'console' ? $app->getResource('console') : Authorization::skip(fn () => $database->getDocument('projects', $projectId));
|
||||
$database->setNamespace("_{$project->getInternalId()}");
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
*
|
||||
@@ -531,10 +533,11 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
Auth::$secret = $session['secret'] ?? '';
|
||||
|
||||
$user = $database->getDocument('users', Auth::$unique);
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
|
||||
if (
|
||||
empty($user->getId()) // Check a document has been found in the DB
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret, $authDuration) // Validate user has valid login token
|
||||
) {
|
||||
// cookie not valid
|
||||
throw new Exception('Session is not valid.', 1003);
|
||||
@@ -544,7 +547,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
|
||||
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
|
||||
|
||||
$user = $response->output($user, Response::MODEL_USER);
|
||||
$user = $response->output($user, Response::MODEL_ACCOUNT);
|
||||
$server->send([$connection], json_encode([
|
||||
'type' => 'response',
|
||||
'data' => [
|
||||
|
||||
@@ -173,6 +173,16 @@ $cli
|
||||
if (empty($input[$var['name']])) {
|
||||
$input[$var['name']] = $var['default'];
|
||||
}
|
||||
|
||||
if ($var['filter'] === 'domainTarget') {
|
||||
if ($input[$var['name']] !== 'localhost') {
|
||||
Console::warning("\nIf you haven't already done so, set the following record for {$input[$var['name']]} on your DNS provider:\n");
|
||||
$mask = "%-15.15s %-10.10s %-30.30s\n";
|
||||
printf($mask, "Type", "Name", "Value");
|
||||
printf($mask, "A or AAAA", "@", "<YOUR PUBLIC IP>");
|
||||
Console::warning("\nUse 'AAAA' if you're using an IPv6 address and 'A' if you're using an IPv4 address.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$templateForCompose = new View(__DIR__ . '/../views/install/compose.phtml');
|
||||
|
||||
+30
-17
@@ -11,6 +11,7 @@ use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
@@ -57,7 +58,7 @@ $cli
|
||||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_EXECUTIONS)
|
||||
->setTimestamp(time() - $interval)
|
||||
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
@@ -65,7 +66,7 @@ $cli
|
||||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_ABUSE)
|
||||
->setTimestamp(time() - $interval)
|
||||
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
@@ -73,16 +74,15 @@ $cli
|
||||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_AUDIT)
|
||||
->setTimestamp(time() - $interval)
|
||||
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
function notifyDeleteUsageStats(int $interval30m, int $interval1d)
|
||||
function notifyDeleteUsageStats(int $usageStatsRetentionHourly)
|
||||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_USAGE)
|
||||
->setTimestamp1d(time() - $interval1d)
|
||||
->setTimestamp30m(time() - $interval30m)
|
||||
->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ $cli
|
||||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_REALTIME)
|
||||
->setTimestamp(time() - 60)
|
||||
->setDatetime(DateTime::addSeconds(new \DateTime(), -60))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
@@ -98,17 +98,18 @@ $cli
|
||||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_SESSIONS)
|
||||
->setTimestamp(time() - Auth::TOKEN_EXPIRATION_LOGIN_LONG)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
function renewCertificates($dbForConsole)
|
||||
{
|
||||
$time = date('d-m-Y H:i:s', time());
|
||||
$time = DateTime::now();
|
||||
|
||||
$certificates = $dbForConsole->find('certificates', [
|
||||
new Query('attempts', Query::TYPE_LESSEREQUAL, [5]), // Maximum 5 attempts
|
||||
new Query('renewDate', Query::TYPE_LESSEREQUAL, [\time()]) // includes 60 days cooldown (we have 30 days to renew)
|
||||
], 200); // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains)
|
||||
Query::lessThan('attempts', 5), // Maximum 5 attempts
|
||||
Query::lessThanEqual('renewDate', $time), // includes 60 days cooldown (we have 30 days to renew)
|
||||
Query::limit(200), // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains)
|
||||
]);
|
||||
|
||||
|
||||
if (\count($certificates) > 0) {
|
||||
@@ -127,25 +128,37 @@ $cli
|
||||
}
|
||||
}
|
||||
|
||||
function notifyDeleteCache($interval)
|
||||
{
|
||||
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_CACHE_BY_TIMESTAMP)
|
||||
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
// # of days in seconds (1 day = 86400s)
|
||||
$interval = (int) App::getEnv('_APP_MAINTENANCE_INTERVAL', '86400');
|
||||
$executionLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', '1209600');
|
||||
$auditLogRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', '1209600');
|
||||
$abuseLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', '86400');
|
||||
$usageStatsRetention30m = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_30M', '129600'); //36 hours
|
||||
$usageStatsRetention1d = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_1D', '8640000'); // 100 days
|
||||
$usageStatsRetentionHourly = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_HOURLY', '8640000'); //100 days
|
||||
|
||||
Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d) {
|
||||
$cacheRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_CACHE', '2592000'); // 30 days
|
||||
|
||||
Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetentionHourly, $cacheRetention) {
|
||||
$database = getConsoleDB();
|
||||
|
||||
$time = date('d-m-Y H:i:s', time());
|
||||
$time = DateTime::now();
|
||||
|
||||
Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
|
||||
notifyDeleteExecutionLogs($executionLogsRetention);
|
||||
notifyDeleteAbuseLogs($abuseLogsRetention);
|
||||
notifyDeleteAuditLogs($auditLogRetention);
|
||||
notifyDeleteUsageStats($usageStatsRetention30m, $usageStatsRetention1d);
|
||||
notifyDeleteUsageStats($usageStatsRetentionHourly);
|
||||
notifyDeleteConnections();
|
||||
notifyDeleteExpiredSessions();
|
||||
renewCertificates($database);
|
||||
notifyDeleteCache($cacheRetention);
|
||||
}, $interval);
|
||||
});
|
||||
|
||||
+30
-5
@@ -9,12 +9,23 @@ use Utopia\Cache\Cache;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
function clearProjectsCache(Redis $redis, Document $project)
|
||||
{
|
||||
try {
|
||||
$redis->del($redis->keys("cache-_{$project->getInternalId()}:*"));
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed to clear project ("' . $project->getId() . '") cache with error: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$cli
|
||||
->task('migrate')
|
||||
->param('version', APP_VERSION_STABLE, new Text(8), 'Version to migrate to.', true)
|
||||
->param('version', APP_VERSION_STABLE, new Text(32), 'Version to migrate to.', true)
|
||||
->action(function ($version) use ($register) {
|
||||
Authorization::disable();
|
||||
if (!array_key_exists($version, Migration::$versions)) {
|
||||
@@ -29,7 +40,7 @@ $cli
|
||||
|
||||
$db = $register->get('db', true);
|
||||
$redis = $register->get('cache', true);
|
||||
$redis->flushAll();
|
||||
|
||||
$cache = new Cache(new RedisCache($redis));
|
||||
|
||||
$projectDB = new Database(new MariaDB($db), $cache);
|
||||
@@ -44,6 +55,9 @@ $cli
|
||||
$limit = 30;
|
||||
$sum = 30;
|
||||
$offset = 0;
|
||||
/**
|
||||
* @var \Utopia\Database\Document[] $projects
|
||||
*/
|
||||
$projects = [$console];
|
||||
$count = 0;
|
||||
|
||||
@@ -59,18 +73,30 @@ $cli
|
||||
|
||||
while (!empty($projects)) {
|
||||
foreach ($projects as $project) {
|
||||
/**
|
||||
* Skip user projects with id 'console'
|
||||
*/
|
||||
if ($project->getId() === 'console' && $project->getInternalId() !== 'console') {
|
||||
continue;
|
||||
}
|
||||
|
||||
clearProjectsCache($redis, $project);
|
||||
|
||||
try {
|
||||
$migration
|
||||
->setProject($project, $projectDB, $consoleDB)
|
||||
->setPDO($register->get('db'))
|
||||
->execute();
|
||||
} catch (\Throwable $th) {
|
||||
throw $th;
|
||||
Console::error('Failed to update project ("' . $project->getId() . '") version with error: ' . $th->getMessage());
|
||||
throw $th;
|
||||
}
|
||||
|
||||
clearProjectsCache($redis, $project);
|
||||
}
|
||||
|
||||
$sum = \count($projects);
|
||||
$projects = $consoleDB->find('projects', limit: $limit, offset: $offset);
|
||||
$projects = $consoleDB->find('projects', [Query::limit($limit), Query::offset($offset)]);
|
||||
|
||||
$offset = $offset + $limit;
|
||||
$count = $count + $sum;
|
||||
@@ -79,6 +105,5 @@ $cli
|
||||
}
|
||||
|
||||
Swoole\Event::wait(); // Wait for Coroutines to finish
|
||||
$redis->flushAll();
|
||||
Console::success('Data Migration Completed');
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user