mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
2777
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d9e840b23 | ||
|
|
093feb58ca | ||
|
|
64a24b2211 | ||
|
|
1170fd0ee9 | ||
|
|
61d583c32e | ||
|
|
d0a28f8062 | ||
|
|
eee9adc325 | ||
|
|
9ae22ae3fe | ||
|
|
f8ddbe5c7a | ||
|
|
5208e69543 | ||
|
|
645b04cf32 | ||
|
|
0148c3ed77 | ||
|
|
d9a065713b | ||
|
|
8887da9e0b | ||
|
|
74dca5b60c | ||
|
|
6c86faa5d4 | ||
|
|
9ea5d98fc7 | ||
|
|
4a8f400f7e | ||
|
|
7ece755abf | ||
|
|
841d260b5f | ||
|
|
53e829408f | ||
|
|
948df2b774 | ||
|
|
08806afb38 | ||
|
|
13c9f79b3d | ||
|
|
edf552fa9a | ||
|
|
8e036b2dd4 | ||
|
|
7ae2c1bb2e | ||
|
|
bbfb93c91b | ||
|
|
8645ca05ba | ||
|
|
8ce3bdb6d7 | ||
|
|
65bb7c6dfd | ||
|
|
23c4fa27be | ||
|
|
0787034bab | ||
|
|
d8103cb57e | ||
|
|
e1dc20b71a | ||
|
|
8b23d4bab1 | ||
|
|
187ba0a575 | ||
|
|
68c85a52fb | ||
|
|
8cb4d2894b | ||
|
|
75efa28125 | ||
|
|
85d48f6ff0 | ||
|
|
9a7c7dee56 | ||
|
|
7b88c80350 | ||
|
|
479577f00b | ||
|
|
7952eafdf0 | ||
|
|
de9b0e6eee | ||
|
|
aeffeb8cc8 | ||
|
|
a11cbc3799 | ||
|
|
b0eb6434b8 | ||
|
|
fe28af292b | ||
|
|
6b4ecbcd23 | ||
|
|
26a2180a44 | ||
|
|
dab6701f79 | ||
|
|
73a6fc2df3 | ||
|
|
05feea5f6d | ||
|
|
62282e9515 | ||
|
|
77739390dd | ||
|
|
51763a6e84 | ||
|
|
60c336bfa1 | ||
|
|
f58c8c5370 | ||
|
|
4228bc9b63 | ||
|
|
ed8c39f649 | ||
|
|
c0bd4401c5 | ||
|
|
21f3221a21 | ||
|
|
00f1723878 | ||
|
|
3d16901ea1 | ||
|
|
bd204ddca5 | ||
|
|
51af7e35bf | ||
|
|
9c2064a5ce | ||
|
|
304f33991f | ||
|
|
8bd89441a4 | ||
|
|
1401e20247 | ||
|
|
bc2e17827c | ||
|
|
d5cb9d0f1f | ||
|
|
fd0d891d45 | ||
|
|
cd647e0c18 | ||
|
|
cb7aad9885 | ||
|
|
8906d94e8f | ||
|
|
1a33b75d42 | ||
|
|
1f51381e4b | ||
|
|
1065f27a44 | ||
|
|
b0d03c5109 | ||
|
|
0bf2318cdd | ||
|
|
53a0424326 | ||
|
|
927489bc5b | ||
|
|
0accc494f0 | ||
|
|
21be819025 | ||
|
|
873b384f70 | ||
|
|
cda521a5d9 | ||
|
|
421bfed8b7 | ||
|
|
6d0d15f469 | ||
|
|
d130e7d3bd | ||
|
|
c9a7902e52 | ||
|
|
73a10a1dca | ||
|
|
7bd3c334ed | ||
|
|
fe287044a7 | ||
|
|
816c3a1401 | ||
|
|
deac241e68 | ||
|
|
68e0083566 | ||
|
|
f4e8d72593 | ||
|
|
f8343e01cd | ||
|
|
208dc37738 | ||
|
|
b8ce3cb9fe | ||
|
|
9993cca484 | ||
|
|
61868a9b01 | ||
|
|
b158c4dec9 | ||
|
|
a2c774de31 | ||
|
|
3c220d603c | ||
|
|
c294386e17 | ||
|
|
f1ff8dd783 | ||
|
|
f803a2268b | ||
|
|
db44598419 | ||
|
|
93db0c3cd2 | ||
|
|
cd9a2b761c | ||
|
|
9f69aa624e | ||
|
|
dd3f92856a | ||
|
|
e6e28f32ff | ||
|
|
fb6ec704a1 | ||
|
|
ba22e5f457 | ||
|
|
04b6f62ac2 | ||
|
|
0f8e1db044 | ||
|
|
ad85fc6184 | ||
|
|
7172ccfc5b | ||
|
|
b2dffe4057 | ||
|
|
d8e5a97266 | ||
|
|
6d3e40d73b | ||
|
|
767e202b34 | ||
|
|
fda0af6694 | ||
|
|
36ab6ce236 | ||
|
|
772ca7d36c | ||
|
|
189f2e05e0 | ||
|
|
b2f7ed48dd | ||
|
|
680b214950 | ||
|
|
cc7d038c3e | ||
|
|
73d1e6c225 | ||
|
|
ecd77cb2e3 | ||
|
|
8d3070416a | ||
|
|
6e422ebe3c | ||
|
|
3c602e84e6 | ||
|
|
1cacb87c65 | ||
|
|
8e4bd363d2 | ||
|
|
b7c2220648 | ||
|
|
bc8683ab75 | ||
|
|
87d14a88d2 | ||
|
|
85de3d27d8 | ||
|
|
733e0498a7 | ||
|
|
e48fadf9fd | ||
|
|
c7ab1dda9f | ||
|
|
9d6cd8249a | ||
|
|
73b2a14ea3 | ||
|
|
c1f62ce0bd | ||
|
|
74d946e42a | ||
|
|
523a8b6ff6 | ||
|
|
06c87a28c9 | ||
|
|
b9e8c1d8bc | ||
|
|
53660f3100 | ||
|
|
bea723ff2e | ||
|
|
ba58a372d1 | ||
|
|
59e43b1035 | ||
|
|
304f67c88f | ||
|
|
9893f77e6d | ||
|
|
8e2571539c | ||
|
|
3e0323dae8 | ||
|
|
d0181787b9 | ||
|
|
2f537d3948 | ||
|
|
e53ec21a2c | ||
|
|
8777fdb417 | ||
|
|
dbe42beb45 | ||
|
|
76c7eb14eb | ||
|
|
8944e1db14 | ||
|
|
c365480aec | ||
|
|
f08ea92df4 | ||
|
|
0fc41b27ee | ||
|
|
40c15077e2 | ||
|
|
60c632363f | ||
|
|
60fafe8feb | ||
|
|
2669e66ae2 | ||
|
|
c3120f4aaa | ||
|
|
969c58fda9 | ||
|
|
49814c2a7e | ||
|
|
f9141ca564 | ||
|
|
4edec1494a | ||
|
|
0b60898ad5 | ||
|
|
82ce35029a | ||
|
|
93f86e57ea | ||
|
|
a46c5d1029 | ||
|
|
88b229b13e | ||
|
|
3ed87a8ab9 | ||
|
|
9bec94c976 | ||
|
|
6596569259 | ||
|
|
6e86527eb6 | ||
|
|
5d9bdd4cc7 | ||
|
|
6852ee1f0f | ||
|
|
81c3d96629 | ||
|
|
1acbff316e | ||
|
|
84ec907a47 | ||
|
|
f02707627f | ||
|
|
0df1396d52 | ||
|
|
d1d36a2372 | ||
|
|
0b6a8ec2ca | ||
|
|
ab73a70c83 | ||
|
|
ed95ac7643 | ||
|
|
42f3e06985 | ||
|
|
470857daf8 | ||
|
|
c4509f5ce7 | ||
|
|
3f0857b36d | ||
|
|
baa1cb19c6 | ||
|
|
e3f64c3234 | ||
|
|
3f5647bcc9 | ||
|
|
2c36fab1c6 | ||
|
|
f8d92c04ad | ||
|
|
91199e746b | ||
|
|
74abf4897b | ||
|
|
6372180537 | ||
|
|
950cc420a2 | ||
|
|
cbb943cc88 | ||
|
|
a1aabd351c | ||
|
|
f31cb0cdfc | ||
|
|
862664dd62 | ||
|
|
498f9e40c8 | ||
|
|
dc40fd7ddc | ||
|
|
a6281992b1 | ||
|
|
3be50d2ab5 | ||
|
|
2a2bf7f154 | ||
|
|
9a8ba13dbf | ||
|
|
ecdea8b487 | ||
|
|
3b8354d2ea | ||
|
|
5f6e65c1aa | ||
|
|
932f34df92 | ||
|
|
fd5a900758 | ||
|
|
fb7223bd1b | ||
|
|
b1685a5ca1 | ||
|
|
2ad967043c | ||
|
|
a64b8e57af | ||
|
|
c3656fd4e7 | ||
|
|
b3c9e6181f | ||
|
|
04733e25c8 | ||
|
|
67084374fb | ||
|
|
4d9521edc6 | ||
|
|
1dd0b419c5 | ||
|
|
636b0775fd | ||
|
|
c4a506445d | ||
|
|
8d046f021c | ||
|
|
2d0885ed70 | ||
|
|
ffe153630d | ||
|
|
6cd9fc72d9 | ||
|
|
312d1029c0 | ||
|
|
fb802c7fe4 | ||
|
|
942775c352 | ||
|
|
f5852a52e2 | ||
|
|
ff89da513f | ||
|
|
52024ccc26 | ||
|
|
d733e8b75f | ||
|
|
2523613056 | ||
|
|
0479f4cca7 | ||
|
|
49ab765ee8 | ||
|
|
2495101dfd | ||
|
|
7bb548aecb | ||
|
|
e78a3e1f83 | ||
|
|
98a5294709 | ||
|
|
39476fa3d5 | ||
|
|
45df0ef854 | ||
|
|
013555cc8e | ||
|
|
b9f50d9f01 | ||
|
|
3c78e7efff | ||
|
|
c6399a2a01 | ||
|
|
e8a0425cc3 | ||
|
|
a53eb6c750 | ||
|
|
db15b05332 | ||
|
|
1a2db06654 | ||
|
|
b4b356e61f | ||
|
|
624538fcaf | ||
|
|
e2f082eea1 | ||
|
|
4a1bf2eb74 | ||
|
|
84595e625d | ||
|
|
7aecd3814a | ||
|
|
2be7e1e8d4 | ||
|
|
5a5d1752f9 | ||
|
|
dff1319e4d | ||
|
|
e7b64113ca | ||
|
|
07cdd66296 | ||
|
|
921311e91e | ||
|
|
e1b4917acf | ||
|
|
796a1b5bd2 | ||
|
|
0b4b7f5296 | ||
|
|
83401c1004 | ||
|
|
be939c6598 | ||
|
|
db699fe0f6 | ||
|
|
387ef142cc | ||
|
|
2689cceb71 | ||
|
|
881525f349 | ||
|
|
8086d6eef0 | ||
|
|
badbcaf1a8 | ||
|
|
e6d9c5c5fd | ||
|
|
86c26f2dc7 | ||
|
|
d205ea61c1 | ||
|
|
2acf241827 | ||
|
|
b862fc6b4c | ||
|
|
f7605453b4 | ||
|
|
0c35498dbb | ||
|
|
5a0b22f638 | ||
|
|
c7a97a16df | ||
|
|
12bf434f6a | ||
|
|
a1a6b40277 | ||
|
|
8d239b7dc8 | ||
|
|
130024e0ef | ||
|
|
ad72805fd6 | ||
|
|
080350f7ad | ||
|
|
2c453d5fd9 | ||
|
|
8d0cdb8e7f | ||
|
|
a0c013fcec | ||
|
|
ed332a776f | ||
|
|
8e32ef1851 | ||
|
|
69a7c23d2b | ||
|
|
3bd8405deb | ||
|
|
89815cba1c | ||
|
|
8dfbc128ac | ||
|
|
9f00ad47f3 | ||
|
|
a6d15d032f | ||
|
|
327cb671a1 | ||
|
|
314da2bb85 | ||
|
|
8db6474d80 | ||
|
|
a20ff31ba3 | ||
|
|
89a0fb8c9b | ||
|
|
fed98ffdfe | ||
|
|
ea1fc208e1 | ||
|
|
31a5a175b3 | ||
|
|
0ae70f8321 | ||
|
|
954ed45a36 | ||
|
|
e718f4252f | ||
|
|
c07c1479f7 | ||
|
|
76fb59fd73 | ||
|
|
14e740e62d | ||
|
|
cb2905c837 | ||
|
|
e46ee904e0 | ||
|
|
b78fbf02af | ||
|
|
092bb1adac | ||
|
|
af7c5f9f40 | ||
|
|
49a514a206 | ||
|
|
1b88aa855c | ||
|
|
23ed68502f | ||
|
|
8e18af5db1 | ||
|
|
179def8f4d | ||
|
|
b6550c88ef | ||
|
|
57bc76edc7 | ||
|
|
fcd31f4cff | ||
|
|
2d7b01aa35 | ||
|
|
86478e463a | ||
|
|
2243f0c72e | ||
|
|
c3c568653f | ||
|
|
403083b0ce | ||
|
|
e5ef825ac4 | ||
|
|
abd953974d | ||
|
|
7297188c1b | ||
|
|
2a672f5a63 | ||
|
|
a160b0f987 | ||
|
|
fb1a19747a | ||
|
|
01e2eb5025 | ||
|
|
baffa615e6 | ||
|
|
bd87672607 | ||
|
|
8139ec0d6f | ||
|
|
31c326fe87 | ||
|
|
0dc319d244 | ||
|
|
5841d5ee57 | ||
|
|
7935658222 | ||
|
|
1c10528585 | ||
|
|
2e784c0e8b | ||
|
|
5d2b0c38d9 | ||
|
|
1660c0b00c | ||
|
|
579f795c81 | ||
|
|
c684d0a2cd | ||
|
|
8fbc517b17 | ||
|
|
13510ffcb5 | ||
|
|
0d9743c8cb | ||
|
|
d1e083338e | ||
|
|
0d7187a5cc | ||
|
|
96327a9ebc | ||
|
|
32a5ff4c8c | ||
|
|
31b1663f6b | ||
|
|
c152d8e001 | ||
|
|
8fd6208502 | ||
|
|
938505da51 | ||
|
|
51d2deca5f | ||
|
|
1bdbb8fce1 | ||
|
|
d87c67eecb | ||
|
|
8a2ec0dcd7 | ||
|
|
9c6551ff7b | ||
|
|
066474010b | ||
|
|
7bbf04e7ac | ||
|
|
feb5d5cbf1 | ||
|
|
a36adbd68b | ||
|
|
30462b1951 | ||
|
|
b46de29735 | ||
|
|
d92413fd6b | ||
|
|
04010ca205 | ||
|
|
840eb1776e | ||
|
|
3fd42d3fc6 | ||
|
|
ae0cdaff63 | ||
|
|
3a3e9cc281 | ||
|
|
7ecba8e5bb | ||
|
|
1af638deef | ||
|
|
dd8b718985 | ||
|
|
ef39f8c2e2 | ||
|
|
4698323f38 | ||
|
|
612b064d2a | ||
|
|
2a7038bb8e | ||
|
|
2c434924de | ||
|
|
e629037da2 | ||
|
|
ad9d953e10 | ||
|
|
11bd9e63f0 | ||
|
|
ac152a76b5 | ||
|
|
d9b7a018ea | ||
|
|
abcaf6cd73 | ||
|
|
81ee68e219 | ||
|
|
e14ae7ea4a | ||
|
|
ec21effd2c | ||
|
|
7399362c20 | ||
|
|
d5ad714f5a | ||
|
|
1389bab3d1 | ||
|
|
100189c0fb | ||
|
|
d36a09eec6 | ||
|
|
1a5e9748f3 | ||
|
|
22939831f2 | ||
|
|
e0a54a2ea6 | ||
|
|
fbd0051118 | ||
|
|
ace7a6baa1 | ||
|
|
2069c9fdad | ||
|
|
a4c18c4b77 | ||
|
|
cba700402b | ||
|
|
85edfc6af8 | ||
|
|
cfe6b8d34b | ||
|
|
aedfe0352d | ||
|
|
60399aaa0a | ||
|
|
9404e9225e | ||
|
|
e263d681d9 | ||
|
|
6b8df26f84 | ||
|
|
e8cc95323e | ||
|
|
7c1fb0d945 | ||
|
|
20f780a58b | ||
|
|
ad7552e676 | ||
|
|
93839ec36d | ||
|
|
17dc8e3f8a | ||
|
|
f4d01f4c37 | ||
|
|
3d1ea6d330 | ||
|
|
cfb1f4fcdb | ||
|
|
05067302d2 | ||
|
|
834872870f | ||
|
|
1946fca9f6 | ||
|
|
e4b9b92cbf | ||
|
|
1a54bd58a6 | ||
|
|
c443a2f999 | ||
|
|
c79e8e5b95 | ||
|
|
9f537a5bee | ||
|
|
a60d19fb42 | ||
|
|
6af57ca48e | ||
|
|
ef7cf41a61 | ||
|
|
3c8e4da34d | ||
|
|
3b1f2afbd5 | ||
|
|
8b1e97bae4 | ||
|
|
7b8f25696c | ||
|
|
34ab21730d | ||
|
|
70fbf69c56 | ||
|
|
62be0e1d00 | ||
|
|
2c55f5aaa6 | ||
|
|
681b6031aa | ||
|
|
ab03b4e3ca | ||
|
|
2e0a6f9b9e | ||
|
|
572544bf55 | ||
|
|
583a18dcd7 | ||
|
|
3b9318c2fc | ||
|
|
91ad4097ff | ||
|
|
d9d38197dd | ||
|
|
e836df13ba | ||
|
|
a8b2ebb356 | ||
|
|
023597842e | ||
|
|
c57ec57277 | ||
|
|
6978f6960e | ||
|
|
c53262a44c | ||
|
|
8339435fb5 | ||
|
|
424a357503 | ||
|
|
35cd2b5295 | ||
|
|
9a7512d1fd | ||
|
|
989fa2b8a0 | ||
|
|
a461d5bf72 | ||
|
|
cb2d0dc592 | ||
|
|
a9d1f6668f | ||
|
|
95ba881ab7 | ||
|
|
708b6279e0 | ||
|
|
0295dfcc63 | ||
|
|
06f138b39d | ||
|
|
9d020fdef5 | ||
|
|
f25143b240 | ||
|
|
d89cdbcf5a | ||
|
|
524ce8789f | ||
|
|
8447732614 | ||
|
|
9f446da0a5 | ||
|
|
62effa7bc4 | ||
|
|
dedcdc2e0e | ||
|
|
8db247c840 | ||
|
|
b11749c452 | ||
|
|
c38a5177dd | ||
|
|
abae54f72c | ||
|
|
bf2b3b68e3 | ||
|
|
11d2b06b67 | ||
|
|
fe94717196 | ||
|
|
5582c6956d | ||
|
|
2acce83257 | ||
|
|
81b3e81d83 | ||
|
|
854256e8a6 | ||
|
|
d72f69f792 | ||
|
|
7fdde1a518 | ||
|
|
6195674611 | ||
|
|
4804bdf110 | ||
|
|
cb356bc410 | ||
|
|
74d9a3c181 | ||
|
|
6bcdc8c769 | ||
|
|
c9c9db5640 | ||
|
|
a20140eb36 | ||
|
|
c0cb4b26a3 | ||
|
|
b842e03f38 | ||
|
|
16aca35bd4 | ||
|
|
2795505e82 | ||
|
|
5799ca1041 | ||
|
|
e11e7e6780 | ||
|
|
9fd8c5ab9d | ||
|
|
e93010c5fe | ||
|
|
d677b8ed88 | ||
|
|
0648de2046 | ||
|
|
3399308ee3 | ||
|
|
42f72b4477 | ||
|
|
871e4b70f7 | ||
|
|
3e7cf6d60a | ||
|
|
25b6d59e30 | ||
|
|
bd18edc197 | ||
|
|
5479e6f8f1 | ||
|
|
586e676299 | ||
|
|
26821c8372 | ||
|
|
56d78b1712 | ||
|
|
e4c4ffdb3c | ||
|
|
6e146889bf | ||
|
|
1580bac1fc | ||
|
|
7b7fd218f5 | ||
|
|
ab1683070b | ||
|
|
9f3d15bd1c | ||
|
|
a7199ff980 | ||
|
|
37dfe0d719 | ||
|
|
86856bf87d | ||
|
|
26142f4f21 | ||
|
|
a62c489a72 | ||
|
|
b358dad817 | ||
|
|
7c2ab4fcf6 | ||
|
|
29ea03b7bb | ||
|
|
434fcdc508 | ||
|
|
ed7c2fa451 | ||
|
|
d661195a5f | ||
|
|
0f182817ec | ||
|
|
65aa218aac | ||
|
|
df943c62ca | ||
|
|
50e7986f84 | ||
|
|
c7301af18a | ||
|
|
c0c0c921dc | ||
|
|
7c795da72f | ||
|
|
13750f7dbd | ||
|
|
21f12879ef | ||
|
|
1091aa3ddc | ||
|
|
69bdf50c85 | ||
|
|
ea02639767 | ||
|
|
c323c227f9 | ||
|
|
d72ba2a4b9 | ||
|
|
ccb96d6525 | ||
|
|
2dcebf3083 | ||
|
|
cb85cfc5d9 | ||
|
|
4aaa07d57b | ||
|
|
d07b543e71 | ||
|
|
08b74007f7 | ||
|
|
bd19da2e7a | ||
|
|
813424aa3c | ||
|
|
e28333ed63 | ||
|
|
172ebb5f7f | ||
|
|
0f5480cbc0 | ||
|
|
e34fc92c7c | ||
|
|
bb01c9e9e9 | ||
|
|
b0b57935da | ||
|
|
7c3f8504bb | ||
|
|
ec6683cfa9 | ||
|
|
6c0ce5cc41 | ||
|
|
c2d0aded6d | ||
|
|
403e5773a2 | ||
|
|
9d82892219 | ||
|
|
5eb7c36f50 | ||
|
|
68e8b7ca2b | ||
|
|
9c54cce5bd | ||
|
|
9345025ade | ||
|
|
31ca35782b | ||
|
|
438d5185a9 | ||
|
|
2505e25da8 | ||
|
|
cc0a97b10e | ||
|
|
18022d680b | ||
|
|
7c75b51b1a | ||
|
|
d3cabcd5ac | ||
|
|
84133ef631 | ||
|
|
2be59a94da | ||
|
|
88d6150db2 | ||
|
|
faed019d92 | ||
|
|
b318c2cc08 | ||
|
|
eb4340cf4c | ||
|
|
ee09e2c91b | ||
|
|
e6e7c5c816 | ||
|
|
e9841e53b9 | ||
|
|
fca2e95fdd | ||
|
|
5683c310db | ||
|
|
7d86ce76d8 | ||
|
|
795928a037 | ||
|
|
e75441a097 | ||
|
|
ffb86ace38 | ||
|
|
aab8bca91e | ||
|
|
963760678c | ||
|
|
70e27748be | ||
|
|
556e00b62b | ||
|
|
e95aeed56c | ||
|
|
362520ff4a | ||
|
|
1299fffdb2 | ||
|
|
7cade1a27d | ||
|
|
20d90e06f2 | ||
|
|
ef163e8517 | ||
|
|
a0d4e4736c | ||
|
|
3f1c4c2095 | ||
|
|
370a8e231c | ||
|
|
83bcad6e17 | ||
|
|
aa5ea7f3a1 | ||
|
|
8a77eaf2bb | ||
|
|
fe308b4f1f | ||
|
|
20e3597a27 | ||
|
|
e43a3109eb | ||
|
|
d9fbe9658d | ||
|
|
1e2f5bcb84 | ||
|
|
088d0998cd | ||
|
|
a7165e6a32 | ||
|
|
5f7ceecb30 | ||
|
|
fd4b43569b | ||
|
|
b4536a94e0 | ||
|
|
e5261e7d70 | ||
|
|
60912cb084 | ||
|
|
7149deeacb | ||
|
|
59f613f9bb | ||
|
|
8184bc8c78 | ||
|
|
6c193f4862 | ||
|
|
bbfd22ce2e | ||
|
|
23b245e058 | ||
|
|
ae5267cda7 | ||
|
|
f1b5807174 | ||
|
|
6e77221da0 | ||
|
|
4de935a322 | ||
|
|
86b66d70bf | ||
|
|
828b9349c3 | ||
|
|
9cb860d446 | ||
|
|
313a6b0f7e | ||
|
|
6eff011100 | ||
|
|
04140c2acc | ||
|
|
0f961f0d04 | ||
|
|
25f7741d3c | ||
|
|
2ff339dad4 | ||
|
|
91dd4c9c6f | ||
|
|
8413909b12 | ||
|
|
a4d4b6ffb5 | ||
|
|
cbdd344490 | ||
|
|
8fd877810d | ||
|
|
04711d8c02 | ||
|
|
5a5d43f3d0 | ||
|
|
3e462ded0a | ||
|
|
de96b2511a | ||
|
|
ab790f6c02 | ||
|
|
ff618a0565 | ||
|
|
b3db296f13 | ||
|
|
e019e426f1 | ||
|
|
90899b92e3 | ||
|
|
adacec4ad1 | ||
|
|
4eece99768 | ||
|
|
971a4b8f30 | ||
|
|
026dd56f55 | ||
|
|
418185b914 | ||
|
|
167011ce87 | ||
|
|
e80bc2bb39 | ||
|
|
3d69a35d5a | ||
|
|
9a4220f2dd | ||
|
|
aac90ea8b8 | ||
|
|
56aa503146 | ||
|
|
875a60fe07 | ||
|
|
3427755695 | ||
|
|
68238193d2 | ||
|
|
07926534bf | ||
|
|
a2a0a1405f | ||
|
|
195edd19ac | ||
|
|
6cf333141c | ||
|
|
12dbb1d50d | ||
|
|
9dff192508 | ||
|
|
650627bfb4 | ||
|
|
4016895b8f | ||
|
|
3b7aea22c2 | ||
|
|
50fe0bf98d | ||
|
|
4f0a7de868 | ||
|
|
1933e48d9c | ||
|
|
90228a5ffb | ||
|
|
063c2be38f | ||
|
|
ff9845c9b9 | ||
|
|
bd5bfc04ef | ||
|
|
9df221d333 | ||
|
|
7a131d3508 | ||
|
|
d77dbebe8b | ||
|
|
8eadaec676 | ||
|
|
c55030387f | ||
|
|
afabd5b87a | ||
|
|
5f2512dc4f | ||
|
|
f6fc425c98 | ||
|
|
810d650aba | ||
|
|
817a3808e5 | ||
|
|
d96910138f | ||
|
|
b8673ac07f | ||
|
|
246c8ce939 | ||
|
|
49d89a4206 | ||
|
|
ba45c191c5 | ||
|
|
74f9464c18 | ||
|
|
c5dfabdb7c | ||
|
|
bfdd2b65dc | ||
|
|
8a4c5bec27 | ||
|
|
2c803b262c | ||
|
|
9bc793b216 | ||
|
|
fcf15689e8 | ||
|
|
97acbd42ba | ||
|
|
aef38aa114 | ||
|
|
79e066a4d7 | ||
|
|
33ab1513d0 | ||
|
|
725dff7f0f | ||
|
|
358e096301 | ||
|
|
74c050ffb6 | ||
|
|
8376b176cb | ||
|
|
69ed3bb776 | ||
|
|
262b2e6cff | ||
|
|
d422f693f7 | ||
|
|
acb9548e85 | ||
|
|
ee1ee7ecfe | ||
|
|
b14d76af0d | ||
|
|
f2ba319e6e | ||
|
|
d81f2f8b5c | ||
|
|
e119bf6777 | ||
|
|
6e0c8724ce | ||
|
|
8f6d45b207 | ||
|
|
1d53cb41de | ||
|
|
dc073b8298 | ||
|
|
8c3aebf735 | ||
|
|
b0d6ffa582 | ||
|
|
556f7338c1 | ||
|
|
301471354e | ||
|
|
5bce1e17cb | ||
|
|
7402145c9d | ||
|
|
4b1e4008a9 | ||
|
|
f1877476d0 | ||
|
|
46693faa67 | ||
|
|
75b3e78098 | ||
|
|
82e16e6d9e | ||
|
|
63710e8ff8 | ||
|
|
ae00a69592 | ||
|
|
794329be82 | ||
|
|
5549cd8900 | ||
|
|
a840d9de20 | ||
|
|
1a90c8aab5 | ||
|
|
7f2fdf623c | ||
|
|
39699d16ba | ||
|
|
8f391db39a | ||
|
|
2f9802afa2 | ||
|
|
3af1910cf9 | ||
|
|
796a845bcf | ||
|
|
56736a79e2 | ||
|
|
7f76bfed11 | ||
|
|
536319b440 | ||
|
|
81159e82d5 | ||
|
|
74f4ebf90e | ||
|
|
5c25b58351 | ||
|
|
ea35b6226d | ||
|
|
d77940d22f | ||
|
|
42cd1df77d | ||
|
|
2052fb975b | ||
|
|
994e2faa42 | ||
|
|
a60d916c51 | ||
|
|
d522b1a04c | ||
|
|
4b740dce86 | ||
|
|
6a05bd4e0d | ||
|
|
ba26dd6df5 | ||
|
|
0170b4c9e8 | ||
|
|
ab7041d653 | ||
|
|
1a56be197c | ||
|
|
b79b320414 | ||
|
|
1b04ae6d96 | ||
|
|
95e96b3bfb | ||
|
|
18e4be2eab | ||
|
|
7f05a74b31 | ||
|
|
733c5ad97d | ||
|
|
71b56fc504 | ||
|
|
d6e53d8e2e | ||
|
|
62eb159e6a | ||
|
|
d4dd7fb757 | ||
|
|
f61db29498 | ||
|
|
1971126e7b | ||
|
|
d8418a222c | ||
|
|
141f0fc376 | ||
|
|
b387823d63 | ||
|
|
e3f59c696d | ||
|
|
107a185953 | ||
|
|
acf86e47e2 | ||
|
|
63c7f4c8bc | ||
|
|
b11ad4b1c9 | ||
|
|
93510d170e | ||
|
|
be3d18ab41 | ||
|
|
efc5b9e353 | ||
|
|
61377ceb6e | ||
|
|
6a6d99a9d0 | ||
|
|
078f953e32 | ||
|
|
0570567b2e | ||
|
|
3a29bcb41b | ||
|
|
d86cf7b437 | ||
|
|
aabee0ac37 | ||
|
|
4e1168654d | ||
|
|
254018071a | ||
|
|
e904ab6232 | ||
|
|
fed6cefa81 | ||
|
|
640f09c49b | ||
|
|
06bb001d0b | ||
|
|
3730ff4895 | ||
|
|
05771c46d6 | ||
|
|
4ff0b6283a | ||
|
|
852a16d01b | ||
|
|
084b6c799a | ||
|
|
eddeac6e4f | ||
|
|
874f60dce6 | ||
|
|
d21814c51f | ||
|
|
65bcf7898e | ||
|
|
7e460f6155 | ||
|
|
1a271ff272 | ||
|
|
5963631c70 | ||
|
|
0fa5460a7e | ||
|
|
12e3d70032 | ||
|
|
5f766f6a13 | ||
|
|
7d27ceef43 | ||
|
|
9c3cfc4f83 | ||
|
|
b4d366eea1 | ||
|
|
8a4954d4e8 | ||
|
|
1943e00e40 | ||
|
|
f75acfa503 | ||
|
|
b69c40345f | ||
|
|
83dd6a9897 | ||
|
|
75f543c6b0 | ||
|
|
232f99c6c9 | ||
|
|
faa1b0a59b | ||
|
|
fe210e0cf0 | ||
|
|
f85bf337b7 | ||
|
|
06020e2b62 | ||
|
|
8ff4d60dbb | ||
|
|
43cd7ac8b3 | ||
|
|
393c4012d9 | ||
|
|
c6a862a0a3 | ||
|
|
1d983a949e | ||
|
|
59a8172e96 | ||
|
|
0fbd2c6425 | ||
|
|
f6b5deda6d | ||
|
|
5e30fc5350 | ||
|
|
a5457da91c | ||
|
|
a37a6d9d7c | ||
|
|
c1ee2f2b41 | ||
|
|
6a584c83c1 | ||
|
|
777fb3fcdd | ||
|
|
6ea20659e6 | ||
|
|
dade177af2 | ||
|
|
97ffbbc44b | ||
|
|
493f536a6e | ||
|
|
d9fc6b70ad | ||
|
|
3af7f0aa3d | ||
|
|
b791e59672 | ||
|
|
c50a0de627 | ||
|
|
f07c351c94 | ||
|
|
a42522feaa | ||
|
|
9400fdbbe5 | ||
|
|
92b696ea21 | ||
|
|
a0605b14cb | ||
|
|
50ec55d595 | ||
|
|
a7fc8cf84b | ||
|
|
6c652ba3ae | ||
|
|
794c6536fb | ||
|
|
3f133cab74 | ||
|
|
2e30c1ba1f | ||
|
|
654d178cf2 | ||
|
|
02e740f8b0 | ||
|
|
62b639ce48 | ||
|
|
825e9639b1 | ||
|
|
1b3fe3046d | ||
|
|
7c91157dc8 | ||
|
|
584e4c0d75 | ||
|
|
9a8a2b5a49 | ||
|
|
4428ed1f7d | ||
|
|
205024657a | ||
|
|
fab43a5f83 | ||
|
|
36b0475298 | ||
|
|
0ce4555f70 | ||
|
|
de5f33f206 | ||
|
|
9a44e8130f | ||
|
|
f0477f8940 | ||
|
|
3bc81c82ec | ||
|
|
2f3c43d0db | ||
|
|
77d27bdd20 | ||
|
|
87a9d72523 | ||
|
|
f6674f07e4 | ||
|
|
049ad14121 | ||
|
|
a0a82b86dc | ||
|
|
326e62334f | ||
|
|
b29b5da006 | ||
|
|
3e9e0652b0 | ||
|
|
46039ded2f | ||
|
|
dfce78c421 | ||
|
|
ce3bff13dd | ||
|
|
a031682596 | ||
|
|
9b8dd02a88 | ||
|
|
bbf37e0a0e | ||
|
|
a734cadba1 | ||
|
|
826b034cd2 | ||
|
|
9ee9a8e0ed | ||
|
|
2afb8a14ac | ||
|
|
6024d01e7b | ||
|
|
f250f454f7 | ||
|
|
2c6f731a38 | ||
|
|
1d2883f472 | ||
|
|
8c1a1604a8 | ||
|
|
ea2fd4b995 | ||
|
|
dfc2e87170 | ||
|
|
f2ad3a501e | ||
|
|
c443c8fdab | ||
|
|
1838b9c4e8 | ||
|
|
09aa383cf3 | ||
|
|
ce054e7088 | ||
|
|
76dc8c29d5 | ||
|
|
f761829d60 | ||
|
|
8b4bc7e4a6 | ||
|
|
6bc72cf43f | ||
|
|
520b96f0d9 | ||
|
|
ea30d976a8 | ||
|
|
9b8e4dd862 | ||
|
|
ef7b9537f7 | ||
|
|
03c9e57773 | ||
|
|
54413f4c5e | ||
|
|
a2abd982cf | ||
|
|
f5a0008445 | ||
|
|
64183f2946 | ||
|
|
d9eb942e8c | ||
|
|
84e1b050a9 | ||
|
|
e94438927e | ||
|
|
2a5b3317bf | ||
|
|
52ba87e77e | ||
|
|
20ad91a906 | ||
|
|
19ad2088a0 | ||
|
|
239bebfe2e | ||
|
|
eff58459ef | ||
|
|
85456696b8 | ||
|
|
0659dce935 | ||
|
|
d5ab989891 | ||
|
|
9b18a031d9 | ||
|
|
4d5d30ba7b | ||
|
|
dd5dd4cc43 | ||
|
|
5d61544444 | ||
|
|
9a6de87f7d | ||
|
|
fe7aeefabf | ||
|
|
fc05a4f1e3 | ||
|
|
2ae61164d4 | ||
|
|
774a6af333 | ||
|
|
8e03db6c9e | ||
|
|
5dd4348d5c | ||
|
|
64e2f904c5 | ||
|
|
07ceb1991d | ||
|
|
60f34ced91 | ||
|
|
084dcdffd9 | ||
|
|
e865ca74d7 | ||
|
|
c6b8e649fe | ||
|
|
b314ae8208 | ||
|
|
c303aa57ce | ||
|
|
2384113a04 | ||
|
|
32329ef3f9 | ||
|
|
5becd0a5dc | ||
|
|
12eb3b4eea | ||
|
|
ef37139592 | ||
|
|
8b8f632abe | ||
|
|
d85b28aabc | ||
|
|
88c8d2aa4c | ||
|
|
adb451234b | ||
|
|
52a73856bc | ||
|
|
921b9154eb | ||
|
|
9849e7b232 | ||
|
|
6900b717da | ||
|
|
aaee99e08b | ||
|
|
712c33064a | ||
|
|
7e00bc493c | ||
|
|
8d47d62d7a | ||
|
|
ef030f4a08 | ||
|
|
580204912c | ||
|
|
02de0d2432 | ||
|
|
df37462716 | ||
|
|
c05ef9121f | ||
|
|
f91e8be136 | ||
|
|
d75b601a7b | ||
|
|
72d2ba0159 | ||
|
|
dc71c50123 | ||
|
|
d6e7db8900 | ||
|
|
559396295f | ||
|
|
dc6143c773 | ||
|
|
a1c621fab2 | ||
|
|
2510cb9687 | ||
|
|
d5cfdc570f | ||
|
|
4efa0a7231 | ||
|
|
e9d8d13fbd | ||
|
|
4afd9771c6 | ||
|
|
dd75081609 | ||
|
|
a3c176a2bd | ||
|
|
8081759c5c | ||
|
|
bf3b21cdb1 | ||
|
|
26a822e4b2 | ||
|
|
cede07b520 | ||
|
|
92daef51f0 | ||
|
|
0dbb9dadf6 | ||
|
|
496cfa92bc | ||
|
|
f31acd214e | ||
|
|
ac51fcbf4e | ||
|
|
5ed1ca617f | ||
|
|
12f274223f | ||
|
|
368b61493f | ||
|
|
388affa3d0 | ||
|
|
6ad34a7bf7 | ||
|
|
0bbcdf8268 | ||
|
|
02e5b935fe | ||
|
|
611f2cad6a | ||
|
|
b9d5621bde | ||
|
|
57c7ebb53d | ||
|
|
c79ed286b1 | ||
|
|
28896081d7 | ||
|
|
7687784515 | ||
|
|
31d6fe2423 | ||
|
|
f5947de508 | ||
|
|
0ddcb9b14c | ||
|
|
458df23797 | ||
|
|
369caae87d | ||
|
|
f48791fba6 | ||
|
|
1c8ec7ba1f | ||
|
|
e7eedcdeb7 | ||
|
|
bb5ed1ccc6 | ||
|
|
ad56782b49 | ||
|
|
ae78e793dd | ||
|
|
24613c2e9f | ||
|
|
ce5a04a37d | ||
|
|
718e44adce | ||
|
|
1816fae5a3 | ||
|
|
7ea108f015 | ||
|
|
69c00d428a | ||
|
|
e998474b97 | ||
|
|
6e82bfe8e7 | ||
|
|
34d91a6518 | ||
|
|
7dfa3193c9 | ||
|
|
93d00abfe1 | ||
|
|
e8fb2f8ae1 | ||
|
|
d79ab99202 | ||
|
|
1e111294df | ||
|
|
1752e8ed83 | ||
|
|
22598ae41d | ||
|
|
e2b9c64f91 | ||
|
|
69e9a1bd8c | ||
|
|
a89f591f63 | ||
|
|
ac9ac3e279 | ||
|
|
859a9c72e1 | ||
|
|
19928bd7dc | ||
|
|
88b3791a24 | ||
|
|
765a0fa891 | ||
|
|
fac42f7ad5 | ||
|
|
d7c163be95 | ||
|
|
f5172f8462 | ||
|
|
542e34bc94 | ||
|
|
16934af49f | ||
|
|
29f660cfd6 | ||
|
|
743ded1e79 | ||
|
|
88ae06af57 | ||
|
|
a54f84fce8 | ||
|
|
f830918c7c | ||
|
|
9f7ba3d4ca | ||
|
|
2ba84bf93f | ||
|
|
66dc3adb98 | ||
|
|
3a2babc906 | ||
|
|
697c30f559 | ||
|
|
03f0e382f4 | ||
|
|
b272a8b1e7 | ||
|
|
676431c355 | ||
|
|
2924f408b3 | ||
|
|
b0547015a9 | ||
|
|
f49346485a | ||
|
|
6f854b5eb6 | ||
|
|
ab6c5c9f8d | ||
|
|
7ac4a5bf27 | ||
|
|
1f560d896f | ||
|
|
5efd231861 | ||
|
|
3f8e1cbedb | ||
|
|
08f4ed2b50 | ||
|
|
474e04479f | ||
|
|
40beecdddc | ||
|
|
427e781b5b | ||
|
|
1f606a591d | ||
|
|
9e909ee3fc | ||
|
|
eb7daf3edd | ||
|
|
b72b118c07 | ||
|
|
a56086f8bf | ||
|
|
392b33ef26 | ||
|
|
6bd14d548f | ||
|
|
06d22f910c | ||
|
|
ae835f6cf0 | ||
|
|
8958070327 | ||
|
|
3f50532c91 | ||
|
|
4c49272389 | ||
|
|
f25df67002 | ||
|
|
94108e6f3f | ||
|
|
f682e4ac6f | ||
|
|
cf41d44de4 | ||
|
|
30327f86fd | ||
|
|
2359c1adb6 | ||
|
|
12b494982b | ||
|
|
65dd5fe9b3 | ||
|
|
666e8f3ec9 | ||
|
|
aeebdadae1 | ||
|
|
0a25463b8d | ||
|
|
7e3b693216 | ||
|
|
34d587d7c5 | ||
|
|
a5fc9463c7 | ||
|
|
08ec153585 | ||
|
|
c8f4667b6a | ||
|
|
f9bc5d0cce | ||
|
|
7397d52c0c | ||
|
|
8661ae6840 | ||
|
|
498c1456ed | ||
|
|
2917c66202 | ||
|
|
26c99dca84 | ||
|
|
0e0bab4c40 | ||
|
|
4ed4c660d5 | ||
|
|
67d6404c70 | ||
|
|
b1629b24ad | ||
|
|
9fa13ddfd8 | ||
|
|
1394b4349c | ||
|
|
ede068d5f4 | ||
|
|
431eea2787 | ||
|
|
1c6204118f | ||
|
|
cdc3cb23ec | ||
|
|
9d4806c792 | ||
|
|
d6c2dfb915 | ||
|
|
f402262ffc | ||
|
|
9d9e44a14f | ||
|
|
397a6a34ed | ||
|
|
54d877fe3e | ||
|
|
c660b9d858 | ||
|
|
f9cc7882aa | ||
|
|
4e71e85b5b | ||
|
|
077fe10d80 | ||
|
|
b0ced9b351 | ||
|
|
c3df0a71e3 | ||
|
|
732e74f114 | ||
|
|
d941d18e8c | ||
|
|
3ccd49e4a5 | ||
|
|
6cbcd695d1 | ||
|
|
2d761b48d8 | ||
|
|
556bcbf33b | ||
|
|
b6166063db | ||
|
|
e6a53a8c15 | ||
|
|
f5f37d6a79 | ||
|
|
f0d8c7382b | ||
|
|
ccb7dfa839 | ||
|
|
6235a1c729 | ||
|
|
e972ccedd6 | ||
|
|
fb19f8d6a6 | ||
|
|
e4369e5e85 | ||
|
|
68de687bdf | ||
|
|
a7c1bd94ed | ||
|
|
7319b12b51 | ||
|
|
45b2d58aa5 | ||
|
|
be1c12147f | ||
|
|
4157e89ab7 | ||
|
|
c151d2aaca | ||
|
|
baa40e4007 | ||
|
|
95aab45cc2 | ||
|
|
4a0d33213a | ||
|
|
322b726196 | ||
|
|
cd25a79694 | ||
|
|
47395c5442 | ||
|
|
d13e8641fd | ||
|
|
acd1dd2479 | ||
|
|
3cc1fb6fb4 | ||
|
|
3932b8bdc1 | ||
|
|
197e32f128 | ||
|
|
3bb4fde7e6 | ||
|
|
165caef9ab | ||
|
|
055411fbe4 | ||
|
|
63a91e30fa | ||
|
|
f7749bbbef | ||
|
|
39e5c5e734 | ||
|
|
c0b574fe9b | ||
|
|
34b8453666 | ||
|
|
55e7633bd1 | ||
|
|
88a6616cf8 | ||
|
|
9f7aaf7029 | ||
|
|
235a357ca3 | ||
|
|
b99d2c3c8c | ||
|
|
d59d6f70a5 | ||
|
|
ac84de7645 | ||
|
|
27d2cc9f00 | ||
|
|
ae1eb53e31 | ||
|
|
3b87b42dac | ||
|
|
a74c6e0b10 | ||
|
|
db8010f7c6 | ||
|
|
c24cebf6ce | ||
|
|
262b152cd9 | ||
|
|
ae91869e76 | ||
|
|
2b29c792ad | ||
|
|
3e02b830ac | ||
|
|
f4e9b89195 | ||
|
|
6ef4e76aa6 | ||
|
|
227f2c476d | ||
|
|
2e4a8dec78 | ||
|
|
c1b613e5bd | ||
|
|
d6b9be8799 | ||
|
|
0d79ae33ba | ||
|
|
685c141a59 | ||
|
|
ff09f1e62a | ||
|
|
07a6d60b01 | ||
|
|
77e83d5cfb | ||
|
|
2ea18c4761 | ||
|
|
28ef7da231 | ||
|
|
fe251c443f | ||
|
|
76b154bfce | ||
|
|
c2a60a8569 | ||
|
|
71103b037f | ||
|
|
9459c87b50 | ||
|
|
d0630eae79 | ||
|
|
d1cf833f15 | ||
|
|
e5e7c50ca4 | ||
|
|
1fbfbbf482 | ||
|
|
ef8520ad2d | ||
|
|
36ce381325 | ||
|
|
30cb7e8ff2 | ||
|
|
2f2a743a7f | ||
|
|
a8a8df602f | ||
|
|
feffab137f | ||
|
|
6155fad3e0 | ||
|
|
0dba22c606 | ||
|
|
6653972e11 | ||
|
|
390cdaa6a5 | ||
|
|
d641177618 | ||
|
|
b3067f562f | ||
|
|
3e5e545304 | ||
|
|
d1978b102f | ||
|
|
b1854fee10 | ||
|
|
62b1c19357 | ||
|
|
463bc4aac3 | ||
|
|
e597ee4493 | ||
|
|
e1dedd2fbb | ||
|
|
51e89f5110 | ||
|
|
81a7ba536c | ||
|
|
fb2649ab41 | ||
|
|
b746642979 | ||
|
|
a6bcc3ce97 | ||
|
|
b30d9eb8ad | ||
|
|
28339f0ed7 | ||
|
|
da9d442473 | ||
|
|
47a6168137 | ||
|
|
51438631da | ||
|
|
993c07d7c6 | ||
|
|
9222fec7f8 | ||
|
|
94f3cf7baf | ||
|
|
399afa1b14 | ||
|
|
410388a587 | ||
|
|
4bd08642be | ||
|
|
3ab8503f61 | ||
|
|
00478e8533 | ||
|
|
75320db428 | ||
|
|
43f0dc0846 | ||
|
|
0f873792ba | ||
|
|
4c7917687d | ||
|
|
9aad6721cd | ||
|
|
9ebb4b9626 | ||
|
|
46c4c7b28b | ||
|
|
f67d32fd35 | ||
|
|
392803b5e8 | ||
|
|
a0138455bc | ||
|
|
a10882d7ee | ||
|
|
ec05a3d668 | ||
|
|
e59d95a108 | ||
|
|
351afffdfe | ||
|
|
3531d223e2 | ||
|
|
2457485230 | ||
|
|
e87fe7467e | ||
|
|
f6fbc253f7 | ||
|
|
313444cb12 | ||
|
|
7143bef111 | ||
|
|
e4ae0d463d | ||
|
|
8778e2e666 | ||
|
|
8f212ccd30 | ||
|
|
e8a306253b | ||
|
|
4592a1cc36 | ||
|
|
608a2cc67b | ||
|
|
bd58ba32c8 | ||
|
|
80241e37c3 | ||
|
|
6abf8ce727 | ||
|
|
ed51a059ba | ||
|
|
43d8374c5c | ||
|
|
d4599e6884 | ||
|
|
1e446948e8 | ||
|
|
503b8a0cde | ||
|
|
745947621f | ||
|
|
721b9b9aea | ||
|
|
29ffd703db | ||
|
|
4b909ed7dc | ||
|
|
f9ce932ac1 | ||
|
|
f67870925f | ||
|
|
f416e39098 | ||
|
|
88c2fe2dd2 | ||
|
|
a0482aca7a | ||
|
|
9989137ed4 | ||
|
|
c4142565aa | ||
|
|
933e762284 | ||
|
|
81bfa5c48d | ||
|
|
0106dc7803 | ||
|
|
3b0d760065 | ||
|
|
e0369ec14f | ||
|
|
2e3a1cf82c | ||
|
|
c78701ded7 | ||
|
|
3fe1147194 | ||
|
|
adb861b785 | ||
|
|
8922d676c6 | ||
|
|
6dbc8c5464 | ||
|
|
d2cc5ea932 | ||
|
|
d259f21c65 | ||
|
|
aae3b40d16 | ||
|
|
6415009f07 | ||
|
|
e435e457de | ||
|
|
4c452417d1 | ||
|
|
2449b76676 | ||
|
|
9427b07580 | ||
|
|
2dffcb29b0 | ||
|
|
dd03a04d58 | ||
|
|
775e6ec9cf | ||
|
|
294f6818fa | ||
|
|
b46eebb23c | ||
|
|
48525ce755 | ||
|
|
978284b36f | ||
|
|
5780ec9547 | ||
|
|
7f706fce5d | ||
|
|
df27e8950b | ||
|
|
00473d4744 | ||
|
|
da2e27650c | ||
|
|
d4c659f88d | ||
|
|
c22ece068f | ||
|
|
e394532bcd | ||
|
|
e140603890 | ||
|
|
2189ca1469 | ||
|
|
86a1fbe86a | ||
|
|
14e4854947 | ||
|
|
1c1f9aee2c | ||
|
|
084b62116f | ||
|
|
3d6351bbe0 | ||
|
|
187f4c841e | ||
|
|
15a0c28093 | ||
|
|
c440a3933b | ||
|
|
11477d67c9 | ||
|
|
f6aff786f1 | ||
|
|
8324b789c5 | ||
|
|
f2fc45da02 | ||
|
|
f2ae6e07cb | ||
|
|
61e65e1eda | ||
|
|
2b2be60e77 | ||
|
|
abaa5c7f48 | ||
|
|
ec5fd1d798 | ||
|
|
704437b5e8 | ||
|
|
bed7ab321e | ||
|
|
d04a5cab0d | ||
|
|
f723948907 | ||
|
|
0634a7eac3 | ||
|
|
1c91604c80 | ||
|
|
940f4d12af | ||
|
|
2902b1bde8 | ||
|
|
5076303c33 | ||
|
|
532160705f | ||
|
|
7490240397 | ||
|
|
f666e18154 | ||
|
|
e5a3866065 | ||
|
|
30bc70b9b1 | ||
|
|
bcdae7f4e3 | ||
|
|
47fbb777ed | ||
|
|
2c4c42de05 | ||
|
|
375f643214 | ||
|
|
2f95b0e2e0 | ||
|
|
bfc0b53163 | ||
|
|
f1989d0aba | ||
|
|
75219b800a | ||
|
|
4c3fa5f246 | ||
|
|
06ed493cfe | ||
|
|
7be0fbf616 | ||
|
|
c77ca64fc0 | ||
|
|
8c3a243a5d | ||
|
|
7a8688932c | ||
|
|
6cf53bbb81 | ||
|
|
06e857e5d9 | ||
|
|
3a766bf970 | ||
|
|
f96291af88 | ||
|
|
8557a9e1b1 | ||
|
|
e8af2338ec | ||
|
|
7caab2f9ff | ||
|
|
33f55c73a7 | ||
|
|
edbe0fb72f | ||
|
|
23a9b519f6 | ||
|
|
aec9d91a68 | ||
|
|
950255fab9 | ||
|
|
a032540b3c | ||
|
|
27709e7683 | ||
|
|
5b786e2a68 | ||
|
|
7c9d6ccde2 | ||
|
|
42a4fe2efc | ||
|
|
1c6bce0e79 | ||
|
|
f6c28322c9 | ||
|
|
3092e765cc | ||
|
|
e8faf2a859 | ||
|
|
2106022507 | ||
|
|
ec9045b488 | ||
|
|
55e30a0ef4 | ||
|
|
abd899186d | ||
|
|
59516408af | ||
|
|
5c286c3334 | ||
|
|
f8b27c0b76 | ||
|
|
6e5d4fb358 | ||
|
|
5e2abc539f | ||
|
|
2f03a33c15 | ||
|
|
7ce0e3c38c | ||
|
|
e269614a51 | ||
|
|
21c224709f | ||
|
|
ac8a29f047 | ||
|
|
859d556461 | ||
|
|
975393ae48 | ||
|
|
0c096b7434 | ||
|
|
681f4b9eda | ||
|
|
3d74b651fe | ||
|
|
111c28592d | ||
|
|
10ab613f3b | ||
|
|
2b46bd2643 | ||
|
|
a322b022eb | ||
|
|
0dc43432e2 | ||
|
|
27de266cb0 | ||
|
|
2e048b0895 | ||
|
|
f8bf532fc0 | ||
|
|
210349dc70 | ||
|
|
eaa4795eb6 | ||
|
|
10b88ba194 | ||
|
|
768e4c4988 | ||
|
|
293f75954d | ||
|
|
f93eb2072c | ||
|
|
9788a126a8 | ||
|
|
ad56994b83 | ||
|
|
2af026eabe | ||
|
|
20c01d0e73 | ||
|
|
989bbbc261 | ||
|
|
0a3f284e14 | ||
|
|
11ed6b8f47 | ||
|
|
a7b1363644 | ||
|
|
1d55bbc0d7 | ||
|
|
1f8448a514 | ||
|
|
8dd99b7d2a | ||
|
|
e6efe5b27e | ||
|
|
df99907120 | ||
|
|
7ac83d7a9f | ||
|
|
f2445c2a37 | ||
|
|
7220abe9a4 | ||
|
|
273ea0abcc | ||
|
|
a4c8b74501 | ||
|
|
b69e78e5bf | ||
|
|
efb30e7f8e | ||
|
|
d1502bc7d6 | ||
|
|
719a771613 | ||
|
|
c46c5e4259 | ||
|
|
c02a8d3997 | ||
|
|
37dbbf226e | ||
|
|
142c5731ca | ||
|
|
1d571e470b | ||
|
|
9e988747d8 | ||
|
|
e992ed11c1 | ||
|
|
c269fc22ed | ||
|
|
fd3da47e8a | ||
|
|
ec9b609817 | ||
|
|
bb696a6c7b | ||
|
|
cd45177558 | ||
|
|
f6b2d9d937 | ||
|
|
c48146b699 | ||
|
|
c27c3499aa | ||
|
|
0a6ebe6661 | ||
|
|
ae9d3f4703 | ||
|
|
2efe1ed9dc | ||
|
|
c3495dde35 | ||
|
|
8da5bfccb6 | ||
|
|
89aa61b17c | ||
|
|
87b3f535bd | ||
|
|
a65b0bb32c | ||
|
|
78dd753e8f | ||
|
|
e0131aa749 | ||
|
|
5882fda501 | ||
|
|
ba0a6f0097 | ||
|
|
97ed0dc89c | ||
|
|
4f63c520ec | ||
|
|
c7611ed382 | ||
|
|
eeed8dbc16 | ||
|
|
0ae9a439ee | ||
|
|
7239b9d7cc | ||
|
|
213e16050b | ||
|
|
480c7eac21 | ||
|
|
412c75ebdd | ||
|
|
f33e4b6f5a | ||
|
|
30f34d5764 | ||
|
|
50400c4b43 | ||
|
|
3cfb013f1e | ||
|
|
2e62837de6 | ||
|
|
24f11dc8d6 | ||
|
|
68c3aa3078 | ||
|
|
3e778c0094 | ||
|
|
4aa3a51ef0 | ||
|
|
cf66bee925 | ||
|
|
be27912a6e | ||
|
|
1adb1e23a9 | ||
|
|
a16f26d378 | ||
|
|
966fc47e25 | ||
|
|
99f23ff25a | ||
|
|
6b930129e5 | ||
|
|
ca7cf765b2 | ||
|
|
292e859133 | ||
|
|
5052fd8528 | ||
|
|
5b91b5cec1 | ||
|
|
3d319e25a9 | ||
|
|
4d43b41482 | ||
|
|
6bdab7213a | ||
|
|
c86afb9933 | ||
|
|
b4e61f8587 | ||
|
|
fe5b9c9525 | ||
|
|
b73b450a2e | ||
|
|
4dc7206e04 | ||
|
|
92b7b1956f | ||
|
|
a98f32b5e1 | ||
|
|
36c9c002d1 | ||
|
|
cea308ef71 | ||
|
|
fccc6d5605 | ||
|
|
7019cea5fa | ||
|
|
ebd6b1b013 | ||
|
|
c8afd2c613 | ||
|
|
d30af2c561 | ||
|
|
9420340871 | ||
|
|
bfb4cb4a04 | ||
|
|
d272a474ab | ||
|
|
66c4003789 | ||
|
|
deb556242e | ||
|
|
6be805fe6e | ||
|
|
fcfef4d474 | ||
|
|
5d243de029 | ||
|
|
deeabc2d86 | ||
|
|
a226e2ce79 | ||
|
|
e4122e236c | ||
|
|
0547dbc454 | ||
|
|
c5d4378069 | ||
|
|
58dc4a0280 | ||
|
|
3ed5e1b65a | ||
|
|
2ad3b68821 | ||
|
|
f51900a69c | ||
|
|
0fe072b17b | ||
|
|
124cff7bf2 | ||
|
|
3aafceab40 | ||
|
|
3b961d18f9 | ||
|
|
e311790365 | ||
|
|
47f4e67a52 | ||
|
|
9f9ce61c3a | ||
|
|
f9430d4d20 | ||
|
|
d5f1b56b90 | ||
|
|
1946df83a7 | ||
|
|
59d87a8af1 | ||
|
|
e1912c621a | ||
|
|
bbc527d4a4 | ||
|
|
50cc28647d | ||
|
|
63c07a456f | ||
|
|
58086fbc74 | ||
|
|
a3324b7453 | ||
|
|
2e8b8367ed | ||
|
|
df017bccb2 | ||
|
|
ade25ec845 | ||
|
|
0be400da19 | ||
|
|
8ab06d053f | ||
|
|
dada8f9bc1 | ||
|
|
b4849cd6ca | ||
|
|
1b91b601bc | ||
|
|
2fa22b4b85 | ||
|
|
3522e63404 | ||
|
|
67ba9a880d | ||
|
|
751f7e3fad | ||
|
|
bd801712b9 | ||
|
|
5a1ad4d5ae | ||
|
|
23111d26c4 | ||
|
|
978f22b56d | ||
|
|
71c265d4e8 | ||
|
|
d31a358392 | ||
|
|
95845f8c0e | ||
|
|
6eb4d3f021 | ||
|
|
074f4417ff | ||
|
|
da7a234038 | ||
|
|
f94d1a5d82 | ||
|
|
3a5342ae24 | ||
|
|
d0724812d7 | ||
|
|
6fedefdeda | ||
|
|
cb31e7954e | ||
|
|
0da3f870f9 | ||
|
|
6801a46d4e | ||
|
|
469dbf0a31 | ||
|
|
3fde465dfa | ||
|
|
41d1af273e | ||
|
|
c2ce273065 | ||
|
|
a05d774370 | ||
|
|
e78020d45a | ||
|
|
22ab23d009 | ||
|
|
31ddb6e48f | ||
|
|
21097041f0 | ||
|
|
a4fa0e020a | ||
|
|
9f043a1b58 | ||
|
|
ecaaa4be53 | ||
|
|
850bed904a | ||
|
|
0457d4d052 | ||
|
|
84f3e62908 | ||
|
|
505a072bdd | ||
|
|
6ef49c8f73 | ||
|
|
850737c953 | ||
|
|
a12fce550a | ||
|
|
c4ab6f5aff | ||
|
|
7be0f4a193 | ||
|
|
891c1c4093 | ||
|
|
507c8bd3ec | ||
|
|
2de4e71662 | ||
|
|
3d9653a3df | ||
|
|
f1f660352e | ||
|
|
f132c2b266 | ||
|
|
0220144615 | ||
|
|
1e71eb84c4 | ||
|
|
2930c32184 | ||
|
|
dbfb158452 | ||
|
|
ccc800762c | ||
|
|
8e42b5c97e | ||
|
|
2e438edcc1 | ||
|
|
6394a30581 | ||
|
|
28e1ce9dc2 | ||
|
|
c73f2afbe1 | ||
|
|
c2aff2c2cb | ||
|
|
c01f67f44f | ||
|
|
af7f599525 | ||
|
|
acadaf2307 | ||
|
|
7bd29501c2 | ||
|
|
e70de37c0b | ||
|
|
34f6dab14e | ||
|
|
f97b3aaa86 | ||
|
|
eecf3aae68 | ||
|
|
222516dbd4 | ||
|
|
cad18dc7c7 | ||
|
|
9561ed99d2 | ||
|
|
71013cab08 | ||
|
|
163f7b8d17 | ||
|
|
63c841b7a8 | ||
|
|
b399ff00a6 | ||
|
|
f95504f829 | ||
|
|
425e0c9a6a | ||
|
|
8cf6a4f393 | ||
|
|
2307160238 | ||
|
|
2cfa44faba | ||
|
|
76680bcbcc | ||
|
|
419f3bee1d | ||
|
|
8fdf574a69 | ||
|
|
fb5aeb1476 | ||
|
|
5593682c3d | ||
|
|
8ceb6e2c25 | ||
|
|
d84f6e4ee0 | ||
|
|
6cef099890 | ||
|
|
dfbe3220d8 | ||
|
|
bbe52db4ff | ||
|
|
fcd920460f | ||
|
|
a85499a4ec | ||
|
|
46532671d6 | ||
|
|
4320e5bef5 | ||
|
|
b070e56226 | ||
|
|
64567ffd04 | ||
|
|
70c119b9cc | ||
|
|
f369e9100f | ||
|
|
c184938a2a | ||
|
|
c0dc43fb29 | ||
|
|
5ac95bc103 | ||
|
|
e9136444d7 | ||
|
|
dc33c26781 | ||
|
|
3f59c087c8 | ||
|
|
d8848e10af | ||
|
|
c30689d664 | ||
|
|
bce71d6177 | ||
|
|
d5e775416d | ||
|
|
f867fddaae | ||
|
|
1db4bf7ae4 | ||
|
|
7b588219f9 | ||
|
|
05f99dcdf0 | ||
|
|
3124554403 | ||
|
|
336b0a7c02 | ||
|
|
f593498c07 | ||
|
|
55cbab6270 | ||
|
|
5d5547c6b9 | ||
|
|
066273d0db | ||
|
|
9955b6b61c | ||
|
|
eea088556d | ||
|
|
5685c8ac25 | ||
|
|
c6360cce06 | ||
|
|
187ea93f81 | ||
|
|
7dbb2ce63e | ||
|
|
f74fe4438f | ||
|
|
d40fbda04a | ||
|
|
b34d64b4d4 | ||
|
|
b18d3bc678 | ||
|
|
42cf1fefa5 | ||
|
|
303f7d4845 | ||
|
|
0cc5088e8e | ||
|
|
2c4f5d3daa | ||
|
|
1675683e76 | ||
|
|
78dcfd2770 | ||
|
|
87de071f53 | ||
|
|
39585d0438 | ||
|
|
270dfd685d | ||
|
|
de29879793 | ||
|
|
8508e6009b | ||
|
|
a70e323a15 | ||
|
|
ff1bfb61f1 | ||
|
|
0e5b59e9f0 | ||
|
|
0124d5254c | ||
|
|
923dc9661f | ||
|
|
674f3d83b3 | ||
|
|
507d210bcb | ||
|
|
f5efc53406 | ||
|
|
ff9fb9bb46 | ||
|
|
49b71ff43a | ||
|
|
d2a4361e8d | ||
|
|
170f3656e1 | ||
|
|
786c9d1013 | ||
|
|
297098b252 | ||
|
|
3cfd592351 | ||
|
|
00a2413366 | ||
|
|
784cddcf9b | ||
|
|
08f2a59766 | ||
|
|
246dc07018 | ||
|
|
2af7a46aca | ||
|
|
f849fb9b30 | ||
|
|
1360c16f65 | ||
|
|
8097f1607f | ||
|
|
03a8f35082 | ||
|
|
5dd3b09ca7 | ||
|
|
91c319bc50 | ||
|
|
687be05d95 | ||
|
|
fe930d8f64 | ||
|
|
77ae8dcc63 | ||
|
|
8cd4000751 | ||
|
|
54c8a890d3 | ||
|
|
74b06cdd96 | ||
|
|
5113c9e50a | ||
|
|
0052487d49 | ||
|
|
9841f6ae87 | ||
|
|
6d404ada74 | ||
|
|
65c2b99dfe | ||
|
|
9b58ae7ff5 | ||
|
|
2a03bbf5b8 | ||
|
|
85539dfb29 | ||
|
|
5e9955ed3e | ||
|
|
b56fda0029 | ||
|
|
871043a700 | ||
|
|
59c41478ca | ||
|
|
37087c37ca | ||
|
|
066008293d | ||
|
|
ee9bf984c7 | ||
|
|
343ef4944b | ||
|
|
e0eea5b13d | ||
|
|
d533113d86 | ||
|
|
758d8496c1 | ||
|
|
df491f6f46 | ||
|
|
5123cf5f8e | ||
|
|
1683fe0a98 | ||
|
|
bc3ac237be | ||
|
|
3ee67e620c | ||
|
|
9a94023bde | ||
|
|
ead7a17645 | ||
|
|
70f1b0c2fc | ||
|
|
7009d1edf1 | ||
|
|
f6385c716e | ||
|
|
15aa0e0315 | ||
|
|
d652a17fd7 | ||
|
|
b9acae5a25 | ||
|
|
c4053b62e4 | ||
|
|
b4e1101a9b | ||
|
|
27459cc2c0 | ||
|
|
9c71af219b | ||
|
|
bc533aeeaa | ||
|
|
3c3559aee5 | ||
|
|
f8f403a66a | ||
|
|
be2929f07f | ||
|
|
af8cb29bfb | ||
|
|
d0452e5d3b | ||
|
|
f94f6642ea | ||
|
|
01dc8fa396 | ||
|
|
da07ca5391 | ||
|
|
430c883a88 | ||
|
|
07630c272d | ||
|
|
6256a09902 | ||
|
|
cf8da3495c | ||
|
|
336dd824f6 | ||
|
|
190b9cdaee | ||
|
|
dbec8c01a9 | ||
|
|
d8f572925c | ||
|
|
2533a95b8b | ||
|
|
0c37530974 | ||
|
|
5fa4a1232f | ||
|
|
9c95c162d9 | ||
|
|
0915f23e99 | ||
|
|
26036db5f9 | ||
|
|
c3e2a3fac2 | ||
|
|
b87c750536 | ||
|
|
ca0459046d | ||
|
|
645fbd4f1c | ||
|
|
8fb772340f | ||
|
|
b2f920d983 | ||
|
|
f4c1c72bcf | ||
|
|
3c152b8349 | ||
|
|
7315294555 | ||
|
|
e7b119abae | ||
|
|
37fc9fdcd3 | ||
|
|
f3b395f3f3 | ||
|
|
0408de3253 | ||
|
|
bad427071e | ||
|
|
419d18bb1c | ||
|
|
6e1675c574 | ||
|
|
e49d9f39a8 | ||
|
|
4a3ea540bf | ||
|
|
561af1d386 | ||
|
|
286f7e6b86 | ||
|
|
fe18afcfca | ||
|
|
028cd935d9 | ||
|
|
fba5060469 | ||
|
|
733434fdb8 | ||
|
|
54a46d7d26 | ||
|
|
bbb6b2d256 | ||
|
|
45a03822ee | ||
|
|
be5c4b753a | ||
|
|
4d86096c20 | ||
|
|
04f9e9b62a | ||
|
|
d21d18fe18 | ||
|
|
1d1b83afb6 | ||
|
|
dc651f632a | ||
|
|
e7d33ebc52 | ||
|
|
e4efaf1efa | ||
|
|
0517ebfb4e | ||
|
|
b81e15f5d4 | ||
|
|
b3fb7cfdb6 | ||
|
|
1b53df4a32 | ||
|
|
5240d0f9c6 | ||
|
|
5b28e7c478 | ||
|
|
31ab247b08 | ||
|
|
ab54280d79 | ||
|
|
40f16fab50 | ||
|
|
e343a1068e | ||
|
|
02e7f91e59 | ||
|
|
55360c3af0 | ||
|
|
983eee9266 | ||
|
|
5d3071e084 | ||
|
|
ef0b836987 | ||
|
|
455bc3370c | ||
|
|
f66f75b808 | ||
|
|
458b7bb877 | ||
|
|
a2962b6b95 | ||
|
|
8bdafb46d6 | ||
|
|
20e87cb82f | ||
|
|
d5604efef8 | ||
|
|
95dcb67747 | ||
|
|
c098b62d59 | ||
|
|
a1031ab921 | ||
|
|
5cc8d7ced1 | ||
|
|
83a911fb88 | ||
|
|
facdf1a362 | ||
|
|
d0760aee20 | ||
|
|
7db65f46ae | ||
|
|
f392b5ab45 | ||
|
|
8c90973b61 | ||
|
|
13687f7fc3 | ||
|
|
1b4a96b1fb | ||
|
|
0afbfe2f84 | ||
|
|
4e1494d16d | ||
|
|
348199712a | ||
|
|
1ae7d730ba | ||
|
|
f52b2d5dcd | ||
|
|
2b8772fad2 | ||
|
|
2a0894f3d1 | ||
|
|
e8f97baf26 | ||
|
|
e278b8a5a2 | ||
|
|
8e474676d3 | ||
|
|
8dc3b4ca60 | ||
|
|
098c1e984e | ||
|
|
8ea1f436c1 | ||
|
|
de6fced412 | ||
|
|
23d20b7d02 | ||
|
|
120ceeedad | ||
|
|
b16b662a21 | ||
|
|
779510a605 | ||
|
|
2ca690ccbd | ||
|
|
c4bdd2fe1b | ||
|
|
098c30cd9a | ||
|
|
cee3da2ef5 | ||
|
|
a3bd26d7f5 | ||
|
|
6067fb77b7 | ||
|
|
9f82cd78fb | ||
|
|
43db5b4552 | ||
|
|
a1566b8974 | ||
|
|
719c0bf440 | ||
|
|
353637b9a8 | ||
|
|
42ef7fd2d8 | ||
|
|
2f83bec071 | ||
|
|
bbeb9d4719 | ||
|
|
c0a0f1016e | ||
|
|
a7f40708a3 | ||
|
|
6e7997a8a8 | ||
|
|
25431adafb | ||
|
|
d43507040c | ||
|
|
eee9c9d693 | ||
|
|
860c917cf6 | ||
|
|
1c7867ee03 | ||
|
|
2571f7d3fc | ||
|
|
76a0167700 | ||
|
|
f54684133d | ||
|
|
99a1c19bad | ||
|
|
fabf581d21 | ||
|
|
d9acec6324 | ||
|
|
04b2fc246e | ||
|
|
e9479db0fa | ||
|
|
3fa78e3c3b | ||
|
|
246c334127 | ||
|
|
e6cb17a6cc | ||
|
|
b5449a8ca6 | ||
|
|
11867bef50 | ||
|
|
8b0465f129 | ||
|
|
4ac21afc90 | ||
|
|
993686c4d1 | ||
|
|
2ab6ab8cb2 | ||
|
|
32c95391bf | ||
|
|
1cd7c397de | ||
|
|
17311bac6d | ||
|
|
d997b558dc | ||
|
|
9e8ed3e206 | ||
|
|
cde1d6e83e | ||
|
|
7db24443ba | ||
|
|
10e036958a | ||
|
|
00a5b8fbb9 | ||
|
|
c6dd2dbf40 | ||
|
|
aa130075e8 | ||
|
|
dd26a564dc | ||
|
|
652ab4946d | ||
|
|
377627118f | ||
|
|
f4f0b3f0fe | ||
|
|
e237f7c05d | ||
|
|
1e62e26ef1 | ||
|
|
86874cbec1 | ||
|
|
f48c843bea | ||
|
|
99f736aa52 | ||
|
|
4fe2611ccc | ||
|
|
48fd3b371e | ||
|
|
acea8812da | ||
|
|
9b404db87f | ||
|
|
1695e90399 | ||
|
|
c2124d6803 | ||
|
|
efe24db5a1 | ||
|
|
13597d7c54 | ||
|
|
5dc04ca8ca | ||
|
|
26c86d465b | ||
|
|
f153e1bebf | ||
|
|
a7fdfc629c | ||
|
|
428efdede0 | ||
|
|
619b716233 | ||
|
|
595e93e59e | ||
|
|
9e5189fa09 | ||
|
|
642eac098c | ||
|
|
a45e0a59dd | ||
|
|
6ebab2f346 | ||
|
|
b76260ecce | ||
|
|
b88d9c51fb | ||
|
|
4bfd132eee | ||
|
|
d86984f6cf | ||
|
|
ec7f76f9fb | ||
|
|
a85abdab4a | ||
|
|
cc7cef59b3 | ||
|
|
ee9e4f90c1 | ||
|
|
9687fdb0a5 | ||
|
|
7c9c3d9214 | ||
|
|
7847dfe9c0 | ||
|
|
0f8ce0e93e | ||
|
|
76e1b27cef | ||
|
|
651a3d1d62 | ||
|
|
d068067a98 | ||
|
|
9089eb4228 | ||
|
|
bce68b2202 | ||
|
|
8a260ceece | ||
|
|
426bcffd1a | ||
|
|
4a196562c5 | ||
|
|
9c37c3a740 | ||
|
|
16b32a1069 | ||
|
|
16b90d723c | ||
|
|
f4d2583865 | ||
|
|
18f17ff9fb | ||
|
|
2ea3bad502 | ||
|
|
0daf4057f3 | ||
|
|
a82a0a2f15 | ||
|
|
11f3c60554 | ||
|
|
e3637e4ba5 | ||
|
|
78c6766baf | ||
|
|
4226e92387 | ||
|
|
addb56ab7d | ||
|
|
88755e6d38 | ||
|
|
2178bb44cd | ||
|
|
1f240ca27a | ||
|
|
ff056fa145 | ||
|
|
3c9ea6caba | ||
|
|
910bd4d8ea | ||
|
|
a39341ada8 | ||
|
|
857046ecb8 | ||
|
|
941c9f1905 | ||
|
|
7f6a43cf7b | ||
|
|
39f34135f0 | ||
|
|
02ab3c850e | ||
|
|
38ee612164 | ||
|
|
71b892132b | ||
|
|
2c04ace022 | ||
|
|
f4af241804 | ||
|
|
077313fe2d | ||
|
|
f196cfe993 | ||
|
|
140b850cef | ||
|
|
b64eb2df38 | ||
|
|
0929ba995a | ||
|
|
3b9cbcb671 | ||
|
|
f2cbdbae87 | ||
|
|
347ba4a127 | ||
|
|
5c097df56a | ||
|
|
c97b48f443 | ||
|
|
a481e958e3 | ||
|
|
40b407027e | ||
|
|
b910d795f2 | ||
|
|
d64ea97ee6 | ||
|
|
fc03075ef8 | ||
|
|
f70222d41a | ||
|
|
1cfcd53ae3 | ||
|
|
0814fe49e2 | ||
|
|
eb8ca0229a | ||
|
|
1bc419b6a1 | ||
|
|
895e79e736 | ||
|
|
1a73847c47 | ||
|
|
c451074278 | ||
|
|
bdb6599087 | ||
|
|
56af9d0991 | ||
|
|
e1c08e33d4 | ||
|
|
b680c5021a | ||
|
|
7910d5652d | ||
|
|
b7651e4f5e | ||
|
|
31f8950b45 | ||
|
|
0203b67b28 | ||
|
|
e8e39dbd56 | ||
|
|
bd083ac280 | ||
|
|
06a5876f35 | ||
|
|
4acc2736e4 | ||
|
|
c5a49cc537 | ||
|
|
e895fd6e0c | ||
|
|
bdb93cd323 | ||
|
|
6b45293f78 | ||
|
|
63cf093020 | ||
|
|
504b7276cb | ||
|
|
e7e095907c | ||
|
|
c0d48efe18 | ||
|
|
fbbc9312ea | ||
|
|
4cc963d296 | ||
|
|
c7841bb36e | ||
|
|
42589de95e | ||
|
|
0f2985cec1 | ||
|
|
c7b2a90a0c | ||
|
|
6bf9adfb51 | ||
|
|
4ce919c8c6 | ||
|
|
a502b1c566 | ||
|
|
a08c71d9d2 | ||
|
|
d3b764a9e2 | ||
|
|
98989d77a2 | ||
|
|
ba04a7760b | ||
|
|
8c75203298 | ||
|
|
dd19936fa5 | ||
|
|
dd0658de19 | ||
|
|
0ab86f4dac | ||
|
|
26bac5a2ea | ||
|
|
dd13e8b3cf | ||
|
|
8e17fc69b3 | ||
|
|
025c9aeeb2 | ||
|
|
c23af8168b | ||
|
|
58fd0d35bd | ||
|
|
daad365667 | ||
|
|
4691407a7a | ||
|
|
769772a0a8 | ||
|
|
8025082464 | ||
|
|
fcb517e679 | ||
|
|
297826a62a | ||
|
|
53860d2b96 | ||
|
|
33a8da45bd | ||
|
|
a7d09118e4 | ||
|
|
0e0009dd27 | ||
|
|
3159e5de0c | ||
|
|
e4bdbdd9ba | ||
|
|
84e5a5f6cb | ||
|
|
452a58de94 | ||
|
|
dae5bf593c | ||
|
|
4b033d8f00 | ||
|
|
fe19b0205a | ||
|
|
85cbc17996 | ||
|
|
cddace70a4 | ||
|
|
1935c7371a | ||
|
|
25953732ab | ||
|
|
ef032a7af0 | ||
|
|
cce6222184 | ||
|
|
e9c8766a23 | ||
|
|
50601bd454 | ||
|
|
0715a8450a | ||
|
|
e2bdb46d48 | ||
|
|
718edd37f7 | ||
|
|
ea4d41d929 | ||
|
|
b97dea7996 | ||
|
|
610121d02c | ||
|
|
6ec640cfd5 | ||
|
|
d03d3c05b4 | ||
|
|
53d8e2b0c3 | ||
|
|
153431246d | ||
|
|
12cb3b10bd | ||
|
|
5a9dbe9c80 | ||
|
|
d0f04126f3 | ||
|
|
599cc5e19a | ||
|
|
0e56743dca | ||
|
|
77a8c27153 | ||
|
|
911edd9457 | ||
|
|
abbb2b544e | ||
|
|
f1a089f8c6 | ||
|
|
e8a3b87f68 | ||
|
|
01393faf0b | ||
|
|
a42fa741b7 | ||
|
|
e24cce4e48 | ||
|
|
d7d69b63a8 | ||
|
|
2fb9ebe71d | ||
|
|
0cdb2a98be | ||
|
|
c7713f58dd | ||
|
|
47785d9c4d | ||
|
|
6372cf59cc | ||
|
|
4c7c4d7c7c | ||
|
|
52ac358f53 | ||
|
|
cc867e2859 | ||
|
|
b55a01c5b3 | ||
|
|
ed918e1748 | ||
|
|
801f311faf | ||
|
|
67996a8a14 | ||
|
|
e8cea70346 | ||
|
|
06c09503df | ||
|
|
ee05f3d3bc | ||
|
|
f785602a4d | ||
|
|
396faa56c2 | ||
|
|
8759330896 | ||
|
|
a24dc79036 | ||
|
|
ab4a444154 | ||
|
|
515b5904b8 | ||
|
|
195ae3fdcb | ||
|
|
cd54ba1b83 | ||
|
|
b8388a4092 | ||
|
|
05baa6090d | ||
|
|
6f3c60d462 | ||
|
|
85656f81a5 | ||
|
|
ba83dd9252 | ||
|
|
3ad865319c | ||
|
|
4ffdc9c103 | ||
|
|
b25772483b | ||
|
|
ae0e1eb302 | ||
|
|
172ef61663 | ||
|
|
44f70dad56 | ||
|
|
dcacb5ee2c | ||
|
|
e154407fc6 | ||
|
|
06b69360f4 | ||
|
|
aa2a45f142 | ||
|
|
830ad99111 | ||
|
|
7ecba8c0f2 | ||
|
|
ac03a72610 | ||
|
|
46038c9fef | ||
|
|
bb3143128f | ||
|
|
9c2b78e612 | ||
|
|
905b4938a4 | ||
|
|
8b3f09a981 | ||
|
|
a148b624b0 | ||
|
|
fcd991fff2 | ||
|
|
ab7ea056e6 | ||
|
|
434aa6d140 | ||
|
|
01019f5055 | ||
|
|
5a0369ce27 | ||
|
|
04139d6901 | ||
|
|
9c4d0f2a79 | ||
|
|
a18472507b | ||
|
|
06adb3bcab | ||
|
|
177fd92094 | ||
|
|
bc38fd2640 | ||
|
|
eb2c3cd73f | ||
|
|
796b06fec5 | ||
|
|
6a9eeeb370 | ||
|
|
717b06950e | ||
|
|
a12b758658 | ||
|
|
ef150ac2c0 | ||
|
|
158954df0b | ||
|
|
c5605ac8be | ||
|
|
dc64fe85e9 | ||
|
|
653db9d7ad | ||
|
|
6a28d4eee6 | ||
|
|
f6157dd2c0 | ||
|
|
9a693ecfb5 | ||
|
|
3949ce5f6b | ||
|
|
c2371e14f2 | ||
|
|
ed66e8e41b | ||
|
|
6dcd679412 | ||
|
|
b145477114 | ||
|
|
75b42e134a | ||
|
|
54d1ef257d | ||
|
|
39e4a04625 | ||
|
|
0c3ed7f81b | ||
|
|
9a89cc1332 | ||
|
|
f4637e1b7d | ||
|
|
8f390a70c7 | ||
|
|
eb2e9eadd7 | ||
|
|
aaf5ab1948 | ||
|
|
f3d6eb09dd | ||
|
|
a735e969ec | ||
|
|
3ca1f3065f | ||
|
|
2c9da8a1c4 | ||
|
|
f5f549b520 | ||
|
|
5da1dd26bc | ||
|
|
fef9b6dfd3 | ||
|
|
1483e08754 | ||
|
|
740baec56a | ||
|
|
c92217afb9 | ||
|
|
db5c29742d | ||
|
|
816a203e73 | ||
|
|
363011ee32 | ||
|
|
51e5b50ae7 | ||
|
|
342b9f2910 | ||
|
|
71c21a7bd8 | ||
|
|
f8c22a6700 | ||
|
|
01465f5c8d | ||
|
|
7301704762 | ||
|
|
f8b89b2294 | ||
|
|
3b93b33dc9 | ||
|
|
17b5b3cf4b | ||
|
|
4984abb0dd | ||
|
|
66c5f959c0 | ||
|
|
142782825d | ||
|
|
071f050aaf | ||
|
|
8531b70089 | ||
|
|
bb6da36b36 | ||
|
|
d80731e7db | ||
|
|
13157837ec | ||
|
|
203e11053f | ||
|
|
834c237cd5 | ||
|
|
f2027bad2c | ||
|
|
4a49629a34 | ||
|
|
478e19f52f | ||
|
|
636dc8fe9f | ||
|
|
6c83eba693 | ||
|
|
30fed93e2a | ||
|
|
f2b202aa52 | ||
|
|
15855e2406 | ||
|
|
8649032b73 | ||
|
|
5ffb81df43 | ||
|
|
c9e6bfbdb3 | ||
|
|
44d3a4f223 | ||
|
|
8e0bd8d18d | ||
|
|
d0be46be74 | ||
|
|
5627082165 | ||
|
|
30a079a6b9 | ||
|
|
a7122ed535 | ||
|
|
82d64c8daf | ||
|
|
96956dbda5 | ||
|
|
15cc64e68b | ||
|
|
65d4bcef47 | ||
|
|
5f39affe74 | ||
|
|
2c87e3f4e5 | ||
|
|
409c8a4d8e | ||
|
|
45f6d9c0b9 | ||
|
|
b36d4c2e2f | ||
|
|
5b75fcc398 | ||
|
|
dd6cde6ae5 | ||
|
|
526f54cf06 | ||
|
|
dfb5ff1feb | ||
|
|
98cf3d4b80 | ||
|
|
7c82ff6491 | ||
|
|
e21467d9f0 | ||
|
|
0770748c0f | ||
|
|
714c2f30d7 | ||
|
|
98149f56b5 | ||
|
|
f4917f7bc6 | ||
|
|
285544d2d6 | ||
|
|
968b16d8bf | ||
|
|
11bf99c871 | ||
|
|
2934bdbd3b | ||
|
|
a461198547 | ||
|
|
f21281ebf5 | ||
|
|
cb1aff42c5 | ||
|
|
e7943e8490 | ||
|
|
136cc5fe46 | ||
|
|
01f579151d | ||
|
|
ec14cc170f | ||
|
|
e536e6ea74 | ||
|
|
fb0da77d2f | ||
|
|
8a59a22c2e | ||
|
|
b8917bc811 | ||
|
|
8206cee449 | ||
|
|
316b739a27 | ||
|
|
b04a85491b | ||
|
|
635dbdb5cb | ||
|
|
d06e41f81e | ||
|
|
fa49244b4b | ||
|
|
ab900d16d4 | ||
|
|
ef21ab7f3b | ||
|
|
554a24e44c | ||
|
|
ee731b8530 | ||
|
|
dd3f2c6538 | ||
|
|
5a4958090e | ||
|
|
cd4f78d619 | ||
|
|
eb46ba354a | ||
|
|
f0b3c95035 | ||
|
|
e15fdc493c | ||
|
|
fdff1664ea | ||
|
|
4bdc9b0c84 | ||
|
|
563031f214 | ||
|
|
33273c1be1 | ||
|
|
1b95790de3 | ||
|
|
2ef1cf5da0 | ||
|
|
4a117adb28 | ||
|
|
5cf4a46555 | ||
|
|
6e4b365992 | ||
|
|
1a6f23c3a1 | ||
|
|
3ce63b3d7d | ||
|
|
ba510a5641 | ||
|
|
cb93b9e1e7 | ||
|
|
3954560e30 | ||
|
|
8349a7e8f7 | ||
|
|
b923893699 | ||
|
|
86bc775791 | ||
|
|
61d030b2c6 | ||
|
|
1d49d8284c | ||
|
|
9a57b09b92 | ||
|
|
65474df642 | ||
|
|
ac27c77426 | ||
|
|
16b5a8507e | ||
|
|
992808f640 | ||
|
|
4dbf11e236 | ||
|
|
48d20b2c67 | ||
|
|
7e98493324 | ||
|
|
1054f877a1 | ||
|
|
a4ff1bf3be | ||
|
|
6be15eee0c | ||
|
|
3d44757e37 | ||
|
|
856be8ce0f | ||
|
|
2c6e5d0bea | ||
|
|
fe7851b97f | ||
|
|
6e09bcf6c6 | ||
|
|
91be70babf | ||
|
|
c062a8f129 | ||
|
|
35005657b9 | ||
|
|
98864b04e1 | ||
|
|
36e3d9c603 | ||
|
|
e39c96e6d1 | ||
|
|
ca6cd349fb | ||
|
|
441e56f445 | ||
|
|
92faa24d38 | ||
|
|
399261bfb1 | ||
|
|
f0b6c729c9 | ||
|
|
135ff1a724 | ||
|
|
df4c36d71b | ||
|
|
793eb53790 | ||
|
|
6b568e8b7f | ||
|
|
442459f6a4 | ||
|
|
74c8a664a8 | ||
|
|
98984a8085 | ||
|
|
3a9ba8a6ad | ||
|
|
ce35567c05 | ||
|
|
3a21ed6333 | ||
|
|
5e3e0b6683 | ||
|
|
9468b08eb2 | ||
|
|
d2bf15b20b | ||
|
|
b1e5a5b987 | ||
|
|
9ca420a0e6 | ||
|
|
580a9f331f | ||
|
|
6f8c38dfa0 | ||
|
|
ddb86127b7 | ||
|
|
ea0a5a186c | ||
|
|
928590b175 | ||
|
|
fec74ee9e4 | ||
|
|
10ef570485 | ||
|
|
d719fd9be4 | ||
|
|
135023b3ed | ||
|
|
2bd2092263 | ||
|
|
33fad2cb23 | ||
|
|
533009c1da | ||
|
|
cb385674a5 | ||
|
|
fb4c71aebb | ||
|
|
7656ba11ff | ||
|
|
9d3bc1f80a | ||
|
|
7ae7ccbe49 | ||
|
|
6d376df00a | ||
|
|
27905c2a90 | ||
|
|
0e03842173 | ||
|
|
aa8af5a213 | ||
|
|
f1a3cd13ff | ||
|
|
00989fcdde | ||
|
|
c677374a11 | ||
|
|
3f0a8fbe21 | ||
|
|
7d5ae2f1c3 | ||
|
|
0f8ad539ad | ||
|
|
9839a4ef36 | ||
|
|
e91c762953 | ||
|
|
8da2b249b7 | ||
|
|
07b3cbaec9 | ||
|
|
98d78163a2 | ||
|
|
014c613c93 | ||
|
|
54ec39f513 | ||
|
|
ad235610da | ||
|
|
3c1e422bb9 | ||
|
|
ae7cb36240 | ||
|
|
33dd4df694 | ||
|
|
4d90ff06fd | ||
|
|
eee7d0422c | ||
|
|
2fcfd1d7c9 | ||
|
|
0d314c00a6 | ||
|
|
a5fc2e0ef7 | ||
|
|
e168ab9ffc | ||
|
|
644c18fc69 | ||
|
|
57717f4690 | ||
|
|
c46a9bdb45 | ||
|
|
d431b53434 | ||
|
|
d07f39c0d2 | ||
|
|
010ace3b57 | ||
|
|
b3c2f8a024 | ||
|
|
cc3edd7b2b | ||
|
|
3f6368f046 | ||
|
|
18d9ebaab3 | ||
|
|
d802639b0e | ||
|
|
3d333633e4 | ||
|
|
40e343d17d | ||
|
|
f6122157c7 | ||
|
|
cbaad2dfb9 | ||
|
|
c509ec56bd | ||
|
|
a25b786fca | ||
|
|
06224aa34e | ||
|
|
4bcbd49d6d | ||
|
|
09e2ded206 | ||
|
|
4a8c063d51 | ||
|
|
2e9e6d38e2 | ||
|
|
53917d6d90 | ||
|
|
d20714eed6 | ||
|
|
2f328bf4cf | ||
|
|
e3c4cdb040 | ||
|
|
c8cf645414 | ||
|
|
aa47d9f07a | ||
|
|
338559967a | ||
|
|
a736a42e26 | ||
|
|
00c6549610 | ||
|
|
510d871379 | ||
|
|
c33c5732e0 | ||
|
|
2da2b5bb7f | ||
|
|
e8079cfd29 | ||
|
|
842e23220a | ||
|
|
43e2d7454a | ||
|
|
b2cd20bf29 | ||
|
|
d32dc0a4d9 | ||
|
|
0d1fec47c6 | ||
|
|
a9da4b7b3b | ||
|
|
cb359e40ba | ||
|
|
7836e48559 | ||
|
|
10dab10cc5 | ||
|
|
33aca37c1c | ||
|
|
c555a707e6 | ||
|
|
179684e8b2 | ||
|
|
24353a1077 | ||
|
|
d95cdc3296 | ||
|
|
2ad0428727 | ||
|
|
0b93b2de19 | ||
|
|
900f64bf8d | ||
|
|
ba74ffd4bf | ||
|
|
ad8693c018 | ||
|
|
a0280e3c43 | ||
|
|
b5efbdbb0f | ||
|
|
a6e9d58f12 | ||
|
|
b496bcb6df | ||
|
|
b88cd8e544 | ||
|
|
f86bf6fdfd | ||
|
|
f3315c69a0 | ||
|
|
76701b0cca | ||
|
|
2cc73151d1 | ||
|
|
39e237dfb4 | ||
|
|
c624d94c45 | ||
|
|
b22188251d | ||
|
|
68d017f452 | ||
|
|
2ed854893d | ||
|
|
eb083a7e10 | ||
|
|
2076a429c6 | ||
|
|
59766b6ce6 | ||
|
|
36bff12939 | ||
|
|
a031b54feb | ||
|
|
081cf5c6aa | ||
|
|
c9e6ef8202 | ||
|
|
87cfd7df3b | ||
|
|
ca887f018e | ||
|
|
4d81a901cb | ||
|
|
c11888c906 | ||
|
|
e6fb296666 | ||
|
|
54c24b24e6 | ||
|
|
c3a8db0ea4 | ||
|
|
2b3e7f67a4 | ||
|
|
c2d5e8ec11 | ||
|
|
4eafce998d | ||
|
|
4963d98331 | ||
|
|
9e07579a52 | ||
|
|
16a2d61874 | ||
|
|
b00b609621 | ||
|
|
cdbd72ec3d | ||
|
|
a6104ad8cd | ||
|
|
4e37699e25 | ||
|
|
8a244708fa | ||
|
|
6874b4f6fb | ||
|
|
eeba650f6c | ||
|
|
54b07e6b99 | ||
|
|
dc8b9478ee | ||
|
|
d97b02add9 | ||
|
|
8427ade0fa | ||
|
|
44d6a0dea1 | ||
|
|
f710ce266b | ||
|
|
45dc9a39d9 | ||
|
|
d703a118c4 | ||
|
|
c20e580a9e | ||
|
|
9b4de5d7bf | ||
|
|
7c9f396b40 | ||
|
|
31b9f2b4f9 | ||
|
|
6ff15f9b53 | ||
|
|
1ebd6c252c | ||
|
|
8424351d49 | ||
|
|
0c4e597819 | ||
|
|
d1917d1a2b | ||
|
|
bb3503f0c4 | ||
|
|
7c22776254 | ||
|
|
2294586371 | ||
|
|
b7f519f86e | ||
|
|
febd5294f4 | ||
|
|
690a23fe89 | ||
|
|
118252d3af | ||
|
|
39c1a88dae | ||
|
|
509f06c2f0 | ||
|
|
12458af84a | ||
|
|
66bfc5748b | ||
|
|
d0808e0c1a | ||
|
|
34fd22e4e9 | ||
|
|
d4fb0d37c2 | ||
|
|
fd02ca00f0 | ||
|
|
8a53d02d0c | ||
|
|
58ee53876b | ||
|
|
853b730def | ||
|
|
be5fdbfd4f | ||
|
|
e6d123b5e8 | ||
|
|
d8a844a2f9 | ||
|
|
dd87cdbebf | ||
|
|
4c1881324e | ||
|
|
cf32ba0351 | ||
|
|
bd27a36d16 | ||
|
|
2f999ba3db | ||
|
|
d8ec390cd6 | ||
|
|
7a0e7bfb95 | ||
|
|
da7ae6b953 | ||
|
|
2dead545f0 | ||
|
|
9c566ec17c | ||
|
|
385fe303cc | ||
|
|
81e3709d25 | ||
|
|
7f17efcdd8 | ||
|
|
a566ac1078 | ||
|
|
44e76b4eaa | ||
|
|
e7e1cfa12e | ||
|
|
77a70cad71 | ||
|
|
0c2db53ee8 | ||
|
|
83807ed247 | ||
|
|
302a135e5a | ||
|
|
6b2160f388 | ||
|
|
86a68c50bc | ||
|
|
e2e7d6578c | ||
|
|
a171e17666 | ||
|
|
d8610e0f2a | ||
|
|
93222bf3f9 | ||
|
|
ea6765c68a | ||
|
|
6ac81dea19 | ||
|
|
e4dd9bc9f9 | ||
|
|
03d4d4c8fa | ||
|
|
fcc51d3352 | ||
|
|
08630cd686 | ||
|
|
9295a4e7ce | ||
|
|
826a1a02a2 | ||
|
|
4ed08cefa7 | ||
|
|
1a33a0c4d1 | ||
|
|
4d3d710211 | ||
|
|
ac93e70dd2 | ||
|
|
dd3ffbb391 | ||
|
|
9d1d101586 | ||
|
|
32da7b8fe2 | ||
|
|
41484113a0 | ||
|
|
5d6a8be66d | ||
|
|
aba3a31663 | ||
|
|
8950f71b0a | ||
|
|
54d3668693 | ||
|
|
b16169f867 | ||
|
|
bbd589f6a8 | ||
|
|
765d8241f2 | ||
|
|
8e041ddeb6 | ||
|
|
a4a7fe0c48 | ||
|
|
a247fc3b48 | ||
|
|
d876085f6d | ||
|
|
bba9c3d57a | ||
|
|
944e971124 | ||
|
|
e512ab1816 | ||
|
|
83b5aecbfd | ||
|
|
2f9d00fc07 | ||
|
|
23b57692ec | ||
|
|
8bda7f1e1e | ||
|
|
12bddc3b1f | ||
|
|
8f5fa84996 | ||
|
|
3af34f078c | ||
|
|
57e596fea2 | ||
|
|
9793cf4ece | ||
|
|
a4bdc23aef | ||
|
|
7be7b3389c | ||
|
|
4240089d41 | ||
|
|
09ebfac307 | ||
|
|
89b6291488 | ||
|
|
54555cb3cd | ||
|
|
1434b6bbff | ||
|
|
de164ff936 | ||
|
|
a8e2fb1ee5 | ||
|
|
7da8b44c63 | ||
|
|
8f0c1a4224 | ||
|
|
c57cb3d2d9 | ||
|
|
5172189e36 | ||
|
|
6c6723e181 | ||
|
|
5b0a05b4b3 | ||
|
|
87cbd8fbf3 | ||
|
|
f8b2878943 | ||
|
|
9060250806 | ||
|
|
bc63c3b383 | ||
|
|
e687c64ca0 | ||
|
|
3ff5472543 | ||
|
|
a503803725 | ||
|
|
a41b767400 | ||
|
|
ba0a495d3c | ||
|
|
6435fa84af | ||
|
|
8743330ad8 | ||
|
|
a37aa5551a | ||
|
|
b2521cf8a3 | ||
|
|
c853b6593a | ||
|
|
d980711c30 | ||
|
|
7ab55e0694 | ||
|
|
db244937ff | ||
|
|
bcb593e6df | ||
|
|
9bfab69062 | ||
|
|
5451a9cd23 | ||
|
|
a80b921927 | ||
|
|
1373d62c52 | ||
|
|
d0481482ae | ||
|
|
051d6e580c | ||
|
|
36e4c8c399 | ||
|
|
7921bd62d9 | ||
|
|
74ecc20751 | ||
|
|
d477de256a | ||
|
|
cffbc77d05 | ||
|
|
16fe23244b | ||
|
|
7d7b2c7544 | ||
|
|
d2c66894fd | ||
|
|
1cd3e69161 | ||
|
|
5850caff35 | ||
|
|
0cce55565a | ||
|
|
6b429c9da9 | ||
|
|
8f6151a575 | ||
|
|
3f19fac0b4 | ||
|
|
08e5ed6a8a | ||
|
|
e4d554689f | ||
|
|
19912c87c9 | ||
|
|
0de1e64958 | ||
|
|
aa0a795ec6 | ||
|
|
d1b062a418 | ||
|
|
812d8a34cf | ||
|
|
ec02d881c9 | ||
|
|
b706571a21 | ||
|
|
134f90a464 | ||
|
|
eb553fa96c | ||
|
|
fd8b7eb6fc | ||
|
|
501411f5b5 | ||
|
|
bee844fcdb | ||
|
|
f2377ce5d2 | ||
|
|
1771b3fed3 | ||
|
|
383cff5eb8 | ||
|
|
0a2eee0118 | ||
|
|
61c5848be4 | ||
|
|
d7b19a8b52 | ||
|
|
d9556ba603 | ||
|
|
55fc9d7fbd | ||
|
|
ca0c38b3c0 | ||
|
|
fe26c82dc1 | ||
|
|
681e382459 | ||
|
|
ae5a60db89 | ||
|
|
daf208fe35 | ||
|
|
760c552ffb | ||
|
|
03000c3ad0 | ||
|
|
af3dc25e2c | ||
|
|
c058d30d0a | ||
|
|
bcde2ea223 | ||
|
|
c21402733f | ||
|
|
0dbc37dacf | ||
|
|
8f667671ad | ||
|
|
61a8457232 | ||
|
|
c20504fc7d | ||
|
|
74a293d425 | ||
|
|
52d27179a9 | ||
|
|
c6d524c25d | ||
|
|
8d67498e52 | ||
|
|
852137b84d | ||
|
|
b752d29854 | ||
|
|
fa40a33631 | ||
|
|
1065abb0a7 | ||
|
|
56087b691b | ||
|
|
c1c98e4ac8 | ||
|
|
fa09a69a7f | ||
|
|
a28312be46 | ||
|
|
eb1562f56b | ||
|
|
324f65c838 | ||
|
|
b23369c548 | ||
|
|
23d0aae23b | ||
|
|
92c2e26f3a | ||
|
|
9ccdabb8e9 | ||
|
|
2e39fbe70f | ||
|
|
bfa941d69c | ||
|
|
07ca18caba | ||
|
|
5a2f06fb83 | ||
|
|
3211867f6a | ||
|
|
d1c5ec4585 | ||
|
|
f90034a510 | ||
|
|
701f0bbd4d | ||
|
|
c0bea3d833 | ||
|
|
53a9c87f42 | ||
|
|
03bfed4e25 | ||
|
|
96359b8f6a | ||
|
|
16c7aa230d | ||
|
|
5ea1268107 | ||
|
|
c856ffef45 | ||
|
|
6509f19773 | ||
|
|
8ec13eb221 | ||
|
|
19f965befe | ||
|
|
4077dc48b4 | ||
|
|
14f38343f5 | ||
|
|
dd04c005aa | ||
|
|
0328134cbc | ||
|
|
a65f3b1f4a | ||
|
|
11c93f800f | ||
|
|
c673662a34 | ||
|
|
092322ff4e | ||
|
|
e4d8d38c6e |
@@ -0,0 +1,11 @@
|
||||
reviews:
|
||||
path_filters:
|
||||
- "!app/config/specs/**"
|
||||
- "!docs/examples/**"
|
||||
- "!docs/references/**"
|
||||
- "!docs/sdks/**"
|
||||
auto_review:
|
||||
base_branches:
|
||||
- main
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
@@ -15,14 +15,19 @@ _APP_SYSTEM_TEAM_EMAIL=team@appwrite.io
|
||||
_APP_EMAIL_SECURITY=security@appwrite.io
|
||||
_APP_EMAIL_CERTIFICATES=certificates@appwrite.io
|
||||
_APP_SYSTEM_RESPONSE_FORMAT=
|
||||
_APP_CUSTOM_DOMAIN_DENY_LIST=
|
||||
_APP_OPTIONS_ABUSE=disabled
|
||||
_APP_OPTIONS_ROUTER_PROTECTION=disabled
|
||||
_APP_OPTIONS_FORCE_HTTPS=disabled
|
||||
_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled
|
||||
_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled
|
||||
_APP_OPENSSL_KEY_V1=your-secret-key
|
||||
_APP_DOMAIN=traefik
|
||||
_APP_DOMAIN_FUNCTIONS=functions.localhost
|
||||
_APP_DOMAIN_TARGET=localhost
|
||||
_APP_DOMAIN_SITES=sites.localhost
|
||||
_APP_DOMAIN_TARGET_CNAME=test.appwrite.io
|
||||
_APP_DOMAIN_TARGET_A=127.0.0.1
|
||||
_APP_DOMAIN_TARGET_AAAA=::1
|
||||
_APP_RULES_FORMAT=md5
|
||||
_APP_REDIS_HOST=redis
|
||||
_APP_REDIS_PORT=6379
|
||||
_APP_REDIS_PASS=
|
||||
@@ -38,6 +43,7 @@ _APP_STORAGE_S3_ACCESS_KEY=
|
||||
_APP_STORAGE_S3_SECRET=
|
||||
_APP_STORAGE_S3_REGION=us-east-1
|
||||
_APP_STORAGE_S3_BUCKET=
|
||||
_APP_STORAGE_S3_ENDPOINT=
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY=
|
||||
_APP_STORAGE_DO_SPACES_SECRET=
|
||||
_APP_STORAGE_DO_SPACES_REGION=us-east-1
|
||||
@@ -67,24 +73,28 @@ _APP_SMS_FROM=+123456789
|
||||
_APP_SMS_PROJECTS_DENY_LIST=
|
||||
_APP_STORAGE_LIMIT=30000000
|
||||
_APP_STORAGE_PREVIEW_LIMIT=20000000
|
||||
_APP_FUNCTIONS_SIZE_LIMIT=30000000
|
||||
_APP_COMPUTE_SIZE_LIMIT=30000000
|
||||
_APP_FUNCTIONS_TIMEOUT=900
|
||||
_APP_FUNCTIONS_BUILD_TIMEOUT=900
|
||||
_APP_FUNCTIONS_CPUS=8
|
||||
_APP_FUNCTIONS_MEMORY=8192
|
||||
_APP_FUNCTIONS_INACTIVE_THRESHOLD=600
|
||||
_APP_FUNCTIONS_MAINTENANCE_INTERVAL=600
|
||||
_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes
|
||||
_APP_SITES_TIMEOUT=30
|
||||
_APP_COMPUTE_BUILD_TIMEOUT=900
|
||||
_APP_COMPUTE_CPUS=8
|
||||
_APP_COMPUTE_MEMORY=8192
|
||||
_APP_COMPUTE_INACTIVE_THRESHOLD=600
|
||||
_APP_COMPUTE_MAINTENANCE_INTERVAL=600
|
||||
_APP_COMPUTE_RUNTIMES_NETWORK=runtimes
|
||||
_APP_EXECUTOR_SECRET=your-secret-key
|
||||
_APP_EXECUTOR_HOST=http://proxy/v1
|
||||
_APP_EXECUTOR_HOST=http://exc1/v1
|
||||
_APP_FUNCTIONS_RUNTIMES=php-8.0,node-18.0,python-3.9,ruby-3.1
|
||||
_APP_SITES_RUNTIMES=static-1,node-22,flutter-3.29
|
||||
_APP_MAINTENANCE_INTERVAL=86400
|
||||
_APP_MAINTENANCE_DELAY=
|
||||
_APP_MAINTENANCE_RETENTION_CACHE=2592000
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE=86400
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE=15778800
|
||||
_APP_USAGE_AGGREGATION_INTERVAL=30
|
||||
_APP_STATS_RESOURCES_INTERVAL=30
|
||||
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
|
||||
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
|
||||
_APP_USAGE_STATS=enabled
|
||||
@@ -108,3 +118,5 @@ _APP_MESSAGE_EMAIL_TEST_DSN=
|
||||
_APP_MESSAGE_PUSH_TEST_DSN=
|
||||
_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
|
||||
_APP_PROJECT_REGIONS=default
|
||||
_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000
|
||||
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
|
||||
@@ -0,0 +1,120 @@
|
||||
name: Benchmark
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.ref }}'
|
||||
cancel-in-progress: true
|
||||
env:
|
||||
IMAGE: appwrite-dev
|
||||
CACHE_KEY: 'appwrite-dev-${{ github.event.pull_request.head.sha }}'
|
||||
'on':
|
||||
- pull_request
|
||||
jobs:
|
||||
setup:
|
||||
name: Setup & Build Appwrite Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build Appwrite
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: '${{ env.IMAGE }}'
|
||||
load: true
|
||||
cache-from: type=gha
|
||||
cache-to: 'type=gha,mode=max'
|
||||
outputs: 'type=docker,dest=/tmp/${{ env.IMAGE }}.tar'
|
||||
build-args: |
|
||||
DEBUG=false
|
||||
TESTING=true
|
||||
VERSION=dev
|
||||
- name: Cache Docker Image
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: '${{ env.CACHE_KEY }}'
|
||||
path: '/tmp/${{ env.IMAGE }}.tar'
|
||||
benchmarking:
|
||||
name: Benchmark
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: '${{ env.CACHE_KEY }}'
|
||||
path: '/tmp/${{ env.IMAGE }}.tar'
|
||||
fail-on-cache-miss: true
|
||||
- name: Load and Start Appwrite
|
||||
run: |
|
||||
sed -i 's/traefik/localhost/g' .env
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
- name: Install Oha
|
||||
run: |
|
||||
echo "deb [signed-by=/usr/share/keyrings/azlux-archive-keyring.gpg] http://packages.azlux.fr/debian/ stable main" | sudo tee /etc/apt/sources.list.d/azlux.list
|
||||
sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg
|
||||
sudo apt update
|
||||
sudo apt install oha
|
||||
- name: Benchmark PR
|
||||
run: 'oha -z 180s http://localhost/v1/health/version -j > benchmark.json'
|
||||
- name: Cleaning
|
||||
run: docker compose down -v
|
||||
- name: Installing latest version
|
||||
run: |
|
||||
rm docker-compose.yml
|
||||
rm .env
|
||||
curl https://appwrite.io/install/compose -o docker-compose.yml
|
||||
curl https://appwrite.io/install/env -o .env
|
||||
sed -i 's/_APP_OPTIONS_ABUSE=enabled/_APP_OPTIONS_ABUSE=disabled/g' .env
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
- name: Benchmark Latest
|
||||
run: oha -z 180s http://localhost/v1/health/version -j > benchmark-latest.json
|
||||
- name: Prepare comment
|
||||
run: |
|
||||
echo '## :sparkles: Benchmark results' > benchmark.txt
|
||||
echo ' ' >> benchmark.txt
|
||||
echo "- Requests per second: $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
|
||||
echo "- Requests with 200 status code: $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
|
||||
echo "- P99 latency: $(jq -r '.latencyPercentiles.p99' benchmark.json )" >> benchmark.txt
|
||||
echo " " >> benchmark.txt
|
||||
echo " " >> benchmark.txt
|
||||
echo "## :zap: Benchmark Comparison" >> benchmark.txt
|
||||
echo " " >> benchmark.txt
|
||||
echo "| Metric | This PR | Latest version | " >> benchmark.txt
|
||||
echo "| --- | --- | --- | " >> benchmark.txt
|
||||
echo "| RPS | $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.summary.requestsPerSec|tonumber|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
|
||||
echo "| 200 | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
|
||||
echo "| P99 | $(jq -r '.latencyPercentiles.p99' benchmark.json ) | $(jq -r '.latencyPercentiles.p99' benchmark-latest.json ) | " >> benchmark.txt
|
||||
- name: Save results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: '${{ !cancelled() }}'
|
||||
with:
|
||||
name: benchmark.json
|
||||
path: benchmark.json
|
||||
retention-days: 7
|
||||
- name: Find Comment
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: peter-evans/find-comment@v3
|
||||
id: fc
|
||||
with:
|
||||
issue-number: '${{ github.event.pull_request.number }}'
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: Benchmark results
|
||||
- name: Comment on PR
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
comment-id: '${{ steps.fc.outputs.comment-id }}'
|
||||
issue-number: '${{ github.event.pull_request.number }}'
|
||||
body-path: benchmark.txt
|
||||
edit-mode: replace
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
|
||||
@@ -13,8 +13,13 @@ on:
|
||||
schedule:
|
||||
- cron: '0 16 * * 0'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
permissions:
|
||||
security-events: write
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -29,7 +34,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
submodules: recursive
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
name: "Static code analysis"
|
||||
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
lint:
|
||||
name: CodeQL
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run CodeQL
|
||||
run: |
|
||||
docker run --rm -v $PWD:/app composer:2.6 sh -c \
|
||||
"composer install --profile --ignore-platform-reqs && composer check"
|
||||
+180
-68
@@ -8,9 +8,33 @@ env:
|
||||
IMAGE: appwrite-dev
|
||||
CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }}
|
||||
|
||||
on: [pull_request]
|
||||
on: [ pull_request ]
|
||||
|
||||
jobs:
|
||||
check_database_changes:
|
||||
name: Check if utopia-php/database changed
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
database_changed: ${{ steps.check.outputs.database_changed }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch base branch
|
||||
run: git fetch origin ${{ github.event.pull_request.base.ref }}
|
||||
|
||||
- name: Check for utopia-php/database changes
|
||||
id: check
|
||||
run: |
|
||||
if git diff origin/${{ github.event.pull_request.base.ref }} HEAD -- composer.lock | grep -q '"name": "utopia-php/database"'; then
|
||||
echo "Database version changed, going to run all mode tests."
|
||||
echo "database_changed=true" >> "$GITHUB_ENV"
|
||||
echo "database_changed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "database_changed=false" >> "$GITHUB_ENV"
|
||||
echo "database_changed=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
setup:
|
||||
name: Setup & Build Appwrite Image
|
||||
runs-on: ubuntu-latest
|
||||
@@ -66,6 +90,9 @@ jobs:
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
|
||||
- name: Logs
|
||||
run: docker compose logs appwrite
|
||||
|
||||
- name: Doctor
|
||||
run: docker compose exec -T appwrite doctor
|
||||
|
||||
@@ -95,6 +122,14 @@ jobs:
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
|
||||
- name: Wait for Open Runtimes
|
||||
timeout-minutes: 3
|
||||
run: |
|
||||
while ! docker compose logs openruntimes-executor | grep -q "Executor is ready."; do
|
||||
echo "Waiting for Executor to come online"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Run General Tests
|
||||
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/General --debug
|
||||
@@ -103,6 +138,73 @@ jobs:
|
||||
name: E2E Service Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
service: [
|
||||
Account,
|
||||
Avatars,
|
||||
Console,
|
||||
Databases,
|
||||
Functions,
|
||||
FunctionsSchedule,
|
||||
GraphQL,
|
||||
Health,
|
||||
Locale,
|
||||
Projects,
|
||||
Realtime,
|
||||
Sites,
|
||||
Proxy,
|
||||
Storage,
|
||||
Tokens,
|
||||
Teams,
|
||||
Users,
|
||||
Webhooks,
|
||||
VCS,
|
||||
Messaging,
|
||||
Migrations
|
||||
]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
sleep 30
|
||||
|
||||
- name: Wait for Open Runtimes
|
||||
timeout-minutes: 3
|
||||
run: |
|
||||
while ! docker compose logs openruntimes-executor | grep -q "Executor is ready."; do
|
||||
echo "Waiting for Executor to come online"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Run ${{ matrix.service }} tests with Project table mode
|
||||
run: |
|
||||
echo "Using project tables"
|
||||
export _APP_DATABASE_SHARED_TABLES=
|
||||
export _APP_DATABASE_SHARED_TABLES_V1=
|
||||
|
||||
docker compose exec -T \
|
||||
-e _APP_DATABASE_SHARED_TABLES \
|
||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
||||
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude=devKeys
|
||||
|
||||
e2e_shared_mode_test:
|
||||
name: E2E Shared Mode Service Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ setup, check_database_changes ]
|
||||
if: needs.check_database_changes.outputs.database_changed == 'true'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -119,16 +221,18 @@ jobs:
|
||||
Locale,
|
||||
Projects,
|
||||
Realtime,
|
||||
Sites,
|
||||
Proxy,
|
||||
Storage,
|
||||
Teams,
|
||||
Users,
|
||||
Webhooks,
|
||||
VCS,
|
||||
Messaging,
|
||||
Migrations
|
||||
Migrations,
|
||||
Tokens
|
||||
]
|
||||
tables-mode: [
|
||||
'Project',
|
||||
'Shared V1',
|
||||
'Shared V2',
|
||||
]
|
||||
@@ -150,6 +254,14 @@ jobs:
|
||||
docker compose up -d
|
||||
sleep 30
|
||||
|
||||
- name: Wait for Open Runtimes
|
||||
timeout-minutes: 3
|
||||
run: |
|
||||
while ! docker compose logs openruntimes-executor | grep -q "Executor is ready."; do
|
||||
echo "Waiting for Executor to come online"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Run ${{ matrix.service }} tests with ${{ matrix.tables-mode }} table mode
|
||||
run: |
|
||||
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
|
||||
@@ -160,91 +272,91 @@ jobs:
|
||||
echo "Using shared tables V2"
|
||||
export _APP_DATABASE_SHARED_TABLES=database_db_main
|
||||
export _APP_DATABASE_SHARED_TABLES_V1=
|
||||
else
|
||||
echo "Using project tables"
|
||||
export _APP_DATABASE_SHARED_TABLES=
|
||||
export _APP_DATABASE_SHARED_TABLES_V1=
|
||||
fi
|
||||
|
||||
docker compose exec -T \
|
||||
-e _APP_DATABASE_SHARED_TABLES \
|
||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
||||
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug
|
||||
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude=devKeys
|
||||
|
||||
benchmarking:
|
||||
name: Benchmark
|
||||
e2e_dev_keys:
|
||||
name: E2E Service Test (Dev Keys)
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
run: |
|
||||
sed -i 's/traefik/localhost/g' .env
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
- name: Install Oha
|
||||
sleep 30
|
||||
|
||||
- name: Run Projects tests with dev keys in ${{ matrix.tables-mode }} table mode
|
||||
run: |
|
||||
echo "deb [signed-by=/usr/share/keyrings/azlux-archive-keyring.gpg] http://packages.azlux.fr/debian/ stable main" | sudo tee /etc/apt/sources.list.d/azlux.list
|
||||
sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg
|
||||
sudo apt update
|
||||
sudo apt install oha
|
||||
- name: Benchmark PR
|
||||
run: oha -z 180s http://localhost/v1/health/version -j > benchmark.json
|
||||
- name: Cleaning
|
||||
run: docker compose down -v
|
||||
- name: Installing latest version
|
||||
echo "Using project tables"
|
||||
export _APP_DATABASE_SHARED_TABLES=
|
||||
export _APP_DATABASE_SHARED_TABLES_V1=
|
||||
|
||||
docker compose exec -T \
|
||||
-e _APP_DATABASE_SHARED_TABLES \
|
||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
||||
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
|
||||
|
||||
e2e_dev_keys_shared_mode:
|
||||
name: E2E Shared Mode Service Test (Dev Keys)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ setup, check_database_changes ]
|
||||
if: needs.check_database_changes.outputs.database_changed == 'true'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tables-mode: [
|
||||
'Shared V1',
|
||||
'Shared V2',
|
||||
]
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
run: |
|
||||
rm docker-compose.yml
|
||||
rm .env
|
||||
curl https://appwrite.io/install/compose -o docker-compose.yml
|
||||
curl https://appwrite.io/install/env -o .env
|
||||
sed -i 's/_APP_OPTIONS_ABUSE=enabled/_APP_OPTIONS_ABUSE=disabled/g' .env
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
- name: Benchmark Latest
|
||||
run: oha -z 180s http://localhost/v1/health/version -j > benchmark-latest.json
|
||||
- name: Prepare comment
|
||||
sleep 30
|
||||
|
||||
- name: Run Projects tests with dev keys in ${{ matrix.tables-mode }} table mode
|
||||
run: |
|
||||
echo '## :sparkles: Benchmark results' > benchmark.txt
|
||||
echo ' ' >> benchmark.txt
|
||||
echo "- Requests per second: $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
|
||||
echo "- Requests with 200 status code: $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
|
||||
echo "- P99 latency: $(jq -r '.latencyPercentiles.p99' benchmark.json )" >> benchmark.txt
|
||||
echo " " >> benchmark.txt
|
||||
echo " " >> benchmark.txt
|
||||
echo "## :zap: Benchmark Comparison" >> benchmark.txt
|
||||
echo " " >> benchmark.txt
|
||||
echo "| Metric | This PR | Latest version | " >> benchmark.txt
|
||||
echo "| --- | --- | --- | " >> benchmark.txt
|
||||
echo "| RPS | $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.summary.requestsPerSec|tonumber|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
|
||||
echo "| 200 | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
|
||||
echo "| P99 | $(jq -r '.latencyPercentiles.p99' benchmark.json ) | $(jq -r '.latencyPercentiles.p99' benchmark-latest.json ) | " >> benchmark.txt
|
||||
- name: Save results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: benchmark.json
|
||||
path: benchmark.json
|
||||
retention-days: 7
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v3
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: Benchmark results
|
||||
- name: Comment on PR
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-path: benchmark.txt
|
||||
edit-mode: replace
|
||||
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
|
||||
echo "Using shared tables V1"
|
||||
export _APP_DATABASE_SHARED_TABLES=database_db_main
|
||||
export _APP_DATABASE_SHARED_TABLES_V1=database_db_main
|
||||
elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then
|
||||
echo "Using shared tables V2"
|
||||
export _APP_DATABASE_SHARED_TABLES=database_db_main
|
||||
export _APP_DATABASE_SHARED_TABLES_V1=
|
||||
fi
|
||||
|
||||
docker compose exec -T \
|
||||
-e _APP_DATABASE_SHARED_TABLES \
|
||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
||||
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@
|
||||
/node_modules/
|
||||
/tests/resources/storage/
|
||||
/tests/resources/functions/**/code.tar.gz
|
||||
/tests/resources/sites/**/code.tar.gz
|
||||
/app/sdks/*
|
||||
/.idea/
|
||||
!/.idea/workspace.xml
|
||||
@@ -16,4 +17,4 @@ dev/yasd_init.php
|
||||
.phpunit.result.cache
|
||||
Makefile
|
||||
appwrite.json
|
||||
/.zed/
|
||||
/.zed/
|
||||
+1
-8
@@ -7,6 +7,7 @@ tasks:
|
||||
docker pull composer
|
||||
command: |
|
||||
docker run --rm --interactive --tty \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--volume $PWD:/app \
|
||||
composer install \
|
||||
--ignore-platform-reqs \
|
||||
@@ -23,11 +24,3 @@ vscode:
|
||||
extensions:
|
||||
- ms-azuretools.vscode-docker
|
||||
- zobo.php-intellisense
|
||||
|
||||
github:
|
||||
# https://www.gitpod.io/docs/prebuilds#github-specific-configuration
|
||||
prebuilds:
|
||||
# enable for pull requests coming from forks (defaults to false)
|
||||
pullRequestsFromForks: true
|
||||
# add a check to pull requests (defaults to true)
|
||||
addCheck: false
|
||||
|
||||
+114
@@ -1,3 +1,117 @@
|
||||
# Version 1.6.1
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Notable changes
|
||||
|
||||
* Remove JPEG fallback for webp by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8746
|
||||
* Add heic and avif support by @lohanidamodar in https://github.com/appwrite/appwrite/pull/7718
|
||||
* Add new runtimes by @Meldiron in https://github.com/appwrite/appwrite/pull/8771
|
||||
* Remove audits deletion by @shimonewman in https://github.com/appwrite/appwrite/pull/8766
|
||||
* Bump assistant by @loks0n in https://github.com/appwrite/appwrite/pull/8801
|
||||
* Change max queries values to 500 by @fogelito in https://github.com/appwrite/appwrite/pull/8802
|
||||
* Allow '.wav' as 'audio/x-wav' as well by @basert in https://github.com/appwrite/appwrite/pull/8846
|
||||
* Use 1 instead of 0.5 cpu for default function specification by @loks0n in https://github.com/appwrite/appwrite/pull/8848
|
||||
* Update function runtimes by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8781
|
||||
* Add a realtime heartbeat by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/8943
|
||||
|
||||
### Fixes
|
||||
|
||||
* Trigger functions event only if event is not paused by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8526
|
||||
* Update docker-compose to restart usage-dump by @feschaffa in https://github.com/appwrite/appwrite/pull/8642
|
||||
* Fix typo in scheduler base by @fogelito in https://github.com/appwrite/appwrite/pull/8691
|
||||
* Add domain and force HTTPS env vars to mail worker by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8722
|
||||
* Fix webp by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8732
|
||||
* Ignore junction tables by @fogelito in https://github.com/appwrite/appwrite/pull/8728
|
||||
* Fix logger throwing fatal error by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8724
|
||||
* Fix missing protocol for testing SMTP by @byawitz in https://github.com/appwrite/appwrite/pull/8749
|
||||
* Make create execution async loose by @loks0n in https://github.com/appwrite/appwrite/pull/8707
|
||||
* Fix invalid cursor value by @fogelito in https://github.com/appwrite/appwrite/pull/8109
|
||||
* Fix target deletes by @abnegate in https://github.com/appwrite/appwrite/pull/8833
|
||||
* Fix translation commas by @loks0n in https://github.com/appwrite/appwrite/pull/8892
|
||||
* Fix Migrations having source creds being overwritten and add Migration tests by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8897
|
||||
* Fix validator usage for updating string size by @abnegate in https://github.com/appwrite/appwrite/pull/8890
|
||||
* Fix create user event not triggering by @loks0n in https://github.com/appwrite/appwrite/pull/8718
|
||||
* Improve error handling and logging in the database worker by @fogelito in https://github.com/appwrite/appwrite/pull/8944
|
||||
* Remove inaccurate info about leaving the URL parameter empty by @ebenezerdon in https://github.com/appwrite/appwrite/pull/8963
|
||||
* Ensure indexes are updated when updating an attribute key by @fogelito in https://github.com/appwrite/appwrite/pull/8971
|
||||
* Remove duplicate dart-2.16 runtime template by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8972
|
||||
* Fix team invites with existing session by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/9006
|
||||
* Improve handling of HTTP requests by dispatching to safe workers by @Meldiron in https://github.com/appwrite/appwrite/pull/9016
|
||||
* Fix users create session secret by @stnguyen90 in https://github.com/appwrite/appwrite/pull/9019
|
||||
* Fix swoole task warning by @Meldiron in https://github.com/appwrite/appwrite/pull/9025
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Update Init copy by @adityaoberai in https://github.com/appwrite/appwrite/pull/8557
|
||||
* Fix security scan permissions and comment by @EVDOG4LIFE in https://github.com/appwrite/appwrite/pull/8525
|
||||
* Add Trivy security scans by @btme0011 in https://github.com/appwrite/appwrite/pull/6876
|
||||
* Update database stack by @abnegate in https://github.com/appwrite/appwrite/pull/8564
|
||||
* Bump database by @abnegate in https://github.com/appwrite/appwrite/pull/8573
|
||||
* Sync main with 1.5.x by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8589
|
||||
* Add AWS to one-click installs by @byawitz in https://github.com/appwrite/appwrite/pull/8593
|
||||
* Update Init copy in readme by @adityaoberai in https://github.com/appwrite/appwrite/pull/8618
|
||||
* Sync main into 1.6.x by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8685
|
||||
* Sync 1.6.x into main by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8686
|
||||
* Feat coroutines by @Meldiron in https://github.com/appwrite/appwrite/pull/7826
|
||||
* Sync main into 1.6.x by @Meldiron in https://github.com/appwrite/appwrite/pull/8719
|
||||
* Sentence casing endpoint API reference by @choir241 in https://github.com/appwrite/appwrite/pull/8617
|
||||
* DB storage metrics by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8404
|
||||
* Fix exception thrown when optional array attribute does not exist by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8391
|
||||
* Add projects channels to realtime by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/8735
|
||||
* Base for console roles support by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8565
|
||||
* Remove DB disk storage calculation by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8745
|
||||
* Messaging adapter default values by @shimonewman in https://github.com/appwrite/appwrite/pull/8742
|
||||
* Add payload response type by @loks0n in https://github.com/appwrite/appwrite/pull/8720
|
||||
* Fix flaky functions tests by @loks0n in https://github.com/appwrite/appwrite/pull/8682
|
||||
* Migrations Backups by @fogelito in https://github.com/appwrite/appwrite/pull/8186
|
||||
* Add test for response and request filters by @vermakhushboo in https://github.com/appwrite/appwrite/pull/8697
|
||||
* Bump version in SECURITY.md by @EVDOG4LIFE in https://github.com/appwrite/appwrite/pull/8755
|
||||
* Add originalId attribute to databases collection by @fogelito in https://github.com/appwrite/appwrite/pull/8764
|
||||
* Fix Walter References by @ItzNotABug in https://github.com/appwrite/appwrite/pull/8757
|
||||
* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8769
|
||||
* Move new attributes by @abnegate in https://github.com/appwrite/appwrite/pull/8777
|
||||
* Add ping endpoint by @loks0n in https://github.com/appwrite/appwrite/pull/8761
|
||||
* Fix GitHub action caching by @loks0n in https://github.com/appwrite/appwrite/pull/8772
|
||||
* Chore release ruby SDK by @abnegate in https://github.com/appwrite/appwrite/pull/8767
|
||||
* Call migration success on success by @abnegate in https://github.com/appwrite/appwrite/pull/8782
|
||||
* Update utopia-php/system to 0.9.0 by @basert in https://github.com/appwrite/appwrite/pull/8780
|
||||
* Move createDocument from api to worker by @vermakhushboo in https://github.com/appwrite/appwrite/pull/8776
|
||||
* Add missing indexes by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8803
|
||||
* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8809
|
||||
* Fix typo in BLR region by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8756
|
||||
* Add tests for project variables by @vermakhushboo in https://github.com/appwrite/appwrite/pull/8815
|
||||
* Replace 'Expires' with 'Cache-Control: private' header to avoid CDN caching by @basert in https://github.com/appwrite/appwrite/pull/8836
|
||||
* Allow blocking based on resource attributes by @basert in https://github.com/appwrite/appwrite/pull/8812
|
||||
* Check if resource is blocked inside functions worker by @basert in https://github.com/appwrite/appwrite/pull/8855
|
||||
* Fix missing allow attribute by @abnegate in https://github.com/appwrite/appwrite/pull/8889
|
||||
* Revert function execution order by @basert in https://github.com/appwrite/appwrite/pull/8857
|
||||
* Use resource type constants by @basert in https://github.com/appwrite/appwrite/pull/8895
|
||||
* Update Database lib by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8680
|
||||
* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8917
|
||||
* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8923
|
||||
* Update database for transaction counter fixes with retries by @abnegate in https://github.com/appwrite/appwrite/pull/8927
|
||||
* Validate string permissions by @fogelito in https://github.com/appwrite/appwrite/pull/8929
|
||||
* Add PubSub adapter support by @basert in https://github.com/appwrite/appwrite/pull/8905
|
||||
* List memberships as client by @loks0n in https://github.com/appwrite/appwrite/pull/8913
|
||||
* Fix XDebug Extension not being removed by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8891
|
||||
* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8946
|
||||
* Use utopia compression by @loks0n in https://github.com/appwrite/appwrite/pull/8938
|
||||
* Make compression minimum size configurable by @loks0n in https://github.com/appwrite/appwrite/pull/8947
|
||||
* Revert "Update database" by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8949
|
||||
* Fix setpaused by @loks0n in https://github.com/appwrite/appwrite/pull/8948
|
||||
* Use getDocument instead of find() for rules by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8951
|
||||
* Remove double fetch from migrations worker by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8956
|
||||
* Fix memberships privacy MFA by @loks0n in https://github.com/appwrite/appwrite/pull/8969
|
||||
* Add telemetry by @basert in https://github.com/appwrite/appwrite/pull/8960
|
||||
* Send migration errors individually by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8959
|
||||
* Add console sdk previews by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/8990
|
||||
* Unset index length by @fogelito in https://github.com/appwrite/appwrite/pull/8978
|
||||
* Update base to 0.9.5 by @basert in https://github.com/appwrite/appwrite/pull/9005
|
||||
* Sync main into 1.6.x by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/9011
|
||||
* Improved shared tables V2 by @abnegate in https://github.com/appwrite/appwrite/pull/9013
|
||||
* Ensure backwards compatibility for 1.6.x by @christyjacob4 in https://github.com/appwrite/appwrite/pull/9018
|
||||
|
||||
# Version 1.6.0
|
||||
|
||||
## What's Changed
|
||||
|
||||
+47
-13
@@ -1,6 +1,6 @@
|
||||
# Contributing
|
||||
|
||||
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.
|
||||
We would :heart: 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.
|
||||
|
||||
## Here for Hacktoberfest?
|
||||
If you're here to contribute during Hacktoberfest, we're so happy to see you here. Appwrite has been a long-time participant of Hacktoberfest and we welcome you, whatever your experience level. This year, we're **only taking contributions for issues tagged** `hacktoberfest`, so we can focus our resources to support your contributions.
|
||||
@@ -9,13 +9,13 @@ You can [find issues using this query](https://github.com/search?q=org%3Aappwrit
|
||||
|
||||
## How to Start?
|
||||
|
||||
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!
|
||||
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 on Twitter](https://twitter.com/appwrite) 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](https://github.com/appwrite/.github/blob/main/CODE_OF_CONDUCT.md).
|
||||
|
||||
## Submit a Pull Request 🚀
|
||||
## Submit a Pull Request :rocket:
|
||||
|
||||
Branch naming convention is as following
|
||||
|
||||
@@ -65,13 +65,13 @@ Now, go a step further by running the linter using the following command to manu
|
||||
composer lint <your file path>
|
||||
```
|
||||
|
||||
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.
|
||||
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 [on PHP_Codesniffer usage Wiki](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
|
||||
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]
|
||||
@@ -163,6 +163,28 @@ Other containes should be named the same as their service, for example `redis` s
|
||||
- [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.)
|
||||
|
||||
## Modules
|
||||
|
||||
As Appwrite grows, we noticed approach of having all service endpoints in `app/controllers/api/[service].php` is not maintainable. Not only it creates massive files, it also doesnt contain all product's features such as workers or tasks. While there might still be some occurances of those controller files, we avoid it in all new development, and gradually migrate existing controllers to **HTTP modules**.
|
||||
|
||||
### HTTP Endpoints
|
||||
|
||||
Every endpoint file follows below structure, making it consistent with HTTP REST endpoint path:
|
||||
|
||||
```
|
||||
src/Appwrite/Platform/Modules/[service]/Http/[resource]/[action].php
|
||||
```
|
||||
|
||||
Tips and tricks:
|
||||
|
||||
1. If endpoint doesn't have resource, use service name as resource name too
|
||||
> Example: `Modules/Sites/Http/Sites/Get.php`
|
||||
|
||||
2. If there are multiple resources, use then all in folder structure
|
||||
> Example: `Modules/Sites/Http/Deployments/Builds/Create.php`
|
||||
|
||||
3. Action can only be `Get`, `Create`, `Update`, `Delete` or `XList`
|
||||
|
||||
## 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.
|
||||
@@ -301,7 +323,7 @@ Adding a new dependency should have vital value for the product with minimum pos
|
||||
|
||||
## Introducing New Features
|
||||
|
||||
We would 💖 you to contribute to Appwrite, but we also want to ensure Appwrite is loyal to its vision and mission statement 🙏.
|
||||
We would :sparkling_heart: you to contribute to Appwrite, but we also want to ensure Appwrite is loyal to its vision and mission statement :pray:.
|
||||
|
||||
For us to find the right balance, please open an issue explaining your ideas before introducing a new pull request.
|
||||
|
||||
@@ -367,7 +389,7 @@ In file `app/controllers/shared/api.php` On the database listener, add to an exi
|
||||
|
||||
```php
|
||||
case $document->getCollection() === 'teams':
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_TEAMS, $value); // per project
|
||||
break;
|
||||
```
|
||||
@@ -379,10 +401,10 @@ In that case you need also to handle children removal using addReduce() method c
|
||||
```php
|
||||
|
||||
case $document->getCollection() === 'buckets': //buckets
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_BUCKETS, $value); // per project
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addReduce($document);
|
||||
}
|
||||
break;
|
||||
@@ -428,16 +450,16 @@ public function __construct()
|
||||
->inject('dbForProject')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('log')
|
||||
->callback(fn (Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Log $log) => $this->action($message, $dbForProject, $queueForFunctions, $queueForEvents, $queueForUsage, $log));
|
||||
->callback(fn (Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log) => $this->action($message, $dbForProject, $queueForFunctions, $queueForEvents, $queueForStatsUsage, $log));
|
||||
}
|
||||
```
|
||||
|
||||
and then trigger the queue with the new metric like so:
|
||||
|
||||
```php
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_BUILDS, 1)
|
||||
->addMetric(METRIC_BUILDS_STORAGE, $build->getAttribute('size', 0))
|
||||
->addMetric(METRIC_BUILDS_COMPUTE, (int)$build->getAttribute('duration', 0) * 1000)
|
||||
@@ -613,6 +635,18 @@ If you need to clear the cache, you can do so by running the following command:
|
||||
docker compose exec redis redis-cli FLUSHALL
|
||||
```
|
||||
|
||||
## Using preview domains locally
|
||||
|
||||
Appwrite Functions are automatically given a domain you can visit to execute the function. This domain has format `[SOMETHING].functions.localhost` unless you changed `_APP_DOMAIN_FUNCTIONS` environment variable. This default value works great when running Appwrite locally, but it can be impossible to use preview domains with Cloud woekspaces such as Gitpod or GitHub Codespaces.
|
||||
|
||||
To use preview domains on Cloud workspaces, you can visit hostname provided by them, and supply function's preview domain as URL parameter:
|
||||
|
||||
```
|
||||
https://8080-appwrite-appwrite-mjeb3ebilwv.ws-eu116.gitpod.io/ping?preview=672b3c7eab1ac523ccf5.functions.localhost
|
||||
```
|
||||
|
||||
The path was set to `/ping` intentionally. Visiting `/` for preview domains might trigger Console background worker, and trigger redirect to Console without our preview URL param. Visiting different path ensures this doesnt happen.
|
||||
|
||||
## Tutorials
|
||||
|
||||
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:
|
||||
@@ -628,7 +662,7 @@ 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 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.
|
||||
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 on Twitter](https://twitter.com/appwrite) 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
|
||||
|
||||
|
||||
+7
-3
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
|
||||
--no-plugins --no-scripts --prefer-dist \
|
||||
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
|
||||
|
||||
FROM appwrite/base:0.9.5 AS final
|
||||
FROM appwrite/base:0.10.1 AS final
|
||||
|
||||
LABEL maintainer="team@appwrite.io"
|
||||
|
||||
@@ -44,12 +44,14 @@ COPY ./dev /usr/src/code/dev
|
||||
|
||||
# Set Volumes
|
||||
RUN mkdir -p /storage/uploads && \
|
||||
mkdir -p /storage/imports && \
|
||||
mkdir -p /storage/cache && \
|
||||
mkdir -p /storage/config && \
|
||||
mkdir -p /storage/certificates && \
|
||||
mkdir -p /storage/functions && \
|
||||
mkdir -p /storage/debug && \
|
||||
chown -Rf www-data.www-data /storage/uploads && chmod -Rf 0755 /storage/uploads && \
|
||||
chown -Rf www-data.www-data /storage/imports && chmod -Rf 0755 /storage/imports && \
|
||||
chown -Rf www-data.www-data /storage/cache && chmod -Rf 0755 /storage/cache && \
|
||||
chown -Rf www-data.www-data /storage/config && chmod -Rf 0755 /storage/config && \
|
||||
chown -Rf www-data.www-data /storage/certificates && chmod -Rf 0755 /storage/certificates && \
|
||||
@@ -68,6 +70,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/sdks && \
|
||||
chmod +x /usr/local/bin/specs && \
|
||||
chmod +x /usr/local/bin/ssl && \
|
||||
chmod +x /usr/local/bin/screenshot && \
|
||||
chmod +x /usr/local/bin/test && \
|
||||
chmod +x /usr/local/bin/upgrade && \
|
||||
chmod +x /usr/local/bin/vars && \
|
||||
@@ -85,8 +88,9 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/worker-messaging && \
|
||||
chmod +x /usr/local/bin/worker-migrations && \
|
||||
chmod +x /usr/local/bin/worker-webhooks && \
|
||||
chmod +x /usr/local/bin/worker-usage && \
|
||||
chmod +x /usr/local/bin/worker-usage-dump
|
||||
chmod +x /usr/local/bin/worker-stats-usage && \
|
||||
chmod +x /usr/local/bin/stats-resources && \
|
||||
chmod +x /usr/local/bin/worker-stats-resources
|
||||
|
||||
# Letsencrypt Permissions
|
||||
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
||||
|
||||
+61
-25
@@ -1,8 +1,8 @@
|
||||
> 好消息!Appwrite 云现已进入公开测试版!立即访问 cloud.appwrite.io 注册,体验无忧的托管服务。今天就加入我们的云端吧!☁️🎉
|
||||
> 好消息!Appwrite 云现已进入公开测试版!立即访问 cloud.appwrite.io 注册,体验无忧的托管服务。今天就加入我们的云端吧!:cloud: :tada:
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite Logo"></a>
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite banner with logo and slogan build like a team of hundreds""></a>
|
||||
<br />
|
||||
<br />
|
||||
<b>适用于[Flutter/Vue/Angular/React/iOS/Android/* 等等平台 *]的完整后端服务</b>
|
||||
@@ -36,6 +36,8 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
|
||||
|
||||
内容:
|
||||
|
||||
|
||||
- [开始](#开始)
|
||||
- [安装](#安装)
|
||||
- [Unix](#unix)
|
||||
- [Windows](#windows)
|
||||
@@ -54,6 +56,9 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
|
||||
- [订阅我们](#订阅我们)
|
||||
- [版权说明](#版权说明)
|
||||
|
||||
## 开始
|
||||
要轻松开始使用Appwrite,您可以[**免费注册Appwrite Cloud**](https://cloud.appwrite.io/)。在Appwrite Cloud公开测试版期间,您可以完全免费使用Appwrite,而且我们不会收集您的信用卡信息。
|
||||
|
||||
## 安装
|
||||
|
||||
Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 [Kubernetes](https://kubernetes.io/docs/home/)、[Docker Swarm](https://docs.docker.com/engine/swarm/) 或 [Rancher](https://rancher.com/docs/))上运行 Appwrite。
|
||||
@@ -67,7 +72,7 @@ docker run -it --rm \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.6.0
|
||||
appwrite/appwrite:1.6.2
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -79,7 +84,7 @@ docker run -it --rm ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.6.0
|
||||
appwrite/appwrite:1.6.2
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -89,7 +94,7 @@ docker run -it --rm `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.6.0
|
||||
appwrite/appwrite:1.6.2
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
@@ -98,7 +103,42 @@ docker run -it --rm `
|
||||
|
||||
### 从旧版本升级
|
||||
|
||||
如果您从旧版本升级 Appwrite 服务器,则应在设置完成后使用 Appwrite 迁移工具。有关这方面的更多信息,请查看 [安装文档](https://appwrite.io/docs/installation)。
|
||||
如果您从旧版本升级 Appwrite 服务器,则应在设置完成后使用 Appwrite 迁移工具。有关这方面的更多信息,请查看 [安装文档](https://appwrite.io/docs/self-hosting)。
|
||||
|
||||
## 一键配置
|
||||
|
||||
除了在本地运行 Appwrite,您还可以使用预配置的设置启动 Appwrite。这样可以让您快速启动并运行 Appwrite,而无需在本地计算机上安装 Docker。
|
||||
|
||||
请从以下提供商中选择一个:
|
||||
|
||||
<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>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://www.linode.com/marketplace/apps/appwrite/appwrite/">
|
||||
<img width="50" height="39" src="public/images/integrations/akamai-logo.svg" alt="Akamai Logo" />
|
||||
<br /><sub><b>Akamai Compute</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://aws.amazon.com/marketplace/pp/prodview-2hiaeo2px4md6">
|
||||
<img width="50" height="39" src="public/images/integrations/aws-logo.svg" alt="AWS Logo" />
|
||||
<br /><sub><b>AWS Marketplace</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 入门
|
||||
|
||||
@@ -146,29 +186,25 @@ docker run -it --rm `
|
||||
以下是当前支持的平台和语言列表。如果您想帮助我们为您选择的平台添加支持,您可以访问我们的 [SDK 生成器](https://github.com/appwrite/sdk-generator) 项目并查看我们的 [贡献指南](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md)。
|
||||
|
||||
#### 客户端
|
||||
|
||||
- ✅ [Web](https://github.com/appwrite/sdk-for-web) (由 Appwrite 团队维护)
|
||||
- ✅ [Flutter](https://github.com/appwrite/sdk-for-flutter) (由 Appwrite 团队维护)
|
||||
- ✅ [Apple](https://github.com/appwrite/sdk-for-apple) (由 Appwrite 团队维护)
|
||||
- ✅ [Android](https://github.com/appwrite/sdk-for-android) (由 Appwrite 团队维护)
|
||||
- ✅ [React Native](https://github.com/appwrite/sdk-for-react-native) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Web](https://github.com/appwrite/sdk-for-web) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Flutter](https://github.com/appwrite/sdk-for-flutter) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Android](https://github.com/appwrite/sdk-for-android) (由 Appwrite 团队维护)
|
||||
|
||||
#### 服务器
|
||||
|
||||
- ✅ [NodeJS](https://github.com/appwrite/sdk-for-node) (由 Appwrite 团队维护)
|
||||
- ✅ [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 团队维护)
|
||||
- ✅ [Python](https://github.com/appwrite/sdk-for-python) (由 Appwrite 团队维护)
|
||||
- ✅ [Kotlin](https://github.com/appwrite/sdk-for-kotlin) (由 Appwrite 团队维护)
|
||||
- ✅ [Swift](https://github.com/appwrite/sdk-for-swift) (由 Appwrite 团队维护)
|
||||
- ✅ [.NET](https://github.com/appwrite/sdk-for-dotnet) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [NodeJS](https://github.com/appwrite/sdk-for-node) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [PHP](https://github.com/appwrite/sdk-for-php) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Dart](https://github.com/appwrite/sdk-for-dart) - (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Deno](https://github.com/appwrite/sdk-for-deno) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Ruby](https://github.com/appwrite/sdk-for-ruby) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Python](https://github.com/appwrite/sdk-for-python) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Kotlin](https://github.com/appwrite/sdk-for-kotlin) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [.NET](https://github.com/appwrite/sdk-for-dotnet) - **公测** (由 Appwrite 团队维护)
|
||||
|
||||
#### 开发者社区
|
||||
|
||||
- ✅ [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (维护者 [Michael Gangolf](https://github.com/m1ga/))
|
||||
- ✅ [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (维护者 [fenix-hub @GodotNuts](https://github.com/fenix-hub))
|
||||
* :white_check_mark: [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (维护者 [Michael Gangolf](https://github.com/m1ga/))
|
||||
* :white_check_mark: [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (维护者 [fenix-hub @GodotNuts](https://github.com/fenix-hub))
|
||||
|
||||
找不到需要的的 SDK? - 欢迎通过发起 PR 来帮助我们完善 Appwrite 的软件生态环境 [SDK 生成器](https://github.com/appwrite/sdk-generator)!
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
> Appwrite Init has concluded! You can check out all the latest announcements [on our Init website](https://appwrite.io/init) 🚀
|
||||
> [Get started with Appwrite](https://apwr.dev/appcloud)
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite Logo"></a>
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite banner, with logo and text saying "Build Like a Team of Hundreds"></a>
|
||||
<br />
|
||||
<br />
|
||||
<b>Appwrite is a backend platform for developing Web, Mobile, and Flutter applications. Built with the open source community and optimized for developer experience in the coding languages you love.</b>
|
||||
@@ -12,11 +12,11 @@
|
||||
|
||||
<!-- [](https://travis-ci.com/appwrite/appwrite) -->
|
||||
|
||||
[](https://appwrite.io/company/careers)
|
||||
[](https://hacktoberfest.appwrite.io)
|
||||
[](https://appwrite.io/discord?r=Github)
|
||||
[](https://github.com/appwrite/appwrite/actions)
|
||||
[](https://twitter.com/appwrite)
|
||||
[](https://appwrite.io/company/careers)
|
||||
[](https://hacktoberfest.appwrite.io)
|
||||
[](https://appwrite.io/discord?r=Github)
|
||||
[](https://github.com/appwrite/appwrite/actions)
|
||||
[](https://twitter.com/appwrite)
|
||||
|
||||
<!-- [](https://hub.docker.com/r/appwrite/appwrite) -->
|
||||
<!-- [](docs/tutorials/add-translations.md) -->
|
||||
@@ -24,11 +24,9 @@
|
||||
|
||||
English | [简体中文](README-CN.md)
|
||||
|
||||
[**Announcing Appwrite Cloud Public Beta! Sign up today!**](https://cloud.appwrite.io)
|
||||
|
||||
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 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).
|
||||
Using Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, messaging, and [more services](https://appwrite.io/docs).
|
||||
|
||||
<p align="center">
|
||||
<br />
|
||||
@@ -37,13 +35,14 @@ Using Appwrite, you can easily integrate your app with user authentication and m
|
||||
<br />
|
||||
</p>
|
||||
|
||||

|
||||

|
||||
|
||||
Find out more at: [https://appwrite.io](https://appwrite.io)
|
||||
Find out more at: [https://appwrite.io](https://appwrite.io).
|
||||
|
||||
Table of Contents:
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Self-Hosting](#self-hosting)
|
||||
- [Unix](#unix)
|
||||
- [Windows](#windows)
|
||||
- [CMD](#cmd)
|
||||
@@ -51,7 +50,7 @@ Table of Contents:
|
||||
- [Upgrade from an Older Version](#upgrade-from-an-older-version)
|
||||
- [One-Click Setups](#one-click-setups)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Services](#services)
|
||||
- [Products](#products)
|
||||
- [SDKs](#sdks)
|
||||
- [Client](#client)
|
||||
- [Server](#server)
|
||||
@@ -62,11 +61,14 @@ Table of Contents:
|
||||
- [Follow Us](#follow-us)
|
||||
- [License](#license)
|
||||
|
||||
## Installation
|
||||
## Getting Started
|
||||
The easiest way to get started with Appwrite is by [signing up for Appwrite Cloud](https://cloud.appwrite.io/). While Appwrite Cloud is in public beta, you can build with Appwrite completely free, and we won't collect you credit card information.
|
||||
|
||||
## Self-Hosting
|
||||
|
||||
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](https://kubernetes.io/docs/home/), [Docker Swarm](https://docs.docker.com/engine/swarm/), or [Rancher](https://rancher.com/docs/).
|
||||
|
||||
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:
|
||||
Before running the installation command, make sure you have [Docker](https://www.docker.com/products/docker-desktop) installed on your machine:
|
||||
|
||||
### Unix
|
||||
|
||||
@@ -75,7 +77,7 @@ docker run -it --rm \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.6.0
|
||||
appwrite/appwrite:1.6.2
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -87,7 +89,7 @@ docker run -it --rm ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.6.0
|
||||
appwrite/appwrite:1.6.2
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -97,7 +99,7 @@ docker run -it --rm `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.6.0
|
||||
appwrite/appwrite:1.6.2
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -106,7 +108,7 @@ For advanced production and custom installation, check out our Docker [environme
|
||||
|
||||
### 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).
|
||||
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/self-hosting).
|
||||
|
||||
## One-Click Setups
|
||||
|
||||
@@ -192,34 +194,34 @@ Below is a list of currently supported platforms and languages. If you would lik
|
||||
|
||||
#### 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) (Maintained by the Appwrite Team)
|
||||
- ✅ [Android](https://github.com/appwrite/sdk-for-android) (Maintained by the Appwrite Team)
|
||||
- ✅ [React Native](https://github.com/appwrite/sdk-for-react-native) - **Beta** (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [Web](https://github.com/appwrite/sdk-for-web) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [Flutter](https://github.com/appwrite/sdk-for-flutter) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [Apple](https://github.com/appwrite/sdk-for-apple) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [Android](https://github.com/appwrite/sdk-for-android) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [React Native](https://github.com/appwrite/sdk-for-react-native) - **Beta** (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) (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) (Maintained by the Appwrite Team)
|
||||
- ✅ [Swift](https://github.com/appwrite/sdk-for-swift) (Maintained by the Appwrite Team)
|
||||
- ✅ [.NET](https://github.com/appwrite/sdk-for-dotnet) - **Beta** (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [NodeJS](https://github.com/appwrite/sdk-for-node) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [PHP](https://github.com/appwrite/sdk-for-php) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [Dart](https://github.com/appwrite/sdk-for-dart) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [Deno](https://github.com/appwrite/sdk-for-deno) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [Ruby](https://github.com/appwrite/sdk-for-ruby) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [Python](https://github.com/appwrite/sdk-for-python) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [Kotlin](https://github.com/appwrite/sdk-for-kotlin) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [Swift](https://github.com/appwrite/sdk-for-swift) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [.NET](https://github.com/appwrite/sdk-for-dotnet) - **Beta** (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))
|
||||
- :white_check_mark: [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (Maintained by [Michael Gangolf](https://github.com/m1ga/))
|
||||
- :white_check_mark: [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, such as REST, WebSocket, and GraphQL to allow you to interact with your resources by leveraging your existing knowledge and protocols of choice.
|
||||
|
||||
@@ -229,7 +231,7 @@ The Appwrite API layer was designed to be extremely fast by leveraging in-memory
|
||||
|
||||
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).
|
||||
We truly :heart: 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).
|
||||
|
||||
## Security
|
||||
|
||||
|
||||
+98
-33
@@ -5,8 +5,11 @@ require_once __DIR__ . '/init.php';
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\StatsResources;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\Runtimes\Runtimes;
|
||||
use Executor\Executor;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\CLI;
|
||||
@@ -19,12 +22,12 @@ use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Service;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\System\System;
|
||||
|
||||
// overwriting runtimes to be architectur agnostic for CLI
|
||||
Config::setParam('runtimes', (new Runtimes('v4'))->getAll(supported: false));
|
||||
// overwriting runtimes to be architecture agnostic for CLI
|
||||
Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
|
||||
|
||||
// require controllers after overwriting runtimes
|
||||
require_once __DIR__ . '/controllers/general.php';
|
||||
@@ -41,8 +44,7 @@ CLI::setResource('cache', function ($pools) {
|
||||
$adapters[] = $pools
|
||||
->get($value)
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
->getResource();
|
||||
}
|
||||
|
||||
return new Cache(new Sharding($adapters));
|
||||
@@ -52,7 +54,7 @@ CLI::setResource('pools', function (Registry $register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
|
||||
CLI::setResource('dbForConsole', function ($pools, $cache) {
|
||||
CLI::setResource('dbForPlatform', function ($pools, $cache) {
|
||||
$sleep = 3;
|
||||
$maxAttempts = 5;
|
||||
$attempts = 0;
|
||||
@@ -67,9 +69,9 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$dbForConsole = new Database($dbAdapter, $cache);
|
||||
$dbForPlatform = new Database($dbAdapter, $cache);
|
||||
|
||||
$dbForConsole
|
||||
$dbForPlatform
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console');
|
||||
@@ -78,7 +80,7 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
|
||||
$collections = Config::getParam('collections', [])['console'];
|
||||
$last = \array_key_last($collections);
|
||||
|
||||
if (!($dbForConsole->exists($dbForConsole->getDatabase(), $last))) { /** TODO cache ready variable using registry */
|
||||
if (!($dbForPlatform->exists($dbForPlatform->getDatabase(), $last))) { /** TODO cache ready variable using registry */
|
||||
throw new Exception('Tables not ready yet.');
|
||||
}
|
||||
|
||||
@@ -94,15 +96,19 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
|
||||
throw new Exception("Console is not ready yet. Please try again later.");
|
||||
}
|
||||
|
||||
return $dbForConsole;
|
||||
return $dbForPlatform;
|
||||
}, ['pools', 'cache']);
|
||||
|
||||
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
|
||||
CLI::setResource('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
}, []);
|
||||
|
||||
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) {
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForConsole;
|
||||
return $dbForPlatform;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -158,22 +164,68 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForConsole', 'cache']);
|
||||
}, ['pools', 'dbForPlatform', 'cache']);
|
||||
|
||||
CLI::setResource('queue', function (Group $pools) {
|
||||
return $pools->get('queue')->pop()->getResource();
|
||||
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
$database = null;
|
||||
return function (?Document $project = null) use ($pools, $cache, $database) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant($project->getInternalId());
|
||||
return $database;
|
||||
}
|
||||
|
||||
$dbAdapter = $pools
|
||||
->get('logs')
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$database = new Database(
|
||||
$dbAdapter,
|
||||
$cache
|
||||
);
|
||||
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setNamespace('logsV1')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
|
||||
|
||||
// set tenant
|
||||
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant($project->getInternalId());
|
||||
}
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache']);
|
||||
|
||||
CLI::setResource('queueForStatsUsage', function (Connection $publisher) {
|
||||
return new StatsUsage($publisher);
|
||||
}, ['publisher']);
|
||||
CLI::setResource('queueForStatsResources', function (Publisher $publisher) {
|
||||
return new StatsResources($publisher);
|
||||
}, ['publisher']);
|
||||
CLI::setResource('publisher', function (Group $pools) {
|
||||
return $pools->get('publisher')->pop()->getResource();
|
||||
}, ['pools']);
|
||||
CLI::setResource('queueForFunctions', function (Connection $queue) {
|
||||
return new Func($queue);
|
||||
}, ['queue']);
|
||||
CLI::setResource('queueForDeletes', function (Connection $queue) {
|
||||
return new Delete($queue);
|
||||
}, ['queue']);
|
||||
CLI::setResource('queueForCertificates', function (Connection $queue) {
|
||||
return new Certificate($queue);
|
||||
}, ['queue']);
|
||||
CLI::setResource('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
}, ['publisher']);
|
||||
CLI::setResource('queueForDeletes', function (Publisher $publisher) {
|
||||
return new Delete($publisher);
|
||||
}, ['publisher']);
|
||||
CLI::setResource('queueForCertificates', function (Publisher $publisher) {
|
||||
return new Certificate($publisher);
|
||||
}, ['publisher']);
|
||||
CLI::setResource('logError', function (Registry $register) {
|
||||
return function (Throwable $error, string $namespace, string $action) use ($register) {
|
||||
Console::error('[Error] Timestamp: ' . date('c', time()));
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
Console::error('[Error] Message: ' . $error->getMessage());
|
||||
Console::error('[Error] File: ' . $error->getFile());
|
||||
Console::error('[Error] Line: ' . $error->getLine());
|
||||
Console::error('[Error] Trace: ' . $error->getTraceAsString());
|
||||
|
||||
$logger = $register->get('logger');
|
||||
|
||||
if ($logger) {
|
||||
@@ -181,7 +233,7 @@ CLI::setResource('logError', function (Registry $register) {
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace($namespace);
|
||||
$log->setServer(\gethostname());
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
@@ -192,6 +244,7 @@ CLI::setResource('logError', function (Registry $register) {
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $error->getTrace());
|
||||
|
||||
$log->setAction($action);
|
||||
|
||||
@@ -205,22 +258,34 @@ CLI::setResource('logError', function (Registry $register) {
|
||||
Console::error('Error pushing log: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Console::warning("Failed: {$error->getMessage()}");
|
||||
Console::warning($error->getTraceAsString());
|
||||
};
|
||||
}, ['register']);
|
||||
|
||||
$platform = new Appwrite();
|
||||
$platform->init(Service::TYPE_TASK);
|
||||
CLI::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST')));
|
||||
|
||||
$platform = new Appwrite();
|
||||
$args = $platform->getEnv('argv');
|
||||
|
||||
if (!isset($args[0])) {
|
||||
Console::error('Missing task name');
|
||||
Console::exit(1);
|
||||
}
|
||||
|
||||
\array_shift($args);
|
||||
$taskName = $args[0];
|
||||
$platform->init(Service::TYPE_TASK);
|
||||
$cli = $platform->getCli();
|
||||
|
||||
$cli
|
||||
->error()
|
||||
->inject('error')
|
||||
->action(function (Throwable $error) {
|
||||
Console::error($error->getMessage());
|
||||
->inject('logError')
|
||||
->action(function (Throwable $error, callable $logError) use ($taskName) {
|
||||
call_user_func_array($logError, [
|
||||
$error,
|
||||
'Task',
|
||||
$taskName,
|
||||
]);
|
||||
});
|
||||
|
||||
$cli->run();
|
||||
|
||||
@@ -16,5 +16,6 @@ return [
|
||||
'union-china-pay' => ['name' => 'Union China Pay', 'path' => __DIR__ . '/credit-cards/union-china-pay.png'],
|
||||
'visa' => ['name' => 'Visa', 'path' => __DIR__ . '/credit-cards/visa.png'],
|
||||
'mir' => ['name' => 'MIR', 'path' => __DIR__ . '/credit-cards/mir.png'],
|
||||
'maestro' => ['name' => 'Maestro', 'path' => __DIR__ . '/credit-cards/maestro.png']
|
||||
'maestro' => ['name' => 'Maestro', 'path' => __DIR__ . '/credit-cards/maestro.png'],
|
||||
'rupay' => ['name' => 'Rupay', 'path' => __DIR__ . '/credit-cards/rupay.png']
|
||||
];
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
+17
-6147
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
return [
|
||||
'collections' => [
|
||||
'$collection' => ID::custom('databases'),
|
||||
'$id' => ID::custom('collections'),
|
||||
'name' => 'Collections',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('databaseInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('databaseId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'signed' => true,
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => 256,
|
||||
'required' => true,
|
||||
'signed' => true,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('enabled'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('documentSecurity'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('attributes'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => 1000000,
|
||||
'required' => false,
|
||||
'signed' => true,
|
||||
'array' => false,
|
||||
'filters' => ['subQueryAttributes'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('indexes'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => 1000000,
|
||||
'required' => false,
|
||||
'signed' => true,
|
||||
'array' => false,
|
||||
'filters' => ['subQueryIndexes'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_fulltext_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_name'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['name'],
|
||||
'lengths' => [256],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_enabled'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['enabled'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_documentSecurity'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['documentSecurity'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
]
|
||||
];
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
$logsCollection = [];
|
||||
|
||||
$logsCollection['stats'] = [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('stats'),
|
||||
'name' => 'stats',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('metric'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 255,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('region'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 255,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('value'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 8,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('time'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('period'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 4,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_time'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['time'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_DESC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_period_time'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['period', 'time'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_metric_period_time'),
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
'attributes' => ['metric', 'period', 'time'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_DESC],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $logsCollection;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Initializes console project document.
|
||||
*/
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\System\System;
|
||||
|
||||
$console = [
|
||||
'$id' => ID::custom('console'),
|
||||
'$internalId' => ID::custom('console'),
|
||||
'name' => 'Appwrite',
|
||||
'$collection' => ID::custom('projects'),
|
||||
'description' => 'Appwrite core engine',
|
||||
'logo' => '',
|
||||
'teamId' => null,
|
||||
'webhooks' => [],
|
||||
'keys' => [],
|
||||
'platforms' => [
|
||||
[
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Localhost',
|
||||
'type' => Origin::CLIENT_TYPE_WEB,
|
||||
'hostname' => 'localhost',
|
||||
], // Current host is added on app init
|
||||
],
|
||||
'region' => 'fra',
|
||||
'legalName' => '',
|
||||
'legalCountry' => '',
|
||||
'legalState' => '',
|
||||
'legalCity' => '',
|
||||
'legalAddress' => '',
|
||||
'legalTaxId' => '',
|
||||
'auths' => [
|
||||
'mockNumbers' => [],
|
||||
'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
|
||||
'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
|
||||
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
|
||||
'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled'
|
||||
],
|
||||
'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
|
||||
'authWhitelistIPs' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
|
||||
'oAuthProviders' => [
|
||||
'githubEnabled' => true,
|
||||
'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
|
||||
'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
|
||||
],
|
||||
];
|
||||
|
||||
return $console;
|
||||
+76
-8
@@ -349,11 +349,6 @@ return [
|
||||
'description' => 'Team with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::TEAM_INVITE_ALREADY_EXISTS => [
|
||||
'name' => Exception::TEAM_INVITE_ALREADY_EXISTS,
|
||||
'description' => 'User has already been invited or is already a member of this team',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::TEAM_INVITE_NOT_FOUND => [
|
||||
'name' => Exception::TEAM_INVITE_NOT_FOUND,
|
||||
'description' => 'The requested team invitation could not be found.',
|
||||
@@ -361,7 +356,7 @@ return [
|
||||
],
|
||||
Exception::TEAM_INVALID_SECRET => [
|
||||
'name' => Exception::TEAM_INVALID_SECRET,
|
||||
'description' => 'The team invitation secret is invalid. Please request a new invitation and try again.',
|
||||
'description' => 'The team invitation secret is invalid. Please request a new invitation and try again.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::TEAM_MEMBERSHIP_MISMATCH => [
|
||||
@@ -380,6 +375,13 @@ return [
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Console */
|
||||
Exception::RESOURCE_ALREADY_EXISTS => [
|
||||
'name' => Exception::RESOURCE_ALREADY_EXISTS,
|
||||
'description' => 'Resource with the requested ID already exists. Please choose a different ID and try again.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Membership */
|
||||
Exception::MEMBERSHIP_NOT_FOUND => [
|
||||
'name' => Exception::MEMBERSHIP_NOT_FOUND,
|
||||
@@ -486,6 +488,18 @@ return [
|
||||
'code' => 403,
|
||||
],
|
||||
|
||||
/** Tokens */
|
||||
Exception::TOKEN_NOT_FOUND => [
|
||||
'name' => Exception::TOKEN_NOT_FOUND,
|
||||
'description' => 'The requested file token could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::TOKEN_EXPIRED => [
|
||||
'name' => Exception::TOKEN_EXPIRED,
|
||||
'description' => 'The requested file token has expired.',
|
||||
'code' => 401,
|
||||
],
|
||||
|
||||
/** VCS */
|
||||
Exception::INSTALLATION_NOT_FOUND => [
|
||||
'name' => Exception::INSTALLATION_NOT_FOUND,
|
||||
@@ -525,7 +539,7 @@ return [
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::FUNCTION_ENTRYPOINT_MISSING => [
|
||||
'name' => Exception::FUNCTION_RUNTIME_UNSUPPORTED,
|
||||
'name' => Exception::FUNCTION_ENTRYPOINT_MISSING,
|
||||
'description' => 'Entrypoint for your Appwrite Function is missing. Please specify it when making deployment or update the entrypoint under your function\'s "Settings" > "Configuration" > "Entrypoint".',
|
||||
'code' => 404,
|
||||
],
|
||||
@@ -539,6 +553,28 @@ return [
|
||||
'description' => 'Function Template with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::FUNCTION_RUNTIME_NOT_DETECTED => [
|
||||
'name' => Exception::FUNCTION_RUNTIME_NOT_DETECTED,
|
||||
'description' => 'Function runtime could not be detected.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::FUNCTION_EXECUTE_PERMISSION_MISSING => [
|
||||
'name' => Exception::FUNCTION_EXECUTE_PERMISSION_MISSING,
|
||||
'description' => 'To execute function using domain, execute permissions must include "any" or "guests".',
|
||||
'code' => 401,
|
||||
],
|
||||
|
||||
/** Sites */
|
||||
Exception::SITE_NOT_FOUND => [
|
||||
'name' => Exception::SITE_NOT_FOUND,
|
||||
'description' => 'Site with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::SITE_TEMPLATE_NOT_FOUND => [
|
||||
'name' => Exception::SITE_TEMPLATE_NOT_FOUND,
|
||||
'description' => 'Site Template with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
/** Builds */
|
||||
Exception::BUILD_NOT_FOUND => [
|
||||
@@ -561,6 +597,16 @@ return [
|
||||
'description' => 'Build with the requested ID is already completed and cannot be canceled.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::BUILD_CANCELED => [
|
||||
'name' => Exception::BUILD_CANCELED,
|
||||
'description' => 'Build with the requested ID has been canceled.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::BUILD_FAILED => [
|
||||
'name' => Exception::BUILD_FAILED,
|
||||
'description' => 'Build with the requested ID failed. Please check the logs for more information.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Deployments */
|
||||
Exception::DEPLOYMENT_NOT_FOUND => [
|
||||
@@ -582,6 +628,13 @@ return [
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Logs */
|
||||
Exception::LOG_NOT_FOUND => [
|
||||
'name' => Exception::LOG_NOT_FOUND,
|
||||
'description' => 'Log with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
/** Databases */
|
||||
Exception::DATABASE_NOT_FOUND => [
|
||||
'name' => Exception::DATABASE_NOT_FOUND,
|
||||
@@ -598,6 +651,11 @@ return [
|
||||
'description' => 'Database timed out. Try adjusting your queries or adding an index.',
|
||||
'code' => 408
|
||||
],
|
||||
Exception::DATABASE_QUERY_ORDER_NULL => [
|
||||
'name' => Exception::DATABASE_QUERY_ORDER_NULL,
|
||||
'description' => 'The order attribute had a null value. Cursor pagination requires all documents order attribute values are non-null.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Collections */
|
||||
Exception::COLLECTION_NOT_FOUND => [
|
||||
@@ -731,6 +789,11 @@ return [
|
||||
'description' => 'Index invalid.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::INDEX_DEPENDENCY => [
|
||||
'name' => Exception::INDEX_DEPENDENCY,
|
||||
'description' => 'Attribute cannot be renamed or deleted. Please remove the associated index first.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Project Errors */
|
||||
Exception::PROJECT_NOT_FOUND => [
|
||||
@@ -801,7 +864,7 @@ return [
|
||||
Exception::RULE_VERIFICATION_FAILED => [
|
||||
'name' => Exception::RULE_VERIFICATION_FAILED,
|
||||
'description' => 'Domain verification failed. Please check if your DNS records are correct and try again.',
|
||||
'code' => 401,
|
||||
'code' => 400,
|
||||
'publish' => true
|
||||
],
|
||||
Exception::PROJECT_SMTP_CONFIG_INVALID => [
|
||||
@@ -844,6 +907,11 @@ return [
|
||||
'description' => 'Variable with the same ID already exists in this project. Try again with a different ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::VARIABLE_CANNOT_UNSET_SECRET => [
|
||||
'name' => Exception::VARIABLE_CANNOT_UNSET_SECRET,
|
||||
'description' => 'Secret variables cannot be marked as non-secret. Please re-create the variable if this is your intention.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::GRAPHQL_NO_QUERY => [
|
||||
'name' => Exception::GRAPHQL_NO_QUERY,
|
||||
'description' => 'Param "query" is not optional.',
|
||||
|
||||
@@ -217,6 +217,34 @@ return [
|
||||
],
|
||||
]
|
||||
],
|
||||
'sites' => [
|
||||
'$model' => Response::MODEL_SITE,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any sites event.',
|
||||
'deployments' => [
|
||||
'$model' => Response::MODEL_DEPLOYMENT,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any deployments event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a deployment is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a deployment is deleted.'
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a deployment is updated.'
|
||||
],
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a site is created.'
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a site is deleted.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a site is updated.',
|
||||
]
|
||||
],
|
||||
'functions' => [
|
||||
'$model' => Response::MODEL_FUNCTION,
|
||||
'$resource' => true,
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* List of Appwrite Sites supported frameworks
|
||||
*/
|
||||
|
||||
use Utopia\Config\Config;
|
||||
|
||||
$templateRuntimes = Config::getParam('template-runtimes');
|
||||
|
||||
function getVersions(array $versions, string $prefix)
|
||||
{
|
||||
return array_map(function ($version) use ($prefix) {
|
||||
return $prefix . '-' . $version;
|
||||
}, $versions);
|
||||
}
|
||||
|
||||
return [
|
||||
'analog' => [
|
||||
'key' => 'analog',
|
||||
'name' => 'Analog',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/analog/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/analog/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist/analog',
|
||||
'startCommand' => 'bash helpers/analog/server.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist/analog/public',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
'fallbackFile' => 'index.html'
|
||||
]
|
||||
]
|
||||
],
|
||||
'angular' => [
|
||||
'key' => 'angular',
|
||||
'name' => 'Angular',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/angular/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/angular/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist/angular',
|
||||
'startCommand' => 'bash helpers/angular/server.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist/angular/browser',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
'fallbackFile' => 'index.csr.html'
|
||||
]
|
||||
]
|
||||
],
|
||||
'nextjs' => [
|
||||
'key' => 'nextjs',
|
||||
'name' => 'Next.js',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/next-js/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/next-js/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './.next',
|
||||
'startCommand' => 'bash helpers/next-js/server.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './out',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
]
|
||||
]
|
||||
],
|
||||
'react' => [
|
||||
'key' => 'react',
|
||||
'name' => 'React',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
'fallbackFile' => 'index.html'
|
||||
]
|
||||
]
|
||||
],
|
||||
'nuxt' => [
|
||||
'key' => 'nuxt',
|
||||
'name' => 'Nuxt',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/nuxt/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/nuxt/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './.output',
|
||||
'startCommand' => 'bash helpers/nuxt/server.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'npm run generate',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './output/public',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
]
|
||||
]
|
||||
],
|
||||
'vue' => [
|
||||
'key' => 'vue',
|
||||
'name' => 'Vue.js',
|
||||
'screenshotSleep' => 5000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
'fallbackFile' => 'index.html'
|
||||
]
|
||||
]
|
||||
],
|
||||
'sveltekit' => [
|
||||
'key' => 'sveltekit',
|
||||
'name' => 'SvelteKit',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/sveltekit/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/sveltekit/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './build',
|
||||
'startCommand' => 'bash helpers/sveltekit/server.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './build',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
]
|
||||
]
|
||||
],
|
||||
'astro' => [
|
||||
'key' => 'astro',
|
||||
'name' => 'Astro',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/astro/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/astro/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist',
|
||||
'startCommand' => 'bash helpers/astro/server.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
]
|
||||
]
|
||||
],
|
||||
'remix' => [
|
||||
'key' => 'remix',
|
||||
'name' => 'Remix',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/remix/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/remix/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './build',
|
||||
'startCommand' => 'bash helpers/remix/server.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './build/client',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
]
|
||||
]
|
||||
],
|
||||
'lynx' => [
|
||||
'key' => 'lynx',
|
||||
'name' => 'Lynx',
|
||||
'screenshotSleep' => 5000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
'fallbackFile' => 'index.html'
|
||||
]
|
||||
]
|
||||
],
|
||||
'flutter' => [
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'screenshotSleep' => 5000,
|
||||
'buildRuntime' => 'flutter-3.29',
|
||||
'runtimes' => getVersions($templateRuntimes['FLUTTER']['versions'], 'flutter'),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'flutter build web',
|
||||
'installCommand' => '',
|
||||
'outputDirectory' => './build/web',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
],
|
||||
],
|
||||
],
|
||||
'vite' => [
|
||||
'key' => 'vite',
|
||||
'name' => 'Vite',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
],
|
||||
]
|
||||
],
|
||||
'other' => [
|
||||
'key' => 'other',
|
||||
'name' => 'Other',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => '',
|
||||
'installCommand' => '',
|
||||
'outputDirectory' => './',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
@@ -1,11 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Continent codes with names and approximate central coordinates
|
||||
*
|
||||
* Coordinates represent approximate geographical centers of each continent
|
||||
* Note: These are simplified centroids and may not represent the exact geographical center
|
||||
*/
|
||||
|
||||
return [
|
||||
'AF',
|
||||
'AN',
|
||||
'AS',
|
||||
'EU',
|
||||
'NA',
|
||||
'OC',
|
||||
'SA',
|
||||
'AF' => [
|
||||
'name' => 'Africa',
|
||||
'latitude' => 8.7832,
|
||||
'longitude' => 34.5085
|
||||
],
|
||||
'AN' => [
|
||||
'name' => 'Antarctica',
|
||||
'latitude' => -82.8628,
|
||||
'longitude' => 135.0000
|
||||
],
|
||||
'AS' => [
|
||||
'name' => 'Asia',
|
||||
'latitude' => 34.0479,
|
||||
'longitude' => 100.6197
|
||||
],
|
||||
'EU' => [
|
||||
'name' => 'Europe',
|
||||
'latitude' => 54.5260,
|
||||
'longitude' => 15.2551
|
||||
],
|
||||
'NA' => [
|
||||
'name' => 'North America',
|
||||
'latitude' => 54.5260,
|
||||
'longitude' => -105.2551
|
||||
],
|
||||
'OC' => [
|
||||
'name' => 'Oceania',
|
||||
'latitude' => -22.7359,
|
||||
'longitude' => 140.0188
|
||||
],
|
||||
'SA' => [
|
||||
'name' => 'South America',
|
||||
'latitude' => -8.7832,
|
||||
'longitude' => -55.4915
|
||||
],
|
||||
];
|
||||
|
||||
+199
-198
@@ -1,209 +1,210 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ISO 3166 standard country codes
|
||||
* ISO 3166 standard country codes with country names and coordinates
|
||||
* https://www.iso.org/iso-3166-country-codes.html
|
||||
*
|
||||
* Source:
|
||||
* https://www.iso.org/obp/ui/#search/code/
|
||||
* Coordinates source: Natural Earth Data (approximate country centroids)
|
||||
*/
|
||||
|
||||
return [
|
||||
'AF',
|
||||
'AO',
|
||||
'AL',
|
||||
'AD',
|
||||
'AE',
|
||||
'AR',
|
||||
'AM',
|
||||
'AG',
|
||||
'AU',
|
||||
'AT',
|
||||
'AZ',
|
||||
'BI',
|
||||
'BE',
|
||||
'BJ',
|
||||
'BF',
|
||||
'BD',
|
||||
'BG',
|
||||
'BH',
|
||||
'BS',
|
||||
'BA',
|
||||
'BY',
|
||||
'BZ',
|
||||
'BO',
|
||||
'BR',
|
||||
'BB',
|
||||
'BN',
|
||||
'BT',
|
||||
'BW',
|
||||
'CF',
|
||||
'CA',
|
||||
'CH',
|
||||
'CL',
|
||||
'CN',
|
||||
'CI',
|
||||
'CM',
|
||||
'CD',
|
||||
'CG',
|
||||
'CO',
|
||||
'KM',
|
||||
'CV',
|
||||
'CR',
|
||||
'CU',
|
||||
'CY',
|
||||
'CZ',
|
||||
'DE',
|
||||
'DJ',
|
||||
'DM',
|
||||
'DK',
|
||||
'DO',
|
||||
'DZ',
|
||||
'EC',
|
||||
'EG',
|
||||
'ER',
|
||||
'ES',
|
||||
'EE',
|
||||
'ET',
|
||||
'FI',
|
||||
'FJ',
|
||||
'FR',
|
||||
'FM',
|
||||
'GA',
|
||||
'GB',
|
||||
'GE',
|
||||
'GH',
|
||||
'GN',
|
||||
'GM',
|
||||
'GW',
|
||||
'GQ',
|
||||
'GR',
|
||||
'GD',
|
||||
'GT',
|
||||
'GY',
|
||||
'HK',
|
||||
'HN',
|
||||
'HR',
|
||||
'HT',
|
||||
'HU',
|
||||
'ID',
|
||||
'IN',
|
||||
'IE',
|
||||
'IR',
|
||||
'IQ',
|
||||
'IS',
|
||||
'IL',
|
||||
'IT',
|
||||
'JM',
|
||||
'JO',
|
||||
'JP',
|
||||
'KZ',
|
||||
'KE',
|
||||
'KG',
|
||||
'KH',
|
||||
'KI',
|
||||
'KN',
|
||||
'KR',
|
||||
'KW',
|
||||
'LA',
|
||||
'LB',
|
||||
'LR',
|
||||
'LY',
|
||||
'LC',
|
||||
'LI',
|
||||
'LK',
|
||||
'LS',
|
||||
'LT',
|
||||
'LU',
|
||||
'LV',
|
||||
'MA',
|
||||
'MC',
|
||||
'MD',
|
||||
'MG',
|
||||
'MV',
|
||||
'MX',
|
||||
'MH',
|
||||
'MK',
|
||||
'ML',
|
||||
'MT',
|
||||
'MM',
|
||||
'ME',
|
||||
'MN',
|
||||
'MZ',
|
||||
'MR',
|
||||
'MU',
|
||||
'MW',
|
||||
'MY',
|
||||
'NA',
|
||||
'NE',
|
||||
'NG',
|
||||
'NI',
|
||||
'NL',
|
||||
'NO',
|
||||
'NP',
|
||||
'NR',
|
||||
'NZ',
|
||||
'OM',
|
||||
'PK',
|
||||
'PS',
|
||||
'PA',
|
||||
'PE',
|
||||
'PH',
|
||||
'PW',
|
||||
'PG',
|
||||
'PL',
|
||||
'KP',
|
||||
'PT',
|
||||
'PY',
|
||||
'QA',
|
||||
'RO',
|
||||
'RU',
|
||||
'RW',
|
||||
'SA',
|
||||
'SD',
|
||||
'SN',
|
||||
'SG',
|
||||
'SB',
|
||||
'SL',
|
||||
'SV',
|
||||
'SM',
|
||||
'SO',
|
||||
'RS',
|
||||
'SS',
|
||||
'ST',
|
||||
'SR',
|
||||
'SK',
|
||||
'SI',
|
||||
'SE',
|
||||
'SZ',
|
||||
'SC',
|
||||
'SY',
|
||||
'TD',
|
||||
'TG',
|
||||
'TH',
|
||||
'TJ',
|
||||
'TM',
|
||||
'TL',
|
||||
'TO',
|
||||
'TT',
|
||||
'TN',
|
||||
'TR',
|
||||
'TV',
|
||||
'TZ',
|
||||
'TW',
|
||||
'UG',
|
||||
'UA',
|
||||
'UY',
|
||||
'US',
|
||||
'UZ',
|
||||
'VA',
|
||||
'VC',
|
||||
'VE',
|
||||
'VN',
|
||||
'VU',
|
||||
'WS',
|
||||
'YE',
|
||||
'ZA',
|
||||
'ZM',
|
||||
'ZW',
|
||||
'AF' => ['name' => 'Afghanistan', 'latitude' => 33.0, 'longitude' => 66.0],
|
||||
'AO' => ['name' => 'Angola', 'latitude' => -12.5, 'longitude' => 18.5],
|
||||
'AL' => ['name' => 'Albania', 'latitude' => 41.0, 'longitude' => 20.0],
|
||||
'AD' => ['name' => 'Andorra', 'latitude' => 42.5, 'longitude' => 1.6],
|
||||
'AE' => ['name' => 'United Arab Emirates', 'latitude' => 24.0, 'longitude' => 54.0],
|
||||
'AR' => ['name' => 'Argentina', 'latitude' => -34.0, 'longitude' => -64.0],
|
||||
'AM' => ['name' => 'Armenia', 'latitude' => 40.0, 'longitude' => 45.0],
|
||||
'AG' => ['name' => 'Antigua and Barbuda', 'latitude' => 17.05, 'longitude' => -61.8],
|
||||
'AU' => ['name' => 'Australia', 'latitude' => -25.0, 'longitude' => 135.0],
|
||||
'AT' => ['name' => 'Austria', 'latitude' => 47.3, 'longitude' => 13.3],
|
||||
'AZ' => ['name' => 'Azerbaijan', 'latitude' => 40.5, 'longitude' => 47.5],
|
||||
'BI' => ['name' => 'Burundi', 'latitude' => -3.5, 'longitude' => 30.0],
|
||||
'BE' => ['name' => 'Belgium', 'latitude' => 50.8, 'longitude' => 4.0],
|
||||
'BJ' => ['name' => 'Benin', 'latitude' => 9.5, 'longitude' => 2.25],
|
||||
'BF' => ['name' => 'Burkina Faso', 'latitude' => 13.0, 'longitude' => -2.0],
|
||||
'BD' => ['name' => 'Bangladesh', 'latitude' => 24.0, 'longitude' => 90.0],
|
||||
'BG' => ['name' => 'Bulgaria', 'latitude' => 43.0, 'longitude' => 25.0],
|
||||
'BH' => ['name' => 'Bahrain', 'latitude' => 26.0, 'longitude' => 50.5],
|
||||
'BS' => ['name' => 'Bahamas', 'latitude' => 24.25, 'longitude' => -76.0],
|
||||
'BA' => ['name' => 'Bosnia and Herzegovina', 'latitude' => 44.0, 'longitude' => 18.0],
|
||||
'BY' => ['name' => 'Belarus', 'latitude' => 53.0, 'longitude' => 28.0],
|
||||
'BZ' => ['name' => 'Belize', 'latitude' => 17.25, 'longitude' => -88.75],
|
||||
'BO' => ['name' => 'Bolivia', 'latitude' => -17.0, 'longitude' => -65.0],
|
||||
'BR' => ['name' => 'Brazil', 'latitude' => -10.0, 'longitude' => -55.0],
|
||||
'BB' => ['name' => 'Barbados', 'latitude' => 13.17, 'longitude' => -59.53],
|
||||
'BN' => ['name' => 'Brunei', 'latitude' => 4.5, 'longitude' => 114.67],
|
||||
'BT' => ['name' => 'Bhutan', 'latitude' => 27.5, 'longitude' => 90.5],
|
||||
'BW' => ['name' => 'Botswana', 'latitude' => -22.0, 'longitude' => 24.0],
|
||||
'CF' => ['name' => 'Central African Republic', 'latitude' => 7.0, 'longitude' => 21.0],
|
||||
'CA' => ['name' => 'Canada', 'latitude' => 60.0, 'longitude' => -95.0],
|
||||
'CH' => ['name' => 'Switzerland', 'latitude' => 47.0, 'longitude' => 8.0],
|
||||
'CL' => ['name' => 'Chile', 'latitude' => -30.0, 'longitude' => -71.0],
|
||||
'CN' => ['name' => 'China', 'latitude' => 35.0, 'longitude' => 105.0],
|
||||
'CI' => ['name' => 'Côte d\'Ivoire', 'latitude' => 8.0, 'longitude' => -5.0],
|
||||
'CM' => ['name' => 'Cameroon', 'latitude' => 6.0, 'longitude' => 12.0],
|
||||
'CD' => ['name' => 'Democratic Republic of the Congo', 'latitude' => -2.5, 'longitude' => 23.5],
|
||||
'CG' => ['name' => 'Republic of the Congo', 'latitude' => -1.0, 'longitude' => 15.0],
|
||||
'CO' => ['name' => 'Colombia', 'latitude' => 4.0, 'longitude' => -72.0],
|
||||
'KM' => ['name' => 'Comoros', 'latitude' => -12.17, 'longitude' => 44.25],
|
||||
'CV' => ['name' => 'Cape Verde', 'latitude' => 16.0, 'longitude' => -24.0],
|
||||
'CR' => ['name' => 'Costa Rica', 'latitude' => 10.0, 'longitude' => -84.0],
|
||||
'CU' => ['name' => 'Cuba', 'latitude' => 21.5, 'longitude' => -80.0],
|
||||
'CY' => ['name' => 'Cyprus', 'latitude' => 35.0, 'longitude' => 33.0],
|
||||
'CZ' => ['name' => 'Czech Republic', 'latitude' => 49.75, 'longitude' => 15.5],
|
||||
'DE' => ['name' => 'Germany', 'latitude' => 51.0, 'longitude' => 9.0],
|
||||
'DJ' => ['name' => 'Djibouti', 'latitude' => 11.5, 'longitude' => 43.0],
|
||||
'DM' => ['name' => 'Dominica', 'latitude' => 15.42, 'longitude' => -61.33],
|
||||
'DK' => ['name' => 'Denmark', 'latitude' => 56.0, 'longitude' => 10.0],
|
||||
'DO' => ['name' => 'Dominican Republic', 'latitude' => 19.0, 'longitude' => -70.67],
|
||||
'DZ' => ['name' => 'Algeria', 'latitude' => 28.0, 'longitude' => 3.0],
|
||||
'EC' => ['name' => 'Ecuador', 'latitude' => -2.0, 'longitude' => -77.5],
|
||||
'EG' => ['name' => 'Egypt', 'latitude' => 27.0, 'longitude' => 30.0],
|
||||
'ER' => ['name' => 'Eritrea', 'latitude' => 15.0, 'longitude' => 39.0],
|
||||
'ES' => ['name' => 'Spain', 'latitude' => 40.0, 'longitude' => -4.0],
|
||||
'EE' => ['name' => 'Estonia', 'latitude' => 59.0, 'longitude' => 26.0],
|
||||
'ET' => ['name' => 'Ethiopia', 'latitude' => 8.0, 'longitude' => 38.0],
|
||||
'FI' => ['name' => 'Finland', 'latitude' => 64.0, 'longitude' => 26.0],
|
||||
'FJ' => ['name' => 'Fiji', 'latitude' => -18.0, 'longitude' => 175.0],
|
||||
'FR' => ['name' => 'France', 'latitude' => 46.0, 'longitude' => 2.0],
|
||||
'FM' => ['name' => 'Micronesia', 'latitude' => 6.92, 'longitude' => 158.25],
|
||||
'GA' => ['name' => 'Gabon', 'latitude' => -1.0, 'longitude' => 11.75],
|
||||
'GB' => ['name' => 'United Kingdom', 'latitude' => 54.0, 'longitude' => -2.0],
|
||||
'GE' => ['name' => 'Georgia', 'latitude' => 42.0, 'longitude' => 43.5],
|
||||
'GH' => ['name' => 'Ghana', 'latitude' => 8.0, 'longitude' => -2.0],
|
||||
'GN' => ['name' => 'Guinea', 'latitude' => 11.0, 'longitude' => -10.0],
|
||||
'GM' => ['name' => 'Gambia', 'latitude' => 13.47, 'longitude' => -16.57],
|
||||
'GW' => ['name' => 'Guinea-Bissau', 'latitude' => 12.0, 'longitude' => -15.0],
|
||||
'GQ' => ['name' => 'Equatorial Guinea', 'latitude' => 2.0, 'longitude' => 10.0],
|
||||
'GR' => ['name' => 'Greece', 'latitude' => 39.0, 'longitude' => 22.0],
|
||||
'GD' => ['name' => 'Grenada', 'latitude' => 12.12, 'longitude' => -61.67],
|
||||
'GT' => ['name' => 'Guatemala', 'latitude' => 15.5, 'longitude' => -90.25],
|
||||
'GY' => ['name' => 'Guyana', 'latitude' => 5.0, 'longitude' => -59.0],
|
||||
'HK' => ['name' => 'Hong Kong', 'latitude' => 22.25, 'longitude' => 114.17],
|
||||
'HN' => ['name' => 'Honduras', 'latitude' => 15.0, 'longitude' => -86.5],
|
||||
'HR' => ['name' => 'Croatia', 'latitude' => 45.17, 'longitude' => 15.5],
|
||||
'HT' => ['name' => 'Haiti', 'latitude' => 19.0, 'longitude' => -72.42],
|
||||
'HU' => ['name' => 'Hungary', 'latitude' => 47.0, 'longitude' => 20.0],
|
||||
'ID' => ['name' => 'Indonesia', 'latitude' => -5.0, 'longitude' => 120.0],
|
||||
'IN' => ['name' => 'India', 'latitude' => 20.0, 'longitude' => 77.0],
|
||||
'IE' => ['name' => 'Ireland', 'latitude' => 53.0, 'longitude' => -8.0],
|
||||
'IR' => ['name' => 'Iran', 'latitude' => 32.0, 'longitude' => 53.0],
|
||||
'IQ' => ['name' => 'Iraq', 'latitude' => 33.0, 'longitude' => 44.0],
|
||||
'IS' => ['name' => 'Iceland', 'latitude' => 65.0, 'longitude' => -18.0],
|
||||
'IL' => ['name' => 'Israel', 'latitude' => 31.5, 'longitude' => 34.75],
|
||||
'IT' => ['name' => 'Italy', 'latitude' => 42.83, 'longitude' => 12.83],
|
||||
'JM' => ['name' => 'Jamaica', 'latitude' => 18.25, 'longitude' => -77.5],
|
||||
'JO' => ['name' => 'Jordan', 'latitude' => 31.0, 'longitude' => 36.0],
|
||||
'JP' => ['name' => 'Japan', 'latitude' => 36.0, 'longitude' => 138.0],
|
||||
'KZ' => ['name' => 'Kazakhstan', 'latitude' => 48.0, 'longitude' => 68.0],
|
||||
'KE' => ['name' => 'Kenya', 'latitude' => 1.0, 'longitude' => 38.0],
|
||||
'KG' => ['name' => 'Kyrgyzstan', 'latitude' => 41.0, 'longitude' => 75.0],
|
||||
'KH' => ['name' => 'Cambodia', 'latitude' => 13.0, 'longitude' => 105.0],
|
||||
'KI' => ['name' => 'Kiribati', 'latitude' => 1.42, 'longitude' => 173.0],
|
||||
'KN' => ['name' => 'Saint Kitts and Nevis', 'latitude' => 17.33, 'longitude' => -62.75],
|
||||
'KR' => ['name' => 'South Korea', 'latitude' => 37.0, 'longitude' => 127.5],
|
||||
'KW' => ['name' => 'Kuwait', 'latitude' => 29.34, 'longitude' => 47.66],
|
||||
'LA' => ['name' => 'Laos', 'latitude' => 18.0, 'longitude' => 105.0],
|
||||
'LB' => ['name' => 'Lebanon', 'latitude' => 33.83, 'longitude' => 35.83],
|
||||
'LR' => ['name' => 'Liberia', 'latitude' => 6.5, 'longitude' => -9.5],
|
||||
'LY' => ['name' => 'Libya', 'latitude' => 25.0, 'longitude' => 17.0],
|
||||
'LC' => ['name' => 'Saint Lucia', 'latitude' => 13.88, 'longitude' => -61.13],
|
||||
'LI' => ['name' => 'Liechtenstein', 'latitude' => 47.17, 'longitude' => 9.53],
|
||||
'LK' => ['name' => 'Sri Lanka', 'latitude' => 7.0, 'longitude' => 81.0],
|
||||
'LS' => ['name' => 'Lesotho', 'latitude' => -29.5, 'longitude' => 28.5],
|
||||
'LT' => ['name' => 'Lithuania', 'latitude' => 56.0, 'longitude' => 24.0],
|
||||
'LU' => ['name' => 'Luxembourg', 'latitude' => 49.75, 'longitude' => 6.17],
|
||||
'LV' => ['name' => 'latitudevia', 'latitude' => 57.0, 'longitude' => 25.0],
|
||||
'MA' => ['name' => 'Morocco', 'latitude' => 32.0, 'longitude' => -5.0],
|
||||
'MC' => ['name' => 'Monaco', 'latitude' => 43.73, 'longitude' => 7.4],
|
||||
'MD' => ['name' => 'Moldova', 'latitude' => 47.0, 'longitude' => 29.0],
|
||||
'MG' => ['name' => 'Madagascar', 'latitude' => -20.0, 'longitude' => 47.0],
|
||||
'MV' => ['name' => 'Maldives', 'latitude' => 3.25, 'longitude' => 73.0],
|
||||
'MX' => ['name' => 'Mexico', 'latitude' => 23.0, 'longitude' => -102.0],
|
||||
'MH' => ['name' => 'Marshall Islands', 'latitude' => 9.0, 'longitude' => 168.0],
|
||||
'MK' => ['name' => 'North Macedonia', 'latitude' => 41.83, 'longitude' => 22.0],
|
||||
'ML' => ['name' => 'Mali', 'latitude' => 17.0, 'longitude' => -4.0],
|
||||
'MT' => ['name' => 'Malta', 'latitude' => 35.83, 'longitude' => 14.58],
|
||||
'MM' => ['name' => 'Myanmar', 'latitude' => 22.0, 'longitude' => 98.0],
|
||||
'ME' => ['name' => 'Montenegro', 'latitude' => 42.5, 'longitude' => 19.3],
|
||||
'MN' => ['name' => 'Mongolia', 'latitude' => 46.0, 'longitude' => 105.0],
|
||||
'MZ' => ['name' => 'Mozambique', 'latitude' => -18.25, 'longitude' => 35.0],
|
||||
'MR' => ['name' => 'Mauritania', 'latitude' => 20.0, 'longitude' => -12.0],
|
||||
'MU' => ['name' => 'Mauritius', 'latitude' => -20.28, 'longitude' => 57.55],
|
||||
'MW' => ['name' => 'Malawi', 'latitude' => -13.5, 'longitude' => 34.0],
|
||||
'MY' => ['name' => 'Malaysia', 'latitude' => 2.5, 'longitude' => 112.5],
|
||||
'NA' => ['name' => 'Namibia', 'latitude' => -22.0, 'longitude' => 17.0],
|
||||
'NE' => ['name' => 'Niger', 'latitude' => 16.0, 'longitude' => 8.0],
|
||||
'NG' => ['name' => 'Nigeria', 'latitude' => 10.0, 'longitude' => 8.0],
|
||||
'NI' => ['name' => 'Nicaragua', 'latitude' => 13.0, 'longitude' => -85.0],
|
||||
'NL' => ['name' => 'Netherlands', 'latitude' => 52.5, 'longitude' => 5.75],
|
||||
'NO' => ['name' => 'Norway', 'latitude' => 62.0, 'longitude' => 10.0],
|
||||
'NP' => ['name' => 'Nepal', 'latitude' => 28.0, 'longitude' => 84.0],
|
||||
'NR' => ['name' => 'Nauru', 'latitude' => -0.53, 'longitude' => 166.92],
|
||||
'NZ' => ['name' => 'New Zealand', 'latitude' => -41.0, 'longitude' => 174.0],
|
||||
'OM' => ['name' => 'Oman', 'latitude' => 21.0, 'longitude' => 57.0],
|
||||
'PK' => ['name' => 'Pakistan', 'latitude' => 30.0, 'longitude' => 70.0],
|
||||
'PS' => ['name' => 'Palestine', 'latitude' => 31.9, 'longitude' => 35.2],
|
||||
'PA' => ['name' => 'Panama', 'latitude' => 9.0, 'longitude' => -80.0],
|
||||
'PE' => ['name' => 'Peru', 'latitude' => -10.0, 'longitude' => -76.0],
|
||||
'PH' => ['name' => 'Philippines', 'latitude' => 13.0, 'longitude' => 122.0],
|
||||
'PW' => ['name' => 'Palau', 'latitude' => 7.5, 'longitude' => 134.5],
|
||||
'PG' => ['name' => 'Papua New Guinea', 'latitude' => -6.0, 'longitude' => 147.0],
|
||||
'PL' => ['name' => 'Poland', 'latitude' => 52.0, 'longitude' => 20.0],
|
||||
'KP' => ['name' => 'North Korea', 'latitude' => 40.0, 'longitude' => 127.0],
|
||||
'PT' => ['name' => 'Portugal', 'latitude' => 39.5, 'longitude' => -8.0],
|
||||
'PY' => ['name' => 'Paraguay', 'latitude' => -23.0, 'longitude' => -58.0],
|
||||
'QA' => ['name' => 'Qatar', 'latitude' => 25.5, 'longitude' => 51.25],
|
||||
'RO' => ['name' => 'Romania', 'latitude' => 46.0, 'longitude' => 25.0],
|
||||
'RU' => ['name' => 'Russia', 'latitude' => 60.0, 'longitude' => 100.0],
|
||||
'RW' => ['name' => 'Rwanda', 'latitude' => -2.0, 'longitude' => 30.0],
|
||||
'SA' => ['name' => 'Saudi Arabia', 'latitude' => 25.0, 'longitude' => 45.0],
|
||||
'SD' => ['name' => 'Sudan', 'latitude' => 15.0, 'longitude' => 30.0],
|
||||
'SN' => ['name' => 'Senegal', 'latitude' => 14.0, 'longitude' => -14.0],
|
||||
'SG' => ['name' => 'Singapore', 'latitude' => 1.37, 'longitude' => 103.8],
|
||||
'SB' => ['name' => 'Solomon Islands', 'latitude' => -8.0, 'longitude' => 159.0],
|
||||
'SL' => ['name' => 'Sierra Leone', 'latitude' => 8.5, 'longitude' => -11.5],
|
||||
'SV' => ['name' => 'El Salvador', 'latitude' => 13.83, 'longitude' => -88.92],
|
||||
'SM' => ['name' => 'San Marino', 'latitude' => 43.77, 'longitude' => 12.42],
|
||||
'SO' => ['name' => 'Somalia', 'latitude' => 10.0, 'longitude' => 49.0],
|
||||
'RS' => ['name' => 'Serbia', 'latitude' => 44.0, 'longitude' => 21.0],
|
||||
'SS' => ['name' => 'South Sudan', 'latitude' => 8.0, 'longitude' => 30.0],
|
||||
'ST' => ['name' => 'São Tomé and Príncipe', 'latitude' => 1.0, 'longitude' => 7.0],
|
||||
'SR' => ['name' => 'Suriname', 'latitude' => 4.0, 'longitude' => -56.0],
|
||||
'SK' => ['name' => 'Slovakia', 'latitude' => 48.67, 'longitude' => 19.5],
|
||||
'SI' => ['name' => 'Slovenia', 'latitude' => 46.0, 'longitude' => 15.0],
|
||||
'SE' => ['name' => 'Sweden', 'latitude' => 62.0, 'longitude' => 15.0],
|
||||
'SZ' => ['name' => 'Eswatini', 'latitude' => -26.5, 'longitude' => 31.5],
|
||||
'SC' => ['name' => 'Seychelles', 'latitude' => -4.58, 'longitude' => 55.67],
|
||||
'SY' => ['name' => 'Syria', 'latitude' => 35.0, 'longitude' => 38.0],
|
||||
'TD' => ['name' => 'Chad', 'latitude' => 15.0, 'longitude' => 19.0],
|
||||
'TG' => ['name' => 'Togo', 'latitude' => 8.0, 'longitude' => 1.17],
|
||||
'TH' => ['name' => 'Thailand', 'latitude' => 15.0, 'longitude' => 100.0],
|
||||
'TJ' => ['name' => 'Tajikistan', 'latitude' => 39.0, 'longitude' => 71.0],
|
||||
'TM' => ['name' => 'Turkmenistan', 'latitude' => 40.0, 'longitude' => 60.0],
|
||||
'TL' => ['name' => 'Timor-Leste', 'latitude' => -8.83, 'longitude' => 125.92],
|
||||
'TO' => ['name' => 'Tonga', 'latitude' => -20.0, 'longitude' => -175.0],
|
||||
'TT' => ['name' => 'Trinidad and Tobago', 'latitude' => 11.0, 'longitude' => -61.0],
|
||||
'TN' => ['name' => 'Tunisia', 'latitude' => 34.0, 'longitude' => 9.0],
|
||||
'TR' => ['name' => 'Turkey', 'latitude' => 39.0, 'longitude' => 35.0],
|
||||
'TV' => ['name' => 'Tuvalu', 'latitude' => -8.0, 'longitude' => 178.0],
|
||||
'TZ' => ['name' => 'Tanzania', 'latitude' => -6.0, 'longitude' => 35.0],
|
||||
'TW' => ['name' => 'Taiwan', 'latitude' => 23.5, 'longitude' => 121.0],
|
||||
'UG' => ['name' => 'Uganda', 'latitude' => 1.0, 'longitude' => 32.0],
|
||||
'UA' => ['name' => 'Ukraine', 'latitude' => 49.0, 'longitude' => 32.0],
|
||||
'UY' => ['name' => 'Uruguay', 'latitude' => -33.0, 'longitude' => -56.0],
|
||||
'US' => ['name' => 'United States', 'latitude' => 38.0, 'longitude' => -97.0],
|
||||
'UZ' => ['name' => 'Uzbekistan', 'latitude' => 41.0, 'longitude' => 64.0],
|
||||
'VA' => ['name' => 'Vatican City', 'latitude' => 41.9, 'longitude' => 12.45],
|
||||
'VC' => ['name' => 'Saint Vincent and the Grenadines', 'latitude' => 13.25, 'longitude' => -61.2],
|
||||
'VE' => ['name' => 'Venezuela', 'latitude' => 8.0, 'longitude' => -66.0],
|
||||
'VN' => ['name' => 'Vietnam', 'latitude' => 16.0, 'longitude' => 106.0],
|
||||
'VU' => ['name' => 'Vanuatu', 'latitude' => -16.0, 'longitude' => 167.0],
|
||||
'WS' => ['name' => 'Samoa', 'latitude' => -13.58, 'longitude' => -172.33],
|
||||
'YE' => ['name' => 'Yemen', 'latitude' => 15.0, 'longitude' => 48.0],
|
||||
'ZA' => ['name' => 'South Africa', 'latitude' => -29.0, 'longitude' => 24.0],
|
||||
'ZM' => ['name' => 'Zambia', 'latitude' => -15.0, 'longitude' => 30.0],
|
||||
'ZW' => ['name' => 'Zimbabwe', 'latitude' => -20.0, 'longitude' => 30.0],
|
||||
];
|
||||
|
||||
@@ -142,6 +142,16 @@ return [
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'figma' => [
|
||||
'name' => 'Figma',
|
||||
'developers' => 'https://www.figma.com/developers/api#oauth2',
|
||||
'icon' => 'icon-figma',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'github' => [
|
||||
'name' => 'GitHub',
|
||||
'developers' => 'https://developer.github.com/',
|
||||
|
||||
+16
-16
@@ -11,7 +11,7 @@ return [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '16.0.2',
|
||||
'version' => '17.0.2',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-web',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -59,7 +59,7 @@ return [
|
||||
[
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'version' => '13.0.0',
|
||||
'version' => '15.0.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-flutter',
|
||||
'package' => 'https://pub.dev/packages/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -77,7 +77,7 @@ return [
|
||||
[
|
||||
'key' => 'apple',
|
||||
'name' => 'Apple',
|
||||
'version' => '7.0.0',
|
||||
'version' => '9.0.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'enabled' => true,
|
||||
@@ -112,7 +112,7 @@ return [
|
||||
[
|
||||
'key' => 'android',
|
||||
'name' => 'Android',
|
||||
'version' => '6.0.0',
|
||||
'version' => '7.0.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-android',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
|
||||
'enabled' => true,
|
||||
@@ -134,7 +134,7 @@ return [
|
||||
[
|
||||
'key' => 'react-native',
|
||||
'name' => 'React Native',
|
||||
'version' => '0.5.0',
|
||||
'version' => '0.7.3',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-react-native',
|
||||
'package' => 'https://npmjs.com/package/react-native-appwrite',
|
||||
'enabled' => true,
|
||||
@@ -217,7 +217,7 @@ return [
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '6.0.0',
|
||||
'version' => '6.2.3',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
@@ -245,7 +245,7 @@ return [
|
||||
[
|
||||
'key' => 'nodejs',
|
||||
'name' => 'Node.js',
|
||||
'version' => '14.1.0',
|
||||
'version' => '16.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-node',
|
||||
'package' => 'https://www.npmjs.com/package/node-appwrite',
|
||||
'enabled' => true,
|
||||
@@ -263,7 +263,7 @@ return [
|
||||
[
|
||||
'key' => 'deno',
|
||||
'name' => 'Deno',
|
||||
'version' => '12.1.0',
|
||||
'version' => '14.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-deno',
|
||||
'package' => 'https://deno.land/x/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -281,7 +281,7 @@ return [
|
||||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '12.1.0',
|
||||
'version' => '14.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -299,7 +299,7 @@ return [
|
||||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '6.1.0',
|
||||
'version' => '10.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
@@ -317,7 +317,7 @@ return [
|
||||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '12.1.1',
|
||||
'version' => '15.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -335,7 +335,7 @@ return [
|
||||
[
|
||||
'key' => 'go',
|
||||
'name' => 'Go',
|
||||
'version' => '0.2.0',
|
||||
'version' => '0.5.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'enabled' => true,
|
||||
@@ -353,7 +353,7 @@ return [
|
||||
[
|
||||
'key' => 'dotnet',
|
||||
'name' => '.NET',
|
||||
'version' => '0.10.1',
|
||||
'version' => '0.12.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
|
||||
'package' => 'https://www.nuget.org/packages/Appwrite',
|
||||
'enabled' => true,
|
||||
@@ -371,7 +371,7 @@ return [
|
||||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '12.1.0',
|
||||
'version' => '15.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_appwrite',
|
||||
'enabled' => true,
|
||||
@@ -389,7 +389,7 @@ return [
|
||||
[
|
||||
'key' => 'kotlin',
|
||||
'name' => 'Kotlin',
|
||||
'version' => '6.1.0',
|
||||
'version' => '8.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
|
||||
'enabled' => true,
|
||||
@@ -411,7 +411,7 @@ return [
|
||||
[
|
||||
'key' => 'swift',
|
||||
'name' => 'Swift',
|
||||
'version' => '6.1.0',
|
||||
'version' => '9.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'enabled' => true,
|
||||
|
||||
+1
-65
@@ -3,72 +3,8 @@
|
||||
return [
|
||||
'default' => [
|
||||
'$id' => 'default',
|
||||
'name' => 'Frankfurt',
|
||||
'name' => 'default',
|
||||
'disabled' => false,
|
||||
'flag' => 'de',
|
||||
'default' => true,
|
||||
],
|
||||
'fra' => [
|
||||
'$id' => 'fra',
|
||||
'name' => 'Frankfurt',
|
||||
'disabled' => false,
|
||||
'flag' => 'de',
|
||||
'default' => true,
|
||||
],
|
||||
'nyc' => [
|
||||
'$id' => 'nyc',
|
||||
'name' => 'New York',
|
||||
'disabled' => true,
|
||||
'flag' => 'us',
|
||||
'default' => true,
|
||||
],
|
||||
'sfo' => [
|
||||
'$id' => 'sfo',
|
||||
'name' => 'San Francisco',
|
||||
'disabled' => true,
|
||||
'flag' => 'us',
|
||||
'default' => true,
|
||||
],
|
||||
'blr' => [
|
||||
'$id' => 'blr',
|
||||
'name' => 'Bengaluru',
|
||||
'disabled' => true,
|
||||
'flag' => 'in',
|
||||
'default' => true,
|
||||
],
|
||||
'lon' => [
|
||||
'$id' => 'lon',
|
||||
'name' => 'London',
|
||||
'disabled' => true,
|
||||
'flag' => 'gb',
|
||||
'default' => true,
|
||||
],
|
||||
'ams' => [
|
||||
'$id' => 'ams',
|
||||
'name' => 'Amsterdam',
|
||||
'disabled' => true,
|
||||
'flag' => 'nl',
|
||||
'default' => true,
|
||||
],
|
||||
'sgp' => [
|
||||
'$id' => 'sgp',
|
||||
'name' => 'Singapore',
|
||||
'disabled' => true,
|
||||
'flag' => 'sg',
|
||||
'default' => true,
|
||||
],
|
||||
'tor' => [
|
||||
'$id' => 'tor',
|
||||
'name' => 'Toronto',
|
||||
'disabled' => true,
|
||||
'flag' => 'ca',
|
||||
'default' => true,
|
||||
],
|
||||
'syd' => [
|
||||
'$id' => 'syd',
|
||||
'name' => 'Sydney',
|
||||
'disabled' => true,
|
||||
'flag' => 'au',
|
||||
'default' => true,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -26,6 +26,7 @@ $member = [
|
||||
'subscribers.write',
|
||||
'subscribers.read',
|
||||
'assistant.read',
|
||||
'rules.read'
|
||||
];
|
||||
|
||||
$admins = [
|
||||
@@ -58,6 +59,10 @@ $admins = [
|
||||
'health.read',
|
||||
'functions.read',
|
||||
'functions.write',
|
||||
'sites.read',
|
||||
'sites.write',
|
||||
'log.read',
|
||||
'log.write',
|
||||
'execution.read',
|
||||
'execution.write',
|
||||
'rules.read',
|
||||
@@ -76,6 +81,8 @@ $admins = [
|
||||
'topics.read',
|
||||
'subscribers.write',
|
||||
'subscribers.read',
|
||||
'tokens.read',
|
||||
'tokens.write',
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
@@ -6,4 +6,4 @@
|
||||
|
||||
use Appwrite\Runtimes\Runtimes;
|
||||
|
||||
return (new Runtimes('v4'))->getAll();
|
||||
return (new Runtimes('v5'))->getAll();
|
||||
|
||||
@@ -64,6 +64,18 @@ return [ // List of publicly visible scopes
|
||||
'functions.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s functions and code deployments',
|
||||
],
|
||||
'sites.read' => [
|
||||
'description' => 'Access to read your project\'s sites and deployments',
|
||||
],
|
||||
'sites.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s sites and deployments',
|
||||
],
|
||||
'log.read' => [
|
||||
'description' => 'Access to read your site\'s logs',
|
||||
],
|
||||
'log.write' => [
|
||||
'description' => 'Access to update, and delete your site\'s logs',
|
||||
],
|
||||
'execution.read' => [
|
||||
'description' => 'Access to read your project\'s execution logs',
|
||||
],
|
||||
@@ -130,4 +142,10 @@ return [ // List of publicly visible scopes
|
||||
'assistant.read' => [
|
||||
'description' => 'Access to read the Assistant service',
|
||||
],
|
||||
'tokens.read' => [
|
||||
'description' => 'Access to read your project\'s tokens',
|
||||
],
|
||||
'tokens.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s tokens',
|
||||
],
|
||||
];
|
||||
|
||||
+14
-1
@@ -173,12 +173,25 @@ return [
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
'sites' => [
|
||||
'key' => 'sites',
|
||||
'name' => 'Sites',
|
||||
'subtitle' => 'The Sites Service allows you view, create and manage your web applications.',
|
||||
'description' => '/docs/services/sites.md',
|
||||
'controller' => '', // Uses modules
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/sites',
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/sites.png',
|
||||
],
|
||||
'functions' => [
|
||||
'key' => 'functions',
|
||||
'name' => 'Functions',
|
||||
'subtitle' => 'The Functions Service allows you view, create and manage your Cloud Functions.',
|
||||
'description' => '/docs/services/functions.md',
|
||||
'controller' => 'api/functions.php',
|
||||
'controller' => '', // Uses modules
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/functions',
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Functions\Specification;
|
||||
use Appwrite\Platform\Modules\Compute\Specification;
|
||||
|
||||
return [
|
||||
Specification::S_05VCPU_512MB => [
|
||||
'slug' => Specification::S_05VCPU_512MB,
|
||||
'memory' => 512,
|
||||
'cpus' => 1 // TODO: revert this, it's a temporary change to test function performance.
|
||||
'cpus' => 0.5
|
||||
],
|
||||
Specification::S_1VCPU_512MB => [
|
||||
'slug' => Specification::S_1VCPU_512MB,
|
||||
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 it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because 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 it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,11 @@
|
||||
<?php
|
||||
|
||||
return [ // Accepted inputs files
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
return [
|
||||
// Accepted inputs files
|
||||
"jpg" => "image/jpeg",
|
||||
"jpeg" => "image/jpeg",
|
||||
"gif" => "image/gif",
|
||||
"png" => "image/png",
|
||||
"heic" => "image/heic",
|
||||
"webp" => "image/webp",
|
||||
];
|
||||
|
||||
@@ -1,70 +1,71 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'image/jpeg',
|
||||
'image/jpeg',
|
||||
'image/gif',
|
||||
'image/png',
|
||||
'image/webp',
|
||||
// 'image/heic',
|
||||
'image/avif',
|
||||
"image/jpeg",
|
||||
"image/jpeg",
|
||||
"image/gif",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/heic",
|
||||
"image/heic-sequence",
|
||||
"image/avif",
|
||||
|
||||
// Video Files
|
||||
'video/mp4',
|
||||
'video/x-flv',
|
||||
'video/webm',
|
||||
'application/x-mpegURL',
|
||||
'video/MP2T',
|
||||
'video/3gpp',
|
||||
'video/quicktime',
|
||||
'video/x-msvideo',
|
||||
'video/x-ms-wmv',
|
||||
"video/mp4",
|
||||
"video/x-flv",
|
||||
"video/webm",
|
||||
"application/x-mpegURL",
|
||||
"video/MP2T",
|
||||
"video/3gpp",
|
||||
"video/quicktime",
|
||||
"video/x-msvideo",
|
||||
"video/x-ms-wmv",
|
||||
|
||||
// Audio Files
|
||||
|
||||
'audio/basic', // au snd RFC 2046
|
||||
'auido/L24', // Linear PCM RFC 3190
|
||||
'audio/mid', // mid rmi
|
||||
'audio/mpeg', // mp3 RFC 3003
|
||||
'audio/mp4', // mp4 audio
|
||||
'audio/x-aiff', // aif aifc aiff
|
||||
'audio/x-mpegurl', // m3u
|
||||
'audio/vnd.rn-realaudio', // ra ram
|
||||
'audio/ogg', // Ogg Vorbis RFC 5334
|
||||
'audio/vorbis', // Vorbis RFC 5215
|
||||
'audio/vnd.wav', // wav RFC 2361
|
||||
'audio/x-wav', // php reads .wav as this - https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
|
||||
'audio/aac', //AAC audio
|
||||
'audio/x-hx-aac-adts', // AAC audio
|
||||
"audio/basic", // au snd RFC 2046
|
||||
"auido/L24", // Linear PCM RFC 3190
|
||||
"audio/mid", // mid rmi
|
||||
"audio/mpeg", // mp3 RFC 3003
|
||||
"audio/mp4", // mp4 audio
|
||||
"audio/x-aiff", // aif aifc aiff
|
||||
"audio/x-mpegurl", // m3u
|
||||
"audio/vnd.rn-realaudio", // ra ram
|
||||
"audio/ogg", // Ogg Vorbis RFC 5334
|
||||
"audio/vorbis", // Vorbis RFC 5215
|
||||
"audio/vnd.wav", // wav RFC 2361
|
||||
"audio/x-wav", // php reads .wav as this - https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
|
||||
"audio/aac", //AAC audio
|
||||
"audio/x-hx-aac-adts", // AAC audio
|
||||
|
||||
// Microsoft Word
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
||||
'application/vnd.ms-word.document.macroEnabled.12',
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
||||
"application/vnd.ms-word.document.macroEnabled.12",
|
||||
|
||||
// Microsoft Excel
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
||||
'application/vnd.ms-excel.sheet.macroEnabled.12',
|
||||
'application/vnd.ms-excel.template.macroEnabled.12',
|
||||
'application/vnd.ms-excel.addin.macroEnabled.12',
|
||||
'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
||||
"application/vnd.ms-excel.sheet.macroEnabled.12",
|
||||
"application/vnd.ms-excel.template.macroEnabled.12",
|
||||
"application/vnd.ms-excel.addin.macroEnabled.12",
|
||||
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
||||
|
||||
// Microsoft Power Point
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.template',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
|
||||
'application/vnd.ms-powerpoint.addin.macroEnabled.12',
|
||||
'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
|
||||
'application/vnd.ms-powerpoint.template.macroEnabled.12',
|
||||
'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.template",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
||||
"application/vnd.ms-powerpoint.addin.macroEnabled.12",
|
||||
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
||||
"application/vnd.ms-powerpoint.template.macroEnabled.12",
|
||||
"application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
|
||||
|
||||
// Microsoft Access
|
||||
'application/vnd.ms-access',
|
||||
"application/vnd.ms-access",
|
||||
|
||||
// Adobe PDF
|
||||
'application/pdf',
|
||||
"application/pdf",
|
||||
];
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [ // Accepted outputs files
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
'webp' => 'image/webp',
|
||||
// 'heic' => 'image/heic',
|
||||
// 'heics' => 'image/heic',
|
||||
'avif' => 'image/avif'
|
||||
return [
|
||||
// Accepted outputs files
|
||||
"jpg" => "image/jpeg",
|
||||
"jpeg" => "image/jpeg",
|
||||
"gif" => "image/gif",
|
||||
"png" => "image/png",
|
||||
"webp" => "image/webp",
|
||||
"heic" => "image/heic",
|
||||
"avif" => "image/avif",
|
||||
];
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
// TODO: Remove, replace with runtimes.php directly
|
||||
// Used in function templates and site frameworks
|
||||
|
||||
return [
|
||||
'NODE' => [
|
||||
'name' => 'node',
|
||||
'versions' => ['22', '21.0', '20.0', '19.0', '18.0', '16.0', '14.5']
|
||||
],
|
||||
'PYTHON' => [
|
||||
'name' => 'python',
|
||||
'versions' => ['3.12', '3.11', '3.10', '3.9', '3.8']
|
||||
],
|
||||
'DART' => [
|
||||
'name' => 'dart',
|
||||
'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
|
||||
],
|
||||
'GO' => [
|
||||
'name' => 'go',
|
||||
'versions' => ['1.23']
|
||||
],
|
||||
'PHP' => [
|
||||
'name' => 'php',
|
||||
'versions' => ['8.3', '8.2', '8.1', '8.0']
|
||||
],
|
||||
'DENO' => [
|
||||
'name' => 'deno',
|
||||
'versions' => ['2.0', '1.46', '1.40', '1.35', '1.24', '1.21']
|
||||
],
|
||||
'BUN' => [
|
||||
'name' => 'bun',
|
||||
'versions' => ['1.1', '1.0']
|
||||
],
|
||||
'RUBY' => [
|
||||
'name' => 'ruby',
|
||||
'versions' => ['3.3', '3.2', '3.1', '3.0']
|
||||
],
|
||||
'FLUTTER' => [
|
||||
'name' => 'flutter',
|
||||
'versions' => ['3.24']
|
||||
],
|
||||
];
|
||||
@@ -1,39 +1,8 @@
|
||||
<?php
|
||||
|
||||
const TEMPLATE_RUNTIMES = [
|
||||
'NODE' => [
|
||||
'name' => 'node',
|
||||
'versions' => ['22', '21.0', '20.0', '19.0', '18.0', '16.0', '14.5']
|
||||
],
|
||||
'PYTHON' => [
|
||||
'name' => 'python',
|
||||
'versions' => ['3.12', '3.11', '3.10', '3.9', '3.8']
|
||||
],
|
||||
'DART' => [
|
||||
'name' => 'dart',
|
||||
'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
|
||||
],
|
||||
'GO' => [
|
||||
'name' => 'go',
|
||||
'versions' => ['1.23']
|
||||
],
|
||||
'PHP' => [
|
||||
'name' => 'php',
|
||||
'versions' => ['8.3', '8.2', '8.1', '8.0']
|
||||
],
|
||||
'DENO' => [
|
||||
'name' => 'deno',
|
||||
'versions' => ['2.0', '1.46', '1.40', '1.35', '1.24', '1.21']
|
||||
],
|
||||
'BUN' => [
|
||||
'name' => 'bun',
|
||||
'versions' => ['1.1', '1.0']
|
||||
],
|
||||
'RUBY' => [
|
||||
'name' => 'ruby',
|
||||
'versions' => ['3.3', '3.2', '3.1', '3.0']
|
||||
],
|
||||
];
|
||||
use Utopia\Config\Config;
|
||||
|
||||
$templateRuntimes = Config::getParam('template-runtimes');
|
||||
|
||||
function getRuntimes($runtime, $commands, $entrypoint, $providerRootDirectory, $versionsDenyList = [])
|
||||
{
|
||||
@@ -54,6 +23,7 @@ return [
|
||||
'icon' => 'icon-lightning-bolt',
|
||||
'id' => 'starter',
|
||||
'name' => 'Starter function',
|
||||
'score' => 5,
|
||||
'tagline' =>
|
||||
'A simple function to get started. Edit this function to explore endless possibilities with Appwrite Functions.',
|
||||
'permissions' => ['any'],
|
||||
@@ -62,24 +32,24 @@ return [
|
||||
'timeout' => 15,
|
||||
'useCases' => ['starter'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(TEMPLATE_RUNTIMES['NODE'], 'npm install', 'src/main.js', 'node/starter'),
|
||||
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/starter'),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PYTHON'],
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/starter'
|
||||
),
|
||||
...getRuntimes(TEMPLATE_RUNTIMES['DART'], 'dart pub get', 'lib/main.dart', 'dart/starter'),
|
||||
...getRuntimes(TEMPLATE_RUNTIMES['GO'], '', 'main.go', 'go/starter'),
|
||||
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/starter'),
|
||||
...getRuntimes($templateRuntimes['GO'], '', 'main.go', 'go/starter'),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PHP'],
|
||||
$templateRuntimes['PHP'],
|
||||
'composer install',
|
||||
'src/index.php',
|
||||
'php/starter'
|
||||
),
|
||||
...getRuntimes(TEMPLATE_RUNTIMES['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter'),
|
||||
...getRuntimes(TEMPLATE_RUNTIMES['BUN'], 'bun install', 'src/main.ts', 'bun/starter'),
|
||||
...getRuntimes(TEMPLATE_RUNTIMES['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter'),
|
||||
...getRuntimes($templateRuntimes['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter'),
|
||||
...getRuntimes($templateRuntimes['BUN'], 'bun install', 'src/main.ts', 'bun/starter'),
|
||||
...getRuntimes($templateRuntimes['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter'),
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/starter">file</a>.',
|
||||
'vcsProvider' => 'github',
|
||||
@@ -93,6 +63,7 @@ return [
|
||||
'icon' => 'icon-upstash',
|
||||
'id' => 'query-upstash-vector',
|
||||
'name' => 'Query Upstash Vector',
|
||||
'score' => 4,
|
||||
'tagline' => 'Vector database that stores text embeddings and context retrieval for LLMs',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -101,7 +72,7 @@ return [
|
||||
'useCases' => ['databases'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/query-upstash-vector'
|
||||
@@ -137,6 +108,7 @@ return [
|
||||
'icon' => 'icon-redis',
|
||||
'id' => 'query-redis-labs',
|
||||
'name' => 'Query Redis Labs',
|
||||
'score' => 4,
|
||||
'tagline' => 'Key-value database with advanced caching capabilities.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -145,7 +117,7 @@ return [
|
||||
'useCases' => ['databases'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/query-redis-labs'
|
||||
@@ -180,6 +152,7 @@ return [
|
||||
'icon' => 'icon-neo4j',
|
||||
'id' => 'query-neo4j-auradb',
|
||||
'name' => 'Query Neo4j AuraDB',
|
||||
'score' => 4,
|
||||
'tagline' => 'Graph database with focus on relations between data.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -188,7 +161,7 @@ return [
|
||||
'useCases' => ['databases'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/query-neo4j-auradb'
|
||||
@@ -231,6 +204,7 @@ return [
|
||||
'icon' => 'icon-mongodb',
|
||||
'id' => 'query-mongo-atlas',
|
||||
'name' => 'Query MongoDB Atlas',
|
||||
'score' => 4,
|
||||
'tagline' =>
|
||||
'Realtime NoSQL document database with geospecial, graph, search, and vector suport.',
|
||||
'permissions' => ['any'],
|
||||
@@ -240,7 +214,7 @@ return [
|
||||
'useCases' => ['databases'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/query-mongo-atlas'
|
||||
@@ -268,6 +242,7 @@ return [
|
||||
'icon' => 'icon-neon',
|
||||
'id' => 'query-neon-postgres',
|
||||
'name' => 'Query Neon Postgres',
|
||||
'score' => 4,
|
||||
'tagline' =>
|
||||
'Reliable SQL database with replication, point-in-time recovery, and pgvector support.',
|
||||
'permissions' => ['any'],
|
||||
@@ -277,7 +252,7 @@ return [
|
||||
'useCases' => ['databases'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/query-neon-postgres'
|
||||
@@ -336,6 +311,7 @@ return [
|
||||
'icon' => 'icon-open-ai',
|
||||
'id' => 'prompt-chatgpt',
|
||||
'name' => 'Prompt ChatGPT',
|
||||
'score' => 7,
|
||||
'tagline' => 'Ask questions and let OpenAI GPT-3.5-turbo answer.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -344,25 +320,25 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/prompt-chatgpt'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PYTHON'],
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/prompt_chatgpt'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PHP'],
|
||||
$templateRuntimes['PHP'],
|
||||
'composer install',
|
||||
'src/index.php',
|
||||
'php/prompt-chatgpt'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['DART'],
|
||||
$templateRuntimes['DART'],
|
||||
'dart pub get',
|
||||
'lib/main.dart',
|
||||
'dart/prompt_chatgpt'
|
||||
@@ -397,6 +373,7 @@ return [
|
||||
'icon' => 'icon-discord',
|
||||
'id' => 'discord-command-bot',
|
||||
'name' => 'Discord Command Bot',
|
||||
'score' => 6,
|
||||
'tagline' => 'Simple command using Discord Interactions.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -405,19 +382,19 @@ return [
|
||||
'useCases' => ['messaging'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/discord-command-bot'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PYTHON'],
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt && python src/setup.py',
|
||||
'src/main.py',
|
||||
'python/discord_command_bot'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['GO'],
|
||||
$templateRuntimes['GO'],
|
||||
'',
|
||||
'main.go',
|
||||
'go/discord-command-bot'
|
||||
@@ -460,6 +437,7 @@ return [
|
||||
'icon' => 'icon-perspective-api',
|
||||
'id' => 'analyze-with-perspectiveapi',
|
||||
'name' => 'Analyze with PerspectiveAPI',
|
||||
'score' => 5,
|
||||
'tagline' => 'Automate moderation by getting toxicity of messages.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -468,7 +446,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/analyze-with-perspectiveapi'
|
||||
@@ -495,6 +473,7 @@ return [
|
||||
'icon' => 'icon-pangea',
|
||||
'id' => 'censor-with-redact',
|
||||
'name' => 'Censor with Redact',
|
||||
'score' => 5,
|
||||
'tagline' =>
|
||||
'Censor sensitive information from a provided text string using Redact API by Pangea.',
|
||||
'permissions' => ['any'],
|
||||
@@ -504,19 +483,19 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/censor-with-redact'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PYTHON'],
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/censor_with_redact'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['DART'],
|
||||
$templateRuntimes['DART'],
|
||||
'dart pub get',
|
||||
'lib/main.dart',
|
||||
'dart/censor_with_redact'
|
||||
@@ -543,6 +522,7 @@ return [
|
||||
'icon' => 'icon-document',
|
||||
'id' => 'generate-pdf',
|
||||
'name' => 'Generate PDF',
|
||||
'score' => 7,
|
||||
'tagline' => 'Document containing sample invoice in PDF format.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -550,7 +530,7 @@ return [
|
||||
'timeout' => 15,
|
||||
'useCases' => ['utilities'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(TEMPLATE_RUNTIMES['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf')
|
||||
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf')
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/generate-pdf">file</a>.',
|
||||
'vcsProvider' => 'github',
|
||||
@@ -564,6 +544,7 @@ return [
|
||||
'icon' => 'icon-github',
|
||||
'id' => 'github-issue-bot',
|
||||
'name' => 'GitHub issue bot',
|
||||
'score' => 4,
|
||||
'tagline' =>
|
||||
'Automate the process of responding to newly opened issues in a GitHub repository.',
|
||||
'permissions' => ['any'],
|
||||
@@ -573,7 +554,7 @@ return [
|
||||
'useCases' => ['dev-tools'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/github-issue-bot'
|
||||
@@ -608,6 +589,7 @@ return [
|
||||
'icon' => 'icon-bookmark',
|
||||
'id' => 'url-shortener',
|
||||
'name' => 'URL shortener',
|
||||
'score' => 3,
|
||||
'tagline' => 'Generate URL with short ID and redirect to the original URL when visited.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -616,7 +598,7 @@ return [
|
||||
'useCases' => ['utilities'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/url-shortener'
|
||||
@@ -659,6 +641,7 @@ return [
|
||||
'icon' => 'icon-algolia',
|
||||
'id' => 'sync-with-algolia',
|
||||
'name' => 'Sync with Algolia',
|
||||
'score' => 4,
|
||||
'tagline' => 'Intuitive search bar for any data in Appwrite Databases.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -667,19 +650,19 @@ return [
|
||||
'useCases' => ['databases'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/sync-with-algolia'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PYTHON'],
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/sync_with_algolia'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PHP'],
|
||||
$templateRuntimes['PHP'],
|
||||
'composer install',
|
||||
'src/index.php',
|
||||
'php/sync-with-algolia'
|
||||
@@ -740,6 +723,7 @@ return [
|
||||
'icon' => 'icon-meilisearch',
|
||||
'id' => 'sync-with-meilisearch',
|
||||
'name' => 'Sync with Meilisearch',
|
||||
'score' => 4,
|
||||
'tagline' => 'Intuitive search bar for any data in Appwrite Databases.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -748,31 +732,31 @@ return [
|
||||
'useCases' => ['databases'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/sync-with-meilisearch'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PYTHON'],
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/sync-with-meilisearch'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PHP'],
|
||||
$templateRuntimes['PHP'],
|
||||
'composer install',
|
||||
'src/index.php',
|
||||
'php/sync-with-meilisearch'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['BUN'],
|
||||
$templateRuntimes['BUN'],
|
||||
'bun install',
|
||||
'src/main.ts',
|
||||
'bun/sync-with-meilisearch'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['RUBY'],
|
||||
$templateRuntimes['RUBY'],
|
||||
'bundle install',
|
||||
'lib/main.rb',
|
||||
'ruby/sync-with-meilisearch'
|
||||
@@ -833,6 +817,7 @@ return [
|
||||
'icon' => 'icon-vonage',
|
||||
'id' => 'whatsapp-with-vonage',
|
||||
'name' => 'WhatsApp with Vonage',
|
||||
'score' => 6,
|
||||
'tagline' => 'Simple bot to answer WhatsApp messages.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -841,37 +826,37 @@ return [
|
||||
'useCases' => ['messaging'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/whatsapp-with-vonage'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PYTHON'],
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/whatsapp_with_vonage'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['DART'],
|
||||
$templateRuntimes['DART'],
|
||||
'dart pub get',
|
||||
'lib/main.dart',
|
||||
'dart/whatsapp-with-vonage'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PHP'],
|
||||
$templateRuntimes['PHP'],
|
||||
'composer install',
|
||||
'src/index.php',
|
||||
'php/whatsapp-with-vonage'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['BUN'],
|
||||
$templateRuntimes['BUN'],
|
||||
'bun install',
|
||||
'src/main.ts',
|
||||
'bun/whatsapp-with-vonage'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['RUBY'],
|
||||
$templateRuntimes['RUBY'],
|
||||
'bundle install',
|
||||
'lib/main.rb',
|
||||
'ruby/whatsapp-with-vonage'
|
||||
@@ -919,6 +904,7 @@ return [
|
||||
'icon' => 'icon-bell',
|
||||
'id' => 'push-notification-with-fcm',
|
||||
'name' => 'Push notification with FCM',
|
||||
'score' => 4,
|
||||
'tagline' => 'Send push notifications to your users using Firebase Cloud Messaging (FCM).',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -927,7 +913,7 @@ return [
|
||||
'useCases' => ['messaging'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/push-notification-with-fcm'
|
||||
@@ -975,6 +961,7 @@ return [
|
||||
'icon' => 'icon-mail',
|
||||
'id' => 'email-contact-form',
|
||||
'name' => 'Email contact form',
|
||||
'score' => 7,
|
||||
'tagline' => 'Sends an email with the contents of a HTML form.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -983,19 +970,19 @@ return [
|
||||
'useCases' => ['utilities'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/email-contact-form'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PYTHON'],
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/email_contact_form'
|
||||
),
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['PHP'],
|
||||
$templateRuntimes['PHP'],
|
||||
'composer install',
|
||||
'src/index.php',
|
||||
'php/email-contact-form'
|
||||
@@ -1058,6 +1045,7 @@ return [
|
||||
'icon' => 'icon-stripe',
|
||||
'id' => 'subscriptions-with-stripe',
|
||||
'name' => 'Subscriptions with Stripe',
|
||||
'score' => 6,
|
||||
'tagline' => 'Receive recurring card payments and grant subscribers extra permissions.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1066,7 +1054,7 @@ return [
|
||||
'useCases' => ['utilities'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/subscriptions-with-stripe'
|
||||
@@ -1099,6 +1087,7 @@ return [
|
||||
'icon' => 'icon-stripe',
|
||||
'id' => 'payments-with-stripe',
|
||||
'name' => 'Payments with Stripe',
|
||||
'score' => 8,
|
||||
'tagline' => 'Receive card payments and store paid orders.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1107,7 +1096,7 @@ return [
|
||||
'useCases' => ['utilities'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/payments-with-stripe'
|
||||
@@ -1156,6 +1145,7 @@ return [
|
||||
'icon' => 'icon-chat',
|
||||
'id' => 'text-generation-with-huggingface',
|
||||
'name' => 'Text generation',
|
||||
'score' => 5,
|
||||
'tagline' => 'Generate text using the Hugging Face inference API.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1164,7 +1154,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/text-generation-with-huggingface'
|
||||
@@ -1190,6 +1180,7 @@ return [
|
||||
'icon' => 'icon-translate',
|
||||
'id' => 'language-translation-with-huggingface',
|
||||
'name' => 'Language translation',
|
||||
'score' => 5,
|
||||
'tagline' => 'Translate text using the Hugging Face inference API.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1198,7 +1189,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/language-translation-with-huggingface'
|
||||
@@ -1224,6 +1215,7 @@ return [
|
||||
'icon' => 'icon-eye',
|
||||
'id' => 'image-classification-with-huggingface',
|
||||
'name' => 'Image classification',
|
||||
'score' => 5,
|
||||
'tagline' => 'Classify images using the Hugging Face inference API.',
|
||||
'permissions' => ['any'],
|
||||
'events' => ['buckets.*.files.*.create'],
|
||||
@@ -1232,7 +1224,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/image-classification-with-huggingface'
|
||||
@@ -1282,6 +1274,7 @@ return [
|
||||
'icon' => 'icon-eye',
|
||||
'id' => 'object-detection-with-huggingface',
|
||||
'name' => 'Object detection',
|
||||
'score' => 5,
|
||||
'tagline' => 'Detect objects in images using the Hugging Face inference API.',
|
||||
'permissions' => ['any'],
|
||||
'events' => ['buckets.*.files.*.create'],
|
||||
@@ -1290,7 +1283,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/object-detection-with-huggingface'
|
||||
@@ -1340,6 +1333,7 @@ return [
|
||||
'icon' => 'icon-text',
|
||||
'id' => 'speech-recognition-with-huggingface',
|
||||
'name' => 'Speech recognition',
|
||||
'score' => 5,
|
||||
'tagline' => 'Transcribe audio to text using the Hugging Face inference API.',
|
||||
'permissions' => ['any'],
|
||||
'events' => ['buckets.*.files.*.create'],
|
||||
@@ -1348,7 +1342,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/speech-recognition-with-huggingface'
|
||||
@@ -1398,6 +1392,7 @@ return [
|
||||
'icon' => 'icon-chat',
|
||||
'id' => 'text-to-speech-with-huggingface',
|
||||
'name' => 'Text to speech',
|
||||
'score' => 5,
|
||||
'tagline' => 'Convert text to speech using the Hugging Face inference API.',
|
||||
'permissions' => ['any'],
|
||||
'events' => ['databases.*.collections.*.documents.*.create'],
|
||||
@@ -1406,7 +1401,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/text-to-speech-with-huggingface'
|
||||
@@ -1456,6 +1451,7 @@ return [
|
||||
'icon' => 'icon-chip',
|
||||
'id' => 'generate-with-replicate',
|
||||
'name' => 'Generate with Replicate',
|
||||
'score' => 5,
|
||||
'tagline' => "Generate text, audio and images using Replicate's API.",
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1464,7 +1460,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/generate-with-replicate'
|
||||
@@ -1491,6 +1487,7 @@ return [
|
||||
'icon' => 'icon-chip',
|
||||
'id' => 'generate-with-together-ai',
|
||||
'name' => 'Generate with Together AI',
|
||||
'score' => 5,
|
||||
'tagline' => "Generate text and images using Together AI's API.",
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1499,7 +1496,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/generate-with-together-ai'
|
||||
@@ -1533,6 +1530,7 @@ return [
|
||||
'icon' => 'icon-chip',
|
||||
'id' => 'chat-with-perplexity-ai',
|
||||
'name' => 'Chat with Perplexity AI',
|
||||
'score' => 5,
|
||||
'tagline' => 'Create a chatbot using the Perplexity AI API.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1541,7 +1539,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/chat-with-perplexity-ai'
|
||||
@@ -1567,12 +1565,14 @@ return [
|
||||
'required' => false,
|
||||
'type' => 'number'
|
||||
]
|
||||
]
|
||||
],
|
||||
'scopes' => []
|
||||
],
|
||||
[
|
||||
'icon' => 'icon-chip',
|
||||
'id' => 'generate-with-replicate',
|
||||
'name' => 'Generate with Replicate',
|
||||
'score' => 5,
|
||||
'tagline' => "Generate text, audio and images using Replicate's API.",
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1581,7 +1581,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/generate-with-replicate'
|
||||
@@ -1608,6 +1608,7 @@ return [
|
||||
'icon' => 'icon-document-search',
|
||||
'id' => 'sync-with-pinecone',
|
||||
'name' => 'Sync with Pinecone',
|
||||
'score' => 4,
|
||||
'tagline' => "Sync your Appwrite database with Pinecone's vector database.",
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1616,7 +1617,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/sync-with-pinecone'
|
||||
@@ -1671,6 +1672,7 @@ return [
|
||||
'icon' => 'icon-chip',
|
||||
'id' => 'rag-with-langchain',
|
||||
'name' => 'RAG with LangChain',
|
||||
'score' => 6,
|
||||
'tagline' => 'Generate text using a LangChain RAG model',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1679,7 +1681,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/rag-with-langchain'
|
||||
@@ -1734,6 +1736,7 @@ return [
|
||||
'icon' => 'icon-chat',
|
||||
'id' => 'speak-with-elevenlabs',
|
||||
'name' => 'Speak with ElevenLabs',
|
||||
'score' => 5,
|
||||
'tagline' => 'Convert text to speech using the ElevenLabs API.',
|
||||
'permissions' => ['any'],
|
||||
'cron' => '',
|
||||
@@ -1742,7 +1745,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/speak-with-elevenlabs'
|
||||
@@ -1789,6 +1792,7 @@ return [
|
||||
'icon' => 'icon-chip',
|
||||
'id' => 'speak-with-lmnt',
|
||||
'name' => 'Speak with LMNT',
|
||||
'score' => 5,
|
||||
'tagline' => 'Convert text to speech using the LMNT API.',
|
||||
'permissions' => ['any'],
|
||||
'cron' => '',
|
||||
@@ -1797,7 +1801,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/speak-with-lmnt'
|
||||
@@ -1830,6 +1834,7 @@ return [
|
||||
'icon' => 'icon-chip',
|
||||
'id' => 'chat-with-anyscale',
|
||||
'name' => 'Chat with AnyScale',
|
||||
'score' => 5,
|
||||
'tagline' => 'Create a chatbot using the AnyScale API.',
|
||||
'permissions' => ['any'],
|
||||
'cron' => '',
|
||||
@@ -1838,7 +1843,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/chat-with-anyscale'
|
||||
@@ -1871,6 +1876,7 @@ return [
|
||||
'icon' => 'icon-music-note',
|
||||
'id' => 'music-generation-with-huggingface',
|
||||
'name' => 'Music generation',
|
||||
'score' => 4,
|
||||
'tagline' => 'Generate music from a text prompt using the Hugging Face inference API.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1879,7 +1885,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/music-generation-with-huggingface'
|
||||
@@ -1913,6 +1919,7 @@ return [
|
||||
'icon' => 'icon-chip',
|
||||
'id' => 'generate-with-fal-ai',
|
||||
'name' => 'Generate with fal.ai',
|
||||
'score' => 5,
|
||||
'tagline' => "Generate images using fal.ai's API.",
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1921,7 +1928,7 @@ return [
|
||||
'useCases' => ['ai'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/generate-with-fal-ai'
|
||||
@@ -1948,6 +1955,7 @@ return [
|
||||
'icon' => 'icon-currency-dollar',
|
||||
'id' => 'subscriptions-with-lemon-squeezy',
|
||||
'name' => 'Subscriptions with Lemon Squeezy',
|
||||
'score' => 6,
|
||||
'tagline' => 'Receive recurring card payments and grant subscribers extra permissions.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -1956,7 +1964,7 @@ return [
|
||||
'useCases' => ['utilities'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/subscriptions-with-lemon-squeezy'
|
||||
@@ -2003,6 +2011,7 @@ return [
|
||||
'icon' => 'icon-currency-dollar',
|
||||
'id' => 'payments-with-lemon-squeezy',
|
||||
'name' => 'Payments with Lemon Squeezy',
|
||||
'score' => 6,
|
||||
'tagline' => 'Receive card payments and store paid orders.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
@@ -2011,7 +2020,7 @@ return [
|
||||
'useCases' => ['utilities'],
|
||||
'runtimes' => [
|
||||
...getRuntimes(
|
||||
TEMPLATE_RUNTIMES['NODE'],
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/payments-with-lemon-squeezy'
|
||||
File diff suppressed because it is too large
Load Diff
+182
-14
@@ -45,7 +45,16 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS',
|
||||
'description' => 'Allows you to force HTTPS connection to function domains. This feature redirects any HTTP call to HTTPS and adds the \'Strict-Transport-Security\' header to all HTTP responses. By default, set to \'enabled\'. To disable, set to \'disabled\'. This feature will work only when your ports are set to default 80 and 443.',
|
||||
'description' => 'Deprecated since 1.7.0. Allows you to force HTTPS connection to function domains. This feature redirects any HTTP call to HTTPS and adds the \'Strict-Transport-Security\' header to all HTTP responses. By default, set to \'enabled\'. To disable, set to \'disabled\'. This feature will work only when your ports are set to default 80 and 443.',
|
||||
'introduction' => '',
|
||||
'default' => 'disabled',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_OPTIONS_ROUTER_FORCE_HTTPS',
|
||||
'description' => 'Allows you to force HTTPS connection to function and site domains. This feature redirects any HTTP call to HTTPS and adds the \'Strict-Transport-Security\' header to all HTTP responses. By default, set to \'enabled\'. To disable, set to \'disabled\'. This feature will work only when your ports are set to default 80 and 443.',
|
||||
'introduction' => '',
|
||||
'default' => 'disabled',
|
||||
'required' => false,
|
||||
@@ -79,6 +88,15 @@ return [
|
||||
'question' => 'Enter your Appwrite hostname',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_CUSTOM_DOMAIN_DENY_LIST',
|
||||
'description' => 'List of reserved or prohibited domains when configuring custom domains.',
|
||||
'introduction' => '',
|
||||
'default' => 'example.com,test.com,app.example.com',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_DOMAIN_FUNCTIONS',
|
||||
'description' => 'A domain to use for function preview URLs. Setting to empty turns off function preview URLs.',
|
||||
@@ -90,13 +108,40 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_DOMAIN_TARGET',
|
||||
'description' => 'A DNS A record hostname to serve as a CNAME target for your Appwrite custom domains. You can use the same value as used for the Appwrite \'_APP_DOMAIN\' variable. The default value is \'localhost\'.',
|
||||
'description' => 'Deprecated since 1.7.0. A DNS A record hostname to serve as a CNAME target for your Appwrite custom domains. You can use the same value as used for the Appwrite \'_APP_DOMAIN\' variable. The default value is \'localhost\'.',
|
||||
'introduction' => '',
|
||||
'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' => 'domainTarget'
|
||||
],
|
||||
[
|
||||
'name' => '_APP_DOMAIN_TARGET_CNAME',
|
||||
'description' => 'A domain that can be used as DNS CNAME record to point to instance of Appwrite server.',
|
||||
'introduction' => '',
|
||||
'default' => 'localhost',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_DOMAIN_TARGET_AAAA',
|
||||
'description' => 'An IPv6 that can be used as DNS AAAA record to point to instance of Appwrite server.',
|
||||
'introduction' => '',
|
||||
'default' => '::1',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_DOMAIN_TARGET_A',
|
||||
'description' => 'An IPV4 that can be used as DNS A record to point to instance of Appwrite server.',
|
||||
'introduction' => '',
|
||||
'default' => '127.0.0.1',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_CONSOLE_WHITELIST_ROOT',
|
||||
'description' => 'This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registration form. New users can be added by inviting them to your project. By default this option is enabled.',
|
||||
@@ -268,6 +313,24 @@ return [
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_COMPRESSION_ENABLED',
|
||||
'description' => 'This option allows you to enable or disable the response compression for the Appwrite API. It\'s enabled by default with value "enabled", and to disable it, pass value "disabled".',
|
||||
'introduction' => '1.6.0',
|
||||
'default' => 'enabled',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_COMPRESSION_MIN_SIZE_BYTES',
|
||||
'description' => 'This option allows you to set the minimum size in bytes for the response compression to be applied. The default value is 1024 bytes.',
|
||||
'introduction' => '1.6.0',
|
||||
'default' => 1024,
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
]
|
||||
],
|
||||
],
|
||||
[
|
||||
@@ -554,7 +617,7 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_S3_ACCESS_KEY',
|
||||
'description' => 'AWS S3 storage access key. Required when the storage adapter is set to S3. You can get your access key from your AWS console',
|
||||
'description' => 'S3 storage access key. Required when the storage adapter is set to S3. You can get your access key from your S3 storage provider',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
@@ -562,7 +625,7 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_S3_SECRET',
|
||||
'description' => 'AWS S3 storage secret key. Required when the storage adapter is set to S3. You can get your secret key from your AWS console.',
|
||||
'description' => 'S3 storage secret key. Required when the storage adapter is set to S3. You can get your secret key from your S3 storage provider.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
@@ -570,7 +633,7 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_S3_REGION',
|
||||
'description' => 'AWS S3 storage region. Required when storage adapter is set to S3. You can find your region info for your bucket from AWS console.',
|
||||
'description' => 'S3 storage region. Required when storage adapter is set to S3. You can find your region info for your bucket from your S3 storage provider.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => 'us-east-1',
|
||||
'required' => false,
|
||||
@@ -578,12 +641,20 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_S3_BUCKET',
|
||||
'description' => 'AWS S3 storage bucket. Required when storage adapter is set to S3. You can create buckets in your AWS console.',
|
||||
'description' => 'S3 storage bucket. Required when storage adapter is set to S3. You can create buckets in your S3 storage provider.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_S3_ENDPOINT',
|
||||
'description' => 'S3 storage endpoint. Required when using S3 storage providers other than AWS.',
|
||||
'introduction' => '0.16.2',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_DO_SPACES_ACCESS_KEY',
|
||||
'description' => 'DigitalOcean spaces access key. Required when the storage adapter is set to DOSpaces. You can get your access key from your DigitalOcean console.',
|
||||
@@ -720,13 +791,22 @@ return [
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_SIZE_LIMIT',
|
||||
'description' => 'The maximum size of a function in bytes. The default value is 30MB.',
|
||||
'description' => 'Deprecated since 1.7.0. The maximum size of a function in bytes. The default value is 30MB.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '30000000',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_COMPUTE_SIZE_LIMIT',
|
||||
'description' => 'The maximum size of a function and site deployments in bytes. The default value is 30MB.',
|
||||
'introduction' => '1.7.0',
|
||||
'default' => '30000000',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_BUILD_SIZE_LIMIT',
|
||||
'description' => 'The maximum size of a built deployment in bytes. The default value is 2,000,000,000 (2GB), and the maximum value is 4,294,967,295 (4.2GB).',
|
||||
@@ -747,13 +827,22 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_BUILD_TIMEOUT',
|
||||
'description' => 'The maximum number of seconds allowed as a timeout value when building a new function. The default value is 900 seconds.',
|
||||
'description' => 'Deprecated since 1.7.0. The maximum number of seconds allowed as a timeout value when building a new function. The default value is 900 seconds.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '900',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_COMPUTE_BUILD_TIMEOUT',
|
||||
'description' => 'The maximum number of seconds allowed as a timeout value when building a new function or site. The default value is 900 seconds.',
|
||||
'introduction' => '1.7.0',
|
||||
'default' => '900',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_CONTAINERS',
|
||||
'description' => 'Deprecated since 1.2.0. Runtimes now timeout by inactivity using \'_APP_FUNCTIONS_INACTIVE_THRESHOLD\'.',
|
||||
@@ -765,22 +854,40 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_CPUS',
|
||||
'description' => 'The maximum number of CPU core a single cloud function is allowed to use. Please note that setting a value higher than available cores will result in a function error, which might result in an error. The default value is empty. When it\'s empty, CPU limit will be disabled.',
|
||||
'description' => 'Deprecated since 1.7.0. The maximum number of CPU core a single cloud function is allowed to use. Please note that setting a value higher than available cores will result in a function error, which might result in an error. The default value is empty. When it\'s empty, CPU limit will be disabled.',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '0',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_COMPUTE_CPUS',
|
||||
'description' => 'The maximum number of CPU core a single cloud function or a site is allowed to use. Please note that setting a value higher than available cores might result in an error. The default value is empty. When it\'s empty, CPU limit will be disabled.',
|
||||
'introduction' => '1.7.0',
|
||||
'default' => '0',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_MEMORY',
|
||||
'description' => 'The maximum amount of memory a single cloud function is allowed to use in megabytes. The default value is empty. When it\'s empty, memory limit will be disabled.',
|
||||
'description' => 'Deprecated since 1.7.0. The maximum amount of memory a single cloud function is allowed to use in megabytes. The default value is empty. When it\'s empty, memory limit will be disabled.',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '0',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_COMPUTE_MEMORY',
|
||||
'description' => 'The maximum amount of memory a single function or site is allowed to use in megabytes. The default value is empty. When it\'s empty, memory limit will be disabled.',
|
||||
'introduction' => '1.7.0',
|
||||
'default' => '0',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_MEMORY_SWAP',
|
||||
'description' => 'Deprecated since 1.2.0. High use of swap memory is not recommended to preserve harddrive health.',
|
||||
@@ -838,13 +945,22 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_INACTIVE_THRESHOLD',
|
||||
'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.',
|
||||
'description' => 'Deprecated since 1.7.0. 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,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_COMPUTE_INACTIVE_THRESHOLD',
|
||||
'description' => 'The minimum time a function or site 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' => '1.7.0',
|
||||
'default' => '60',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => 'DOCKERHUB_PULL_USERNAME',
|
||||
'description' => 'Deprecated with 1.2.0, use \'_APP_DOCKER_HUB_USERNAME\' instead.',
|
||||
@@ -883,13 +999,22 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_RUNTIMES_NETWORK',
|
||||
'description' => 'The docker network used for communication between the executor and runtimes.',
|
||||
'description' => 'Deprecated since 1.7.0. The docker network used for communication between the executor and runtimes.',
|
||||
'introduction' => '1.2.0',
|
||||
'default' => 'runtimes',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_COMPUTE_RUNTIMES_NETWORK',
|
||||
'description' => 'The docker network used for communication between the executor and runtimes for sites and functions.',
|
||||
'introduction' => '1.7.0',
|
||||
'default' => 'runtimes',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_DOCKER_HUB_USERNAME',
|
||||
'description' => 'The username for hub.docker.com. This variable is used to pull images from hub.docker.com.',
|
||||
@@ -910,7 +1035,7 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_MAINTENANCE_INTERVAL',
|
||||
'description' => 'Interval value containing the number of seconds that the executor should wait before checking for inactive runtimes. The default value is 3600 seconds (1 hour).',
|
||||
'description' => 'Deprecated since 1.7.0. Interval value containing the number of seconds that the executor should wait before checking for inactive runtimes. The default value is 3600 seconds (1 hour).',
|
||||
'introduction' => '1.4.0',
|
||||
'default' => '3600',
|
||||
'required' => false,
|
||||
@@ -918,8 +1043,42 @@ return [
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_COMPUTE_MAINTENANCE_INTERVAL',
|
||||
'description' => 'Interval value containing the number of seconds that the executor should wait before checking for inactive runtimes of functions and sites. The default value is 3600 seconds (1 hour).',
|
||||
'introduction' => '1.7.0',
|
||||
'default' => '3600',
|
||||
'required' => false,
|
||||
'overwrite' => true,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'category' => 'Sites',
|
||||
'description' => '',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_SITES_TIMEOUT',
|
||||
'description' => 'The maximum number of seconds allowed as a timeout value when creating a new site. The default value is 900 seconds. This is the global limit, timeout for individual functions are configured in the sites\'s settings or in appwrite.json.',
|
||||
'introduction' => '1.7.0',
|
||||
'default' => '900',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_SITES_RUNTIMES',
|
||||
'description' => "This option allows you to enable or disable runtime environments for Sites. Disable unused runtimes to save disk space.\n\nTo enable cloud site runtimes, pass a list of enabled environments separated by a comma.\n\nCurrently, supported environments are: " . \implode(', ', \array_keys(Config::getParam('runtimes'))),
|
||||
'introduction' => '1.7.0',
|
||||
'default' => 'static-1,node-22,flutter-3.29',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'category' => 'VCS (Version Control System)',
|
||||
'description' => '',
|
||||
@@ -1022,13 +1181,22 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_AUDIT',
|
||||
'description' => 'IThe maximum duration (in seconds) upto which to retain audit logs. The default value is 1209600 seconds (14 days).',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain audit logs. The default value is 1209600 seconds (14 days).',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '1209600',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain console audit logs. The default value is 15778800 seconds (6 months).',
|
||||
'introduction' => '1.6.2',
|
||||
'default' => '15778800',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_ABUSE',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain abuse logs. The default value is 86400 seconds (1 day).',
|
||||
|
||||
+811
-479
File diff suppressed because it is too large
Load Diff
+127
-65
@@ -1,6 +1,11 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\URL\URL as URLParse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
@@ -61,9 +66,9 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
|
||||
unset($image);
|
||||
};
|
||||
|
||||
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger) {
|
||||
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForPlatform, ?Logger $logger) {
|
||||
try {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
@@ -122,7 +127,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
|
||||
do {
|
||||
$previousAccessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
$gitHubSession = new Document();
|
||||
@@ -164,13 +169,21 @@ App::get('/v1/avatars/credit-cards/:code')
|
||||
->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')
|
||||
->label('sdk.methodType', 'location')
|
||||
->label('sdk.description', '/docs/references/avatars/get-credit-card.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getCreditCard',
|
||||
description: '/docs/references/avatars/get-credit-card.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: ' . \implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))) . '.')
|
||||
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
@@ -184,13 +197,21 @@ App::get('/v1/avatars/browsers/:code')
|
||||
->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')
|
||||
->label('sdk.methodType', 'location')
|
||||
->label('sdk.description', '/docs/references/avatars/get-browser.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getBrowser',
|
||||
description: '/docs/references/avatars/get-browser.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-browsers'))), 'Browser Code.')
|
||||
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
@@ -204,13 +225,21 @@ App::get('/v1/avatars/flags/:code')
|
||||
->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')
|
||||
->label('sdk.methodType', 'location')
|
||||
->label('sdk.description', '/docs/references/avatars/get-flag.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getFlag',
|
||||
description: '/docs/references/avatars/get-flag.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-flags'))), 'Country Code. ISO Alpha-2 country code format.')
|
||||
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
@@ -224,13 +253,21 @@ App::get('/v1/avatars/image')
|
||||
->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')
|
||||
->label('sdk.methodType', 'location')
|
||||
->label('sdk.description', '/docs/references/avatars/get-image.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getImage',
|
||||
description: '/docs/references/avatars/get-image.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE
|
||||
))
|
||||
->param('url', '', new URL(['http', 'https']), 'Image URL which you want to crop.')
|
||||
->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000. Defaults to 400.', true)
|
||||
->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000. Defaults to 400.', true)
|
||||
@@ -287,13 +324,21 @@ App::get('/v1/avatars/favicon')
|
||||
->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')
|
||||
->label('sdk.methodType', 'location')
|
||||
->label('sdk.description', '/docs/references/avatars/get-favicon.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getFavicon',
|
||||
description: '/docs/references/avatars/get-favicon.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE
|
||||
))
|
||||
->param('url', '', new URL(['http', 'https']), 'Website URL which you want to fetch the favicon from.')
|
||||
->inject('response')
|
||||
->action(function (string $url, Response $response) {
|
||||
@@ -430,13 +475,21 @@ App::get('/v1/avatars/qr')
|
||||
->desc('Get QR code')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'avatars')
|
||||
->label('sdk.method', 'getQR')
|
||||
->label('sdk.methodType', 'location')
|
||||
->label('sdk.description', '/docs/references/avatars/get-qr.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getQR',
|
||||
description: '/docs/references/avatars/get-qr.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('text', '', new Text(512), 'Plain text to be converted to QR code image.')
|
||||
->param('size', 400, new Range(1, 1000), 'QR code size. Pass an integer between 1 to 1000. Defaults to 400.', true)
|
||||
->param('margin', 1, new Range(0, 10), 'Margin from edge. Pass an integer between 0 to 10. Defaults to 1.', true)
|
||||
@@ -449,6 +502,7 @@ App::get('/v1/avatars/qr')
|
||||
'addQuietzone' => true,
|
||||
'quietzoneSize' => $margin,
|
||||
'outputType' => QRCode::OUTPUT_IMAGICK,
|
||||
'scale' => 15,
|
||||
]);
|
||||
|
||||
$qrcode = new QRCode($options);
|
||||
@@ -463,7 +517,7 @@ App::get('/v1/avatars/qr')
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->send($image->output('png', 9));
|
||||
->send($image->output('png', 90));
|
||||
});
|
||||
|
||||
App::get('/v1/avatars/initials')
|
||||
@@ -471,13 +525,21 @@ App::get('/v1/avatars/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')
|
||||
->label('sdk.methodType', 'location')
|
||||
->label('sdk.description', '/docs/references/avatars/get-initials.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getInitials',
|
||||
description: '/docs/references/avatars/get-initials.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->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)
|
||||
@@ -565,14 +627,14 @@ App::get('/v1/cards/cloud')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -583,7 +645,7 @@ App::get('/v1/cards/cloud')
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
@@ -772,14 +834,14 @@ App::get('/v1/cards/cloud-back')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -789,7 +851,7 @@ App::get('/v1/cards/cloud-back')
|
||||
$userId = $user->getId();
|
||||
$email = $user->getAttribute('email', '');
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
@@ -850,14 +912,14 @@ App::get('/v1/cards/cloud-og')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -872,7 +934,7 @@ App::get('/v1/cards/cloud-og')
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
App::init()
|
||||
@@ -21,19 +27,37 @@ 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)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'console',
|
||||
group: 'console',
|
||||
name: 'variables',
|
||||
description: '/docs/references/console/variables.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_CONSOLE_VARIABLES,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$isDomainEnabled = !empty(System::getEnv('_APP_DOMAIN', ''))
|
||||
&& !empty(System::getEnv('_APP_DOMAIN_TARGET', ''))
|
||||
&& System::getEnv('_APP_DOMAIN', '') !== 'localhost'
|
||||
&& System::getEnv('_APP_DOMAIN_TARGET', '') !== 'localhost';
|
||||
$validator = new Domain(System::getEnv('_APP_DOMAIN'));
|
||||
$isDomainValid = !empty(System::getEnv('_APP_DOMAIN', '')) && $validator->isKnown() && !$validator->isTest();
|
||||
|
||||
$validator = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME'));
|
||||
$isCNAMEValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')) && $validator->isKnown() && !$validator->isTest();
|
||||
|
||||
$validator = new IP(IP::V4);
|
||||
$isAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_A', '')) && ($validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_A')));
|
||||
|
||||
$validator = new IP(IP::V6);
|
||||
$isAAAAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_AAAA', '')) && $validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA'));
|
||||
|
||||
$isDomainEnabled = $isDomainValid && (
|
||||
$isAAAAValid || $isAValid || $isCNAMEValid
|
||||
);
|
||||
|
||||
$isVcsEnabled = !empty(System::getEnv('_APP_VCS_GITHUB_APP_NAME', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', ''))
|
||||
@@ -44,34 +68,48 @@ App::get('/v1/console/variables')
|
||||
$isAssistantEnabled = !empty(System::getEnv('_APP_ASSISTANT_OPENAI_API_KEY', ''));
|
||||
|
||||
$variables = new Document([
|
||||
'_APP_DOMAIN_TARGET' => System::getEnv('_APP_DOMAIN_TARGET'),
|
||||
'_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'),
|
||||
'_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'),
|
||||
'_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'),
|
||||
'_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'),
|
||||
'_APP_FUNCTIONS_SIZE_LIMIT' => +System::getEnv('_APP_FUNCTIONS_SIZE_LIMIT'),
|
||||
'_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'),
|
||||
'_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'),
|
||||
'_APP_VCS_ENABLED' => $isVcsEnabled,
|
||||
'_APP_DOMAIN_ENABLED' => $isDomainEnabled,
|
||||
'_APP_ASSISTANT_ENABLED' => $isAssistantEnabled
|
||||
'_APP_ASSISTANT_ENABLED' => $isAssistantEnabled,
|
||||
'_APP_DOMAIN_SITES' => System::getEnv('_APP_DOMAIN_SITES'),
|
||||
'_APP_DOMAIN_FUNCTIONS' => System::getEnv('_APP_DOMAIN_FUNCTIONS'),
|
||||
'_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS'),
|
||||
'_APP_DOMAINS_NAMESERVERS' => System::getEnv('_APP_DOMAINS_NAMESERVERS'),
|
||||
]);
|
||||
|
||||
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
|
||||
});
|
||||
|
||||
App::post('/v1/console/assistant')
|
||||
->desc('Ask query')
|
||||
->desc('Create assistant query')
|
||||
->groups(['api', 'assistant'])
|
||||
->label('scope', 'assistant.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'assistant')
|
||||
->label('sdk.method', 'chat')
|
||||
->label('sdk.description', '/docs/references/assistant/chat.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_TEXT)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'assistant',
|
||||
group: 'console',
|
||||
name: 'chat',
|
||||
description: '/docs/references/assistant/chat.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::TEXT
|
||||
))
|
||||
->label('abuse-limit', 15)
|
||||
->label('abuse-key', 'userId:{userId}')
|
||||
->param('prompt', '', new Text(2000), 'Prompt. A string containing questions asked to the AI assistant.')
|
||||
->inject('response')
|
||||
->action(function (string $prompt, Response $response) {
|
||||
$ch = curl_init('http://appwrite-assistant:3003/');
|
||||
$ch = curl_init('http://appwrite-assistant:3003/v1/models/assistant/prompt');
|
||||
$responseHeaders = [];
|
||||
$query = json_encode(['prompt' => $prompt]);
|
||||
$headers = ['accept: text/event-stream'];
|
||||
|
||||
+870
-422
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,10 @@ use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\GraphQL\Promises\Adapter;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use GraphQL\Error\DebugFlag;
|
||||
@@ -38,13 +42,20 @@ 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('sdk', new Method(
|
||||
namespace: 'graphql',
|
||||
group: 'graphql',
|
||||
name: 'get',
|
||||
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
hide: true,
|
||||
description: '/docs/references/graphql/get.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_ANY,
|
||||
)
|
||||
]
|
||||
))
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->param('query', '', new Text(0, 0), 'The query to execute.')
|
||||
@@ -78,17 +89,23 @@ 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('sdk', new Method(
|
||||
namespace: 'graphql',
|
||||
group: 'graphql',
|
||||
name: 'mutation',
|
||||
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
description: '/docs/references/graphql/post.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_ANY,
|
||||
)
|
||||
],
|
||||
type: MethodType::GRAPHQL,
|
||||
additionalParameters: [
|
||||
'query' => ['default' => [], 'validator' => new JSON(), 'description' => 'The query or queries to execute.', 'optional' => false],
|
||||
],
|
||||
))
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->inject('request')
|
||||
@@ -123,17 +140,23 @@ App::post('/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.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('sdk', new Method(
|
||||
namespace: 'graphql',
|
||||
group: 'graphql',
|
||||
name: 'query',
|
||||
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
description: '/docs/references/graphql/post.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_ANY,
|
||||
)
|
||||
],
|
||||
type: MethodType::GRAPHQL,
|
||||
additionalParameters: [
|
||||
'query' => ['default' => [], 'validator' => new JSON(), 'description' => 'The query or queries to execute.', 'optional' => false],
|
||||
],
|
||||
))
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->inject('request')
|
||||
|
||||
+362
-279
@@ -3,14 +3,18 @@
|
||||
use Appwrite\ClamAV\Network;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Domains\Validator\PublicDomain;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\Local;
|
||||
@@ -26,13 +30,20 @@ App::get('/v1/health')
|
||||
->desc('Get HTTP')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'get')
|
||||
->label('sdk.description', '/docs/references/health/get.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'health',
|
||||
group: 'health',
|
||||
name: 'get',
|
||||
auth: [AuthType::KEY],
|
||||
description: '/docs/references/health/get.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_STATUS,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
@@ -49,9 +60,6 @@ App::get('/v1/health/version')
|
||||
->desc('Get version')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_VERSION)
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$response->dynamic(new Document([ 'version' => APP_VERSION_STABLE ]), Response::MODEL_HEALTH_VERSION);
|
||||
@@ -61,13 +69,20 @@ App::get('/v1/health/db')
|
||||
->desc('Get DB')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getDB')
|
||||
->label('sdk.description', '/docs/references/health/get-db.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'health',
|
||||
name: 'getDB',
|
||||
description: '/docs/references/health/get-db.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_STATUS,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->inject('response')
|
||||
->inject('pools')
|
||||
->action(function (Response $response, Group $pools) {
|
||||
@@ -115,13 +130,20 @@ App::get('/v1/health/cache')
|
||||
->desc('Get cache')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getCache')
|
||||
->label('sdk.description', '/docs/references/health/get-cache.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'health',
|
||||
name: 'getCache',
|
||||
description: '/docs/references/health/get-cache.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_STATUS,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->inject('response')
|
||||
->inject('pools')
|
||||
->action(function (Response $response, Group $pools) {
|
||||
@@ -169,74 +191,24 @@ App::get('/v1/health/cache')
|
||||
]), Response::MODEL_HEALTH_STATUS_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/health/queue')
|
||||
->desc('Get queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueue')
|
||||
->label('sdk.description', '/docs/references/health/get-queue.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->inject('response')
|
||||
->inject('pools')
|
||||
->action(function (Response $response, Group $pools) {
|
||||
|
||||
$output = [];
|
||||
|
||||
$configs = [
|
||||
'Queue' => Config::getParam('pools-queue'),
|
||||
];
|
||||
|
||||
foreach ($configs as $key => $config) {
|
||||
foreach ($config as $database) {
|
||||
$checkStart = \microtime(true);
|
||||
try {
|
||||
/** @var Connection $adapter */
|
||||
$adapter = $pools->get($database)->pop()->getResource();
|
||||
|
||||
if ($adapter->ping()) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'pass',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
} else {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'statuses' => $output,
|
||||
'total' => count($output),
|
||||
]), Response::MODEL_HEALTH_STATUS_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/health/pubsub')
|
||||
->desc('Get pubsub')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getPubSub')
|
||||
->label('sdk.description', '/docs/references/health/get-pubsub.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'health',
|
||||
name: 'getPubSub',
|
||||
description: '/docs/references/health/get-pubsub.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_STATUS,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->inject('response')
|
||||
->inject('pools')
|
||||
->action(function (Response $response, Group $pools) {
|
||||
@@ -288,13 +260,20 @@ App::get('/v1/health/time')
|
||||
->desc('Get time')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getTime')
|
||||
->label('sdk.description', '/docs/references/health/get-time.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_TIME)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'health',
|
||||
name: 'getTime',
|
||||
description: '/docs/references/health/get-time.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_TIME,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
@@ -345,21 +324,27 @@ App::get('/v1/health/queue/webhooks')
|
||||
->desc('Get webhooks queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueWebhooks')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-webhooks.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getQueueWebhooks',
|
||||
description: '/docs/references/health/get-queue-webhooks.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('response')
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::WEBHOOK_QUEUE_NAME, $queue);
|
||||
$size = $client->getQueueSize();
|
||||
$size = $publisher->getQueueSize(new Queue(Event::WEBHOOK_QUEUE_NAME));
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
@@ -372,21 +357,27 @@ App::get('/v1/health/queue/logs')
|
||||
->desc('Get logs queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueLogs')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-logs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getQueueLogs',
|
||||
description: '/docs/references/health/get-queue-logs.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('response')
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::AUDITS_QUEUE_NAME, $queue);
|
||||
$size = $client->getQueueSize();
|
||||
$size = $publisher->getQueueSize(new Queue(Event::AUDITS_QUEUE_NAME));
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
@@ -399,13 +390,20 @@ App::get('/v1/health/certificate')
|
||||
->desc('Get the SSL certificate for a domain')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getCertificate')
|
||||
->label('sdk.description', '/docs/references/health/get-certificate.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_CERTIFICATE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'health',
|
||||
name: 'getCertificate',
|
||||
description: '/docs/references/health/get-certificate.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_CERTIFICATE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('domain', null, new Multiple([new Domain(), new PublicDomain()]), Multiple::TYPE_STRING, 'Domain name')
|
||||
->inject('response')
|
||||
->action(function (string $domain, Response $response) {
|
||||
@@ -449,21 +447,27 @@ App::get('/v1/health/queue/certificates')
|
||||
->desc('Get certificates queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueCertificates')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-certificates.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getQueueCertificates',
|
||||
description: '/docs/references/health/get-queue-certificates.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('response')
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::CERTIFICATES_QUEUE_NAME, $queue);
|
||||
$size = $client->getQueueSize();
|
||||
$size = $publisher->getQueueSize(new Queue(Event::CERTIFICATES_QUEUE_NAME));
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
@@ -476,21 +480,27 @@ App::get('/v1/health/queue/builds')
|
||||
->desc('Get builds queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueBuilds')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-builds.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getQueueBuilds',
|
||||
description: '/docs/references/health/get-queue-builds.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('response')
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::BUILDS_QUEUE_NAME, $queue);
|
||||
$size = $client->getQueueSize();
|
||||
$size = $publisher->getQueueSize(new Queue(Event::BUILDS_QUEUE_NAME));
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
@@ -503,22 +513,28 @@ App::get('/v1/health/queue/databases')
|
||||
->desc('Get databases queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueDatabases')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-databases.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getQueueDatabases',
|
||||
description: '/docs/references/health/get-queue-databases.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('name', 'database_db_main', new Text(256), 'Queue name for which to check the queue size', true)
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('response')
|
||||
->action(function (string $name, int|string $threshold, Connection $queue, Response $response) {
|
||||
->action(function (string $name, int|string $threshold, Publisher $publisher, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client($name, $queue);
|
||||
$size = $client->getQueueSize();
|
||||
$size = $publisher->getQueueSize(new Queue($name));
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
@@ -531,21 +547,27 @@ App::get('/v1/health/queue/deletes')
|
||||
->desc('Get deletes queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueDeletes')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-deletes.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getQueueDeletes',
|
||||
description: '/docs/references/health/get-queue-deletes.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('response')
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::DELETE_QUEUE_NAME, $queue);
|
||||
$size = $client->getQueueSize();
|
||||
$size = $publisher->getQueueSize(new Queue(Event::DELETE_QUEUE_NAME));
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
@@ -558,21 +580,27 @@ App::get('/v1/health/queue/mails')
|
||||
->desc('Get mails queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueMails')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-mails.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getQueueMails',
|
||||
description: '/docs/references/health/get-queue-mails.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('response')
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::MAILS_QUEUE_NAME, $queue);
|
||||
$size = $client->getQueueSize();
|
||||
$size = $publisher->getQueueSize(new Queue(Event::MAILS_QUEUE_NAME));
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
@@ -585,21 +613,27 @@ App::get('/v1/health/queue/messaging')
|
||||
->desc('Get messaging queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueMessaging')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-messaging.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getQueueMessaging',
|
||||
description: '/docs/references/health/get-queue-messaging.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('response')
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::MESSAGING_QUEUE_NAME, $queue);
|
||||
$size = $client->getQueueSize();
|
||||
$size = $publisher->getQueueSize(new Queue(Event::MESSAGING_QUEUE_NAME));
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
@@ -612,21 +646,27 @@ App::get('/v1/health/queue/migrations')
|
||||
->desc('Get migrations queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueMigrations')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-migrations.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getQueueMigrations',
|
||||
description: '/docs/references/health/get-queue-migrations.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('response')
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::MIGRATIONS_QUEUE_NAME, $queue);
|
||||
$size = $client->getQueueSize();
|
||||
$size = $publisher->getQueueSize(new Queue(Event::MIGRATIONS_QUEUE_NAME));
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
@@ -639,21 +679,27 @@ App::get('/v1/health/queue/functions')
|
||||
->desc('Get functions queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueFunctions')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-functions.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getQueueFunctions',
|
||||
description: '/docs/references/health/get-queue-functions.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('response')
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::FUNCTIONS_QUEUE_NAME, $queue);
|
||||
$size = $client->getQueueSize();
|
||||
$size = $publisher->getQueueSize(new Queue(Event::FUNCTIONS_QUEUE_NAME));
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
@@ -662,25 +708,31 @@ App::get('/v1/health/queue/functions')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/usage')
|
||||
->desc('Get usage queue')
|
||||
App::get('/v1/health/queue/stats-resources')
|
||||
->desc('Get stats resources queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueUsage')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-usage.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getQueueStatsResources',
|
||||
description: '/docs/references/health/get-queue-stats-resources.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('response')
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::USAGE_QUEUE_NAME, $queue);
|
||||
$size = $client->getQueueSize();
|
||||
$size = $publisher->getQueueSize(new Queue(Event::STATS_RESOURCES_QUEUE_NAME));
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
@@ -689,25 +741,31 @@ App::get('/v1/health/queue/usage')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
});
|
||||
|
||||
App::get('/v1/health/queue/usage-dump')
|
||||
->desc('Get usage dump queue')
|
||||
App::get('/v1/health/queue/stats-usage')
|
||||
->desc('Get stats usage queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueUsageDump')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-usage-dump.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getQueueUsage',
|
||||
description: '/docs/references/health/get-queue-stats-usage.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('response')
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::USAGE_DUMP_QUEUE_NAME, $queue);
|
||||
$size = $client->getQueueSize();
|
||||
$size = $publisher->getQueueSize(new Queue(Event::STATS_USAGE_QUEUE_NAME));
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
@@ -720,13 +778,20 @@ App::get('/v1/health/storage/local')
|
||||
->desc('Get local storage')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getStorageLocal')
|
||||
->label('sdk.description', '/docs/references/health/get-storage-local.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'storage',
|
||||
name: 'getStorageLocal',
|
||||
description: '/docs/references/health/get-storage-local.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_STATUS,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
@@ -763,19 +828,27 @@ App::get('/v1/health/storage')
|
||||
->desc('Get storage')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getStorage')
|
||||
->label('sdk.description', '/docs/references/health/get-storage.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'storage',
|
||||
name: 'getStorage',
|
||||
description: '/docs/references/health/get-storage.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_STATUS,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->inject('response')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForFunctions')
|
||||
->inject('deviceForSites')
|
||||
->inject('deviceForBuilds')
|
||||
->action(function (Response $response, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds) {
|
||||
$devices = [$deviceForFiles, $deviceForFunctions, $deviceForBuilds];
|
||||
->action(function (Response $response, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForSites, Device $deviceForBuilds) {
|
||||
$devices = [$deviceForFiles, $deviceForFunctions, $deviceForSites, $deviceForBuilds];
|
||||
$checkStart = \microtime(true);
|
||||
|
||||
foreach ($devices as $device) {
|
||||
@@ -804,13 +877,20 @@ App::get('/v1/health/anti-virus')
|
||||
->desc('Get antivirus')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getAntivirus')
|
||||
->label('sdk.description', '/docs/references/health/get-storage-anti-virus.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_ANTIVIRUS)
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'health',
|
||||
name: 'getAntivirus',
|
||||
description: '/docs/references/health/get-storage-anti-virus.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_ANTIVIRUS,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
@@ -843,17 +923,28 @@ App::get('/v1/health/queue/failed/:name')
|
||||
->desc('Get number of failed queue jobs')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getFailedJobs')
|
||||
->label('sdk', new Method(
|
||||
auth: [AuthType::KEY],
|
||||
namespace: 'health',
|
||||
group: 'queue',
|
||||
name: 'getFailedJobs',
|
||||
description: '/docs/references/health/get-failed-queue-jobs.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_HEALTH_QUEUE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('name', '', new WhiteList([
|
||||
Event::DATABASE_QUEUE_NAME,
|
||||
Event::DELETE_QUEUE_NAME,
|
||||
Event::AUDITS_QUEUE_NAME,
|
||||
Event::MAILS_QUEUE_NAME,
|
||||
Event::FUNCTIONS_QUEUE_NAME,
|
||||
Event::USAGE_QUEUE_NAME,
|
||||
Event::USAGE_DUMP_QUEUE_NAME,
|
||||
Event::STATS_RESOURCES_QUEUE_NAME,
|
||||
Event::STATS_USAGE_QUEUE_NAME,
|
||||
Event::WEBHOOK_QUEUE_NAME,
|
||||
Event::CERTIFICATES_QUEUE_NAME,
|
||||
Event::BUILDS_QUEUE_NAME,
|
||||
@@ -861,17 +952,12 @@ App::get('/v1/health/queue/failed/:name')
|
||||
Event::MIGRATIONS_QUEUE_NAME
|
||||
]), 'The name of the queue')
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->label('sdk.description', '/docs/references/health/get-failed-queue-jobs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->inject('response')
|
||||
->inject('queue')
|
||||
->action(function (string $name, int|string $threshold, Response $response, Connection $queue) {
|
||||
->inject('publisher')
|
||||
->action(function (string $name, int|string $threshold, Response $response, Publisher $publisher) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client($name, $queue);
|
||||
$failed = $client->countFailedJobs();
|
||||
$failed = $publisher->getQueueSize(new Queue($name), failedJobs: true);
|
||||
|
||||
if ($failed >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue failed jobs threshold hit. Current size is {$failed} and threshold is {$threshold}.");
|
||||
@@ -884,9 +970,6 @@ App::get('/v1/health/stats') // Currently only used internally
|
||||
->desc('Get system stats')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'root')
|
||||
// ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
// ->label('sdk.namespace', 'health')
|
||||
// ->label('sdk.method', 'getStats')
|
||||
->label('docs', false)
|
||||
->inject('response')
|
||||
->inject('register')
|
||||
|
||||
+109
-74
@@ -1,5 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use MaxMind\Db\Reader;
|
||||
@@ -12,15 +15,19 @@ App::get('/v1/locale')
|
||||
->desc('Get user locale')
|
||||
->groups(['api', 'locale'])
|
||||
->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', 'get')
|
||||
->label('sdk.description', '/docs/references/locale/get-locale.md')
|
||||
->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', '/localed')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'locale',
|
||||
group: null,
|
||||
name: 'get',
|
||||
description: '/docs/references/locale/get-locale.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_LOCALE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
@@ -72,15 +79,19 @@ App::get('/v1/locale/codes')
|
||||
->desc('List locale codes')
|
||||
->groups(['api', 'locale'])
|
||||
->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', 'listCodes')
|
||||
->label('sdk.description', '/docs/references/locale/list-locale-codes.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LOCALE_CODE_LIST)
|
||||
->label('sdk.offline.model', '/locale/localeCode')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'locale',
|
||||
group: null,
|
||||
name: 'listCodes',
|
||||
description: '/docs/references/locale/list-locale-codes.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_LOCALE_CODE_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$codes = Config::getParam('locale-codes');
|
||||
@@ -94,19 +105,23 @@ App::get('/v1/locale/countries')
|
||||
->desc('List countries')
|
||||
->groups(['api', 'locale'])
|
||||
->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', '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')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'locale',
|
||||
group: null,
|
||||
name: 'listCountries',
|
||||
description: '/docs/references/locale/list-countries.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_COUNTRY_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
$list = Config::getParam('locale-countries'); /* @var $list array */
|
||||
$list = array_keys(Config::getParam('locale-countries')); /* @var $list array */
|
||||
$output = [];
|
||||
|
||||
foreach ($list as $value) {
|
||||
@@ -127,15 +142,19 @@ App::get('/v1/locale/countries/eu')
|
||||
->desc('List EU countries')
|
||||
->groups(['api', 'locale'])
|
||||
->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', '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')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'locale',
|
||||
group: null,
|
||||
name: 'listCountriesEU',
|
||||
description: '/docs/references/locale/list-countries-eu.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_COUNTRY_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
@@ -162,15 +181,19 @@ App::get('/v1/locale/countries/phones')
|
||||
->desc('List countries phone codes')
|
||||
->groups(['api', 'locale'])
|
||||
->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', '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')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'locale',
|
||||
group: null,
|
||||
name: 'listCountriesPhones',
|
||||
description: '/docs/references/locale/list-countries-phones.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PHONE_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
@@ -196,19 +219,23 @@ App::get('/v1/locale/continents')
|
||||
->desc('List continents')
|
||||
->groups(['api', 'locale'])
|
||||
->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', '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')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'locale',
|
||||
group: null,
|
||||
name: 'listContinents',
|
||||
description: '/docs/references/locale/list-continents.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_CONTINENT_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
$list = Config::getParam('locale-continents');
|
||||
$list = array_keys(Config::getParam('locale-continents'));
|
||||
|
||||
foreach ($list as $value) {
|
||||
$output[] = new Document([
|
||||
@@ -228,15 +255,19 @@ App::get('/v1/locale/currencies')
|
||||
->desc('List currencies')
|
||||
->groups(['api', 'locale'])
|
||||
->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', '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')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'locale',
|
||||
group: null,
|
||||
name: 'listCurrencies',
|
||||
description: '/docs/references/locale/list-currencies.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_CURRENCY_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$list = Config::getParam('locale-currencies');
|
||||
@@ -251,15 +282,19 @@ App::get('/v1/locale/languages')
|
||||
->desc('List languages')
|
||||
->groups(['api', 'locale'])
|
||||
->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', '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')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'locale',
|
||||
group: null,
|
||||
name: 'listLanguages',
|
||||
description: '/docs/references/locale/list-languages.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_LANGUAGE_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$list = Config::getParam('locale-languages');
|
||||
|
||||
+766
-400
File diff suppressed because it is too large
Load Diff
+319
-569
File diff suppressed because it is too large
Load Diff
+173
-59
@@ -1,6 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
@@ -13,6 +17,7 @@ use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Datetime as DateTimeValidator;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -20,23 +25,35 @@ App::get('/v1/project/usage')
|
||||
->desc('Get project usage stats')
|
||||
->groups(['api', 'usage'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'project')
|
||||
->label('sdk.method', 'getUsage')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USAGE_PROJECT)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: null,
|
||||
name: 'getUsage',
|
||||
description: '/docs/references/project/get-usage.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_USAGE_PROJECT,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('startDate', '', new DateTimeValidator(), 'Starting date for the usage')
|
||||
->param('endDate', '', new DateTimeValidator(), 'End date for the usage')
|
||||
->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject) {
|
||||
->inject('getLogsDB')
|
||||
->inject('smsRates')
|
||||
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, callable $getLogsDB, array $smsRates) {
|
||||
$stats = $total = $usage = [];
|
||||
$format = 'Y-m-d 00:00:00';
|
||||
$firstDay = (new DateTime($startDate))->format($format);
|
||||
$lastDay = (new DateTime($endDate))->format($format);
|
||||
|
||||
$dbForLogs = call_user_func($getLogsDB, $project);
|
||||
|
||||
$metrics = [
|
||||
'total' => [
|
||||
METRIC_EXECUTIONS,
|
||||
@@ -49,7 +66,10 @@ App::get('/v1/project/usage')
|
||||
METRIC_FILES_STORAGE,
|
||||
METRIC_DATABASES_STORAGE,
|
||||
METRIC_DEPLOYMENTS_STORAGE,
|
||||
METRIC_BUILDS_STORAGE
|
||||
METRIC_BUILDS_STORAGE,
|
||||
METRIC_DATABASES_OPERATIONS_READS,
|
||||
METRIC_DATABASES_OPERATIONS_WRITES,
|
||||
METRIC_FILES_IMAGES_TRANSFORMED,
|
||||
],
|
||||
'period' => [
|
||||
METRIC_NETWORK_REQUESTS,
|
||||
@@ -59,7 +79,10 @@ App::get('/v1/project/usage')
|
||||
METRIC_EXECUTIONS,
|
||||
METRIC_DATABASES_STORAGE,
|
||||
METRIC_EXECUTIONS_MB_SECONDS,
|
||||
METRIC_BUILDS_MB_SECONDS
|
||||
METRIC_BUILDS_MB_SECONDS,
|
||||
METRIC_DATABASES_OPERATIONS_READS,
|
||||
METRIC_DATABASES_OPERATIONS_WRITES,
|
||||
METRIC_FILES_IMAGES_TRANSFORMED,
|
||||
]
|
||||
];
|
||||
|
||||
@@ -78,9 +101,11 @@ App::get('/v1/project/usage')
|
||||
'1d' => 'Y-m-d\T00:00:00.000P',
|
||||
};
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
|
||||
Authorization::skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
|
||||
foreach ($metrics['total'] as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
$db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
|
||||
|
||||
$result = $db->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
@@ -88,7 +113,9 @@ App::get('/v1/project/usage')
|
||||
}
|
||||
|
||||
foreach ($metrics['period'] as $metric) {
|
||||
$results = $dbForProject->find('stats', [
|
||||
$db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
|
||||
|
||||
$results = $db->find('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', [$period]),
|
||||
Query::greaterThanEqual('time', $firstDay),
|
||||
@@ -123,7 +150,7 @@ App::get('/v1/project/usage')
|
||||
$executionsBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
$id = $function->getId();
|
||||
$name = $function->getAttribute('name');
|
||||
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS);
|
||||
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS);
|
||||
$value = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
@@ -139,7 +166,7 @@ App::get('/v1/project/usage')
|
||||
$executionsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
$id = $function->getId();
|
||||
$name = $function->getAttribute('name');
|
||||
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS);
|
||||
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS);
|
||||
$value = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
@@ -155,7 +182,7 @@ App::get('/v1/project/usage')
|
||||
$buildsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
$id = $function->getId();
|
||||
$name = $function->getAttribute('name');
|
||||
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS);
|
||||
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_BUILDS_MB_SECONDS);
|
||||
$value = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
@@ -204,13 +231,13 @@ App::get('/v1/project/usage')
|
||||
$functionsStorageBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
$id = $function->getId();
|
||||
$name = $function->getAttribute('name');
|
||||
$deploymentMetric = str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $function->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE);
|
||||
$deploymentMetric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE);
|
||||
$deploymentValue = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$deploymentMetric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
$buildMetric = str_replace(['{functionInternalId}'], [$function->getInternalId()], METRIC_FUNCTION_ID_BUILDS_STORAGE);
|
||||
$buildMetric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_BUILDS_STORAGE);
|
||||
$buildValue = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$buildMetric]),
|
||||
Query::equal('period', ['inf'])
|
||||
@@ -228,7 +255,7 @@ App::get('/v1/project/usage')
|
||||
$executionsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
$id = $function->getId();
|
||||
$name = $function->getAttribute('name');
|
||||
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS);
|
||||
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS);
|
||||
$value = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
@@ -244,7 +271,7 @@ App::get('/v1/project/usage')
|
||||
$buildsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
$id = $function->getId();
|
||||
$name = $function->getAttribute('name');
|
||||
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS);
|
||||
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_BUILDS_MB_SECONDS);
|
||||
$value = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
@@ -257,6 +284,46 @@ App::get('/v1/project/usage')
|
||||
];
|
||||
}, $dbForProject->find('functions'));
|
||||
|
||||
// This total is includes free and paid SMS usage
|
||||
$authPhoneTotal = Authorization::skip(fn () => $dbForProject->sum('stats', 'value', [
|
||||
Query::equal('metric', [METRIC_AUTH_METHOD_PHONE]),
|
||||
Query::equal('period', ['1d']),
|
||||
Query::greaterThanEqual('time', $firstDay),
|
||||
Query::lessThan('time', $lastDay),
|
||||
]));
|
||||
|
||||
// This estimate is only for paid SMS usage
|
||||
$authPhoneMetrics = Authorization::skip(fn () => $dbForProject->find('stats', [
|
||||
Query::startsWith('metric', METRIC_AUTH_METHOD_PHONE . '.'),
|
||||
Query::equal('period', ['1d']),
|
||||
Query::greaterThanEqual('time', $firstDay),
|
||||
Query::lessThan('time', $lastDay),
|
||||
]));
|
||||
|
||||
$authPhoneEstimate = 0.0;
|
||||
$authPhoneCountryBreakdown = [];
|
||||
foreach ($authPhoneMetrics as $metric) {
|
||||
$parts = explode('.', $metric->getAttribute('metric'));
|
||||
$countryCode = $parts[3] ?? null;
|
||||
if ($countryCode === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $metric->getAttribute('value', 0);
|
||||
|
||||
if (isset($smsRates[$countryCode])) {
|
||||
$authPhoneEstimate += $value * $smsRates[$countryCode];
|
||||
}
|
||||
|
||||
$authPhoneCountryBreakdown[] = [
|
||||
'name' => $countryCode,
|
||||
'value' => $value,
|
||||
'estimate' => isset($smsRates[$countryCode])
|
||||
? $value * $smsRates[$countryCode]
|
||||
: 0.0,
|
||||
];
|
||||
}
|
||||
|
||||
// merge network inbound + outbound
|
||||
$projectBandwidth = [];
|
||||
foreach ($usage[METRIC_NETWORK_INBOUND] as $item) {
|
||||
@@ -295,14 +362,21 @@ App::get('/v1/project/usage')
|
||||
'functionsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE] + $total[METRIC_BUILDS_STORAGE],
|
||||
'buildsStorageTotal' => $total[METRIC_BUILDS_STORAGE],
|
||||
'deploymentsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE],
|
||||
'databasesReadsTotal' => $total[METRIC_DATABASES_OPERATIONS_READS],
|
||||
'databasesWritesTotal' => $total[METRIC_DATABASES_OPERATIONS_WRITES],
|
||||
'executionsBreakdown' => $executionsBreakdown,
|
||||
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
|
||||
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
|
||||
'bucketsBreakdown' => $bucketsBreakdown,
|
||||
'databasesReads' => $usage[METRIC_DATABASES_OPERATIONS_READS],
|
||||
'databasesWrites' => $usage[METRIC_DATABASES_OPERATIONS_WRITES],
|
||||
'databasesStorageBreakdown' => $databasesStorageBreakdown,
|
||||
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
|
||||
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
|
||||
'functionsStorageBreakdown' => $functionsStorageBreakdown,
|
||||
'authPhoneTotal' => $authPhoneTotal,
|
||||
'authPhoneEstimate' => $authPhoneEstimate,
|
||||
'authPhoneCountryBreakdown' => $authPhoneCountryBreakdown,
|
||||
'imageTransformations' => $usage[METRIC_FILES_IMAGES_TRANSFORMED],
|
||||
'imageTransformationsTotal' => $total[METRIC_FILES_IMAGES_TRANSFORMED],
|
||||
]), Response::MODEL_USAGE_PROJECT);
|
||||
});
|
||||
|
||||
@@ -313,20 +387,27 @@ App::post('/v1/project/variables')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('audits.event', 'variable.create')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'project')
|
||||
->label('sdk.method', 'createVariable')
|
||||
->label('sdk.description', '/docs/references/project/create-variable.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_VARIABLE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: null,
|
||||
name: 'createVariable',
|
||||
description: '/docs/references/project/create-variable.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_VARIABLE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
|
||||
->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $key, string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $key, string $value, bool $secret, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) {
|
||||
$variableId = ID::unique();
|
||||
|
||||
$variable = new Document([
|
||||
@@ -341,6 +422,7 @@ App::post('/v1/project/variables')
|
||||
'resourceType' => 'project',
|
||||
'key' => $key,
|
||||
'value' => $value,
|
||||
'secret' => $secret,
|
||||
'search' => implode(' ', [$variableId, $key, 'project']),
|
||||
]);
|
||||
|
||||
@@ -367,13 +449,19 @@ App::get('/v1/project/variables')
|
||||
->desc('List variables')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'project')
|
||||
->label('sdk.method', 'listVariables')
|
||||
->label('sdk.description', '/docs/references/project/list-variables.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_VARIABLE_LIST)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: null,
|
||||
name: 'listVariables',
|
||||
description: '/docs/references/project/list-variables.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_VARIABLE_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (Response $response, Database $dbForProject) {
|
||||
@@ -392,13 +480,19 @@ App::get('/v1/project/variables/:variableId')
|
||||
->desc('Get variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'project')
|
||||
->label('sdk.method', 'getVariable')
|
||||
->label('sdk.description', '/docs/references/project/get-variable.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_VARIABLE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: null,
|
||||
name: 'getVariable',
|
||||
description: '/docs/references/project/get-variable.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_VARIABLE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('variableId', '', new UID(), 'Variable unique ID.', false)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
@@ -416,29 +510,41 @@ App::put('/v1/project/variables/:variableId')
|
||||
->desc('Update variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'project')
|
||||
->label('sdk.method', 'updateVariable')
|
||||
->label('sdk.description', '/docs/references/project/update-variable.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_VARIABLE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: null,
|
||||
name: 'updateVariable',
|
||||
description: '/docs/references/project/update-variable.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_VARIABLE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('variableId', '', new UID(), 'Variable unique ID.', false)
|
||||
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
|
||||
->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $variableId, string $key, ?string $value, ?bool $secret, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) {
|
||||
$variable = $dbForProject->getDocument('variables', $variableId);
|
||||
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceType') !== 'project') {
|
||||
throw new Exception(Exception::VARIABLE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($variable->getAttribute('secret') === true && $secret === false) {
|
||||
throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET);
|
||||
}
|
||||
|
||||
$variable
|
||||
->setAttribute('key', $key)
|
||||
->setAttribute('value', $value ?? $variable->getAttribute('value'))
|
||||
->setAttribute('secret', $secret ?? $variable->getAttribute('secret'))
|
||||
->setAttribute('search', implode(' ', [$variableId, $key, 'project']));
|
||||
|
||||
try {
|
||||
@@ -462,12 +568,20 @@ App::delete('/v1/project/variables/:variableId')
|
||||
->desc('Delete variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'project')
|
||||
->label('sdk.method', 'deleteVariable')
|
||||
->label('sdk.description', '/docs/references/project/delete-variable.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: null,
|
||||
name: 'deleteVariable',
|
||||
description: '/docs/references/project/delete-variable.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE
|
||||
))
|
||||
->param('variableId', '', new UID(), 'Variable unique ID.', false)
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
|
||||
+838
-496
File diff suppressed because it is too large
Load Diff
+101
-178
@@ -4,7 +4,11 @@ use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\CNAME;
|
||||
use Appwrite\Network\Validator\DNS;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Rules;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
@@ -17,152 +21,33 @@ use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
App::post('/v1/proxy/rules')
|
||||
->groups(['api', 'proxy'])
|
||||
->desc('Create rule')
|
||||
->label('scope', 'rules.write')
|
||||
->label('event', 'rules.[ruleId].create')
|
||||
->label('audits.event', 'rule.create')
|
||||
->label('audits.resource', 'rule/{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'proxy')
|
||||
->label('sdk.method', 'createRule')
|
||||
->label('sdk.description', '/docs/references/proxy/create-rule.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROXY_RULE)
|
||||
->param('domain', null, new ValidatorDomain(), 'Domain name.')
|
||||
->param('resourceType', null, new WhiteList(['api', 'function']), 'Action definition for the rule. Possible values are "api", "function"')
|
||||
->param('resourceId', '', new UID(), 'ID of resource for the action type. If resourceType is "api", leave empty. If resourceType is "function", provide ID of the function.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForConsole, Database $dbForProject) {
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
if ($domain === $mainDomain) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your main domain to specific resource. Please use subdomain or a different domain.');
|
||||
}
|
||||
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
if ($functionsDomain != '' && str_ends_with($domain, $functionsDomain)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your functions domain or it\'s subdomain to specific resource. Please use different domain.');
|
||||
}
|
||||
|
||||
if ($domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
|
||||
}
|
||||
|
||||
$ruleId = md5($domain);
|
||||
$document = $dbForConsole->getDocument('rules', $ruleId);
|
||||
|
||||
if (!$document->isEmpty()) {
|
||||
if ($document->getAttribute('projectId') === $project->getId()) {
|
||||
$resourceType = $document->getAttribute('resourceType');
|
||||
$resourceId = $document->getAttribute('resourceId');
|
||||
$message = "Domain already assigned to '{$resourceType}' service";
|
||||
if (!empty($resourceId)) {
|
||||
$message .= " with ID '{$resourceId}'";
|
||||
}
|
||||
|
||||
$message .= '.';
|
||||
} else {
|
||||
$message = 'Domain already assigned to different project.';
|
||||
}
|
||||
|
||||
throw new Exception(Exception::RULE_ALREADY_EXISTS, $message);
|
||||
}
|
||||
|
||||
$resourceInternalId = '';
|
||||
|
||||
if ($resourceType == 'function') {
|
||||
if (empty($resourceId)) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $resourceId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$resourceInternalId = $function->getInternalId();
|
||||
}
|
||||
|
||||
try {
|
||||
$domain = new Domain($domain);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
|
||||
}
|
||||
|
||||
$ruleId = md5($domain->get());
|
||||
$rule = new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain->get(),
|
||||
'resourceType' => $resourceType,
|
||||
'resourceId' => $resourceId,
|
||||
'resourceInternalId' => $resourceInternalId,
|
||||
'certificateId' => '',
|
||||
]);
|
||||
|
||||
$status = 'created';
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS');
|
||||
if (!empty($functionsDomain) && \str_ends_with($domain->get(), $functionsDomain)) {
|
||||
$status = 'verified';
|
||||
}
|
||||
|
||||
if ($status === 'created') {
|
||||
$target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
$validator = new CNAME($target->get()); // Verify Domain with DNS records
|
||||
|
||||
if ($validator->isValid($domain->get())) {
|
||||
$status = 'verifying';
|
||||
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
'domain' => $rule->getAttribute('domain')
|
||||
]))
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
$rule->setAttribute('status', $status);
|
||||
$rule = $dbForConsole->createDocument('rules', $rule);
|
||||
|
||||
$queueForEvents->setParam('ruleId', $rule->getId());
|
||||
|
||||
$rule->setAttribute('logs', '');
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($rule, Response::MODEL_PROXY_RULE);
|
||||
});
|
||||
|
||||
App::get('/v1/proxy/rules')
|
||||
->groups(['api', 'proxy'])
|
||||
->desc('List rules')
|
||||
->label('scope', 'rules.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'proxy')
|
||||
->label('sdk.method', 'listRules')
|
||||
->label('sdk.description', '/docs/references/proxy/list-rules.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROXY_RULE_LIST)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'proxy',
|
||||
group: null,
|
||||
name: 'listRules',
|
||||
description: '/docs/references/proxy/list-rules.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROXY_RULE_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('queries', [], new Rules(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Rules::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForPlatform) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -191,7 +76,7 @@ App::get('/v1/proxy/rules')
|
||||
}
|
||||
|
||||
$ruleId = $cursor->getValue();
|
||||
$cursorDocument = $dbForConsole->getDocument('rules', $ruleId);
|
||||
$cursorDocument = $dbForPlatform->getDocument('rules', $ruleId);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Rule '{$ruleId}' for the 'cursor' value not found.");
|
||||
@@ -202,16 +87,16 @@ App::get('/v1/proxy/rules')
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$rules = $dbForConsole->find('rules', $queries);
|
||||
$rules = $dbForPlatform->find('rules', $queries);
|
||||
foreach ($rules as $rule) {
|
||||
$certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', ''));
|
||||
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
|
||||
$rule->setAttribute('logs', $certificate->getAttribute('logs', ''));
|
||||
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'rules' => $rules,
|
||||
'total' => $dbForConsole->count('rules', $filterQueries, APP_LIMIT_COUNT),
|
||||
'total' => $dbForPlatform->count('rules', $filterQueries, APP_LIMIT_COUNT),
|
||||
]), Response::MODEL_PROXY_RULE_LIST);
|
||||
});
|
||||
|
||||
@@ -219,25 +104,31 @@ App::get('/v1/proxy/rules/:ruleId')
|
||||
->groups(['api', 'proxy'])
|
||||
->desc('Get rule')
|
||||
->label('scope', 'rules.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'proxy')
|
||||
->label('sdk.method', 'getRule')
|
||||
->label('sdk.description', '/docs/references/proxy/get-rule.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROXY_RULE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'proxy',
|
||||
group: null,
|
||||
name: 'getRule',
|
||||
description: '/docs/references/proxy/get-rule.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROXY_RULE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('ruleId', '', new UID(), 'Rule ID.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $ruleId, Response $response, Document $project, Database $dbForConsole) {
|
||||
$rule = $dbForConsole->getDocument('rules', $ruleId);
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $ruleId, Response $response, Document $project, Database $dbForPlatform) {
|
||||
$rule = $dbForPlatform->getDocument('rules', $ruleId);
|
||||
|
||||
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
|
||||
throw new Exception(Exception::RULE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', ''));
|
||||
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
|
||||
$rule->setAttribute('logs', $certificate->getAttribute('logs', ''));
|
||||
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
|
||||
|
||||
@@ -251,26 +142,34 @@ App::delete('/v1/proxy/rules/:ruleId')
|
||||
->label('event', 'rules.[ruleId].delete')
|
||||
->label('audits.event', 'rules.delete')
|
||||
->label('audits.resource', 'rule/{request.ruleId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'proxy')
|
||||
->label('sdk.method', 'deleteRule')
|
||||
->label('sdk.description', '/docs/references/proxy/delete-rule.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'proxy',
|
||||
group: null,
|
||||
name: 'deleteRule',
|
||||
description: '/docs/references/proxy/delete-rule.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE
|
||||
))
|
||||
->param('ruleId', '', new UID(), 'Rule ID.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $ruleId, Response $response, Document $project, Database $dbForConsole, Delete $queueForDeletes, Event $queueForEvents) {
|
||||
$rule = $dbForConsole->getDocument('rules', $ruleId);
|
||||
->action(function (string $ruleId, Response $response, Document $project, Database $dbForPlatform, Delete $queueForDeletes, Event $queueForEvents) {
|
||||
$rule = $dbForPlatform->getDocument('rules', $ruleId);
|
||||
|
||||
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
|
||||
throw new Exception(Exception::RULE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$dbForConsole->deleteDocument('rules', $rule->getId());
|
||||
$dbForPlatform->deleteDocument('rules', $rule->getId());
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
@@ -288,37 +187,54 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
|
||||
->label('event', 'rules.[ruleId].update')
|
||||
->label('audits.event', 'rule.update')
|
||||
->label('audits.resource', 'rule/{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'proxy')
|
||||
->label('sdk.method', 'updateRuleVerification')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROXY_RULE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'proxy',
|
||||
group: null,
|
||||
name: 'updateRuleVerification',
|
||||
description: '/docs/references/proxy/update-rule-verification.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROXY_RULE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('ruleId', '', new UID(), 'Rule ID.')
|
||||
->inject('response')
|
||||
->inject('queueForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('log')
|
||||
->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForConsole, Log $log) {
|
||||
$rule = $dbForConsole->getDocument('rules', $ruleId);
|
||||
->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForPlatform, Log $log) {
|
||||
$rule = $dbForPlatform->getDocument('rules', $ruleId);
|
||||
|
||||
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
|
||||
throw new Exception(Exception::RULE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
$validators = [];
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
}
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
}
|
||||
|
||||
if (!$target->isKnown() || $target->isTest()) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Domain target must be configured as environment variable.');
|
||||
if (empty($validators)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'At least one of domain targets environment variable must be configured.');
|
||||
}
|
||||
|
||||
if ($rule->getAttribute('verification') === true) {
|
||||
return $response->dynamic($rule, Response::MODEL_PROXY_RULE);
|
||||
}
|
||||
|
||||
$validator = new CNAME($target->get()); // Verify Domain with DNS records
|
||||
$validator = new AnyOf($validators, AnyOf::TYPE_STRING);
|
||||
$domain = new Domain($rule->getAttribute('domain', ''));
|
||||
|
||||
$validationStart = \microtime(true);
|
||||
@@ -326,13 +242,20 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
|
||||
$log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
|
||||
$error = $validator->getLogs();
|
||||
$errors = [];
|
||||
foreach ($validators as $validator) {
|
||||
if (!empty($validator->getLogs())) {
|
||||
$errors[] = $validator->getLogs();
|
||||
}
|
||||
}
|
||||
|
||||
$error = \implode("\n", $errors);
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
|
||||
throw new Exception(Exception::RULE_VERIFICATION_FAILED);
|
||||
}
|
||||
|
||||
$dbForConsole->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying'));
|
||||
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying'));
|
||||
|
||||
// Issue a TLS certificate when domain is verified
|
||||
$queueForCertificates
|
||||
@@ -343,7 +266,7 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
|
||||
|
||||
$queueForEvents->setParam('ruleId', $rule->getId());
|
||||
|
||||
$certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', ''));
|
||||
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
|
||||
$rule->setAttribute('logs', $certificate->getAttribute('logs', ''));
|
||||
|
||||
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
|
||||
|
||||
+275
-128
@@ -8,6 +8,11 @@ use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Buckets;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Files;
|
||||
@@ -15,9 +20,11 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Exception\NotFound as NotFoundException;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
@@ -54,13 +61,19 @@ App::post('/v1/storage/buckets')
|
||||
->label('event', 'buckets.[bucketId].create')
|
||||
->label('audits.event', 'bucket.create')
|
||||
->label('audits.resource', 'bucket/{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'createBucket')
|
||||
->label('sdk.description', '/docs/references/storage/create-bucket.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BUCKET)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'buckets',
|
||||
name: 'createBucket',
|
||||
description: '/docs/references/storage/create-bucket.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_BUCKET,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('bucketId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Bucket name')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
@@ -74,13 +87,14 @@ App::post('/v1/storage/buckets')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, ?string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, ?string $compression, ?bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
|
||||
$bucketId = $bucketId === 'unique()' ? ID::unique() : $bucketId;
|
||||
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions);
|
||||
$compression ??= Compression::NONE;
|
||||
$encryption ??= true;
|
||||
try {
|
||||
$files = (Config::getParam('collections', [])['buckets'] ?? [])['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
@@ -150,13 +164,19 @@ App::get('/v1/storage/buckets')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'buckets.read')
|
||||
->label('resourceType', RESOURCE_TYPE_BUCKETS)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'listBuckets')
|
||||
->label('sdk.description', '/docs/references/storage/list-buckets.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BUCKET_LIST)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'buckets',
|
||||
name: 'listBuckets',
|
||||
description: '/docs/references/storage/list-buckets.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_BUCKET_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('queries', [], new Buckets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Buckets::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
@@ -199,10 +219,15 @@ App::get('/v1/storage/buckets')
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
try {
|
||||
$buckets = $dbForProject->find('buckets', $queries);
|
||||
$total = $dbForProject->count('buckets', $filterQueries, APP_LIMIT_COUNT);
|
||||
} catch (OrderException $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
$response->dynamic(new Document([
|
||||
'buckets' => $dbForProject->find('buckets', $queries),
|
||||
'total' => $dbForProject->count('buckets', $filterQueries, APP_LIMIT_COUNT),
|
||||
'buckets' => $buckets,
|
||||
'total' => $total,
|
||||
]), Response::MODEL_BUCKET_LIST);
|
||||
});
|
||||
|
||||
@@ -211,13 +236,19 @@ App::get('/v1/storage/buckets/:bucketId')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'buckets.read')
|
||||
->label('resourceType', RESOURCE_TYPE_BUCKETS)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getBucket')
|
||||
->label('sdk.description', '/docs/references/storage/get-bucket.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BUCKET)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'buckets',
|
||||
name: 'getBucket',
|
||||
description: '/docs/references/storage/get-bucket.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_BUCKET,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Bucket unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -240,13 +271,19 @@ App::put('/v1/storage/buckets/:bucketId')
|
||||
->label('event', 'buckets.[bucketId].update')
|
||||
->label('audits.event', 'bucket.update')
|
||||
->label('audits.resource', 'bucket/{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'updateBucket')
|
||||
->label('sdk.description', '/docs/references/storage/update-bucket.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BUCKET)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'buckets',
|
||||
name: 'updateBucket',
|
||||
description: '/docs/references/storage/update-bucket.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_BUCKET,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Bucket unique ID.')
|
||||
->param('name', null, new Text(128), 'Bucket name', false)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
@@ -260,7 +297,7 @@ App::put('/v1/storage/buckets/:bucketId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, ?string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, ?string $compression, ?bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
||||
if ($bucket->isEmpty()) {
|
||||
@@ -305,12 +342,20 @@ App::delete('/v1/storage/buckets/:bucketId')
|
||||
->label('audits.event', 'bucket.delete')
|
||||
->label('event', 'buckets.[bucketId].delete')
|
||||
->label('audits.resource', 'bucket/{request.bucketId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'deleteBucket')
|
||||
->label('sdk.description', '/docs/references/storage/delete-bucket.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'buckets',
|
||||
name: 'deleteBucket',
|
||||
description: '/docs/references/storage/delete-bucket.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Bucket unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -351,15 +396,21 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId},chunkId:{chunkId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'createFile')
|
||||
->label('sdk.description', '/docs/references/storage/create-file.md')
|
||||
->label('sdk.request.type', 'multipart/form-data')
|
||||
->label('sdk.methodType', 'upload')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_FILE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'files',
|
||||
name: 'createFile',
|
||||
description: '/docs/references/storage/create-file.md',
|
||||
type: MethodType::UPLOAD,
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
requestType: 'multipart/form-data',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_FILE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new CustomId(), 'File ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('file', [], new File(), 'Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/products/storage/upload-download#input-file).', skipValidation: true)
|
||||
@@ -715,13 +766,19 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', RESOURCE_TYPE_BUCKETS)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'listFiles')
|
||||
->label('sdk.description', '/docs/references/storage/list-files.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_FILE_LIST)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'files',
|
||||
name: 'listFiles',
|
||||
description: '/docs/references/storage/list-files.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_FILE_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('queries', [], new Files(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Files::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
@@ -793,6 +850,8 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
||||
}
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
} catch (OrderException $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
@@ -807,13 +866,19 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', RESOURCE_TYPE_BUCKETS)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getFile')
|
||||
->label('sdk.description', '/docs/references/storage/get-file.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_FILE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'files',
|
||||
name: 'getFile',
|
||||
description: '/docs/references/storage/get-file.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_FILE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new UID(), 'File ID.')
|
||||
->inject('response')
|
||||
@@ -858,13 +923,21 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'bucket/{request.bucketId}')
|
||||
->label('cache.resource', 'file/{request.fileId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getFilePreview')
|
||||
->label('sdk.description', '/docs/references/storage/get-file-preview.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
|
||||
->label('sdk.methodType', 'location')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'files',
|
||||
name: 'getFilePreview',
|
||||
description: '/docs/references/storage/get-file-preview.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE
|
||||
)
|
||||
],
|
||||
type: MethodType::LOCATION,
|
||||
contentType: ContentType::IMAGE
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new UID(), 'File ID')
|
||||
->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true)
|
||||
@@ -878,14 +951,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
->param('rotation', 0, new Range(-360, 360), 'Preview image rotation in degrees. Pass an integer between -360 and 360.', true)
|
||||
->param('background', '', new HexColor(), 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true)
|
||||
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('resourceToken')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForLocal')
|
||||
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal) {
|
||||
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Response $response, Database $dbForProject, Document $resourceToken, Device $deviceForFiles, Device $deviceForLocal) {
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
@@ -900,19 +971,24 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getInternalId();
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
if (!$fileSecurity && !$valid && !$isToken) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
if ($fileSecurity && !$valid && !$isToken) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getInternalId()) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
@@ -1013,11 +1089,19 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
|
||||
$contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg'];
|
||||
|
||||
//Do not update transformedAt if it's a console user
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
$transformedAt = $file->getAttribute('transformedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
|
||||
$file->setAttribute('transformedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
|
||||
}
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType($contentType)
|
||||
->file($data)
|
||||
;
|
||||
->file($data);
|
||||
|
||||
unset($image);
|
||||
});
|
||||
@@ -1028,21 +1112,30 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', RESOURCE_TYPE_BUCKETS)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getFileDownload')
|
||||
->label('sdk.description', '/docs/references/storage/get-file-download.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', '*/*')
|
||||
->label('sdk.methodType', 'location')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'files',
|
||||
name: 'getFileDownload',
|
||||
description: '/docs/references/storage/get-file-download.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE
|
||||
)
|
||||
],
|
||||
type: MethodType::LOCATION,
|
||||
contentType: ContentType::ANY,
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Storage bucket ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new UID(), 'File ID.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('resourceToken')
|
||||
->inject('deviceForFiles')
|
||||
->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceForFiles) {
|
||||
->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles) {
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
@@ -1053,10 +1146,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getInternalId();
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
if (!$fileSecurity && !$valid && !$isToken) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@@ -1066,6 +1160,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getInternalId()) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
@@ -1169,21 +1267,30 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', RESOURCE_TYPE_BUCKETS)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getFileView')
|
||||
->label('sdk.description', '/docs/references/storage/get-file-view.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', '*/*')
|
||||
->label('sdk.methodType', 'location')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'files',
|
||||
name: 'getFileView',
|
||||
description: '/docs/references/storage/get-file-view.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
type: MethodType::LOCATION,
|
||||
contentType: ContentType::ANY,
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new UID(), 'File ID.')
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('resourceToken')
|
||||
->inject('deviceForFiles')
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceForFiles) {
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
@@ -1193,10 +1300,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getInternalId();
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
if (!$fileSecurity && !$valid && !$isToken) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@@ -1206,6 +1314,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getInternalId()) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
@@ -1321,9 +1433,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'public')
|
||||
->label('resourceType', RESOURCE_TYPE_BUCKETS)
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', '*/*')
|
||||
->label('sdk.methodType', 'location')
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new UID(), 'File ID.')
|
||||
->param('jwt', '', new Text(2048, 0), 'JSON Web Token to validate', true)
|
||||
@@ -1482,13 +1591,19 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'updateFile')
|
||||
->label('sdk.description', '/docs/references/storage/update-file.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_FILE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'files',
|
||||
name: 'updateFile',
|
||||
description: '/docs/references/storage/update-file.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_FILE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new UID(), 'File unique ID.')
|
||||
->param('name', null, new Text(255), 'Name of the file', true)
|
||||
@@ -1591,12 +1706,20 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'deleteFile')
|
||||
->label('sdk.description', '/docs/references/storage/delete-file.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'files',
|
||||
name: 'deleteFile',
|
||||
description: '/docs/references/storage/delete-file.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new UID(), 'File ID.')
|
||||
->inject('response')
|
||||
@@ -1678,17 +1801,25 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
/** Storage usage */
|
||||
App::get('/v1/storage/usage')
|
||||
->desc('Get storage usage stats')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', RESOURCE_TYPE_BUCKETS)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getUsage')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USAGE_STORAGE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: null,
|
||||
name: 'getUsage',
|
||||
description: '/docs/references/storage/get-usage.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_USAGE_STORAGE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -1763,18 +1894,28 @@ App::get('/v1/storage/:bucketId/usage')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', RESOURCE_TYPE_BUCKETS)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getBucketUsage')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USAGE_BUCKETS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: null,
|
||||
name: 'getBucketUsage',
|
||||
description: '/docs/references/storage/get-bucket-usage.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_USAGE_BUCKETS,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Bucket ID.')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $bucketId, string $range, Response $response, Database $dbForProject) {
|
||||
->inject('getLogsDB')
|
||||
->action(function (string $bucketId, string $range, Response $response, Document $project, Database $dbForProject, callable $getLogsDB) {
|
||||
|
||||
$dbForLogs = call_user_func($getLogsDB, $project);
|
||||
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
||||
if ($bucket->isEmpty()) {
|
||||
@@ -1787,12 +1928,16 @@ App::get('/v1/storage/:bucketId/usage')
|
||||
$metrics = [
|
||||
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES),
|
||||
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE),
|
||||
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED),
|
||||
];
|
||||
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
|
||||
Authorization::skip(function () use ($dbForProject, $dbForLogs, $bucket, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
$db = ($metric === str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED))
|
||||
? $dbForLogs
|
||||
: $dbForProject;
|
||||
|
||||
$result = $db->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
@@ -1800,7 +1945,7 @@ App::get('/v1/storage/:bucketId/usage')
|
||||
$stats[$metric]['total'] = $result['value'] ?? 0;
|
||||
$limit = $days['limit'];
|
||||
$period = $days['period'];
|
||||
$results = $dbForProject->find('stats', [
|
||||
$results = $db->find('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', [$period]),
|
||||
Query::limit($limit),
|
||||
@@ -1841,5 +1986,7 @@ App::get('/v1/storage/:bucketId/usage')
|
||||
'filesStorageTotal' => $usage[$metrics[1]]['total'],
|
||||
'files' => $usage[$metrics[0]]['data'],
|
||||
'storage' => $usage[$metrics[1]]['data'],
|
||||
'imageTransformations' => $usage[$metrics[2]]['data'],
|
||||
'imageTransformationsTotal' => $usage[$metrics[2]]['total'],
|
||||
]), Response::MODEL_USAGE_BUCKETS);
|
||||
});
|
||||
|
||||
+320
-173
@@ -8,16 +8,23 @@ use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Platform\Workers\Deletes;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Memberships;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Teams;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Config\Config;
|
||||
@@ -26,6 +33,7 @@ use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
@@ -44,6 +52,7 @@ use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
App::post('/v1/teams')
|
||||
@@ -53,13 +62,19 @@ App::post('/v1/teams')
|
||||
->label('scope', 'teams.write')
|
||||
->label('audits.event', 'team.create')
|
||||
->label('audits.resource', 'team/{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'create')
|
||||
->label('sdk.description', '/docs/references/teams/create-team.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEAM)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'teams',
|
||||
name: 'create',
|
||||
description: '/docs/references/teams/create-team.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_TEAM,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('teamId', '', new CustomId(), 'Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', null, new Text(128), 'Team name. Max length: 128 chars.')
|
||||
->param('roles', ['owner'], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', true)
|
||||
@@ -138,14 +153,19 @@ App::get('/v1/teams')
|
||||
->desc('List teams')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'list')
|
||||
->label('sdk.description', '/docs/references/teams/list-teams.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEAM_LIST)
|
||||
->label('sdk.offline.model', '/teams')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'teams',
|
||||
name: 'list',
|
||||
description: '/docs/references/teams/list-teams.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_TEAM_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('queries', [], new Teams(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Teams::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
@@ -188,9 +208,12 @@ App::get('/v1/teams')
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$results = $dbForProject->find('teams', $queries);
|
||||
$total = $dbForProject->count('teams', $filterQueries, APP_LIMIT_COUNT);
|
||||
try {
|
||||
$results = $dbForProject->find('teams', $queries);
|
||||
$total = $dbForProject->count('teams', $filterQueries, APP_LIMIT_COUNT);
|
||||
} catch (OrderException $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'teams' => $results,
|
||||
@@ -202,15 +225,19 @@ App::get('/v1/teams/:teamId')
|
||||
->desc('Get team')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'get')
|
||||
->label('sdk.description', '/docs/references/teams/get-team.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEAM)
|
||||
->label('sdk.offline.model', '/teams')
|
||||
->label('sdk.offline.key', '{teamId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'teams',
|
||||
name: 'get',
|
||||
description: '/docs/references/teams/get-team.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_TEAM,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -229,14 +256,19 @@ App::get('/v1/teams/:teamId/prefs')
|
||||
->desc('Get team preferences')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'getPrefs')
|
||||
->label('sdk.description', '/docs/references/teams/get-team-prefs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PREFERENCES)
|
||||
->label('sdk.offline.model', '/teams/{teamId}/prefs')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'teams',
|
||||
name: 'getPrefs',
|
||||
description: '/docs/references/teams/get-team-prefs.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PREFERENCES,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -260,15 +292,19 @@ App::put('/v1/teams/:teamId')
|
||||
->label('scope', 'teams.write')
|
||||
->label('audits.event', 'team.update')
|
||||
->label('audits.resource', 'team/{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'updateName')
|
||||
->label('sdk.description', '/docs/references/teams/update-team-name.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEAM)
|
||||
->label('sdk.offline.model', '/teams')
|
||||
->label('sdk.offline.key', '{teamId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'teams',
|
||||
name: 'updateName',
|
||||
description: '/docs/references/teams/update-team-name.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_TEAM,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
|
||||
->inject('requestTimestamp')
|
||||
@@ -304,14 +340,19 @@ App::put('/v1/teams/:teamId/prefs')
|
||||
->label('audits.event', 'team.update')
|
||||
->label('audits.resource', 'team/{response.$id}')
|
||||
->label('audits.userId', '{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'updatePrefs')
|
||||
->label('sdk.description', '/docs/references/teams/update-team-prefs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PREFERENCES)
|
||||
->label('sdk.offline.model', '/teams/{teamId}/prefs')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'teams',
|
||||
name: 'updatePrefs',
|
||||
description: '/docs/references/teams/update-team-prefs.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PREFERENCES,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
|
||||
->inject('response')
|
||||
@@ -339,12 +380,20 @@ App::delete('/v1/teams/:teamId')
|
||||
->label('scope', 'teams.write')
|
||||
->label('audits.event', 'team.delete')
|
||||
->label('audits.resource', 'team/{request.teamId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'delete')
|
||||
->label('sdk.description', '/docs/references/teams/delete-team.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'teams',
|
||||
name: 'delete',
|
||||
description: '/docs/references/teams/delete-team.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE
|
||||
))
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->inject('response')
|
||||
->inject('getProjectDB')
|
||||
@@ -390,13 +439,19 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->label('audits.event', 'membership.create')
|
||||
->label('audits.resource', 'team/{request.teamId}')
|
||||
->label('audits.userId', '{request.userId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'createMembership')
|
||||
->label('sdk.description', '/docs/references/teams/create-team-membership.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'memberships',
|
||||
name: 'createMembership',
|
||||
description: '/docs/references/teams/create-team-membership.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_MEMBERSHIP,
|
||||
)
|
||||
]
|
||||
))
|
||||
->label('abuse-limit', 10)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('email', '', new Email(), 'Email of the new team member.', true)
|
||||
@@ -413,7 +468,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
}
|
||||
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
|
||||
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // TODO add our own built-in confirm page
|
||||
->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey']) // TODO add our own built-in confirm page
|
||||
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
@@ -423,13 +478,16 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents) {
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
->inject('timelimit')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
$url = htmlentities($url);
|
||||
if (empty($url)) {
|
||||
if (!$isAPIKey && !$isPrivilegedUser) {
|
||||
if (!$isAppUser && !$isPrivilegedUser) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'URL is required');
|
||||
}
|
||||
}
|
||||
@@ -437,15 +495,13 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
if (empty($userId) && empty($email) && empty($phone)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'At least one of userId, email, or phone is required');
|
||||
}
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser && empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED);
|
||||
}
|
||||
|
||||
$email = \strtolower($email);
|
||||
$name = (empty($name)) ? $email : $name;
|
||||
$name = empty($name) ? $email : $name;
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
@@ -464,7 +520,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
}
|
||||
$email = $invitee->getAttribute('email', '');
|
||||
$phone = $invitee->getAttribute('phone', '');
|
||||
$name = empty($name) ? $invitee->getAttribute('name', '') : $name;
|
||||
$name = $invitee->getAttribute('name', '') ?: $name;
|
||||
} elseif (!empty($email)) {
|
||||
$invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address
|
||||
if (!$invitee->isEmpty() && !empty($phone) && $invitee->getAttribute('phone', '') !== $phone) {
|
||||
@@ -540,49 +596,64 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team');
|
||||
}
|
||||
|
||||
$secret = Auth::tokenGenerator();
|
||||
|
||||
$membershipId = ID::unique();
|
||||
$membership = new Document([
|
||||
'$id' => $membershipId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::user($invitee->getId())),
|
||||
Permission::update(Role::team($team->getId(), 'owner')),
|
||||
Permission::delete(Role::user($invitee->getId())),
|
||||
Permission::delete(Role::team($team->getId(), 'owner')),
|
||||
],
|
||||
'userId' => $invitee->getId(),
|
||||
'userInternalId' => $invitee->getInternalId(),
|
||||
'teamId' => $team->getId(),
|
||||
'teamInternalId' => $team->getInternalId(),
|
||||
'roles' => $roles,
|
||||
'invited' => DateTime::now(),
|
||||
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
|
||||
'confirm' => ($isPrivilegedUser || $isAppUser),
|
||||
'secret' => Auth::hash($secret),
|
||||
'search' => implode(' ', [$membershipId, $invitee->getId()])
|
||||
$membership = $dbForProject->findOne('memberships', [
|
||||
Query::equal('userInternalId', [$invitee->getInternalId()]),
|
||||
Query::equal('teamInternalId', [$team->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership
|
||||
try {
|
||||
$membership = Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
|
||||
$secret = Auth::tokenGenerator();
|
||||
if ($membership->isEmpty()) {
|
||||
$membershipId = ID::unique();
|
||||
$membership = new Document([
|
||||
'$id' => $membershipId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::user($invitee->getId())),
|
||||
Permission::update(Role::team($team->getId(), 'owner')),
|
||||
Permission::delete(Role::user($invitee->getId())),
|
||||
Permission::delete(Role::team($team->getId(), 'owner')),
|
||||
],
|
||||
'userId' => $invitee->getId(),
|
||||
'userInternalId' => $invitee->getInternalId(),
|
||||
'teamId' => $team->getId(),
|
||||
'teamInternalId' => $team->getInternalId(),
|
||||
'roles' => $roles,
|
||||
'invited' => DateTime::now(),
|
||||
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
|
||||
'confirm' => ($isPrivilegedUser || $isAppUser),
|
||||
'secret' => Auth::hash($secret),
|
||||
'search' => implode(' ', [$membershipId, $invitee->getId()])
|
||||
]);
|
||||
|
||||
$membership = ($isPrivilegedUser || $isAppUser) ?
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
|
||||
$dbForProject->createDocument('memberships', $membership);
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) {
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
}
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
} elseif ($membership->getAttribute('confirm') === false) {
|
||||
$membership->setAttribute('secret', Auth::hash($secret));
|
||||
$membership->setAttribute('invited', DateTime::now());
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) {
|
||||
$membership->setAttribute('joined', DateTime::now());
|
||||
$membership->setAttribute('confirm', true);
|
||||
}
|
||||
|
||||
$membership = ($isPrivilegedUser || $isAppUser) ?
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) :
|
||||
$dbForProject->updateDocument('memberships', $membership->getId(), $membership);
|
||||
} else {
|
||||
throw new Exception(Exception::MEMBERSHIP_ALREADY_CONFIRMED);
|
||||
}
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) {
|
||||
$dbForProject->purgeCachedDocument('users', $invitee->getId());
|
||||
} else {
|
||||
try {
|
||||
$membership = $dbForProject->createDocument('memberships', $membership);
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$url = Template::parseURL($url);
|
||||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
|
||||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId, 'teamName' => $team->getAttribute('name')]);
|
||||
$url = Template::unParseURL($url);
|
||||
if (!empty($email)) {
|
||||
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
|
||||
@@ -650,7 +721,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
'owner' => $user->getAttribute('name'),
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
/* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */
|
||||
'user' => $user->getAttribute('name'),
|
||||
'user' => $name,
|
||||
'team' => $team->getAttribute('name'),
|
||||
'redirect' => $url,
|
||||
'project' => $projectName
|
||||
@@ -660,10 +731,10 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setRecipient($invitee->getAttribute('email'))
|
||||
->setName($invitee->getAttribute('name'))
|
||||
->setName($invitee->getAttribute('name', ''))
|
||||
->setVariables($emailVariables)
|
||||
->trigger()
|
||||
;
|
||||
->trigger();
|
||||
|
||||
} elseif (!empty($phone)) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
@@ -691,6 +762,27 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$phone])
|
||||
->setProviderType('SMS');
|
||||
|
||||
if (isset($plan['authPhone'])) {
|
||||
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
|
||||
$timelimit
|
||||
->setParam('{organizationId}', $project->getAttribute('teamId'));
|
||||
|
||||
$abuse = new Abuse($timelimit);
|
||||
if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
$countryCode = $helper->parse($phone)->getCountryCode();
|
||||
|
||||
if (!empty($countryCode)) {
|
||||
$queueForStatsUsage
|
||||
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
|
||||
}
|
||||
}
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,14 +807,19 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
->desc('List team memberships')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'listMemberships')
|
||||
->label('sdk.description', '/docs/references/teams/list-team-members.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST)
|
||||
->label('sdk.offline.model', '/teams/{teamId}/memberships')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'memberships',
|
||||
name: 'listMemberships',
|
||||
description: '/docs/references/teams/list-team-members.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_MEMBERSHIP_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
@@ -776,17 +873,20 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
try {
|
||||
$memberships = $dbForProject->find(
|
||||
collection: 'memberships',
|
||||
queries: $queries,
|
||||
);
|
||||
$total = $dbForProject->count(
|
||||
collection: 'memberships',
|
||||
queries: $filterQueries,
|
||||
max: APP_LIMIT_COUNT
|
||||
);
|
||||
} catch (OrderException $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$memberships = $dbForProject->find(
|
||||
collection: 'memberships',
|
||||
queries: $queries,
|
||||
);
|
||||
|
||||
$total = $dbForProject->count(
|
||||
collection: 'memberships',
|
||||
queries: $filterQueries,
|
||||
max: APP_LIMIT_COUNT
|
||||
);
|
||||
|
||||
$memberships = array_filter($memberships, fn (Document $membership) => !empty($membership->getAttribute('userId')));
|
||||
|
||||
@@ -849,15 +949,19 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->desc('Get team membership')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'getMembership')
|
||||
->label('sdk.description', '/docs/references/teams/get-team-member.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
|
||||
->label('sdk.offline.model', '/teams/{teamId}/memberships')
|
||||
->label('sdk.offline.key', '{membershipId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'memberships',
|
||||
name: 'getMembership',
|
||||
description: '/docs/references/teams/get-team-member.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_MEMBERSHIP,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||
->inject('response')
|
||||
@@ -932,18 +1036,23 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->label('scope', 'teams.write')
|
||||
->label('audits.event', 'membership.update')
|
||||
->label('audits.resource', 'team/{request.teamId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'updateMembership')
|
||||
->label('sdk.description', '/docs/references/teams/update-team-membership.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'memberships',
|
||||
name: 'updateMembership',
|
||||
description: '/docs/references/teams/update-team-membership.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_MEMBERSHIP,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||
->param('roles', [], function (Document $project) {
|
||||
if ($project->getId() === 'console') {
|
||||
;
|
||||
$roles = array_keys(Config::getParam('roles', []));
|
||||
array_filter($roles, function ($role) {
|
||||
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
|
||||
@@ -955,9 +1064,10 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
if ($team->isEmpty()) {
|
||||
@@ -978,6 +1088,21 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
// Quick check: fetch up to 2 owners to determine if only one exists
|
||||
$ownersCount = $dbForProject->count(
|
||||
collection: 'memberships',
|
||||
queries: [Query::contains('roles', ['owner'])],
|
||||
max: 2
|
||||
);
|
||||
|
||||
// Prevent role change if there's only one owner left,
|
||||
// the requester is that owner, and the new `$roles` no longer include 'owner'!
|
||||
if ($ownersCount === 1 && $isOwner && !\in_array('owner', $roles)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'There must be at least one owner in the organization.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to modify roles');
|
||||
}
|
||||
@@ -1015,13 +1140,19 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->label('audits.event', 'membership.update')
|
||||
->label('audits.resource', 'team/{request.teamId}')
|
||||
->label('audits.userId', '{request.userId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'updateMembershipStatus')
|
||||
->label('sdk.description', '/docs/references/teams/update-team-membership-status.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'memberships',
|
||||
name: 'updateMembershipStatus',
|
||||
description: '/docs/references/teams/update-team-membership-status.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_MEMBERSHIP,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
@@ -1167,12 +1298,20 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->label('scope', 'teams.write')
|
||||
->label('audits.event', 'membership.delete')
|
||||
->label('audits.resource', 'team/{request.teamId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'deleteMembership')
|
||||
->label('sdk.description', '/docs/references/teams/delete-team-membership.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'memberships',
|
||||
name: 'deleteMembership',
|
||||
description: '/docs/references/teams/delete-team-membership.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE
|
||||
))
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||
->inject('response')
|
||||
@@ -1230,13 +1369,19 @@ App::get('/v1/teams/:teamId/logs')
|
||||
->desc('List team logs')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'listLogs')
|
||||
->label('sdk.description', '/docs/references/teams/get-team-logs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LOG_LIST)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'teams',
|
||||
group: 'logs',
|
||||
name: 'listLogs',
|
||||
description: '/docs/references/teams/get-team-logs.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_LOG_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
@@ -1257,13 +1402,15 @@ App::get('/v1/teams/:teamId/logs')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'team/' . $team->getId();
|
||||
$logs = $audit->getLogsByResource($resource, $limit, $offset);
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
|
||||
$output = [];
|
||||
|
||||
@@ -1310,7 +1457,7 @@ App::get('/v1/teams/:teamId/logs')
|
||||
}
|
||||
}
|
||||
$response->dynamic(new Document([
|
||||
'total' => $audit->countLogsByResource($resource),
|
||||
'total' => $audit->countLogsByResource($resource, $queries),
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
+631
-314
File diff suppressed because it is too large
Load Diff
+630
-309
File diff suppressed because it is too large
Load Diff
+691
-189
File diff suppressed because it is too large
Load Diff
+11
-11
@@ -24,7 +24,7 @@ App::get('/v1/mock/tests/general/oauth2')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->label('sdk.mock', true)
|
||||
->label('mock', true)
|
||||
->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
|
||||
->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.') // Important to deny an open redirect attack
|
||||
->param('scope', '', new Text(100), 'OAuth2 scope list.')
|
||||
@@ -40,7 +40,7 @@ App::get('/v1/mock/tests/general/oauth2/token')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->label('sdk.mock', true)
|
||||
->label('mock', true)
|
||||
->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
|
||||
->param('client_secret', '', new Text(100), 'OAuth2 scope list.')
|
||||
->param('grant_type', 'authorization_code', new WhiteList(['refresh_token', 'authorization_code']), 'OAuth2 Grant Type.', true)
|
||||
@@ -162,15 +162,15 @@ App::post('/v1/mock/api-key-unprefixed')
|
||||
->label('docs', false)
|
||||
->param('projectId', '', new UID(), 'Project ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform) {
|
||||
$isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development';
|
||||
|
||||
if (!$isDevelopment) {
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
@@ -195,9 +195,9 @@ App::post('/v1/mock/api-key-unprefixed')
|
||||
'secret' => \bin2hex(\random_bytes(128)),
|
||||
]);
|
||||
|
||||
$key = $dbForConsole->createDocument('keys', $key);
|
||||
$key = $dbForPlatform->createDocument('keys', $key);
|
||||
|
||||
$dbForConsole->purgeCachedDocument('projects', $project->getId());
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -214,15 +214,15 @@ App::get('/v1/mock/github/callback')
|
||||
->inject('gitHub')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForPlatform) {
|
||||
$isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development';
|
||||
|
||||
if (!$isDevelopment) {
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
$error = 'Project with the ID from state could not be found.';
|
||||
@@ -256,7 +256,7 @@ App::get('/v1/mock/github/callback')
|
||||
'personal' => false
|
||||
]);
|
||||
|
||||
$installation = $dbForConsole->createDocument('installations', $installation);
|
||||
$installation = $dbForPlatform->createDocument('installations', $installation);
|
||||
}
|
||||
|
||||
$response->json([
|
||||
|
||||
+252
-209
@@ -1,8 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Build;
|
||||
@@ -12,14 +11,14 @@ use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Usage;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\Database\TimeLimit;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Utopia\Cache\Cache;
|
||||
@@ -29,7 +28,7 @@ use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -59,7 +58,7 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
|
||||
return $label;
|
||||
};
|
||||
|
||||
$eventDatabaseListener = function (Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) {
|
||||
$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) {
|
||||
// Only trigger events for user creation with the database listener.
|
||||
if ($document->getCollection() !== 'users') {
|
||||
return;
|
||||
@@ -75,20 +74,23 @@ $eventDatabaseListener = function (Document $document, Response $response, Event
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
if ($queueForEvents->getProject()->getId() === 'console') {
|
||||
return;
|
||||
/** Trigger webhooks events only if a project has them enabled */
|
||||
if (!empty($project->getAttribute('webhooks'))) {
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$queueForRealtime
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
/** Trigger realtime events only for non console events */
|
||||
if ($queueForEvents->getProject()->getId() !== 'console') {
|
||||
$queueForRealtime
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
};
|
||||
|
||||
$usageDatabaseListener = function (string $event, Document $document, Usage $queueForUsage) {
|
||||
$usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) {
|
||||
$value = 1;
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$value = -1;
|
||||
@@ -96,84 +98,87 @@ $usageDatabaseListener = function (string $event, Document $document, Usage $que
|
||||
|
||||
switch (true) {
|
||||
case $document->getCollection() === 'teams':
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_TEAMS, $value); // per project
|
||||
$queueForStatsUsage->addMetric(METRIC_TEAMS, $value); // per project
|
||||
break;
|
||||
case $document->getCollection() === 'users':
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_USERS, $value); // per project
|
||||
$queueForStatsUsage->addMetric(METRIC_USERS, $value); // per project
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForUsage
|
||||
->addReduce($document);
|
||||
$queueForStatsUsage->addReduce($document);
|
||||
}
|
||||
break;
|
||||
case $document->getCollection() === 'sessions': // sessions
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_SESSIONS, $value); //per project
|
||||
$queueForStatsUsage->addMetric(METRIC_SESSIONS, $value); //per project
|
||||
break;
|
||||
case $document->getCollection() === 'databases': // databases
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_DATABASES, $value); // per project
|
||||
$queueForStatsUsage->addMetric(METRIC_DATABASES, $value); // per project
|
||||
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForUsage
|
||||
->addReduce($document);
|
||||
$queueForStatsUsage->addReduce($document);
|
||||
}
|
||||
break;
|
||||
case str_starts_with($document->getCollection(), 'database_') && !str_contains($document->getCollection(), 'collection'): //collections
|
||||
$parts = explode('_', $document->getCollection());
|
||||
$databaseInternalId = $parts[1] ?? 0;
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_COLLECTIONS, $value) // per project
|
||||
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value)
|
||||
;
|
||||
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value);
|
||||
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForUsage
|
||||
->addReduce($document);
|
||||
$queueForStatsUsage->addReduce($document);
|
||||
}
|
||||
break;
|
||||
case str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_'): //documents
|
||||
$parts = explode('_', $document->getCollection());
|
||||
$databaseInternalId = $parts[1] ?? 0;
|
||||
$collectionInternalId = $parts[3] ?? 0;
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DOCUMENTS, $value) // per project
|
||||
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS), $value) // per database
|
||||
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), $value); // per collection
|
||||
break;
|
||||
case $document->getCollection() === 'buckets': //buckets
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_BUCKETS, $value); // per project
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addReduce($document);
|
||||
}
|
||||
break;
|
||||
case str_starts_with($document->getCollection(), 'bucket_'): // files
|
||||
$parts = explode('_', $document->getCollection());
|
||||
$bucketInternalId = $parts[1];
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_FILES, $value) // per project
|
||||
->addMetric(METRIC_FILES_STORAGE, $document->getAttribute('sizeOriginal') * $value) // per project
|
||||
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES), $value) // per bucket
|
||||
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES_STORAGE), $document->getAttribute('sizeOriginal') * $value); // per bucket
|
||||
break;
|
||||
case $document->getCollection() === 'functions':
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_FUNCTIONS, $value); // per project
|
||||
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addReduce($document);
|
||||
}
|
||||
break;
|
||||
case $document->getCollection() === 'sites':
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_SITES, $value); // per project
|
||||
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$queueForStatsUsage
|
||||
->addReduce($document);
|
||||
}
|
||||
break;
|
||||
case $document->getCollection() === 'deployments':
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DEPLOYMENTS, $value) // per project
|
||||
->addMetric(METRIC_DEPLOYMENTS_STORAGE, $document->getAttribute('size') * $value) // per project
|
||||
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_FUNCTION_ID_DEPLOYMENTS), $value) // per function
|
||||
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value);
|
||||
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS), $value) // per function
|
||||
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value)
|
||||
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS), $value) // per function
|
||||
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -184,127 +189,98 @@ App::init()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForAudits')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('session')
|
||||
->inject('servers')
|
||||
->inject('mode')
|
||||
->inject('team')
|
||||
->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) {
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey) {
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
/** Default role */
|
||||
$roles = Config::getParam('roles', []);
|
||||
$role = ($user->isEmpty())
|
||||
|
||||
$role = $user->isEmpty()
|
||||
? Role::guests()->toString()
|
||||
: Role::users()->toString();
|
||||
|
||||
/** Allowed Scopes for the role */
|
||||
$scopes = $roles[$role]['scopes'];
|
||||
|
||||
$apiKey = $request->getHeader('x-appwrite-key', '');
|
||||
|
||||
// API Key authentication
|
||||
if (!empty($apiKey)) {
|
||||
// Do not allow API key and session to be set at the same time
|
||||
if (!$user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
|
||||
}
|
||||
|
||||
// Remove after migration
|
||||
if (!\str_contains($apiKey, '_')) {
|
||||
$keyType = API_KEY_STANDARD;
|
||||
$authKey = $apiKey;
|
||||
} else {
|
||||
[ $keyType, $authKey ] = \explode('_', $apiKey, 2);
|
||||
if ($apiKey->isExpired()) {
|
||||
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
if ($keyType === API_KEY_DYNAMIC) {
|
||||
// Dynamic key
|
||||
$role = $apiKey->getRole();
|
||||
$scopes = $apiKey->getScopes();
|
||||
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
|
||||
// Disable authorization checks for API keys
|
||||
Authorization::setDefaultStatus(false);
|
||||
|
||||
try {
|
||||
$payload = $jwtObj->decode($authKey);
|
||||
} catch (JWTException $error) {
|
||||
throw new Exception(Exception::API_KEY_EXPIRED);
|
||||
}
|
||||
if ($apiKey->getRole() === Auth::USER_ROLE_APPS) {
|
||||
$user = new Document([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'type' => Auth::ACTIVITY_TYPE_APP,
|
||||
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => $apiKey->getName(),
|
||||
]);
|
||||
|
||||
$projectId = $payload['projectId'] ?? '';
|
||||
$tokenScopes = $payload['scopes'] ?? [];
|
||||
|
||||
// JWT includes project ID for better security
|
||||
if ($projectId === $project->getId()) {
|
||||
$user = new Document([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => $project->getAttribute('name', 'Untitled'),
|
||||
]);
|
||||
|
||||
$role = Auth::USER_ROLE_APPS;
|
||||
$scopes = \array_merge($roles[$role]['scopes'], $tokenScopes);
|
||||
|
||||
Authorization::setRole(Auth::USER_ROLE_APPS);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
}
|
||||
} elseif ($keyType === API_KEY_STANDARD) {
|
||||
// No underline means no prefix. Backwards compatibility.
|
||||
// Regular key
|
||||
|
||||
// Check if given key match project API keys
|
||||
$key = $project->find('secret', $apiKey, 'keys');
|
||||
if ($key) {
|
||||
$user = new Document([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => $project->getAttribute('name', 'Untitled'),
|
||||
]);
|
||||
|
||||
$role = Auth::USER_ROLE_APPS;
|
||||
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
|
||||
|
||||
$expire = $key->getAttribute('expire');
|
||||
if (!empty($expire) && $expire < DateTime::formatTz(DateTime::now())) {
|
||||
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
Authorization::setRole(Auth::USER_ROLE_APPS);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
|
||||
$accessedAt = $key->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
|
||||
$key->setAttribute('accessedAt', DateTime::now());
|
||||
$dbForConsole->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForConsole->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
|
||||
$sdkValidator = new WhiteList($servers, true);
|
||||
$sdk = $request->getHeader('x-sdk-name', 'UNKNOWN');
|
||||
if ($sdkValidator->isValid($sdk)) {
|
||||
$sdks = $key->getAttribute('sdks', []);
|
||||
if (!in_array($sdk, $sdks)) {
|
||||
array_push($sdks, $sdk);
|
||||
$key->setAttribute('sdks', $sdks);
|
||||
|
||||
/** Update access time as well */
|
||||
$key->setAttribute('accessedAt', Datetime::now());
|
||||
$dbForConsole->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForConsole->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
$queueForAudits->setUser($user);
|
||||
}
|
||||
}
|
||||
// Admin User Authentication
|
||||
|
||||
if ($apiKey->getType() === API_KEY_STANDARD) {
|
||||
$dbKey = $project->find(
|
||||
key: 'secret',
|
||||
find: $request->getHeader('x-appwrite-key', ''),
|
||||
subject: 'keys'
|
||||
);
|
||||
|
||||
if (!$dbKey) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$accessedAt = $dbKey->getAttribute('accessedAt', 0);
|
||||
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
|
||||
$dbKey->setAttribute('accessedAt', DateTime::now());
|
||||
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
|
||||
$sdkValidator = new WhiteList($servers, true);
|
||||
$sdk = $request->getHeader('x-sdk-name', 'UNKNOWN');
|
||||
|
||||
if ($sdk !== 'UNKNOWN' && $sdkValidator->isValid($sdk)) {
|
||||
$sdks = $dbKey->getAttribute('sdks', []);
|
||||
|
||||
if (!in_array($sdk, $sdks)) {
|
||||
$sdks[] = $sdk;
|
||||
$dbKey->setAttribute('sdks', $sdks);
|
||||
|
||||
/** Update access time as well */
|
||||
$dbKey->setAttribute('accessedAt', Datetime::now());
|
||||
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
$queueForAudits->setUser($user);
|
||||
}
|
||||
} // Admin User Authentication
|
||||
elseif (($project->getId() === 'console' && !$team->isEmpty() && !$user->isEmpty()) || ($project->getId() !== 'console' && !$user->isEmpty() && $mode === APP_MODE_ADMIN)) {
|
||||
$teamId = $team->getId();
|
||||
$adminRoles = [];
|
||||
@@ -320,7 +296,7 @@ App::init()
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$scopes = []; // reset scope if admin
|
||||
$scopes = []; // Reset scope if admin
|
||||
foreach ($adminRoles as $role) {
|
||||
$scopes = \array_merge($scopes, $roles[$role]['scopes']);
|
||||
}
|
||||
@@ -335,25 +311,57 @@ App::init()
|
||||
Authorization::setRole($authRole);
|
||||
}
|
||||
|
||||
/** Do not allow access to disabled services */
|
||||
$service = $route->getLabel('sdk.namespace', '');
|
||||
if (!empty($service)) {
|
||||
// Update project last activity
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$accessedAt = $project->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
}
|
||||
}
|
||||
|
||||
// Update user last activity
|
||||
if (!empty($user->getId())) {
|
||||
$accessedAt = $user->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
|
||||
$user->setAttribute('accessedAt', DateTime::now());
|
||||
|
||||
if (APP_MODE_ADMIN !== $mode) {
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
} else {
|
||||
$dbForPlatform->updateDocument('users', $user->getId(), $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ?Method $method
|
||||
*/
|
||||
$method = $route->getLabel('sdk', false);
|
||||
|
||||
if (\is_array($method)) {
|
||||
$method = $method[0];
|
||||
}
|
||||
|
||||
if (!empty($method)) {
|
||||
$namespace = $method->getNamespace();
|
||||
|
||||
if (
|
||||
array_key_exists($service, $project->getAttribute('services', []))
|
||||
&& !$project->getAttribute('services', [])[$service]
|
||||
array_key_exists($namespace, $project->getAttribute('services', []))
|
||||
&& !$project->getAttribute('services', [])[$namespace]
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
) {
|
||||
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
/** Do now allow access if scope is not allowed */
|
||||
// Do now allow access if scope is not allowed
|
||||
$scope = $route->getLabel('scope', 'none');
|
||||
if (!\in_array($scope, $scopes)) {
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')');
|
||||
}
|
||||
|
||||
/** Do not allow access to blocked accounts */
|
||||
// Do not allow access to blocked accounts
|
||||
if (false === $user->getAttribute('status')) { // Account is blocked
|
||||
throw new Exception(Exception::USER_BLOCKED);
|
||||
}
|
||||
@@ -383,17 +391,22 @@ App::init()
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('queue')
|
||||
->inject('publisher')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForAudits')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForBuilds')
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('dbForProject')
|
||||
->inject('timelimit')
|
||||
->inject('resourceToken')
|
||||
->inject('mode')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Connection $queue, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
->inject('apiKey')
|
||||
->inject('plan')
|
||||
->inject('devKey')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
@@ -416,7 +429,7 @@ App::init()
|
||||
foreach ($abuseKeyLabel as $abuseKey) {
|
||||
$start = $request->getContentRangeStart();
|
||||
$end = $request->getContentRangeEnd();
|
||||
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
|
||||
$timeLimit = $timelimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600));
|
||||
$timeLimit
|
||||
->setParam('{projectId}', $project->getId())
|
||||
->setParam('{userId}', $user->getId())
|
||||
@@ -424,7 +437,7 @@ App::init()
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getHostname() . $route->getPath())
|
||||
->setParam('{method}', $request->getMethod())
|
||||
->setParam('{chunkId}', (int) ($start / ($end + 1 - $start)));
|
||||
->setParam('{chunkId}', (int)($start / ($end + 1 - $start)));
|
||||
$timeLimitArray[] = $timeLimit;
|
||||
}
|
||||
|
||||
@@ -444,7 +457,7 @@ App::init()
|
||||
$abuse = new Abuse($timeLimit);
|
||||
$remaining = $timeLimit->remaining();
|
||||
$limit = $timeLimit->limit();
|
||||
$time = (new \DateTime($timeLimit->time()))->getTimestamp() + $route->getLabel('abuse-time', 3600);
|
||||
$time = $timeLimit->time() + $route->getLabel('abuse-time', 3600);
|
||||
|
||||
if ($limit && ($remaining < $closestLimit || is_null($closestLimit))) {
|
||||
$closestLimit = $remaining;
|
||||
@@ -460,6 +473,7 @@ App::init()
|
||||
$enabled // Abuse is enabled
|
||||
&& !$isAppUser // User is not API key
|
||||
&& !$isPrivilegedUser // User is not an admin
|
||||
&& $devKey->isEmpty() // request doesn't not contain development key
|
||||
&& $abuse->check() // Route is rate-limited
|
||||
) {
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED);
|
||||
@@ -478,9 +492,23 @@ App::init()
|
||||
->setMode($mode)
|
||||
->setUserAgent($request->getUserAgent(''))
|
||||
->setIP($request->getIP())
|
||||
->setHostname($request->getHostname())
|
||||
->setEvent($route->getLabel('audits.event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user);
|
||||
->setProject($project);
|
||||
|
||||
/* If a session exists, use the user associated with the session */
|
||||
if (!$user->isEmpty()) {
|
||||
$userClone = clone $user;
|
||||
// $user doesn't support `type` and can cause unintended effects.
|
||||
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
|
||||
$queueForAudits->setUser($userClone);
|
||||
}
|
||||
|
||||
if (!empty($apiKey) && !empty($apiKey->getDisabledMetrics())) {
|
||||
foreach ($apiKey->getDisabledMetrics() as $key) {
|
||||
$queueForStatsUsage->disableMetric($key);
|
||||
}
|
||||
}
|
||||
|
||||
$queueForDeletes->setProject($project);
|
||||
$queueForDatabase->setProject($project);
|
||||
@@ -489,15 +517,16 @@ App::init()
|
||||
|
||||
// Clone the queues, to prevent events triggered by the database listener
|
||||
// from overwriting the events that are supposed to be triggered in the shutdown hook.
|
||||
$queueForEventsClone = new Event($queue);
|
||||
$queueForFunctions = new Func($queue);
|
||||
$queueForWebhooks = new Webhook($queue);
|
||||
$queueForEventsClone = new Event($publisher);
|
||||
$queueForFunctions = new Func($publisher);
|
||||
$queueForWebhooks = new Webhook($publisher);
|
||||
$queueForRealtime = new Realtime();
|
||||
|
||||
$dbForProject
|
||||
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage))
|
||||
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage))
|
||||
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
|
||||
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
|
||||
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
|
||||
$project,
|
||||
$document,
|
||||
$response,
|
||||
$queueForEventsClone->from($queueForEvents),
|
||||
@@ -508,6 +537,10 @@ App::init()
|
||||
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
if ($useCache) {
|
||||
$route = $utopia->match($request);
|
||||
$isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview';
|
||||
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
$key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$cache = new Cache(
|
||||
@@ -517,54 +550,67 @@ App::init()
|
||||
$data = $cache->load($key, $timestamp);
|
||||
|
||||
if (!empty($data) && !$cacheLog->isEmpty()) {
|
||||
$parts = explode('/', $cacheLog->getAttribute('resourceType'));
|
||||
$parts = explode('/', $cacheLog->getAttribute('resourceType', ''));
|
||||
$type = $parts[0] ?? null;
|
||||
|
||||
if ($type === 'bucket') {
|
||||
if ($type === 'bucket' && (!$isImageTransformation || !$isDisabled)) {
|
||||
$bucketId = $parts[1] ?? null;
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getInternalId();
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
|
||||
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) {
|
||||
if (!$fileSecurity && !$valid && !$isToken) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$parts = explode('/', $cacheLog->getAttribute('resource'));
|
||||
$fileId = $parts[1] ?? null;
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
if ($fileSecurity && !$valid && !$isToken) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getInternalId()) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
//Do not update transformedAt if it's a console user
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
$transformedAt = $file->getAttribute('transformedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
|
||||
$file->setAttribute('transformedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', sprintf('private, max-age=%d', $timestamp))
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->setContentType($cacheLog->getAttribute('mimeType'))
|
||||
->send($data);
|
||||
->setContentType($cacheLog->getAttribute('mimeType'));
|
||||
if (!$isImageTransformation || !$isDisabled) {
|
||||
$response->send($data);
|
||||
}
|
||||
} else {
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->addHeader('Expires', '0')
|
||||
->addHeader('X-Appwrite-Cache', 'miss')
|
||||
;
|
||||
->addHeader('X-Appwrite-Cache', 'miss');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -632,7 +678,7 @@ App::shutdown()
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForAudits')
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForBuilds')
|
||||
@@ -641,9 +687,7 @@ App::shutdown()
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForRealtime')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('dbForConsole')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, string $mode, Database $dbForConsole) use ($parseLabel) {
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject) use ($parseLabel) {
|
||||
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
@@ -652,10 +696,6 @@ App::shutdown()
|
||||
$queueForEvents->setPayload($responsePayload);
|
||||
}
|
||||
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
@@ -665,6 +705,17 @@ App::shutdown()
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
/** Trigger webhooks events only if a project has them enabled
|
||||
* A future optimisation is to only trigger webhooks if the webhook is "enabled"
|
||||
* But it might have performance implications on the API due to the number of webhooks etc.
|
||||
* Some profiling is needed to see if this is a problem.
|
||||
*/
|
||||
if (!empty($project->getAttribute('webhooks'))) {
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
@@ -682,10 +733,32 @@ App::shutdown()
|
||||
}
|
||||
|
||||
if (!$user->isEmpty()) {
|
||||
$userClone = clone $user;
|
||||
// $user doesn't support `type` and can cause unintended effects.
|
||||
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
|
||||
$queueForAudits->setUser($userClone);
|
||||
} elseif ($queueForAudits->getUser() === null || $queueForAudits->getUser()->isEmpty()) {
|
||||
/**
|
||||
* User in the request is empty, and no user was set for auditing previously.
|
||||
* This indicates:
|
||||
* - No API Key was used.
|
||||
* - No active session exists.
|
||||
*
|
||||
* Therefore, we consider this an anonymous request and create a relevant user.
|
||||
*/
|
||||
$user = new Document([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'type' => Auth::ACTIVITY_TYPE_GUEST,
|
||||
'email' => 'guest.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => 'Guest',
|
||||
]);
|
||||
|
||||
$queueForAudits->setUser($user);
|
||||
}
|
||||
|
||||
if (!empty($queueForAudits->getResource()) && !empty($queueForAudits->getUser()->getId())) {
|
||||
if (!empty($queueForAudits->getResource()) && !$queueForAudits->getUser()->isEmpty()) {
|
||||
/**
|
||||
* audits.payload is switched to default true
|
||||
* in order to auto audit payload for all endpoints
|
||||
@@ -698,6 +771,7 @@ App::shutdown()
|
||||
foreach ($queueForEvents->getParams() as $key => $value) {
|
||||
$queueForAudits->setParam($key, $value);
|
||||
}
|
||||
|
||||
$queueForAudits->trigger();
|
||||
}
|
||||
|
||||
@@ -717,9 +791,7 @@ App::shutdown()
|
||||
$queueForMessaging->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache label
|
||||
*/
|
||||
// Cache label
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
if ($useCache) {
|
||||
$resource = $resourceType = null;
|
||||
@@ -738,7 +810,7 @@ App::shutdown()
|
||||
$key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
|
||||
$signature = md5($data['payload']);
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
|
||||
$accessedAt = $cacheLog->getAttribute('accessedAt', 0);
|
||||
$now = DateTime::now();
|
||||
if ($cacheLog->isEmpty()) {
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
|
||||
@@ -763,8 +835,6 @@ App::shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ($project->getId() !== 'console') {
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
$fileSize = 0;
|
||||
@@ -773,43 +843,16 @@ App::shutdown()
|
||||
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
|
||||
}
|
||||
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_NETWORK_REQUESTS, 1)
|
||||
->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize)
|
||||
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize());
|
||||
}
|
||||
|
||||
$queueForUsage
|
||||
$queueForStatsUsage
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update project last activity
|
||||
*/
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$accessedAt = $project->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('projects', $project->getId(), $project));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user last activity
|
||||
*/
|
||||
if (!$user->isEmpty()) {
|
||||
$accessedAt = $user->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
|
||||
$user->setAttribute('accessedAt', DateTime::now());
|
||||
|
||||
if (APP_MODE_ADMIN !== $mode) {
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
} else {
|
||||
$dbForConsole->updateDocument('users', $user->getId(), $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
App::init()
|
||||
|
||||
@@ -5,6 +5,7 @@ use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Request;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
@@ -57,44 +58,44 @@ App::init()
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
switch ($route->getLabel('auth.type', '')) {
|
||||
case 'emailPassword':
|
||||
if (($auths['emailPassword'] ?? true) === false) {
|
||||
case 'email-password':
|
||||
if (($auths[Config::getParam('auth')['email-password']['key']] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Email / Password authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'magic-url':
|
||||
if (($auths['usersAuthMagicURL'] ?? true) === false) {
|
||||
if (($auths[Config::getParam('auth')['magic-url']['key']] ?? 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) {
|
||||
if (($auths[Config::getParam('auth')['anonymous']['key']] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Anonymous authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'phone':
|
||||
if (($auths['phone'] ?? true) === false) {
|
||||
if (($auths[Config::getParam('auth')['phone']['key']] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Phone authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'invites':
|
||||
if (($auths['invites'] ?? true) === false) {
|
||||
if (($auths[Config::getParam('auth')['invites']['key']] ?? 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) {
|
||||
if (($auths[Config::getParam('auth')['jwt']['key']] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'JWT authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'email-otp':
|
||||
if (($auths['emailOTP'] ?? true) === false) {
|
||||
if (($auths[Config::getParam('auth')['email-otp']['key']] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Email OTP authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
+353
-90
@@ -9,18 +9,19 @@ use Swoole\Http\Request as SwooleRequest;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
use Swoole\Http\Server;
|
||||
use Swoole\Process;
|
||||
use Utopia\Abuse\Adapters\Database\TimeLimit;
|
||||
use Swoole\Table;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Compression\Compression;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
@@ -28,6 +29,14 @@ use Utopia\Pools\Group;
|
||||
use Utopia\Swoole\Files;
|
||||
use Utopia\System\System;
|
||||
|
||||
Files::load(__DIR__.'/../public');
|
||||
|
||||
const DOMAIN_SYNC_TIMER = 30; // 30 seconds
|
||||
|
||||
$domains = new Table(1_000_000); // 1 million rows
|
||||
$domains->column('value', Table::TYPE_INT, 1);
|
||||
$domains->create();
|
||||
|
||||
$http = new Server(
|
||||
host: "0.0.0.0",
|
||||
port: System::getEnv('PORT', 80),
|
||||
@@ -35,15 +44,17 @@ $http = new Server(
|
||||
);
|
||||
|
||||
$payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
|
||||
$workerNumber = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
$totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
|
||||
$http
|
||||
->set([
|
||||
'worker_num' => $workerNumber,
|
||||
'worker_num' => $totalWorkers,
|
||||
'dispatch_func' => 'dispatch',
|
||||
'open_http2_protocol' => true,
|
||||
'http_compression' => false,
|
||||
'package_max_length' => $payloadSize,
|
||||
'buffer_output_size' => $payloadSize,
|
||||
'task_worker_num' => 1, // required for the task to fetch domains background
|
||||
]);
|
||||
|
||||
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
|
||||
@@ -58,8 +69,168 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
|
||||
Console::success('Reload completed...');
|
||||
});
|
||||
|
||||
/**
|
||||
* Assigns HTTP requests to worker threads by analyzing its payload/content.
|
||||
*
|
||||
* Routes requests as 'safe' or 'risky' based on specific content patterns (like POST actions or certain domains)
|
||||
* to optimize load distribution between the workers. Utilizes `$safeThreadsPercent` to manage risk by assigning
|
||||
* riskier tasks to a dedicated worker subset. Prefers idle workers, with fallback to random selection if necessary.
|
||||
* doc: https://openswoole.com/docs/modules/swoole-server/configuration#dispatch_func
|
||||
*
|
||||
* @param Server $server Swoole server instance.
|
||||
* @param int $fd client ID
|
||||
* @param int $type the type of data and its current state
|
||||
* @param string|null $data Request content for categorization.
|
||||
* @global int $totalThreads Total number of workers.
|
||||
* @return int Chosen worker ID for the request.
|
||||
*/
|
||||
function dispatch(Server $server, int $fd, int $type, $data = null): int
|
||||
{
|
||||
global $totalWorkers, $domains;
|
||||
|
||||
// If data is not set we can send request to any worker
|
||||
// first we try to pick idle worker, if not we randomly pick a worker
|
||||
if ($data === null) {
|
||||
for ($i = 0; $i < $totalWorkers; $i++) {
|
||||
if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) {
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
return rand(0, $totalWorkers - 1);
|
||||
}
|
||||
|
||||
$riskyWorkersPercent = intval(System::getEnv('_APP_RISKY_WORKERS_PERCENT', 80)) / 100; // Decimal form 0 to 1
|
||||
|
||||
// Each worker has numeric ID, starting from 0 and incrementing
|
||||
// From 0 to riskyWorkers, we consider safe workers
|
||||
// From riskyWorkers to totalWorkers, we consider risky workers
|
||||
$riskyWorkers = (int) floor($totalWorkers * $riskyWorkersPercent); // Absolute amount of risky workers
|
||||
|
||||
$domain = '';
|
||||
// max up to 3 as first line has request details and second line has host
|
||||
$lines = explode("\n", $data, 3);
|
||||
$request = $lines[0];
|
||||
if (count($lines) > 1) {
|
||||
$domain = trim(explode('Host: ', $lines[1])[1]);
|
||||
}
|
||||
|
||||
// Sync executions are considered risky
|
||||
$risky = false;
|
||||
if (str_starts_with($request, 'POST') && str_contains($request, '/executions')) {
|
||||
$risky = true;
|
||||
} elseif (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) {
|
||||
$risky = true;
|
||||
} elseif ($domains->get(md5($domain), 'value') === 1) {
|
||||
// executions request coming from custom domain
|
||||
$risky = true;
|
||||
}
|
||||
|
||||
if ($risky) {
|
||||
// If risky request, only consider risky workers
|
||||
for ($j = $riskyWorkers; $j < $totalWorkers; $j++) {
|
||||
/** Reference https://openswoole.com/docs/modules/swoole-server-getWorkerStatus#description */
|
||||
if ($server->getWorkerStatus($j) === SWOOLE_WORKER_IDLE) {
|
||||
// If idle worker found, give to him
|
||||
return $j;
|
||||
}
|
||||
}
|
||||
|
||||
// If no idle workers, give to random risky worker
|
||||
$worker = rand($riskyWorkers, $totalWorkers - 1);
|
||||
Console::warning("swoole_dispatch: Risky branch: did not find a idle worker, picking random worker {$worker}");
|
||||
return $worker;
|
||||
}
|
||||
|
||||
// If safe request, give to any idle worker
|
||||
// Its fine to pick risky worker here, because it's idle. Idle is never actually risky
|
||||
for ($i = 0; $i < $totalWorkers; $i++) {
|
||||
if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) {
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
|
||||
// If no idle worker found, give to random safe worker
|
||||
// We avoid risky workers here, as it could be in work - not idle. Thats exactly when they are risky.
|
||||
$worker = rand(0, $riskyWorkers - 1);
|
||||
Console::warning("swoole_dispatch: Non-risky branch: did not find a idle worker, picking random worker {$worker}");
|
||||
return $worker;
|
||||
}
|
||||
|
||||
include __DIR__ . '/controllers/general.php';
|
||||
|
||||
function createDatabase(App $app, string $resourceKey, string $dbName, array $collections, mixed $pools, callable $extraSetup = null): void
|
||||
{
|
||||
$max = 10;
|
||||
$sleep = 1;
|
||||
$attempts = 0;
|
||||
|
||||
do {
|
||||
try {
|
||||
$attempts++;
|
||||
$resource = $app->getResource($resourceKey);
|
||||
/* @var $database Database */
|
||||
$database = is_callable($resource) ? $resource() : $resource;
|
||||
break; // exit loop on success
|
||||
} catch (\Exception $e) {
|
||||
Console::warning(" └── Database not ready. Retrying connection ({$attempts})...");
|
||||
$pools->reclaim();
|
||||
if ($attempts >= $max) {
|
||||
throw new \Exception(' └── Failed to connect to database: ' . $e->getMessage());
|
||||
}
|
||||
sleep($sleep);
|
||||
}
|
||||
} while ($attempts < $max);
|
||||
|
||||
Console::success("[Setup] - $dbName database init started...");
|
||||
|
||||
// Attempt to create the database
|
||||
try {
|
||||
Console::info(" └── Creating database: $dbName...");
|
||||
$database->create();
|
||||
} catch (\Exception $e) {
|
||||
Console::info(" └── Skip: metadata table already exists");
|
||||
}
|
||||
|
||||
// Process collections
|
||||
foreach ($collections as $key => $collection) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$database->getCollection($key)->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::info(" └── Creating collection: {$collection['$id']}...");
|
||||
|
||||
$attributes = array_map(fn ($attr) => new Document([
|
||||
'$id' => ID::custom($attr['$id']),
|
||||
'type' => $attr['type'],
|
||||
'size' => $attr['size'],
|
||||
'required' => $attr['required'],
|
||||
'signed' => $attr['signed'],
|
||||
'array' => $attr['array'],
|
||||
'filters' => $attr['filters'],
|
||||
'default' => $attr['default'] ?? null,
|
||||
'format' => $attr['format'] ?? ''
|
||||
]), $collection['attributes']);
|
||||
|
||||
$indexes = array_map(fn ($index) => new Document([
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
'orders' => $index['orders'],
|
||||
]), $collection['indexes']);
|
||||
|
||||
$database->createCollection($key, $attributes, $indexes);
|
||||
}
|
||||
|
||||
if ($extraSetup) {
|
||||
$extraSetup($database);
|
||||
}
|
||||
}
|
||||
|
||||
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
|
||||
$app = new App('UTC');
|
||||
|
||||
@@ -68,99 +239,120 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
/** @var Group $pools */
|
||||
App::setResource('pools', fn () => $pools);
|
||||
|
||||
// wait for database to be ready
|
||||
$attempts = 0;
|
||||
$max = 10;
|
||||
$sleep = 1;
|
||||
|
||||
do {
|
||||
try {
|
||||
$attempts++;
|
||||
$dbForConsole = $app->getResource('dbForConsole');
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
break; // leave the do-while if successful
|
||||
} catch (\Throwable $e) {
|
||||
Console::warning("Database not ready. Retrying connection ({$attempts})...");
|
||||
if ($attempts >= $max) {
|
||||
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
|
||||
}
|
||||
sleep($sleep);
|
||||
}
|
||||
} while ($attempts < $max);
|
||||
|
||||
Console::success('[Setup] - Server database init started...');
|
||||
|
||||
try {
|
||||
Console::success('[Setup] - Creating console database...');
|
||||
$dbForConsole->create();
|
||||
} catch (Duplicate) {
|
||||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
}
|
||||
|
||||
if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($dbForConsole);
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) {
|
||||
$adapter = new TimeLimit("", 0, 1, $dbForConsole);
|
||||
$adapter->setup();
|
||||
}
|
||||
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
$consoleCollections = $collections['console'];
|
||||
foreach ($consoleCollections as $key => $collection) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
}
|
||||
if (!$dbForConsole->getCollection($key)->isEmpty()) {
|
||||
continue;
|
||||
|
||||
// create logs database first, `getLogsDB` is a callable.
|
||||
createDatabase($app, 'getLogsDB', 'logs', $collections['logs'], $pools);
|
||||
|
||||
// create appwrite database, `dbForPlatform` is a direct access call.
|
||||
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections) {
|
||||
if ($dbForPlatform->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($dbForPlatform);
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
Console::success('[Setup] - Creating console collection: ' . $collection['$id'] . '...');
|
||||
if ($dbForPlatform->getDocument('buckets', 'default')->isEmpty()) {
|
||||
Console::info(" └── Creating default bucket...");
|
||||
$dbForPlatform->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('default'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'name' => 'Default',
|
||||
'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0),
|
||||
'allowedFileExtensions' => [],
|
||||
'enabled' => true,
|
||||
'compression' => 'gzip',
|
||||
'encryption' => true,
|
||||
'antivirus' => true,
|
||||
'fileSecurity' => true,
|
||||
'$permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'search' => 'buckets Default',
|
||||
]));
|
||||
|
||||
$attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
|
||||
$indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
|
||||
$bucket = $dbForPlatform->getDocument('buckets', 'default');
|
||||
|
||||
$dbForConsole->createCollection($key, $attributes, $indexes);
|
||||
}
|
||||
Console::info(" └── Creating files collection for default bucket...");
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
throw new Exception('Files collection is not configured.');
|
||||
}
|
||||
|
||||
if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) {
|
||||
Console::success('[Setup] - Creating default bucket...');
|
||||
$dbForConsole->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('default'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'name' => 'Default',
|
||||
'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB
|
||||
'allowedFileExtensions' => [],
|
||||
'enabled' => true,
|
||||
'compression' => 'gzip',
|
||||
'encryption' => true,
|
||||
'antivirus' => true,
|
||||
'fileSecurity' => true,
|
||||
'$permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'search' => 'buckets Default',
|
||||
]));
|
||||
$attributes = array_map(fn ($attr) => new Document([
|
||||
'$id' => ID::custom($attr['$id']),
|
||||
'type' => $attr['type'],
|
||||
'size' => $attr['size'],
|
||||
'required' => $attr['required'],
|
||||
'signed' => $attr['signed'],
|
||||
'array' => $attr['array'],
|
||||
'filters' => $attr['filters'],
|
||||
'default' => $attr['default'] ?? null,
|
||||
'format' => $attr['format'] ?? ''
|
||||
]), $files['attributes']);
|
||||
|
||||
$bucket = $dbForConsole->getDocument('buckets', 'default');
|
||||
$indexes = array_map(fn ($index) => new Document([
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
'orders' => $index['orders'],
|
||||
]), $files['indexes']);
|
||||
|
||||
Console::success('[Setup] - Creating files collection for default bucket...');
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
throw new Exception('Files collection is not configured.');
|
||||
$dbForPlatform->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
|
||||
}
|
||||
|
||||
$attributes = \array_map(fn ($attribute) => new Document($attribute), $files['attributes']);
|
||||
$indexes = \array_map(fn (array $index) => new Document($index), $files['indexes']);
|
||||
if (Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
|
||||
Console::info(" └── Creating screenshots bucket...");
|
||||
Authorization::skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('screenshots'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'name' => 'Screenshots',
|
||||
'maximumFileSize' => 5000000, // ~5MB
|
||||
'allowedFileExtensions' => [ 'png' ],
|
||||
'enabled' => true,
|
||||
'compression' => Compression::GZIP,
|
||||
'encryption' => false,
|
||||
'antivirus' => false,
|
||||
'fileSecurity' => true,
|
||||
'$permissions' => [],
|
||||
'search' => 'buckets Screenshots',
|
||||
])));
|
||||
|
||||
$dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
|
||||
}
|
||||
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
|
||||
|
||||
Console::info(" └── Creating files collection for screenshots bucket...");
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
throw new Exception('Files collection is not configured.');
|
||||
}
|
||||
|
||||
$attributes = array_map(fn ($attr) => new Document([
|
||||
'$id' => ID::custom($attr['$id']),
|
||||
'type' => $attr['type'],
|
||||
'size' => $attr['size'],
|
||||
'required' => $attr['required'],
|
||||
'signed' => $attr['signed'],
|
||||
'array' => $attr['array'],
|
||||
'filters' => $attr['filters'],
|
||||
'default' => $attr['default'] ?? null,
|
||||
'format' => $attr['format'] ?? ''
|
||||
]), $files['attributes']);
|
||||
|
||||
$indexes = array_map(fn ($index) => new Document([
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
'orders' => $index['orders'],
|
||||
]), $files['indexes']);
|
||||
|
||||
Authorization::skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes));
|
||||
}
|
||||
});
|
||||
|
||||
$projectCollections = $collections['projects'];
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
@@ -188,6 +380,11 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
}
|
||||
|
||||
if ($dbForProject->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($dbForProject);
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
foreach ($projectCollections as $key => $collection) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
@@ -206,13 +403,15 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
}
|
||||
|
||||
$pools->reclaim();
|
||||
|
||||
Console::success('[Setup] - Server database init completed...');
|
||||
});
|
||||
|
||||
Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)');
|
||||
Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}");
|
||||
|
||||
// Start the task that starts fetching custom domains
|
||||
$http->task([], 0);
|
||||
|
||||
// listen ctrl + c
|
||||
Process::signal(2, function () use ($http) {
|
||||
Console::log('Stop by Ctrl+C');
|
||||
@@ -240,7 +439,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
}
|
||||
|
||||
$app = new App('UTC');
|
||||
$app->setCompression(true);
|
||||
$app->setCompression(System::getEnv('_APP_COMPRESSION_ENABLED', 'enabled') === 'enabled');
|
||||
$app->setCompressionMinSize(intval(System::getEnv('_APP_COMPRESSION_MIN_SIZE_BYTES', '1024'))); // 1KB
|
||||
|
||||
$pools = $register->get('pools');
|
||||
@@ -269,10 +468,12 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
} else {
|
||||
$log->setUser(new User('guest-' . hash('sha256', $request->getIP())));
|
||||
}
|
||||
|
||||
$log->setNamespace("http");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($th->getMessage());
|
||||
@@ -290,8 +491,16 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
$log->addExtra('trace', $th->getTraceAsString());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
$sdk = $route->getLabel("sdk", false);
|
||||
|
||||
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD';
|
||||
if (!empty($sdk)) {
|
||||
/** @var Appwrite\SDK\Method $sdk */
|
||||
$action = $sdk->getNamespace() . '.' . $sdk->getMethodName();
|
||||
}
|
||||
|
||||
$log->setAction($action);
|
||||
$log->addTag('service', $action);
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
@@ -330,4 +539,58 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory
|
||||
$http->on('Task', function () use ($register, $domains) {
|
||||
$lastSyncUpdate = null;
|
||||
$pools = $register->get('pools');
|
||||
App::setResource('pools', fn () => $pools);
|
||||
$app = new App('UTC');
|
||||
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
$dbForPlatform = $app->getResource('dbForPlatform');
|
||||
|
||||
Console::loop(function () use ($dbForPlatform, $domains, &$lastSyncUpdate) {
|
||||
try {
|
||||
$time = DateTime::now();
|
||||
$limit = 1000;
|
||||
$sum = $limit;
|
||||
$latestDocument = null;
|
||||
|
||||
while ($sum === $limit) {
|
||||
$queries = [Query::limit($limit)];
|
||||
if ($latestDocument !== null) {
|
||||
$queries[] = Query::cursorAfter($latestDocument);
|
||||
}
|
||||
if ($lastSyncUpdate != null) {
|
||||
$queries[] = Query::greaterThanEqual('$updatedAt', $lastSyncUpdate);
|
||||
}
|
||||
$results = [];
|
||||
try {
|
||||
$results = Authorization::skip(fn () => $dbForPlatform->find('rules', $queries));
|
||||
} catch (Throwable $th) {
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
|
||||
$sum = count($results);
|
||||
foreach ($results as $document) {
|
||||
$domain = $document->getAttribute('domain');
|
||||
if (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS')) || str_ends_with($domain, System::getEnv('_APP_DOMAIN_SITES'))) {
|
||||
continue;
|
||||
}
|
||||
$domains->set(md5($domain), ['value' => 1]);
|
||||
}
|
||||
$latestDocument = !empty(array_key_last($results)) ? $results[array_key_last($results)] : null;
|
||||
}
|
||||
$lastSyncUpdate = $time;
|
||||
if ($sum > 0) {
|
||||
Console::log("Sync domains tick: {$sum} domains were updated");
|
||||
}
|
||||
} catch (Throwable $th) {
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
}, DOMAIN_SYNC_TIMER, 0, function ($error) {
|
||||
Console::error($error);
|
||||
});
|
||||
});
|
||||
|
||||
$http->start();
|
||||
|
||||
+9
-1798
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Utopia\Config\Config;
|
||||
|
||||
Config::load('template-runtimes', __DIR__ . '/../config/template-runtimes.php');
|
||||
Config::load('events', __DIR__ . '/../config/events.php');
|
||||
Config::load('auth', __DIR__ . '/../config/auth.php');
|
||||
Config::load('apis', __DIR__ . '/../config/apis.php'); // List of APIs
|
||||
Config::load('errors', __DIR__ . '/../config/errors.php');
|
||||
Config::load('oAuthProviders', __DIR__ . '/../config/oAuthProviders.php');
|
||||
Config::load('platforms', __DIR__ . '/../config/platforms.php');
|
||||
Config::load('console', __DIR__ . '/../config/console.php');
|
||||
Config::load('collections', __DIR__ . '/../config/collections.php');
|
||||
Config::load('frameworks', __DIR__ . '/../config/frameworks.php');
|
||||
Config::load('runtimes', __DIR__ . '/../config/runtimes.php');
|
||||
Config::load('runtimes-v2', __DIR__ . '/../config/runtimes-v2.php');
|
||||
Config::load('usage', __DIR__ . '/../config/usage.php');
|
||||
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');
|
||||
Config::load('locale-codes', __DIR__ . '/../config/locale/codes.php');
|
||||
Config::load('locale-currencies', __DIR__ . '/../config/locale/currencies.php');
|
||||
Config::load('locale-eu', __DIR__ . '/../config/locale/eu.php');
|
||||
Config::load('locale-languages', __DIR__ . '/../config/locale/languages.php');
|
||||
Config::load('locale-phones', __DIR__ . '/../config/locale/phones.php');
|
||||
Config::load('locale-countries', __DIR__ . '/../config/locale/countries.php');
|
||||
Config::load('locale-continents', __DIR__ . '/../config/locale/continents.php');
|
||||
Config::load('locale-templates', __DIR__ . '/../config/locale/templates.php');
|
||||
Config::load('storage-logos', __DIR__ . '/../config/storage/logos.php');
|
||||
Config::load('storage-mimes', __DIR__ . '/../config/storage/mimes.php');
|
||||
Config::load('storage-inputs', __DIR__ . '/../config/storage/inputs.php');
|
||||
Config::load('storage-outputs', __DIR__ . '/../config/storage/outputs.php');
|
||||
Config::load('specifications', __DIR__ . '/../config/specifications.php');
|
||||
Config::load('templates-function', __DIR__ . '/../config/templates/function.php');
|
||||
Config::load('templates-site', __DIR__ . '/../config/templates/site.php');
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Platform\Modules\Compute\Specification;
|
||||
|
||||
const APP_NAME = 'Appwrite';
|
||||
const APP_DOMAIN = 'appwrite.io';
|
||||
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
|
||||
const APP_EMAIL_SECURITY = ''; // Default security email address
|
||||
const APP_USERAGENT = APP_NAME . '-Server v%s. Please report abuse at %s';
|
||||
const APP_MODE_DEFAULT = 'default';
|
||||
const APP_MODE_ADMIN = 'admin';
|
||||
const APP_PAGING_LIMIT = 12;
|
||||
const APP_LIMIT_COUNT = 5000;
|
||||
const APP_LIMIT_USERS = 10_000;
|
||||
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 = 20_000_000; //20MB
|
||||
const APP_LIMIT_ENCRYPTION = 20_000_000; //20MB
|
||||
const APP_LIMIT_COMPRESSION = 20_000_000; //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_LABELS_SIZE = 1000; // Default maximum of how many labels 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_LIMIT_SUBSCRIBERS_SUBQUERY = 1_000_000;
|
||||
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_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_RESOURCE_TOKEN_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 4318;
|
||||
const APP_VERSION_STABLE = '1.7.0';
|
||||
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';
|
||||
const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1_073_741_824; // 2^32 bits / 4 bits per char
|
||||
const APP_DATABASE_TIMEOUT_MILLISECONDS_API = 15 * 1000; // 15 seconds
|
||||
const APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER = 300 * 1000; // 5 minutes
|
||||
const APP_DATABASE_TIMEOUT_MILLISECONDS_TASK = 300 * 1000; // 5 minutes
|
||||
const APP_DATABASE_QUERY_MAX_VALUES = 500;
|
||||
const APP_STORAGE_UPLOADS = '/storage/uploads';
|
||||
const APP_STORAGE_SITES = '/storage/sites';
|
||||
const APP_STORAGE_FUNCTIONS = '/storage/functions';
|
||||
const APP_STORAGE_BUILDS = '/storage/builds';
|
||||
const APP_STORAGE_CACHE = '/storage/cache';
|
||||
const APP_STORAGE_IMPORTS = '/storage/imports'; // Temporary storage for csv imports
|
||||
const APP_STORAGE_CERTIFICATES = '/storage/certificates';
|
||||
const APP_STORAGE_CONFIG = '/storage/config';
|
||||
const APP_STORAGE_READ_BUFFER = 20 * (1000 * 1000); //20MB other names `APP_STORAGE_MEMORY_LIMIT`, `APP_STORAGE_MEMORY_BUFFER`, `APP_STORAGE_READ_LIMIT`, `APP_STORAGE_BUFFER_LIMIT`
|
||||
const APP_SOCIAL_TWITTER = 'https://twitter.com/appwrite';
|
||||
const APP_SOCIAL_TWITTER_HANDLE = 'appwrite';
|
||||
const APP_SOCIAL_FACEBOOK = 'https://www.facebook.com/appwrite.io';
|
||||
const APP_SOCIAL_LINKEDIN = 'https://www.linkedin.com/company/appwrite';
|
||||
const APP_SOCIAL_INSTAGRAM = 'https://www.instagram.com/appwrite.io';
|
||||
const APP_SOCIAL_GITHUB = 'https://github.com/appwrite';
|
||||
const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord';
|
||||
const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
|
||||
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
|
||||
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
|
||||
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
|
||||
const APP_HOSTNAME_INTERNAL = 'appwrite';
|
||||
const APP_COMPUTE_CPUS_DEFAULT = 0.5;
|
||||
const APP_COMPUTE_MEMORY_DEFAULT = 512;
|
||||
const APP_COMPUTE_SPECIFICATION_DEFAULT = Specification::S_1VCPU_512MB;
|
||||
const APP_PLATFORM_SERVER = 'server';
|
||||
const APP_PLATFORM_CLIENT = 'client';
|
||||
const APP_PLATFORM_CONSOLE = 'console';
|
||||
|
||||
// Database Reconnect
|
||||
const DATABASE_RECONNECT_SLEEP = 2;
|
||||
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
|
||||
|
||||
// Database Worker Types
|
||||
const DATABASE_TYPE_CREATE_ATTRIBUTE = 'createAttribute';
|
||||
const DATABASE_TYPE_CREATE_INDEX = 'createIndex';
|
||||
const DATABASE_TYPE_DELETE_ATTRIBUTE = 'deleteAttribute';
|
||||
const DATABASE_TYPE_DELETE_INDEX = 'deleteIndex';
|
||||
const DATABASE_TYPE_DELETE_COLLECTION = 'deleteCollection';
|
||||
const DATABASE_TYPE_DELETE_DATABASE = 'deleteDatabase';
|
||||
|
||||
// Build Worker Types
|
||||
const BUILD_TYPE_DEPLOYMENT = 'deployment';
|
||||
const BUILD_TYPE_RETRY = 'retry';
|
||||
|
||||
// Deletion Types
|
||||
const DELETE_TYPE_DATABASES = 'databases';
|
||||
const DELETE_TYPE_DOCUMENT = 'document';
|
||||
const DELETE_TYPE_COLLECTIONS = 'collections';
|
||||
const DELETE_TYPE_PROJECTS = 'projects';
|
||||
const DELETE_TYPE_SITES = 'sites';
|
||||
const DELETE_TYPE_FUNCTIONS = 'functions';
|
||||
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
|
||||
const DELETE_TYPE_USERS = 'users';
|
||||
const DELETE_TYPE_TEAM_PROJECTS = 'teams_projects';
|
||||
const DELETE_TYPE_EXECUTIONS = 'executions';
|
||||
const DELETE_TYPE_AUDIT = 'audit';
|
||||
const DELETE_TYPE_ABUSE = 'abuse';
|
||||
const DELETE_TYPE_USAGE = 'usage';
|
||||
const DELETE_TYPE_REALTIME = 'realtime';
|
||||
const DELETE_TYPE_BUCKETS = 'buckets';
|
||||
const DELETE_TYPE_INSTALLATIONS = 'installations';
|
||||
const DELETE_TYPE_RULES = 'rules';
|
||||
const DELETE_TYPE_SESSIONS = 'sessions';
|
||||
const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';
|
||||
const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource';
|
||||
const DELETE_TYPE_SCHEDULES = 'schedules';
|
||||
const DELETE_TYPE_TOPIC = 'topic';
|
||||
const DELETE_TYPE_TARGET = 'target';
|
||||
const DELETE_TYPE_EXPIRED_TARGETS = 'invalid_targets';
|
||||
const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
|
||||
const DELETE_TYPE_MAINTENANCE = 'maintenance';
|
||||
|
||||
// Message types
|
||||
const MESSAGE_SEND_TYPE_INTERNAL = 'internal';
|
||||
const MESSAGE_SEND_TYPE_EXTERNAL = 'external';
|
||||
// Mail Types
|
||||
const MAIL_TYPE_VERIFICATION = 'verification';
|
||||
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
|
||||
const MAIL_TYPE_RECOVERY = 'recovery';
|
||||
const MAIL_TYPE_INVITATION = 'invitation';
|
||||
const MAIL_TYPE_CERTIFICATE = 'certificate';
|
||||
// Auth Types
|
||||
const APP_AUTH_TYPE_SESSION = 'Session';
|
||||
const APP_AUTH_TYPE_JWT = 'JWT';
|
||||
const APP_AUTH_TYPE_KEY = 'Key';
|
||||
const APP_AUTH_TYPE_ADMIN = 'Admin';
|
||||
// Response related
|
||||
const MAX_OUTPUT_CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
// Function headers
|
||||
const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host'];
|
||||
const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
|
||||
// Message types
|
||||
const MESSAGE_TYPE_EMAIL = 'email';
|
||||
const MESSAGE_TYPE_SMS = 'sms';
|
||||
const MESSAGE_TYPE_PUSH = 'push';
|
||||
// API key types
|
||||
const API_KEY_STANDARD = 'standard';
|
||||
const API_KEY_DYNAMIC = 'dynamic';
|
||||
// Usage metrics
|
||||
const METRIC_TEAMS = 'teams';
|
||||
const METRIC_USERS = 'users';
|
||||
const METRIC_WEBHOOKS_SENT = 'webhooks.events.sent';
|
||||
const METRIC_WEBHOOKS_FAILED = 'webhooks.events.failed';
|
||||
const METRIC_WEBHOOK_ID_SENT = '{webhookInternalId}.webhooks.events.sent';
|
||||
const METRIC_WEBHOOK_ID_FAILED = '{webhookInternalId}.webhooks.events.failed';
|
||||
const METRIC_AUTH_METHOD_PHONE = 'auth.method.phone';
|
||||
const METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE = METRIC_AUTH_METHOD_PHONE . '.{countryCode}';
|
||||
const METRIC_MESSAGES = 'messages';
|
||||
const METRIC_MESSAGES_SENT = METRIC_MESSAGES . '.sent';
|
||||
const METRIC_MESSAGES_FAILED = METRIC_MESSAGES . '.failed';
|
||||
const METRIC_MESSAGES_TYPE = METRIC_MESSAGES . '.{type}';
|
||||
const METRIC_MESSAGES_TYPE_SENT = METRIC_MESSAGES . '.{type}.sent';
|
||||
const METRIC_MESSAGES_TYPE_FAILED = METRIC_MESSAGES . '.{type}.failed';
|
||||
const METRIC_MESSAGES_TYPE_PROVIDER = METRIC_MESSAGES . '.{type}.{provider}';
|
||||
const METRIC_MESSAGES_TYPE_PROVIDER_SENT = METRIC_MESSAGES . '.{type}.{provider}.sent';
|
||||
const METRIC_MESSAGES_TYPE_PROVIDER_FAILED = METRIC_MESSAGES . '.{type}.{provider}.failed';
|
||||
const METRIC_SESSIONS = 'sessions';
|
||||
const METRIC_DATABASES = 'databases';
|
||||
const METRIC_COLLECTIONS = 'collections';
|
||||
const METRIC_DATABASES_STORAGE = 'databases.storage';
|
||||
const METRIC_DATABASE_ID_COLLECTIONS = '{databaseInternalId}.collections';
|
||||
const METRIC_DATABASE_ID_STORAGE = '{databaseInternalId}.databases.storage';
|
||||
const METRIC_DOCUMENTS = 'documents';
|
||||
const METRIC_DATABASE_ID_DOCUMENTS = '{databaseInternalId}.documents';
|
||||
const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS = '{databaseInternalId}.{collectionInternalId}.documents';
|
||||
const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE = '{databaseInternalId}.{collectionInternalId}.databases.storage';
|
||||
const METRIC_DATABASES_OPERATIONS_READS = 'databases.operations.reads';
|
||||
const METRIC_DATABASE_ID_OPERATIONS_READS = '{databaseInternalId}.databases.operations.reads';
|
||||
const METRIC_DATABASES_OPERATIONS_WRITES = 'databases.operations.writes';
|
||||
const METRIC_DATABASE_ID_OPERATIONS_WRITES = '{databaseInternalId}.databases.operations.writes';
|
||||
const METRIC_BUCKETS = 'buckets';
|
||||
const METRIC_FILES = 'files';
|
||||
const METRIC_FILES_STORAGE = 'files.storage';
|
||||
const METRIC_FILES_TRANSFORMATIONS = 'files.transformations';
|
||||
const METRIC_BUCKET_ID_FILES_TRANSFORMATIONS = '{bucketInternalId}.files.transformations';
|
||||
const METRIC_FILES_IMAGES_TRANSFORMED = 'files.imagesTransformed';
|
||||
const METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED = '{bucketInternalId}.files.imagesTransformed';
|
||||
const METRIC_BUCKET_ID_FILES = '{bucketInternalId}.files';
|
||||
const METRIC_BUCKET_ID_FILES_STORAGE = '{bucketInternalId}.files.storage';
|
||||
const METRIC_SITES = 'sites';
|
||||
const METRIC_FUNCTIONS = 'functions';
|
||||
const METRIC_DEPLOYMENTS = 'deployments';
|
||||
const METRIC_DEPLOYMENTS_STORAGE = 'deployments.storage';
|
||||
const METRIC_BUILDS = 'builds';
|
||||
const METRIC_BUILDS_SUCCESS = 'builds.success';
|
||||
const METRIC_BUILDS_FAILED = 'builds.failed';
|
||||
const METRIC_BUILDS_STORAGE = 'builds.storage';
|
||||
const METRIC_BUILDS_COMPUTE = 'builds.compute';
|
||||
const METRIC_BUILDS_COMPUTE_SUCCESS = 'builds.compute.success';
|
||||
const METRIC_BUILDS_COMPUTE_FAILED = 'builds.compute.failed';
|
||||
const METRIC_BUILDS_MB_SECONDS = 'builds.mbSeconds';
|
||||
const METRIC_EXECUTIONS = 'executions';
|
||||
const METRIC_EXECUTIONS_COMPUTE = 'executions.compute';
|
||||
const METRIC_EXECUTIONS_MB_SECONDS = 'executions.mbSeconds';
|
||||
const METRIC_RESOURCE_TYPE_ID_EXECUTIONS = '{resourceType}.{resourceInternalId}.executions';
|
||||
const METRIC_RESOURCE_TYPE_ID_EXECUTIONS_COMPUTE = '{resourceType}.{resourceInternalId}.executions.compute';
|
||||
const METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS = '{resourceType}.{resourceInternalId}.executions.mbSeconds';
|
||||
const METRIC_RESOURCE_TYPE_ID_BUILDS_SUCCESS = '{resourceType}.{resourceInternalId}.builds.success';
|
||||
const METRIC_RESOURCE_TYPE_ID_BUILDS_FAILED = '{resourceType}.{resourceInternalId}.builds.failed';
|
||||
const METRIC_RESOURCE_TYPE_ID_BUILDS_COMPUTE = '{resourceType}.{resourceInternalId}.builds.compute';
|
||||
const METRIC_RESOURCE_TYPE_ID_BUILDS_COMPUTE_SUCCESS = '{resourceType}.{resourceInternalId}.builds.compute.success';
|
||||
const METRIC_RESOURCE_TYPE_ID_BUILDS_COMPUTE_FAILED = '{resourceType}.{resourceInternalId}.builds.compute.failed';
|
||||
const METRIC_RESOURCE_TYPE_ID_BUILDS_MB_SECONDS = '{resourceType}.{resourceInternalId}.builds.mbSeconds';
|
||||
const METRIC_RESOURCE_TYPE_ID_BUILDS = '{resourceType}.{resourceInternalId}.builds';
|
||||
const METRIC_RESOURCE_TYPE_ID_BUILDS_STORAGE = '{resourceType}.{resourceInternalId}.builds.storage';
|
||||
const METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
|
||||
const METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
|
||||
const METRIC_RESOURCE_TYPE_EXECUTIONS = '{resourceType}.executions';
|
||||
const METRIC_RESOURCE_TYPE_EXECUTIONS_COMPUTE = '{resourceType}.executions.compute';
|
||||
const METRIC_RESOURCE_TYPE_EXECUTIONS_MB_SECONDS = '{resourceType}.executions.mbSeconds';
|
||||
const METRIC_RESOURCE_TYPE_BUILDS_SUCCESS = '{resourceType}.builds.success';
|
||||
const METRIC_RESOURCE_TYPE_BUILDS_FAILED = '{resourceType}.builds.failed';
|
||||
const METRIC_RESOURCE_TYPE_BUILDS_COMPUTE = '{resourceType}.builds.compute';
|
||||
const METRIC_RESOURCE_TYPE_BUILDS_COMPUTE_SUCCESS = '{resourceType}.builds.compute.success';
|
||||
const METRIC_RESOURCE_TYPE_BUILDS_COMPUTE_FAILED = '{resourceType}.builds.compute.failed';
|
||||
const METRIC_RESOURCE_TYPE_BUILDS_MB_SECONDS = '{resourceType}.builds.mbSeconds';
|
||||
const METRIC_RESOURCE_TYPE_BUILDS = '{resourceType}.builds';
|
||||
const METRIC_RESOURCE_TYPE_BUILDS_STORAGE = '{resourceType}.builds.storage';
|
||||
const METRIC_RESOURCE_TYPE_DEPLOYMENTS = '{resourceType}.deployments';
|
||||
const METRIC_RESOURCE_TYPE_DEPLOYMENTS_STORAGE = '{resourceType}.deployments.storage';
|
||||
const METRIC_NETWORK_REQUESTS = 'network.requests';
|
||||
const METRIC_NETWORK_INBOUND = 'network.inbound';
|
||||
const METRIC_NETWORK_OUTBOUND = 'network.outbound';
|
||||
const METRIC_MAU = 'users.mau';
|
||||
const METRIC_DAU = 'users.dau';
|
||||
const METRIC_WAU = 'users.wau';
|
||||
const METRIC_WEBHOOKS = 'webhooks';
|
||||
const METRIC_PLATFORMS = 'platforms';
|
||||
const METRIC_PROVIDERS = 'providers';
|
||||
const METRIC_TOPICS = 'topics';
|
||||
const METRIC_TARGETS = 'targets';
|
||||
const METRIC_PROVIDER_TYPE_TARGETS = '{providerType}.targets';
|
||||
const METRIC_KEYS = 'keys';
|
||||
const METRIC_DOMAINS = 'domains';
|
||||
const METRIC_SITES_REQUESTS = 'sites.requests';
|
||||
const METRIC_SITES_INBOUND = 'sites.inbound';
|
||||
const METRIC_SITES_OUTBOUND = 'sites.outbound';
|
||||
const METRIC_SITES_ID_REQUESTS = 'sites.{siteInternalId}.requests';
|
||||
const METRIC_SITES_ID_INBOUND = 'sites.{siteInternalId}.inbound';
|
||||
const METRIC_SITES_ID_OUTBOUND = 'sites.{siteInternalId}.outbound';
|
||||
|
||||
// Resource types
|
||||
|
||||
const RESOURCE_TYPE_PROJECTS = 'projects';
|
||||
const RESOURCE_TYPE_FUNCTIONS = 'functions';
|
||||
const RESOURCE_TYPE_SITES = 'sites';
|
||||
const RESOURCE_TYPE_DATABASES = 'databases';
|
||||
const RESOURCE_TYPE_BUCKETS = 'buckets';
|
||||
const RESOURCE_TYPE_PROVIDERS = 'providers';
|
||||
const RESOURCE_TYPE_TOPICS = 'topics';
|
||||
const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers';
|
||||
const RESOURCE_TYPE_MESSAGES = 'messages';
|
||||
|
||||
// Resource types for Tokens
|
||||
|
||||
const TOKENS_RESOURCE_TYPE_FILES = 'files';
|
||||
const TOKENS_RESOURCE_TYPE_SITES = 'sites';
|
||||
const TOKENS_RESOURCE_TYPE_FUNCTIONS = 'functions';
|
||||
const TOKENS_RESOURCE_TYPE_DATABASES = 'databases';
|
||||
@@ -0,0 +1,418 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\System\System;
|
||||
|
||||
Database::addFilter(
|
||||
'casting',
|
||||
function (mixed $value) {
|
||||
return json_encode(['value' => $value], JSON_PRESERVE_ZERO_FRACTION);
|
||||
},
|
||||
function (mixed $value) {
|
||||
if (is_null($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return json_decode($value, true)['value'];
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'enum',
|
||||
function (mixed $value, Document $attribute) {
|
||||
if ($attribute->isSet('elements')) {
|
||||
$attribute->removeAttribute('elements');
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
function (mixed $value, Document $attribute) {
|
||||
$formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true);
|
||||
if (isset($formatOptions['elements'])) {
|
||||
$attribute->setAttribute('elements', $formatOptions['elements']);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'range',
|
||||
function (mixed $value, Document $attribute) {
|
||||
if ($attribute->isSet('min')) {
|
||||
$attribute->removeAttribute('min');
|
||||
}
|
||||
if ($attribute->isSet('max')) {
|
||||
$attribute->removeAttribute('max');
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
function (mixed $value, Document $attribute) {
|
||||
$formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true);
|
||||
if (isset($formatOptions['min']) || isset($formatOptions['max'])) {
|
||||
$attribute
|
||||
->setAttribute('min', $formatOptions['min'])
|
||||
->setAttribute('max', $formatOptions['max']);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryAttributes',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
$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;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryIndexes',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('indexes', [
|
||||
Query::equal('collectionInternalId', [$document->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit($database->getLimitForIndexes()),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryPlatforms',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('platforms', [
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryKeys',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('keys', [
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryDevKeys',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('devKeys', [
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryWebhooks',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('webhooks', [
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQuerySessions',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database->find('sessions', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryTokens',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
->find('tokens', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryChallenges',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
->find('challenges', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryAuthenticators',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
->find('authenticators', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryMemberships',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
->find('memberships', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryVariables',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('variables', [
|
||||
Query::equal('resourceInternalId', [$document->getInternalId()]),
|
||||
Query::equal('resourceType', ['function', 'site']),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'encrypt',
|
||||
function (mixed $value) {
|
||||
$key = System::getEnv('_APP_OPENSSL_KEY_V1');
|
||||
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
|
||||
$tag = null;
|
||||
|
||||
return json_encode([
|
||||
'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
|
||||
'method' => OpenSSL::CIPHER_AES_128_GCM,
|
||||
'iv' => \bin2hex($iv),
|
||||
'tag' => \bin2hex($tag ?? ''),
|
||||
'version' => '1',
|
||||
]);
|
||||
},
|
||||
function (mixed $value) {
|
||||
if (is_null($value)) {
|
||||
return;
|
||||
}
|
||||
$value = json_decode($value, true);
|
||||
$key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']);
|
||||
|
||||
return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag']));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryProjectVariables',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('variables', [
|
||||
Query::equal('resourceType', ['project']),
|
||||
Query::limit(APP_LIMIT_SUBQUERY)
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'userSearch',
|
||||
function (mixed $value, Document $user) {
|
||||
$searchValues = [
|
||||
$user->getId(),
|
||||
$user->getAttribute('email', ''),
|
||||
$user->getAttribute('name', ''),
|
||||
$user->getAttribute('phone', '')
|
||||
];
|
||||
|
||||
foreach ($user->getAttribute('labels', []) as $label) {
|
||||
$searchValues[] = 'label:' . $label;
|
||||
}
|
||||
|
||||
$search = implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryTargets',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
->find('targets', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY)
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryTopicTargets',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
$targetIds = Authorization::skip(fn () => \array_map(
|
||||
fn ($document) => $document->getAttribute('targetInternalId'),
|
||||
$database->find('subscribers', [
|
||||
Query::equal('topicInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY)
|
||||
])
|
||||
));
|
||||
if (\count($targetIds) > 0) {
|
||||
return $database->skipValidation(fn () => $database->find('targets', [
|
||||
Query::equal('$internalId', $targetIds)
|
||||
]));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'providerSearch',
|
||||
function (mixed $value, Document $provider) {
|
||||
$searchValues = [
|
||||
$provider->getId(),
|
||||
$provider->getAttribute('name', ''),
|
||||
$provider->getAttribute('provider', ''),
|
||||
$provider->getAttribute('type', '')
|
||||
];
|
||||
|
||||
$search = \implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'topicSearch',
|
||||
function (mixed $value, Document $topic) {
|
||||
$searchValues = [
|
||||
$topic->getId(),
|
||||
$topic->getAttribute('name', ''),
|
||||
$topic->getAttribute('description', ''),
|
||||
];
|
||||
|
||||
$search = \implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'messageSearch',
|
||||
function (mixed $value, Document $message) {
|
||||
$searchValues = [
|
||||
$message->getId(),
|
||||
$message->getAttribute('description', ''),
|
||||
$message->getAttribute('status', ''),
|
||||
];
|
||||
|
||||
$data = \json_decode($message->getAttribute('data', []), true);
|
||||
$providerType = $message->getAttribute('providerType', '');
|
||||
|
||||
switch ($providerType) {
|
||||
case MESSAGE_TYPE_EMAIL:
|
||||
$searchValues[] = $data['subject'];
|
||||
$searchValues[] = MESSAGE_TYPE_EMAIL;
|
||||
break;
|
||||
case MESSAGE_TYPE_SMS:
|
||||
$searchValues[] = $data['content'];
|
||||
$searchValues[] = MESSAGE_TYPE_SMS;
|
||||
break;
|
||||
case MESSAGE_TYPE_PUSH:
|
||||
$searchValues[] = $data['title'] ?? '';
|
||||
$searchValues[] = MESSAGE_TYPE_PUSH;
|
||||
break;
|
||||
}
|
||||
|
||||
$search = \implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
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);
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function () {
|
||||
return new IP();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function () {
|
||||
return new URL();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) {
|
||||
$min = $attribute['formatOptions']['min'] ?? -INF;
|
||||
$max = $attribute['formatOptions']['max'] ?? INF;
|
||||
return new Range($min, $max, Range::TYPE_INTEGER);
|
||||
}, Database::VAR_INTEGER);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) {
|
||||
$min = $attribute['formatOptions']['min'] ?? -INF;
|
||||
$max = $attribute['formatOptions']['max'] ?? INF;
|
||||
return new Range($min, $max, Range::TYPE_FLOAT);
|
||||
}, Database::VAR_FLOAT);
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Locale\Locale;
|
||||
|
||||
Locale::$exceptions = false;
|
||||
|
||||
$locales = Config::getParam('locale-codes', []);
|
||||
|
||||
foreach ($locales as $locale) {
|
||||
$code = $locale['code'];
|
||||
|
||||
$path = __DIR__ . '/../config/locale/translations/' . $code . '.json';
|
||||
|
||||
if (!\file_exists($path)) {
|
||||
$path = __DIR__ . '/../config/locale/translations/' . \substr($code, 0, 2) . '.json'; // if `ar-ae` doesn't exist, look for `ar`
|
||||
if (!\file_exists($path)) {
|
||||
$path = __DIR__ . '/../config/locale/translations/en.json'; // if none translation exists, use default from `en.json`
|
||||
}
|
||||
}
|
||||
|
||||
Locale::setLanguageFromJSON($code, $path);
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\GraphQL\Promises\Adapter\Swoole;
|
||||
use Appwrite\Hooks\Hooks;
|
||||
use Appwrite\PubSub\Adapter\Redis as PubSub;
|
||||
use Appwrite\URL\URL as AppwriteURL;
|
||||
use MaxMind\Db\Reader;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Swoole\Database\PDOProxy;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Adapter\MySQL;
|
||||
use Utopia\Database\Adapter\SQL;
|
||||
use Utopia\Domains\Validator\PublicDomain;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Adapter\AppSignal;
|
||||
use Utopia\Logger\Adapter\LogOwl;
|
||||
use Utopia\Logger\Adapter\Raygun;
|
||||
use Utopia\Logger\Adapter\Sentry;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Pools\Pool;
|
||||
use Utopia\Queue;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\System\System;
|
||||
|
||||
$register = new Registry();
|
||||
|
||||
App::setMode(System::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
|
||||
|
||||
if (!App::isProduction()) {
|
||||
// Allow specific domains to skip public domain validation in dev environment
|
||||
// Useful for existing tests involving webhooks
|
||||
PublicDomain::allow(['request-catcher']);
|
||||
}
|
||||
$register->set('logger', function () {
|
||||
// Register error logger
|
||||
$providerName = System::getEnv('_APP_LOGGING_PROVIDER', '');
|
||||
$providerConfig = System::getEnv('_APP_LOGGING_CONFIG', '');
|
||||
|
||||
if (empty($providerConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$loggingProvider = new DSN($providerConfig ?? '');
|
||||
|
||||
$providerName = $loggingProvider->getScheme();
|
||||
$providerConfig = match ($providerName) {
|
||||
'sentry' => ['key' => $loggingProvider->getPassword(), 'projectId' => $loggingProvider->getUser() ?? '', 'host' => 'https://' . $loggingProvider->getHost()],
|
||||
'logowl' => ['ticket' => $loggingProvider->getUser() ?? '', 'host' => $loggingProvider->getHost()],
|
||||
default => ['key' => $loggingProvider->getHost()],
|
||||
};
|
||||
} catch (Throwable $th) {
|
||||
// Fallback for older Appwrite versions up to 1.5.x that use _APP_LOGGING_PROVIDER and _APP_LOGGING_CONFIG environment variables
|
||||
Console::warning('Using deprecated logging configuration. Please update your configuration to use DSN format.' . $th->getMessage());
|
||||
$configChunks = \explode(";", $providerConfig);
|
||||
|
||||
$providerConfig = match ($providerName) {
|
||||
'sentry' => [ 'key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',],
|
||||
'logowl' => ['ticket' => $configChunks[0] ?? '', 'host' => ''],
|
||||
default => ['key' => $providerConfig],
|
||||
};
|
||||
}
|
||||
|
||||
if (empty($providerName) || empty($providerConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Logger::hasProvider($providerName)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled");
|
||||
}
|
||||
|
||||
try {
|
||||
$adapter = match ($providerName) {
|
||||
'sentry' => new Sentry($providerConfig['projectId'], $providerConfig['key'], $providerConfig['host']),
|
||||
'logowl' => new LogOwl($providerConfig['ticket'], $providerConfig['host']),
|
||||
'raygun' => new Raygun($providerConfig['key']),
|
||||
'appsignal' => new AppSignal($providerConfig['key']),
|
||||
default => null
|
||||
};
|
||||
} catch (Throwable $th) {
|
||||
$adapter = null;
|
||||
}
|
||||
|
||||
if ($adapter === null) {
|
||||
Console::error("Logging provider not supported. Logging is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
return new Logger($adapter);
|
||||
});
|
||||
|
||||
$register->set('pools', function () {
|
||||
$group = new Group();
|
||||
|
||||
$fallbackForDB = 'db_main=' . AppwriteURL::unparse([
|
||||
'scheme' => 'mariadb',
|
||||
'host' => System::getEnv('_APP_DB_HOST', 'mariadb'),
|
||||
'port' => System::getEnv('_APP_DB_PORT', '3306'),
|
||||
'user' => System::getEnv('_APP_DB_USER', ''),
|
||||
'pass' => System::getEnv('_APP_DB_PASS', ''),
|
||||
'path' => System::getEnv('_APP_DB_SCHEMA', ''),
|
||||
]);
|
||||
$fallbackForRedis = 'redis_main=' . AppwriteURL::unparse([
|
||||
'scheme' => 'redis',
|
||||
'host' => System::getEnv('_APP_REDIS_HOST', 'redis'),
|
||||
'port' => System::getEnv('_APP_REDIS_PORT', '6379'),
|
||||
'user' => System::getEnv('_APP_REDIS_USER', ''),
|
||||
'pass' => System::getEnv('_APP_REDIS_PASS', ''),
|
||||
]);
|
||||
|
||||
$connections = [
|
||||
'console' => [
|
||||
'type' => 'database',
|
||||
'dsns' => $fallbackForDB,
|
||||
'multiple' => false,
|
||||
'schemes' => ['mariadb', 'mysql'],
|
||||
],
|
||||
'database' => [
|
||||
'type' => 'database',
|
||||
'dsns' => $fallbackForDB,
|
||||
'multiple' => true,
|
||||
'schemes' => ['mariadb', 'mysql'],
|
||||
],
|
||||
'logs' => [
|
||||
'type' => 'database',
|
||||
'dsns' => System::getEnv('_APP_CONNECTIONS_DB_LOGS', $fallbackForDB),
|
||||
'multiple' => false,
|
||||
'schemes' => ['mariadb', 'mysql'],
|
||||
],
|
||||
'publisher' => [
|
||||
'type' => 'publisher',
|
||||
'dsns' => $fallbackForRedis,
|
||||
'multiple' => false,
|
||||
'schemes' => ['redis'],
|
||||
],
|
||||
'consumer' => [
|
||||
'type' => 'consumer',
|
||||
'dsns' => $fallbackForRedis,
|
||||
'multiple' => false,
|
||||
'schemes' => ['redis'],
|
||||
],
|
||||
'pubsub' => [
|
||||
'type' => 'pubsub',
|
||||
'dsns' => $fallbackForRedis,
|
||||
'multiple' => false,
|
||||
'schemes' => ['redis'],
|
||||
],
|
||||
'cache' => [
|
||||
'type' => 'cache',
|
||||
'dsns' => $fallbackForRedis,
|
||||
'multiple' => true,
|
||||
'schemes' => ['redis'],
|
||||
],
|
||||
];
|
||||
|
||||
$maxConnections = System::getEnv('_APP_CONNECTIONS_MAX', 151);
|
||||
$instanceConnections = $maxConnections / System::getEnv('_APP_POOL_CLIENTS', 14);
|
||||
|
||||
$multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled';
|
||||
|
||||
if ($multiprocessing) {
|
||||
$workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
} else {
|
||||
$workerCount = 1;
|
||||
}
|
||||
|
||||
if ($workerCount > $instanceConnections) {
|
||||
throw new \Exception('Pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500);
|
||||
}
|
||||
|
||||
$poolSize = (int)($instanceConnections / $workerCount);
|
||||
|
||||
foreach ($connections as $key => $connection) {
|
||||
$type = $connection['type'] ?? '';
|
||||
$multiple = $connection['multiple'] ?? false;
|
||||
$schemes = $connection['schemes'] ?? [];
|
||||
$config = [];
|
||||
$dsns = explode(',', $connection['dsns'] ?? '');
|
||||
foreach ($dsns as &$dsn) {
|
||||
$dsn = explode('=', $dsn);
|
||||
$name = ($multiple) ? $key . '_' . $dsn[0] : $key;
|
||||
$dsn = $dsn[1] ?? '';
|
||||
$config[] = $name;
|
||||
if (empty($dsn)) {
|
||||
//throw new Exception(Exception::GENERAL_SERVER_ERROR, "Missing value for DSN connection in {$key}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$dsn = new DSN($dsn);
|
||||
$dsnHost = $dsn->getHost();
|
||||
$dsnPort = $dsn->getPort();
|
||||
$dsnUser = $dsn->getUser();
|
||||
$dsnPass = $dsn->getPassword();
|
||||
$dsnScheme = $dsn->getScheme();
|
||||
$dsnDatabase = $dsn->getPath();
|
||||
|
||||
if (!in_array($dsnScheme, $schemes)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid console database scheme");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Resource
|
||||
*
|
||||
* Creation could be reused across connection types like database, cache, queue, etc.
|
||||
*
|
||||
* Resource assignment to an adapter will happen below.
|
||||
*/
|
||||
$resource = match ($dsnScheme) {
|
||||
'mysql',
|
||||
'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
|
||||
return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
|
||||
return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array(
|
||||
PDO::ATTR_TIMEOUT => 3, // Seconds
|
||||
PDO::ATTR_PERSISTENT => false,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => true,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => true
|
||||
));
|
||||
});
|
||||
},
|
||||
'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
|
||||
$redis = new \Redis();
|
||||
@$redis->pconnect($dsnHost, (int)$dsnPort);
|
||||
if ($dsnPass) {
|
||||
$redis->auth($dsnPass);
|
||||
}
|
||||
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
|
||||
|
||||
return $redis;
|
||||
},
|
||||
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'),
|
||||
};
|
||||
|
||||
$pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) {
|
||||
// Get Adapter
|
||||
switch ($type) {
|
||||
case 'database':
|
||||
$adapter = match ($dsn->getScheme()) {
|
||||
'mariadb' => new MariaDB($resource()),
|
||||
'mysql' => new MySQL($resource()),
|
||||
default => null
|
||||
};
|
||||
|
||||
$adapter->setDatabase($dsn->getPath());
|
||||
return $adapter;
|
||||
case 'pubsub':
|
||||
return match ($dsn->getScheme()) {
|
||||
'redis' => new PubSub($resource()),
|
||||
default => null
|
||||
};
|
||||
case 'publisher':
|
||||
case 'consumer':
|
||||
return match ($dsn->getScheme()) {
|
||||
'redis' => new Queue\Broker\Redis(new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort())),
|
||||
default => null
|
||||
};
|
||||
case 'cache':
|
||||
return match ($dsn->getScheme()) {
|
||||
'redis' => new RedisCache($resource()),
|
||||
default => null
|
||||
};
|
||||
default:
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
|
||||
}
|
||||
});
|
||||
|
||||
$group->add($pool);
|
||||
}
|
||||
|
||||
Config::setParam('pools-' . $key, $config);
|
||||
}
|
||||
|
||||
return $group;
|
||||
});
|
||||
|
||||
$register->set('db', function () {
|
||||
// This is usually for our workers or CLI commands scope
|
||||
$dbHost = System::getEnv('_APP_DB_HOST', '');
|
||||
$dbPort = System::getEnv('_APP_DB_PORT', '');
|
||||
$dbUser = System::getEnv('_APP_DB_USER', '');
|
||||
$dbPass = System::getEnv('_APP_DB_PASS', '');
|
||||
$dbScheme = System::getEnv('_APP_DB_SCHEMA', '');
|
||||
|
||||
return new PDO(
|
||||
"mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4",
|
||||
$dbUser,
|
||||
$dbPass,
|
||||
SQL::getPDOAttributes()
|
||||
);
|
||||
});
|
||||
|
||||
$register->set('smtp', function () {
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
$mail->isSMTP();
|
||||
|
||||
$username = System::getEnv('_APP_SMTP_USERNAME');
|
||||
$password = System::getEnv('_APP_SMTP_PASSWORD');
|
||||
|
||||
$mail->XMailer = 'Appwrite Mailer';
|
||||
$mail->Host = System::getEnv('_APP_SMTP_HOST', 'smtp');
|
||||
$mail->Port = System::getEnv('_APP_SMTP_PORT', 25);
|
||||
$mail->SMTPAuth = !empty($username) && !empty($password);
|
||||
$mail->Username = $username;
|
||||
$mail->Password = $password;
|
||||
$mail->SMTPSecure = System::getEnv('_APP_SMTP_SECURE', '');
|
||||
$mail->SMTPAutoTLS = false;
|
||||
$mail->CharSet = 'UTF-8';
|
||||
|
||||
$from = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
|
||||
$email = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
|
||||
|
||||
$mail->setFrom($email, $from);
|
||||
$mail->addReplyTo($email, $from);
|
||||
|
||||
$mail->isHTML(true);
|
||||
|
||||
return $mail;
|
||||
});
|
||||
$register->set('geodb', function () {
|
||||
return new Reader(__DIR__ . '/../assets/dbip/dbip-country-lite-2024-09.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('promiseAdapter', function () {
|
||||
return new Swoole();
|
||||
});
|
||||
$register->set('hooks', function () {
|
||||
return new Hooks();
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user