mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
1614
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0209d40a38 | ||
|
|
06367eca69 | ||
|
|
55343379f2 | ||
|
|
a66b565150 | ||
|
|
ff160871ae | ||
|
|
92a3eca194 | ||
|
|
07135956df | ||
|
|
b5a363a2a7 | ||
|
|
31dc9ff873 | ||
|
|
408f9c2d39 | ||
|
|
910bd69b16 | ||
|
|
dfd5965d25 | ||
|
|
2a38eedc98 | ||
|
|
f875b965be | ||
|
|
2e57e5a868 | ||
|
|
69ce99d1b4 | ||
|
|
6b881d4aaf | ||
|
|
aad7d6dbca | ||
|
|
33ffe4aca7 | ||
|
|
77eb3d4bcd | ||
|
|
49989f38f7 | ||
|
|
18a49ccc44 | ||
|
|
73173a7f8c | ||
|
|
e3412bc554 | ||
|
|
c1e50c7abd | ||
|
|
6499f436ca | ||
|
|
55fbd77bf1 | ||
|
|
4046829fc6 | ||
|
|
57e218266f | ||
|
|
188dd5c423 | ||
|
|
d8d3f22883 | ||
|
|
15dc316535 | ||
|
|
7581019e76 | ||
|
|
c4fcec0a8a | ||
|
|
ca877fa71d | ||
|
|
425dd9639c | ||
|
|
e60391bdb3 | ||
|
|
57157a71b4 | ||
|
|
8a52975048 | ||
|
|
45e3267930 | ||
|
|
2b96d60c1e | ||
|
|
417bb22790 | ||
|
|
1b70bc812b | ||
|
|
e7e429142f | ||
|
|
80bb9a1f33 | ||
|
|
774e3af61c | ||
|
|
30373980f1 | ||
|
|
8cf12f685a | ||
|
|
f99cb20d05 | ||
|
|
a7724028b3 | ||
|
|
a0514d2aac | ||
|
|
ac137cee3d | ||
|
|
f4f4ad9c7d | ||
|
|
da7738edaa | ||
|
|
58e091099f | ||
|
|
6410a245eb | ||
|
|
670edd845e | ||
|
|
834c18f9fa | ||
|
|
77930a0cec | ||
|
|
5d9201466b | ||
|
|
d583b5e228 | ||
|
|
c463a9a662 | ||
|
|
bd9f02b689 | ||
|
|
472e2c282d | ||
|
|
7a89779827 | ||
|
|
126de78ce2 | ||
|
|
e919896eef | ||
|
|
fd3475f945 | ||
|
|
9c8f565211 | ||
|
|
08f30224b6 | ||
|
|
6d02554f2a | ||
|
|
61c619f373 | ||
|
|
419d56b4b1 | ||
|
|
b32dd316e1 | ||
|
|
d2cda9770b | ||
|
|
22042b4749 | ||
|
|
aa17eeb6e3 | ||
|
|
1529357821 | ||
|
|
3a98361736 | ||
|
|
0c425dbac3 | ||
|
|
69b861cd87 | ||
|
|
0599d23cc5 | ||
|
|
a37b4a40d9 | ||
|
|
7e37046959 | ||
|
|
f9efdfd98e | ||
|
|
d53090647b | ||
|
|
e8b8a8fe52 | ||
|
|
69430154e6 | ||
|
|
0601305545 | ||
|
|
73d3d15ed8 | ||
|
|
b78be65376 | ||
|
|
5519086c29 | ||
|
|
33b9f33f44 | ||
|
|
0e75793bef | ||
|
|
2f4a362e1b | ||
|
|
cda843d8f6 | ||
|
|
d6a40a35ee | ||
|
|
e111b4cc18 | ||
|
|
cdbd80ceda | ||
|
|
58751bbdf1 | ||
|
|
21334ecdff | ||
|
|
47ea2893c3 | ||
|
|
bbeca28026 | ||
|
|
9ab9f6d709 | ||
|
|
309fde38f7 | ||
|
|
3310d271bf | ||
|
|
4e12b6236e | ||
|
|
4df8157d74 | ||
|
|
37e76e52f6 | ||
|
|
cd824faf64 | ||
|
|
96b11b02e6 | ||
|
|
9f24cb4aa3 | ||
|
|
9e4e23ad3f | ||
|
|
3e96f9c2f1 | ||
|
|
dd9f6ed820 | ||
|
|
f7f1bc8352 | ||
|
|
bf50fbeb6c | ||
|
|
1f7bfb3352 | ||
|
|
406181e887 | ||
|
|
a7b243aff9 | ||
|
|
dc85d44647 | ||
|
|
71621f4ccf | ||
|
|
68deee4a56 | ||
|
|
a20392d13a | ||
|
|
e43292052c | ||
|
|
ee7103c4b0 | ||
|
|
1b1ef80b10 | ||
|
|
33f90fcf6a | ||
|
|
2f1eee390b | ||
|
|
35bf13cfab | ||
|
|
f814fd980b | ||
|
|
3ddfd9c3b0 | ||
|
|
75d8110ad9 | ||
|
|
c958f3f1cb | ||
|
|
06e3a144a3 | ||
|
|
36519c13a8 | ||
|
|
9fb1aaadaf | ||
|
|
d9ac4f2f2d | ||
|
|
e61f3c7a42 | ||
|
|
1b6d51a116 | ||
|
|
0058e88217 | ||
|
|
81b4065921 | ||
|
|
d094d6a081 | ||
|
|
cf90653deb | ||
|
|
bae194e866 | ||
|
|
144e88452e | ||
|
|
89980b1f0e | ||
|
|
16fb25ce5a | ||
|
|
7dea3f19a9 | ||
|
|
e78887001a | ||
|
|
aa4ecdf138 | ||
|
|
9bda83104d | ||
|
|
3783378288 | ||
|
|
937cac454a | ||
|
|
36eb28dc22 | ||
|
|
5aff78a619 | ||
|
|
e6781cb4c3 | ||
|
|
62b391ef68 | ||
|
|
6f1d2d094a | ||
|
|
36690229c5 | ||
|
|
fe5d9cc662 | ||
|
|
f46ec96040 | ||
|
|
88bd35ce98 | ||
|
|
2dc813342a | ||
|
|
fd08f7dd36 | ||
|
|
13f4aa9a80 | ||
|
|
22fba9defc | ||
|
|
fcbb073bcc | ||
|
|
db0dbeb27b | ||
|
|
0e27088e2a | ||
|
|
70a7deaa38 | ||
|
|
514934649e | ||
|
|
ee8e7333d8 | ||
|
|
8d94b6585e | ||
|
|
9fd92ca5ac | ||
|
|
6d9a6051bf | ||
|
|
461c5895a4 | ||
|
|
94fc0416e6 | ||
|
|
ad54a8c334 | ||
|
|
e2b72e1f58 | ||
|
|
e1a9eb4994 | ||
|
|
b0c3ab71c4 | ||
|
|
ce8f72bee9 | ||
|
|
ee4f3e2df4 | ||
|
|
930db2bcc4 | ||
|
|
fadcb39765 | ||
|
|
5034597750 | ||
|
|
f73b48d4ca | ||
|
|
0a7f31333d | ||
|
|
8964fbc821 | ||
|
|
8baa5cb659 | ||
|
|
2e5bea1aee | ||
|
|
759792c447 | ||
|
|
86deab8f6c | ||
|
|
3cf8f151c4 | ||
|
|
4e0945d0ed | ||
|
|
052c8d4ead | ||
|
|
3c3aa6d416 | ||
|
|
eabe06d981 | ||
|
|
8d4c6a8218 | ||
|
|
1a09913a27 | ||
|
|
e8e47746ea | ||
|
|
546cd6596a | ||
|
|
58be8f411a | ||
|
|
990209b624 | ||
|
|
850a834211 | ||
|
|
dfccbd0e16 | ||
|
|
cda5b73600 | ||
|
|
1f4534d674 | ||
|
|
9ba970b3c1 | ||
|
|
ef54603367 | ||
|
|
2c855427aa | ||
|
|
9b62d341d2 | ||
|
|
d3e0050739 | ||
|
|
af8a27c559 | ||
|
|
b986cd806c | ||
|
|
5ba7501842 | ||
|
|
421696a26e | ||
|
|
c6214ef4a8 | ||
|
|
b0b9c412fc | ||
|
|
1c165b8c8d | ||
|
|
6fbacb484b | ||
|
|
91a3ee0c9d | ||
|
|
49b1c3db79 | ||
|
|
dfb53895eb | ||
|
|
7cdb7af07c | ||
|
|
109967953e | ||
|
|
e3e7f56bb8 | ||
|
|
80057383e1 | ||
|
|
c7fda6cc3f | ||
|
|
5ae148f026 | ||
|
|
c744354973 | ||
|
|
ddb0d5db26 | ||
|
|
c7fd31285d | ||
|
|
8cd000c42d | ||
|
|
e931938b78 | ||
|
|
4b58e1a85e | ||
|
|
c6b6b7cdac | ||
|
|
2a61cacdf9 | ||
|
|
f4bca74a59 | ||
|
|
7e6846f8f8 | ||
|
|
03a19cc83c | ||
|
|
8a35762843 | ||
|
|
3869269e0c | ||
|
|
0d7eb88a45 | ||
|
|
cd90974e15 | ||
|
|
54ba59da64 | ||
|
|
f837e6be03 | ||
|
|
cec5cd7bdc | ||
|
|
63c2d72b2e | ||
|
|
e9d9aa653f | ||
|
|
801219374c | ||
|
|
dc3459edd0 | ||
|
|
ace9d86744 | ||
|
|
2d3e34fd5a | ||
|
|
69ad4ae930 | ||
|
|
0037305e16 | ||
|
|
da0b2a7517 | ||
|
|
6aafe2dd12 | ||
|
|
1a205c3f3b | ||
|
|
99966b5a24 | ||
|
|
2437d2be77 | ||
|
|
2043470bd0 | ||
|
|
667285d536 | ||
|
|
1199c1fc52 | ||
|
|
f270e47b48 | ||
|
|
5678860d13 | ||
|
|
b83125a41e | ||
|
|
5daacb7161 | ||
|
|
2dcb131778 | ||
|
|
30083598c6 | ||
|
|
a0599d2658 | ||
|
|
c50db111d6 | ||
|
|
9441295510 | ||
|
|
e4e6c84362 | ||
|
|
70395cfacc | ||
|
|
7d2a95c573 | ||
|
|
f3f3ac80f8 | ||
|
|
45d5f14b83 | ||
|
|
ce9a54fd4b | ||
|
|
8e434b710b | ||
|
|
37b43b0ce0 | ||
|
|
2052496635 | ||
|
|
1cc6c9c0f1 | ||
|
|
bbc76ffe37 | ||
|
|
6589c6a786 | ||
|
|
2cb86c57ef | ||
|
|
90fb5b6321 | ||
|
|
d4517384dc | ||
|
|
6787b0c7f1 | ||
|
|
a94ccdb4f5 | ||
|
|
9518764cae | ||
|
|
d174233cd6 | ||
|
|
278679ab10 | ||
|
|
72ae883587 | ||
|
|
3d0aa74ffe | ||
|
|
e4a7b1b103 | ||
|
|
c6ffa181a2 | ||
|
|
cd8e93db60 | ||
|
|
ceb2108012 | ||
|
|
383f0c1ef1 | ||
|
|
52ccbfcdf7 | ||
|
|
35fe622548 | ||
|
|
d502ab0cd5 | ||
|
|
72f86ce5ef | ||
|
|
062233cdbc | ||
|
|
102d3fd245 | ||
|
|
cb9705f0a2 | ||
|
|
61d1546d14 | ||
|
|
2e4ac091d1 | ||
|
|
ef5f0ed6b4 | ||
|
|
22a1a5a452 | ||
|
|
e738bae523 | ||
|
|
f765eb92e7 | ||
|
|
50c6a35079 | ||
|
|
f8c39c8f06 | ||
|
|
aa12ef6569 | ||
|
|
5912a4c4a0 | ||
|
|
e85079a42f | ||
|
|
4c9a94f5e2 | ||
|
|
7fc8c6ae43 | ||
|
|
61067bd8d3 | ||
|
|
fb3b3ae51e | ||
|
|
39aa4c4a9a | ||
|
|
b03aa8d632 | ||
|
|
0215577bc7 | ||
|
|
768d293cf5 | ||
|
|
bdfb9e305e | ||
|
|
6c1cbfddea | ||
|
|
e0a937912c | ||
|
|
8951a8465c | ||
|
|
2fa95b52a0 | ||
|
|
676d53b18e | ||
|
|
3a3c92140b | ||
|
|
cb3b22c9f9 | ||
|
|
e4ebf755e4 | ||
|
|
6a712e236d | ||
|
|
1cf8b103a8 | ||
|
|
3ca9468b11 | ||
|
|
6156b97ea0 | ||
|
|
0f84f21bb4 | ||
|
|
a14d5c584e | ||
|
|
689e61c84f | ||
|
|
aec7f27ce4 | ||
|
|
c7bdf2a99d | ||
|
|
6320721f51 | ||
|
|
77917c8b46 | ||
|
|
d1b47a7916 | ||
|
|
b79ff7fbff | ||
|
|
0a0cf26269 | ||
|
|
5df786be4b | ||
|
|
65bd9a41d5 | ||
|
|
425bd514c2 | ||
|
|
be5f066dc3 | ||
|
|
b2155acc56 | ||
|
|
a241eab792 | ||
|
|
8ed4fa6ff1 | ||
|
|
4289215620 | ||
|
|
7462b7ae17 | ||
|
|
decf2c7a1b | ||
|
|
43d57bbe4e | ||
|
|
a26efc7aa5 | ||
|
|
549570a29e | ||
|
|
3c9548bc9d | ||
|
|
3459133fd4 | ||
|
|
1d91ecd9fb | ||
|
|
48038512b9 | ||
|
|
b62a1d8b5c | ||
|
|
4ebc67c235 | ||
|
|
ed80450bd5 | ||
|
|
104368966d | ||
|
|
07be361c7c | ||
|
|
bc508353db | ||
|
|
da1285bb22 | ||
|
|
b06b33016c | ||
|
|
27ede0079f | ||
|
|
a0cf7315fc | ||
|
|
fad29ee3cd | ||
|
|
121f94676d | ||
|
|
df94765cf3 | ||
|
|
c6ccbfaae1 | ||
|
|
4713ae5baa | ||
|
|
e47802d1e1 | ||
|
|
c4cc8595d6 | ||
|
|
77face0f2d | ||
|
|
5878a82b8a | ||
|
|
d483fbd43b | ||
|
|
0353b826ac | ||
|
|
331e294ef5 | ||
|
|
e00dacf9a2 | ||
|
|
252a4e12ff | ||
|
|
dce4c6e9f1 | ||
|
|
4de4b475a1 | ||
|
|
7d26131019 | ||
|
|
8b4657ff8a | ||
|
|
c09c2848af | ||
|
|
68ffa5258c | ||
|
|
91a1e4cfd0 | ||
|
|
7aceae05d5 | ||
|
|
0c114b8d66 | ||
|
|
8fe1ec48d0 | ||
|
|
379b85bb7b | ||
|
|
97038c9d05 | ||
|
|
13f886f07f | ||
|
|
c65e551697 | ||
|
|
b4f9d97a81 | ||
|
|
7041cae48c | ||
|
|
b5537bb714 | ||
|
|
7392f6aeff | ||
|
|
c34649cb50 | ||
|
|
fde30b6216 | ||
|
|
e0e9b2ba6f | ||
|
|
250a7da1ee | ||
|
|
f208aa206d | ||
|
|
d472978c1a | ||
|
|
5cc6845a44 | ||
|
|
0386fde126 | ||
|
|
866bd5ebc8 | ||
|
|
d0b1f5f529 | ||
|
|
98dbb03155 | ||
|
|
088359c257 | ||
|
|
e114d49789 | ||
|
|
1df5b71e32 | ||
|
|
a166ae5a03 | ||
|
|
1d0c522fad | ||
|
|
c5b2b67361 | ||
|
|
4c62ecbc95 | ||
|
|
1a4adc50b2 | ||
|
|
aaafdaae52 | ||
|
|
beea3abfc7 | ||
|
|
2dfa7d9542 | ||
|
|
22c90ca7ad | ||
|
|
2563a8f967 | ||
|
|
eb1c119c21 | ||
|
|
27d88a421e | ||
|
|
e4755c624f | ||
|
|
21ef33ea45 | ||
|
|
5b2fdf7e5b | ||
|
|
38af4b83c0 | ||
|
|
cc50ba6b7d | ||
|
|
cf6605746d | ||
|
|
b061f95f6d | ||
|
|
c5a45aaded | ||
|
|
adc0d3dbd0 | ||
|
|
27b8b23c07 | ||
|
|
6aa054bc7c | ||
|
|
d3fac8ae07 | ||
|
|
013e961c37 | ||
|
|
b899e13763 | ||
|
|
5b3659af7c | ||
|
|
95e29d1046 | ||
|
|
64dbb28612 | ||
|
|
0b5d67a77a | ||
|
|
e204419b8b | ||
|
|
851fb58645 | ||
|
|
128478ed00 | ||
|
|
6f0c4d6c4e | ||
|
|
aed0079cad | ||
|
|
67311fe5d6 | ||
|
|
5da1f791a5 | ||
|
|
c4366c9de1 | ||
|
|
d517ca8444 | ||
|
|
67ae0b088a | ||
|
|
adc5426ce9 | ||
|
|
c09f7d5cf9 | ||
|
|
f216ea5d65 | ||
|
|
2a88341edc | ||
|
|
95abe61bab | ||
|
|
2fdfbf6e61 | ||
|
|
c2585e16b1 | ||
|
|
fd466dfcea | ||
|
|
fb95a05599 | ||
|
|
b76f01b144 | ||
|
|
35e3294dc4 | ||
|
|
81c2ff501b | ||
|
|
2bd308c236 | ||
|
|
885a48b598 | ||
|
|
63652cc155 | ||
|
|
06c876906c | ||
|
|
ef8d7a45f9 | ||
|
|
1bfd413040 | ||
|
|
c2ea1b5877 | ||
|
|
4dc4cc2c11 | ||
|
|
830eda7460 | ||
|
|
b69bf3d269 | ||
|
|
04dd0f807d | ||
|
|
5dd3f9c574 | ||
|
|
cbc8b2ca0d | ||
|
|
f6b04ae63e | ||
|
|
4fd0af509e | ||
|
|
d539186808 | ||
|
|
cee9906073 | ||
|
|
4ad249f918 | ||
|
|
1ed3cf0e59 | ||
|
|
11b8742a73 | ||
|
|
7e9d74e141 | ||
|
|
aa98fbb2dc | ||
|
|
8f1b3a8e30 | ||
|
|
3d8cee4676 | ||
|
|
7efbae4310 | ||
|
|
1c9fd39fd6 | ||
|
|
8c854b7d07 | ||
|
|
c713357131 | ||
|
|
fe9ecb6fcb | ||
|
|
ab1541808b | ||
|
|
623a8a747d | ||
|
|
21237f8e02 | ||
|
|
f0ed75f585 | ||
|
|
34ce5099e2 | ||
|
|
10171c80da | ||
|
|
c6e4afdb49 | ||
|
|
448e60479d | ||
|
|
8fe7de7ff9 | ||
|
|
8e70a5239d | ||
|
|
28beb27759 | ||
|
|
b1f5b6946a | ||
|
|
9ac594c537 | ||
|
|
6547637c96 | ||
|
|
13a7ead598 | ||
|
|
d40e793137 | ||
|
|
149fee503f | ||
|
|
5a1c257a37 | ||
|
|
94ba8ee5f2 | ||
|
|
e5fa23ef6c | ||
|
|
053665eef9 | ||
|
|
66afd1064d | ||
|
|
e2758691af | ||
|
|
9a8a42f479 | ||
|
|
3347573f17 | ||
|
|
cceb108fd3 | ||
|
|
b905686c0e | ||
|
|
ad43be5dab | ||
|
|
ceb11e039f | ||
|
|
63e06750cf | ||
|
|
a4f7cf9f73 | ||
|
|
ba10bed271 | ||
|
|
e2979410da | ||
|
|
9b68b54743 | ||
|
|
0fbdbb386f | ||
|
|
b484dd8806 | ||
|
|
50f26e8702 | ||
|
|
57758296f5 | ||
|
|
a2463f8cc3 | ||
|
|
a0a1cda2e5 | ||
|
|
0cdabbf959 | ||
|
|
4b752c15e8 | ||
|
|
fcce6f8d34 | ||
|
|
2443266ac5 | ||
|
|
26f506b7ba | ||
|
|
536537553b | ||
|
|
4616a2e95d | ||
|
|
9a0e1c1840 | ||
|
|
bdbe94b56f | ||
|
|
0974e1335f | ||
|
|
61f8c99da7 | ||
|
|
150d09f656 | ||
|
|
bfce804f79 | ||
|
|
a479ce63fc | ||
|
|
8765d5650a | ||
|
|
e3ee724178 | ||
|
|
ce2f2d0ebc | ||
|
|
9f7e3146a4 | ||
|
|
e6039cf65e | ||
|
|
7b3bceeb7d | ||
|
|
90ef76ad82 | ||
|
|
5a8a900b18 | ||
|
|
c2645be4e1 | ||
|
|
20221cd6b6 | ||
|
|
275ff3dbf2 | ||
|
|
b6fbddad81 | ||
|
|
39c5b6c4d0 | ||
|
|
f9274dad3f | ||
|
|
3568af54b3 | ||
|
|
71b6ac0364 | ||
|
|
919b01a1b1 | ||
|
|
3d69998f9c | ||
|
|
d2000ee9d8 | ||
|
|
0718f90405 | ||
|
|
73f5b4227d | ||
|
|
52766cfdba | ||
|
|
ba542554ce | ||
|
|
deb5039352 | ||
|
|
793057c823 | ||
|
|
4a86b2d5b1 | ||
|
|
d36e42377b | ||
|
|
d9522ac6b8 | ||
|
|
db1b87ecbb | ||
|
|
99d16dfc60 | ||
|
|
725870b1b1 | ||
|
|
cc7b5b7dcf | ||
|
|
38ef0ad399 | ||
|
|
a1dff370a4 | ||
|
|
c961316f46 | ||
|
|
2349401a86 | ||
|
|
6274d5a3e4 | ||
|
|
d0057ec404 | ||
|
|
ab90563989 | ||
|
|
e783f4c234 | ||
|
|
83b9815644 | ||
|
|
afb0fcbc6f | ||
|
|
082afcec19 | ||
|
|
2ab36f4cd6 | ||
|
|
72c71785a6 | ||
|
|
463eb0e66a | ||
|
|
0588fb405d | ||
|
|
b9211d7141 | ||
|
|
a11cf6b002 | ||
|
|
521a72f448 | ||
|
|
d41746f29a | ||
|
|
a2799b16f8 | ||
|
|
298e6f9260 | ||
|
|
3df2efb7ae | ||
|
|
b0a0454c55 | ||
|
|
b2d228e102 | ||
|
|
b695ddc7e3 | ||
|
|
3bbd87ecb2 | ||
|
|
fe8c4cb479 | ||
|
|
5e4c74d501 | ||
|
|
1da4736d5d | ||
|
|
c78da61304 | ||
|
|
5722241ff8 | ||
|
|
fe8593fecc | ||
|
|
b07674ccd3 | ||
|
|
aa5e3bf9a9 | ||
|
|
c1b50e9941 | ||
|
|
3a76249d80 | ||
|
|
b2991709dc | ||
|
|
fcc07533e1 | ||
|
|
3a6d907b42 | ||
|
|
b793a11cc8 | ||
|
|
9cda4d8928 | ||
|
|
6f351284e6 | ||
|
|
babddf2d1e | ||
|
|
73d8fbfebc | ||
|
|
d411b741b3 | ||
|
|
bc19e66775 | ||
|
|
08c9db7c60 | ||
|
|
e8f2e78ac4 | ||
|
|
37f16ef0bb | ||
|
|
46d785bbb8 | ||
|
|
a9c9f602ec | ||
|
|
d90b4c2f19 | ||
|
|
79d733486c | ||
|
|
d862245090 | ||
|
|
aacaadcce0 | ||
|
|
69ee6e0659 | ||
|
|
e564793089 | ||
|
|
37910a4c5c | ||
|
|
0c7551b0c1 | ||
|
|
c9582978ed | ||
|
|
ae21fadf0d | ||
|
|
cfd62cf0a1 | ||
|
|
279682941a | ||
|
|
eae26d90af | ||
|
|
b46ebb95a3 | ||
|
|
ffcb6a4677 | ||
|
|
142e359dba | ||
|
|
4130983210 | ||
|
|
e01ac65ce8 | ||
|
|
97454c0768 | ||
|
|
8b4a56cd9b | ||
|
|
da0a6b167f | ||
|
|
27d0c511ac | ||
|
|
8a58f7b6d7 | ||
|
|
cc97d78b5a | ||
|
|
91c24b671b | ||
|
|
b28e536967 | ||
|
|
ab5aac00c4 | ||
|
|
653ac184e1 | ||
|
|
353e84bb74 | ||
|
|
94614d32d0 | ||
|
|
b20c493fa6 | ||
|
|
1efe9fa693 | ||
|
|
74ff9a955b | ||
|
|
ce7f40dd34 | ||
|
|
49318b5dac | ||
|
|
da736631b2 | ||
|
|
7d568af9cc | ||
|
|
b91268b3cd | ||
|
|
9db6f9cb75 | ||
|
|
6096c42ff9 | ||
|
|
831a261c04 | ||
|
|
c784cf950b | ||
|
|
b06354b99a | ||
|
|
a797951bec | ||
|
|
bb0007a1e8 | ||
|
|
507717fdf7 | ||
|
|
5afae248ae | ||
|
|
98255fe628 | ||
|
|
057397197b | ||
|
|
c398d5fba9 | ||
|
|
64b4ea9551 | ||
|
|
47b653fd64 | ||
|
|
4dc700927e | ||
|
|
4a148e6ef9 | ||
|
|
2ff0ae61c8 | ||
|
|
c8e6fcc090 | ||
|
|
b3df65cf33 | ||
|
|
b06460b562 | ||
|
|
74492ded44 | ||
|
|
9ecffbf4e9 | ||
|
|
0ed4b5dbe7 | ||
|
|
eb17ffd5d2 | ||
|
|
0bf8cead51 | ||
|
|
add1142d1f | ||
|
|
86896a6d3c | ||
|
|
4fd4af312d | ||
|
|
3ef6154ebe | ||
|
|
a96ba5499e | ||
|
|
599aa1dcca | ||
|
|
4b7a7496ff | ||
|
|
eb0e42b79c | ||
|
|
62e5f54fcb | ||
|
|
09933ce658 | ||
|
|
b225503dc5 | ||
|
|
a77a279539 | ||
|
|
b1e65725b5 | ||
|
|
0c93e7d545 | ||
|
|
469d0859fc | ||
|
|
7c408d675e | ||
|
|
4a96d89ee6 | ||
|
|
4d47bc2ebf | ||
|
|
119d6c1789 | ||
|
|
2aa2b8df91 | ||
|
|
84ebbc6993 | ||
|
|
4f1940ac0c | ||
|
|
94db75fd6c | ||
|
|
2e4397a951 | ||
|
|
2f255ce01c | ||
|
|
ef938a1c39 | ||
|
|
ebee879855 | ||
|
|
ae2ffd259c | ||
|
|
3d471bae56 | ||
|
|
d0f2293755 | ||
|
|
d6bd26da43 | ||
|
|
16d6c9e5e3 | ||
|
|
e342009c9e | ||
|
|
41d89f1b7a | ||
|
|
ad6aff6af2 | ||
|
|
9b52f6f8f6 | ||
|
|
ad46d057d5 | ||
|
|
9ef6fa9b4c | ||
|
|
9fe74bdd22 | ||
|
|
68ae8ce348 | ||
|
|
87b0bfcdfe | ||
|
|
fa2b46c7e7 | ||
|
|
13f9900802 | ||
|
|
3c93004cde | ||
|
|
631fd2ea4c | ||
|
|
8a9d0a3f33 | ||
|
|
d37bab37e6 | ||
|
|
7810d54238 | ||
|
|
b3f9404b93 | ||
|
|
d4b5d7df73 | ||
|
|
e93f479157 | ||
|
|
a1b1f97162 | ||
|
|
1f0e3e0f51 | ||
|
|
26d3cef528 | ||
|
|
fb7691be3b | ||
|
|
eb7be569eb | ||
|
|
88cbc36a02 | ||
|
|
6a60b7d25b | ||
|
|
343d443903 | ||
|
|
12c27edaf9 | ||
|
|
0f9b13e251 | ||
|
|
965c4738e7 | ||
|
|
4679eff4b8 | ||
|
|
3cfb5d695f | ||
|
|
501cba216b | ||
|
|
7ef5a7edaf | ||
|
|
8adeb40c1d | ||
|
|
ae105165ab | ||
|
|
5dfee10369 | ||
|
|
44b5ed90d5 | ||
|
|
e70d5c46b6 | ||
|
|
c9d66c52b4 | ||
|
|
4d22f4252f | ||
|
|
9130a7102e | ||
|
|
2a7d89ea7b | ||
|
|
4ce2426c3d | ||
|
|
25b3680b1d | ||
|
|
c910c98b32 | ||
|
|
fe9d49c346 | ||
|
|
f3fd2fd191 | ||
|
|
a970969c4d | ||
|
|
6c1e4434a6 | ||
|
|
f0e4c9184d | ||
|
|
ba6e389f81 | ||
|
|
c442dd3ef1 | ||
|
|
bf5aa7b86c | ||
|
|
b42cc11eba | ||
|
|
decad9ff9d | ||
|
|
e264ef53f4 | ||
|
|
31ffc9ea92 | ||
|
|
81480f4492 | ||
|
|
4b15fec1d7 | ||
|
|
918ad4a545 | ||
|
|
dff7aa6ce0 | ||
|
|
5ef717d0fd | ||
|
|
79573b1d47 | ||
|
|
3739ee26d8 | ||
|
|
2d332264c6 | ||
|
|
437d914e90 | ||
|
|
897f512b47 | ||
|
|
df1f14c853 | ||
|
|
68cce71527 | ||
|
|
2ae474971d | ||
|
|
617c1aaee3 | ||
|
|
2f843153da | ||
|
|
df8fb23e75 | ||
|
|
60e57eb9c5 | ||
|
|
10a9845898 | ||
|
|
dc43b5977f | ||
|
|
e1fbaf14eb | ||
|
|
d721425cfe | ||
|
|
02dd275be4 | ||
|
|
50cd6a737d | ||
|
|
a575c07468 | ||
|
|
071ec671e0 | ||
|
|
3391563829 | ||
|
|
5709265f6c | ||
|
|
89d5a28c28 | ||
|
|
f613a06d19 | ||
|
|
30f4b846d2 | ||
|
|
a3742bc65e | ||
|
|
cd1b2b6c05 | ||
|
|
55103a1d04 | ||
|
|
6e71a3ca4c | ||
|
|
1f173eb3c3 | ||
|
|
2db205d186 | ||
|
|
1d84f1666d | ||
|
|
aa6390062f | ||
|
|
455b9b3ca7 | ||
|
|
45ead583c6 | ||
|
|
750ccdf33d | ||
|
|
c13aed651a | ||
|
|
dbccb16784 | ||
|
|
c0be333c0f | ||
|
|
d96c7eb3e9 | ||
|
|
4fca374fe9 | ||
|
|
9b82dd0411 | ||
|
|
0904881de8 | ||
|
|
6a41f8b2b9 | ||
|
|
503145734b | ||
|
|
371cab0658 | ||
|
|
ca8cb6f260 | ||
|
|
745e9e2bea | ||
|
|
1f9afc8cab | ||
|
|
c5d4551cab | ||
|
|
230148c06e | ||
|
|
3197ebb655 | ||
|
|
af3a0bb34b | ||
|
|
ea1099b112 | ||
|
|
f9542e2be2 | ||
|
|
3e291492fb | ||
|
|
e7dc7a6fba | ||
|
|
b525605d1b | ||
|
|
dc968a1fa4 | ||
|
|
ebee3536f5 | ||
|
|
8c8073d7aa | ||
|
|
d284c29e2a | ||
|
|
0fae37a999 | ||
|
|
fce4f72ca9 | ||
|
|
77968be454 | ||
|
|
4f905e06e2 | ||
|
|
ed39fd7999 | ||
|
|
71cd882324 | ||
|
|
68d7ff9c55 | ||
|
|
8ca083ed27 | ||
|
|
600da2a504 | ||
|
|
d1511d5ba1 | ||
|
|
4a8651ab39 | ||
|
|
de4f473da1 | ||
|
|
ce539972a5 | ||
|
|
458100cd69 | ||
|
|
fea1630909 | ||
|
|
e40e88c322 | ||
|
|
6bd52875dc | ||
|
|
88dffcffbf | ||
|
|
07214de370 | ||
|
|
d56bd44f67 | ||
|
|
a39970c59a | ||
|
|
b30890c1d6 | ||
|
|
5c1f6244c8 | ||
|
|
7156036120 | ||
|
|
627adb43e0 | ||
|
|
9ad08694ed | ||
|
|
d38399dcc0 | ||
|
|
68b0fe8c70 | ||
|
|
7c2a1ade1f | ||
|
|
5c2828bc78 | ||
|
|
111b2abeeb | ||
|
|
a869ae3323 | ||
|
|
baa7a170d9 | ||
|
|
60726757ab | ||
|
|
7b55a9810f | ||
|
|
d995dbb159 | ||
|
|
1180dc0781 | ||
|
|
2951a1b7b8 | ||
|
|
9503507011 | ||
|
|
a7dbb3daea | ||
|
|
a6d1f46133 | ||
|
|
e5cd093a7b | ||
|
|
33000134df | ||
|
|
2078e52798 | ||
|
|
08c51b8761 | ||
|
|
37dfce0f5b | ||
|
|
b893599ba8 | ||
|
|
8f87b9cb5e | ||
|
|
b303bd1051 | ||
|
|
60a4266204 | ||
|
|
637bf4f231 | ||
|
|
92f776b525 | ||
|
|
145b884628 | ||
|
|
0e22b06f0a | ||
|
|
9e79d883a0 | ||
|
|
02e8a7b429 | ||
|
|
ca861119f9 | ||
|
|
0bfbae4cdd | ||
|
|
61de3b9e08 | ||
|
|
7b1e163c77 | ||
|
|
3f710ddc70 | ||
|
|
1546add5d5 | ||
|
|
734c44c1da | ||
|
|
68524067cf | ||
|
|
cc6319a2c7 | ||
|
|
2a260c3d5f | ||
|
|
4aaaa460b2 | ||
|
|
b8321d3bb0 | ||
|
|
dee3d375db | ||
|
|
66610cfd4e | ||
|
|
149fa141d0 | ||
|
|
9305c86367 | ||
|
|
462634d311 | ||
|
|
0e6d3279d7 | ||
|
|
c0cb4681ae | ||
|
|
b9fb10a379 | ||
|
|
b39a118bba | ||
|
|
9f2ff0e1f7 | ||
|
|
15cabd018d | ||
|
|
8a5a6037b1 | ||
|
|
eec3d210ee | ||
|
|
b729c8119d | ||
|
|
30a093a0d5 | ||
|
|
dee3ddffca | ||
|
|
e769cf4e48 | ||
|
|
fbaffa980a | ||
|
|
26f61c1855 | ||
|
|
dae62d863a | ||
|
|
681fa198c9 | ||
|
|
e7b8e6c90d | ||
|
|
d7cc3895cb | ||
|
|
dde6968e50 | ||
|
|
71349385b4 | ||
|
|
a0b21a0bd4 | ||
|
|
65e99ff0de | ||
|
|
ecd6880fe0 | ||
|
|
e20adbba61 | ||
|
|
30d58e6371 | ||
|
|
037d60633b | ||
|
|
6f299e922c | ||
|
|
7047ffe3dd | ||
|
|
73e3f2869f | ||
|
|
218bb4c537 | ||
|
|
e4f63ed0c9 | ||
|
|
6ed2000478 | ||
|
|
720dfb97e8 | ||
|
|
f18aa6b648 | ||
|
|
b760a88af7 | ||
|
|
74bcfe1e08 | ||
|
|
2afba50a8c | ||
|
|
15b12551a0 | ||
|
|
22f44c51d6 | ||
|
|
128dc34a52 | ||
|
|
ecc3f61f1e | ||
|
|
61f4c7957a | ||
|
|
ffb2a02ea0 | ||
|
|
f6135a0e04 | ||
|
|
f13c029751 | ||
|
|
6ded8dce67 | ||
|
|
d8bde641e9 | ||
|
|
21b95c2645 | ||
|
|
3733df6fc5 | ||
|
|
b5d26bb426 | ||
|
|
fa4529846c | ||
|
|
1fc790802d | ||
|
|
52ababa800 | ||
|
|
b3c1dea8d7 | ||
|
|
f51c387cd8 | ||
|
|
3dd1c3de13 | ||
|
|
4472437696 | ||
|
|
ca39c2cde5 | ||
|
|
a7b74cede8 | ||
|
|
24bfad100d | ||
|
|
a8de338d5f | ||
|
|
9c0772f125 | ||
|
|
d90e2621a1 | ||
|
|
572870e320 | ||
|
|
046d1190cf | ||
|
|
a6af0cc73a | ||
|
|
1468231de7 | ||
|
|
f12481ce20 | ||
|
|
a280a565f6 | ||
|
|
c80b40cec7 | ||
|
|
5ebb7e23a9 | ||
|
|
6f188a03d5 | ||
|
|
c13a70e226 | ||
|
|
7318ebf1d8 | ||
|
|
e222221ec9 | ||
|
|
fdf5367e39 | ||
|
|
42c75c43de | ||
|
|
28a6f6de00 | ||
|
|
c03b56c0de | ||
|
|
e21f53f4c7 | ||
|
|
462a2d505d | ||
|
|
654125a6a8 | ||
|
|
d4e5a1bdf2 | ||
|
|
26e07b8c26 | ||
|
|
2a4f191a69 | ||
|
|
aec26277c7 | ||
|
|
887820dbec | ||
|
|
18371eb511 | ||
|
|
c38d319aeb | ||
|
|
9c55da5b99 | ||
|
|
3019551db5 | ||
|
|
25403ea35e | ||
|
|
815c75fa9a | ||
|
|
8b3b07a710 | ||
|
|
33f88fa5e0 | ||
|
|
d787408e1d | ||
|
|
c86702430b | ||
|
|
597dc308a6 | ||
|
|
f89dc6c1ae | ||
|
|
95902fa897 | ||
|
|
d2aa971ed3 | ||
|
|
9a1b5d4eee | ||
|
|
a5486128d4 | ||
|
|
14b1cc6e48 | ||
|
|
f9257c1ec9 | ||
|
|
45e938b4ba | ||
|
|
c177d54f59 | ||
|
|
de7f69ae3f | ||
|
|
7249083152 | ||
|
|
e00a73f442 | ||
|
|
d5bd6a0475 | ||
|
|
c2d4997810 | ||
|
|
c9d83cb3f5 | ||
|
|
de03506c0a | ||
|
|
b6349b17f2 | ||
|
|
2e699ef8b9 | ||
|
|
32dae9ad7c | ||
|
|
f5034a6ad8 | ||
|
|
be5c5f9420 | ||
|
|
929baa17db | ||
|
|
8ded7c5942 | ||
|
|
9c30f32281 | ||
|
|
94c309d61e | ||
|
|
72c608bf3b | ||
|
|
dfe87a0d37 | ||
|
|
bb77319703 | ||
|
|
dc7bb627d5 | ||
|
|
2652a21332 | ||
|
|
f35b80ba1a | ||
|
|
6cc5d1595d | ||
|
|
60b4835539 | ||
|
|
5073a86581 | ||
|
|
4619e03d64 | ||
|
|
d48ef373a7 | ||
|
|
4cb20b3fe3 | ||
|
|
f1c68af091 | ||
|
|
803244ccf7 | ||
|
|
215d55a997 | ||
|
|
16a2dd0b27 | ||
|
|
ab1e510348 | ||
|
|
232f1b1c05 | ||
|
|
1606141a5a | ||
|
|
c8f8b6ff0c | ||
|
|
095ee8511b | ||
|
|
bb0889fdba | ||
|
|
8154a8524a | ||
|
|
0e567e3e68 | ||
|
|
610a359160 | ||
|
|
a3f51298fe | ||
|
|
46f249fd3b | ||
|
|
97312d1a6d | ||
|
|
01765fd27f | ||
|
|
0a809f85f2 | ||
|
|
a9b18811ea | ||
|
|
59f82a0dd7 | ||
|
|
00615c2f38 | ||
|
|
f150cffa17 | ||
|
|
f4efb81832 | ||
|
|
ddde13a78f | ||
|
|
a643cde26d | ||
|
|
5f3316ff18 | ||
|
|
9be7f4d72c | ||
|
|
76f4483dda | ||
|
|
fb4e3e1b9d | ||
|
|
e2d3624713 | ||
|
|
55a33c135c | ||
|
|
411951f9d5 | ||
|
|
caa699e181 | ||
|
|
d9773f44b1 | ||
|
|
46dd8fdc68 | ||
|
|
94d02aff4f | ||
|
|
d23f993a6c | ||
|
|
bfe639fb70 | ||
|
|
568651291e | ||
|
|
09075a05db | ||
|
|
9f80952251 | ||
|
|
d0e0f67fee | ||
|
|
f16de4abb9 | ||
|
|
7bbe08f23e | ||
|
|
3229956eef | ||
|
|
254f026458 | ||
|
|
248d3aea7a | ||
|
|
11042af051 | ||
|
|
07d55d17a3 | ||
|
|
abfab60eec | ||
|
|
fc3ac122dd | ||
|
|
3963b632d8 | ||
|
|
1936a01464 | ||
|
|
2cbfa6930f | ||
|
|
33a856af41 | ||
|
|
5d1d937fde | ||
|
|
83ec855f4b | ||
|
|
5ce9b8eaeb | ||
|
|
be983eb68a | ||
|
|
c6f144313d | ||
|
|
ae8ec1c736 | ||
|
|
19d7f02371 | ||
|
|
f0660f3fda | ||
|
|
9ded176cf2 | ||
|
|
2e10cd0009 | ||
|
|
7ff664e62b | ||
|
|
9dd1939d1f | ||
|
|
8dfdfcb522 | ||
|
|
b63c2804f1 | ||
|
|
ced2270571 | ||
|
|
d3436077a1 | ||
|
|
e06349e803 | ||
|
|
9849b9d678 | ||
|
|
22136867ed | ||
|
|
2df621f9c5 | ||
|
|
9a599e2015 | ||
|
|
431dd96b98 | ||
|
|
4d8f950955 | ||
|
|
08e559180d | ||
|
|
4cb63068de | ||
|
|
f062b39cfa | ||
|
|
635d77350a | ||
|
|
9f07a2bca4 | ||
|
|
06ee421965 | ||
|
|
c3c69cc43b | ||
|
|
e47d53ddcc | ||
|
|
290a7b27db | ||
|
|
56a15efe3f | ||
|
|
bad74bd0b8 | ||
|
|
3e078027a7 | ||
|
|
97f448f984 | ||
|
|
07727298f7 | ||
|
|
752520470d | ||
|
|
128cd68ec8 | ||
|
|
a832343348 | ||
|
|
3e47f9442f | ||
|
|
08d661d8c3 | ||
|
|
3544649903 | ||
|
|
6f5ac232c5 | ||
|
|
3b82141de2 | ||
|
|
e303501d4a | ||
|
|
935011b9e9 | ||
|
|
de7dd6079e | ||
|
|
5e5b22d649 | ||
|
|
53212dc654 | ||
|
|
1b17c32405 | ||
|
|
d56dff954f | ||
|
|
c525e15ea5 | ||
|
|
95268c49ce | ||
|
|
1c6b56385c | ||
|
|
9819ee518d | ||
|
|
751d51ea2e | ||
|
|
1f322a2700 | ||
|
|
d89173ad57 | ||
|
|
d6fc2806dc | ||
|
|
a8b9d75b2a | ||
|
|
ab5556919e | ||
|
|
f9e0bc4cd7 | ||
|
|
12a1653ae0 | ||
|
|
821b3b86e4 | ||
|
|
2830ab55f0 | ||
|
|
f95e8a965a | ||
|
|
7512a0db6b | ||
|
|
cdac840071 | ||
|
|
aa43902d32 | ||
|
|
276b135799 | ||
|
|
bf589f7485 | ||
|
|
852f06cc28 | ||
|
|
d82171b4de | ||
|
|
1dbcfcc154 | ||
|
|
32d6eaa21c | ||
|
|
a3103bf4d4 | ||
|
|
9cd284f5cd | ||
|
|
0877aa2964 | ||
|
|
3b225b8ec3 | ||
|
|
5727b0fb45 | ||
|
|
8193f0fcac | ||
|
|
3d3f50064d | ||
|
|
432faaf209 | ||
|
|
10b1f1f145 | ||
|
|
dcad82c46b | ||
|
|
07ce00d36d | ||
|
|
8d2df2b744 | ||
|
|
adf9ec3e75 | ||
|
|
1c691cf3e8 | ||
|
|
e935fc2510 | ||
|
|
0c64eaf441 | ||
|
|
ca24ff808d | ||
|
|
248e538518 | ||
|
|
e3b1146dbb | ||
|
|
7f62b19240 | ||
|
|
6e0d376746 | ||
|
|
066e0bc236 | ||
|
|
eb7306a4fa | ||
|
|
1a07123abe | ||
|
|
f653348f93 | ||
|
|
35578bfada | ||
|
|
9e3c439c51 | ||
|
|
61b073a6a2 | ||
|
|
92ecd08a18 | ||
|
|
8baf4d429e | ||
|
|
aa48c6e63e | ||
|
|
024d13c1b2 | ||
|
|
52ac68b511 | ||
|
|
99c0ccacf1 | ||
|
|
5d09189737 | ||
|
|
af68c46702 | ||
|
|
7b207ccfd2 | ||
|
|
a60ae0a9ed | ||
|
|
cb70a6e2c8 | ||
|
|
caf18372ce | ||
|
|
eeebea1dbb | ||
|
|
29dbe99840 | ||
|
|
56076ad649 | ||
|
|
c618144d78 | ||
|
|
4348a176d5 | ||
|
|
3190d0437e | ||
|
|
06a6240877 | ||
|
|
1311c73d1a | ||
|
|
b7f7fb00af | ||
|
|
fa4044d922 | ||
|
|
875c8f8d1d | ||
|
|
02b982f3a1 | ||
|
|
d7edaef34f | ||
|
|
1da034d00c | ||
|
|
a06d96699b | ||
|
|
ee7917999d | ||
|
|
2566f4d98e | ||
|
|
24f6da5610 | ||
|
|
68124d1d14 | ||
|
|
04ae6a582a | ||
|
|
0314547c95 | ||
|
|
5aa94036d1 | ||
|
|
81bd4cbe50 | ||
|
|
f5af1ee7bb | ||
|
|
236585c20b | ||
|
|
ce52dba419 | ||
|
|
5be2489e55 | ||
|
|
12f6b07fbf | ||
|
|
6881df6ae5 | ||
|
|
52892ee46e | ||
|
|
57e3e8f5f4 | ||
|
|
3e77810e13 | ||
|
|
01ef058adb | ||
|
|
17a5019606 | ||
|
|
9edc0c16d8 | ||
|
|
2c7cf7826b | ||
|
|
3fd5e0337b | ||
|
|
742ddc517b | ||
|
|
e93e03c248 | ||
|
|
89a47953d8 | ||
|
|
b145c609bd | ||
|
|
be67da9d64 | ||
|
|
59d5c49818 | ||
|
|
9d4592d90e | ||
|
|
ce86758cdc | ||
|
|
b811fc6252 | ||
|
|
deb3d75375 | ||
|
|
60433ef22d | ||
|
|
2bc90db1f6 | ||
|
|
59ae403391 | ||
|
|
71b2164154 | ||
|
|
848b932ea1 | ||
|
|
a61385e534 | ||
|
|
64c3b7a2ff | ||
|
|
61a8f589ac | ||
|
|
92ccb6e4ea | ||
|
|
a3dff6a640 | ||
|
|
770c58d33c | ||
|
|
76d3dc3d5a | ||
|
|
522a4d2a62 | ||
|
|
44e7068a9a | ||
|
|
0aaa276f03 | ||
|
|
d54058b59a | ||
|
|
5da2ca292a | ||
|
|
f4830b1672 | ||
|
|
7fd2502dd5 | ||
|
|
36a8365ebb | ||
|
|
45da32d5d9 | ||
|
|
a9a97b2fe0 | ||
|
|
6581d68e6d | ||
|
|
51d5ddb463 | ||
|
|
9813b98133 | ||
|
|
93d8f1cfe7 | ||
|
|
473be9284d | ||
|
|
f667149556 | ||
|
|
490341af09 | ||
|
|
ae661e9961 | ||
|
|
26ae33522d | ||
|
|
070d5f59d1 | ||
|
|
cc6f5fea6d | ||
|
|
2841c1b792 | ||
|
|
bd772c97cf | ||
|
|
558904e103 | ||
|
|
12e3af6ad7 | ||
|
|
f0b6eeb303 | ||
|
|
f7e594a51a | ||
|
|
9a468e8297 | ||
|
|
b4dd1df0c7 | ||
|
|
3d43f85145 | ||
|
|
397c41e68b | ||
|
|
b40769b086 | ||
|
|
a0e8711d9b | ||
|
|
372a50f9a5 | ||
|
|
ce1f7d4da3 | ||
|
|
ef147cde21 | ||
|
|
4e91904608 | ||
|
|
e06c358db4 | ||
|
|
8b663f041a | ||
|
|
7a03084e83 | ||
|
|
97f76899bf | ||
|
|
4a3cbdafca | ||
|
|
52d656194c | ||
|
|
16821a28fc | ||
|
|
5572c4a375 | ||
|
|
2494c9afb3 | ||
|
|
223523f722 | ||
|
|
61d9db8c67 | ||
|
|
4970aa7426 | ||
|
|
5e8951fbe0 | ||
|
|
c8993d7f71 | ||
|
|
752368327f | ||
|
|
5963b2baf7 | ||
|
|
97c5705447 | ||
|
|
5d5c1d8f43 | ||
|
|
0228c74f71 | ||
|
|
a94d33649b | ||
|
|
2f1046db2b | ||
|
|
2f768aa30d | ||
|
|
9fde875904 | ||
|
|
a0afce666f | ||
|
|
7d4486b9e6 | ||
|
|
1a19e01d69 | ||
|
|
2a145bc284 | ||
|
|
5fd2dfafb7 | ||
|
|
0812ac97a9 | ||
|
|
9fe3e94a66 | ||
|
|
dedc7a35c9 | ||
|
|
2021396f39 | ||
|
|
8e32fb05d1 | ||
|
|
de8538b622 | ||
|
|
4540362f42 | ||
|
|
3065f53d83 | ||
|
|
74f181d7a8 | ||
|
|
337eda8538 | ||
|
|
ddb852c150 | ||
|
|
63aee11312 | ||
|
|
b38b2bba0c | ||
|
|
21f25b4ce4 | ||
|
|
614285f1ae | ||
|
|
e6be9bac07 | ||
|
|
f8ab95b3e1 | ||
|
|
20c3641ac1 | ||
|
|
33f7056e7a | ||
|
|
73e7c98131 | ||
|
|
1157d6fd10 | ||
|
|
fb65d7a965 | ||
|
|
42d981c0ef | ||
|
|
a7cff1b53d | ||
|
|
536cff9cc6 | ||
|
|
62014fda26 | ||
|
|
0b9e43c9f8 | ||
|
|
fea3544d4b | ||
|
|
679de3574f | ||
|
|
cfd82d9709 | ||
|
|
21245836c7 | ||
|
|
b5ca4c1166 | ||
|
|
780799f87a | ||
|
|
4579e41ace | ||
|
|
19cf94bd7e | ||
|
|
981a1278e2 | ||
|
|
6d25eb81b6 | ||
|
|
fca2727966 | ||
|
|
cb03bfe74c | ||
|
|
2b1e3bd947 | ||
|
|
13b98409ae | ||
|
|
518389d32c | ||
|
|
800db0b99d | ||
|
|
cfa4530797 | ||
|
|
d42841d80a | ||
|
|
3b668f7806 | ||
|
|
32b290a231 | ||
|
|
365aaca56d | ||
|
|
1c726d0466 | ||
|
|
c903caabcc | ||
|
|
53a117bd0f | ||
|
|
4858317891 | ||
|
|
6e16340f16 | ||
|
|
0f9c2001f7 | ||
|
|
5c09e8ae00 | ||
|
|
e5a95ed990 | ||
|
|
edc637b762 | ||
|
|
304ebf12b9 | ||
|
|
4f916fae22 | ||
|
|
7120bce761 | ||
|
|
38dc92bfa7 | ||
|
|
1403868d05 | ||
|
|
65ad2c9ff5 | ||
|
|
09753cd6dc | ||
|
|
ef685c5bc1 | ||
|
|
fd6ddf6926 | ||
|
|
67b05a3f1a | ||
|
|
b74e5ce5cd | ||
|
|
6652e27dd5 | ||
|
|
c7dba3690a | ||
|
|
dd64afa970 | ||
|
|
be81c73c40 | ||
|
|
72f5793928 | ||
|
|
e6c83f364c | ||
|
|
8706828c22 | ||
|
|
b8e3a19586 | ||
|
|
e36de6b72b | ||
|
|
bcec5c0922 | ||
|
|
72186f1882 | ||
|
|
e4d70a4d4f | ||
|
|
6d19d76bac | ||
|
|
55bebd92f3 | ||
|
|
0d4df67958 | ||
|
|
498484ca4e | ||
|
|
f9b92ddead | ||
|
|
461c8681af | ||
|
|
28a2b577e7 | ||
|
|
356fbed325 | ||
|
|
4578819a58 | ||
|
|
6099313cb4 | ||
|
|
487663f892 | ||
|
|
d8e769ef1f | ||
|
|
41dbe1a384 | ||
|
|
631c00023e | ||
|
|
4478edc211 | ||
|
|
2be8895dfd | ||
|
|
78ffab5116 | ||
|
|
495eeeb929 | ||
|
|
7c3ba74710 | ||
|
|
abf6c0a0b8 | ||
|
|
812a09f556 | ||
|
|
098b0cd028 | ||
|
|
391942ba72 | ||
|
|
f4bf1756c9 | ||
|
|
3d5caaa0bb | ||
|
|
c2916c7a2d | ||
|
|
d28643bd2b | ||
|
|
7eba6582c6 | ||
|
|
5702def9d2 | ||
|
|
de5df4b72e | ||
|
|
903e9b52c2 | ||
|
|
00f91c444c | ||
|
|
c34efeff1e | ||
|
|
76287beca0 | ||
|
|
1d8d757960 | ||
|
|
2202dc9747 | ||
|
|
5528e9964b | ||
|
|
04a9ff9c23 | ||
|
|
7861ee8cbd | ||
|
|
70ef7059c3 | ||
|
|
3f0ad7f6c7 | ||
|
|
c9cf38d630 | ||
|
|
f836fbeba5 | ||
|
|
ac604b11e3 | ||
|
|
f68d49c4d6 | ||
|
|
1722e9e416 | ||
|
|
57a46b98ec | ||
|
|
74c0f6a140 | ||
|
|
14002dc20d | ||
|
|
8579a01cfc | ||
|
|
237263a194 | ||
|
|
ef8d1b525d | ||
|
|
4b4969e79b | ||
|
|
44daaae981 | ||
|
|
d444bb66b8 | ||
|
|
2b3795224d | ||
|
|
46f8d76d7a | ||
|
|
ad8cebc6e3 | ||
|
|
bed46be720 | ||
|
|
0533568f9e | ||
|
|
56d7716ddb | ||
|
|
e9c730e0c1 | ||
|
|
875338d133 | ||
|
|
2a5ab1f8aa | ||
|
|
6e6364638d | ||
|
|
dfeadfd50f | ||
|
|
9838571184 | ||
|
|
5cd99de6e4 | ||
|
|
fb31fbea9b | ||
|
|
d6544f412d | ||
|
|
3a7d8d2296 | ||
|
|
84bf2641aa | ||
|
|
ede88a533e | ||
|
|
3d1ae4feda | ||
|
|
2235848262 | ||
|
|
76034f801b | ||
|
|
2dd8dd8edc | ||
|
|
c8afcbccee | ||
|
|
ef63d421b3 | ||
|
|
ba25f9f8d5 | ||
|
|
31a7df7214 | ||
|
|
335052bc84 | ||
|
|
dffe030d16 | ||
|
|
6d477688d7 | ||
|
|
1da2812864 | ||
|
|
7ca9754ebf | ||
|
|
a71333ac60 | ||
|
|
5cec34b802 | ||
|
|
8d648a43c2 | ||
|
|
5886c53ce3 | ||
|
|
480f8c97ca | ||
|
|
cbf210b9e6 | ||
|
|
892cfe01b2 | ||
|
|
bf1af094c1 | ||
|
|
2f5dc7782f | ||
|
|
c70915306d | ||
|
|
93cc4e1df8 | ||
|
|
07cce5ad3c | ||
|
|
b5c5f76acc | ||
|
|
8d7c76a434 | ||
|
|
c927a1f459 | ||
|
|
88239a23f9 | ||
|
|
651af6a2c5 | ||
|
|
2745d8dce7 | ||
|
|
ac32947908 | ||
|
|
b471bd0f58 | ||
|
|
910f27016b | ||
|
|
75469eed7d | ||
|
|
5d5a6fbc5d | ||
|
|
aba75abc93 | ||
|
|
1e82c7f0ef | ||
|
|
0b00ce28e0 | ||
|
|
145cb54bc0 | ||
|
|
bd9d827a16 | ||
|
|
4da95f765b | ||
|
|
d13081b4f2 | ||
|
|
b71688a587 | ||
|
|
14c0d27019 | ||
|
|
705e5dd45b | ||
|
|
4e2727292e | ||
|
|
cfc6d5fe2b | ||
|
|
c11fd38f0f | ||
|
|
11ce394181 | ||
|
|
fc4b82d017 | ||
|
|
b556846be3 | ||
|
|
6f861a91ee | ||
|
|
0483c7efb5 | ||
|
|
fb29fe7b3c | ||
|
|
1bf1766822 | ||
|
|
57edb4a385 | ||
|
|
17c555ab6c | ||
|
|
d6bd72cfd3 | ||
|
|
8c9123beaa | ||
|
|
3d967e695f | ||
|
|
a7d7e39dfd | ||
|
|
38cb95e940 | ||
|
|
b2b20c48b3 | ||
|
|
fed6579491 | ||
|
|
8cb85cafbd | ||
|
|
55e560bb41 | ||
|
|
dd9a40221e | ||
|
|
24300cd3bd | ||
|
|
4d5961c3ab | ||
|
|
afb40218d7 | ||
|
|
3daa3821ef | ||
|
|
84cec0e32c | ||
|
|
7bae15d4fe | ||
|
|
0180f72067 | ||
|
|
f537091eb2 | ||
|
|
c99c83a006 | ||
|
|
113828cd00 | ||
|
|
477add3022 | ||
|
|
8aa5714173 | ||
|
|
fdb83c51f7 | ||
|
|
c15739bba7 | ||
|
|
1ce84f1650 | ||
|
|
f618a8b563 | ||
|
|
9346efc24a | ||
|
|
a5e57a9a67 | ||
|
|
cc724218b0 | ||
|
|
706ce4b3b6 | ||
|
|
c08355df8d | ||
|
|
2d5904e3d0 | ||
|
|
d1318cf016 | ||
|
|
91921859a2 | ||
|
|
a058925252 | ||
|
|
fadb24f295 | ||
|
|
748119334d | ||
|
|
cb05b87dda | ||
|
|
aa36d944db |
@@ -21,13 +21,13 @@ _APP_OPTIONS_ROUTER_PROTECTION=disabled
|
||||
_APP_OPTIONS_FORCE_HTTPS=disabled
|
||||
_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled
|
||||
_APP_OPENSSL_KEY_V1=your-secret-key
|
||||
_APP_DNS=8.8.8.8
|
||||
_APP_DOMAIN=traefik
|
||||
_APP_DNS=172.16.238.100 # CoreDNS
|
||||
_APP_DOMAIN=appwrite.test
|
||||
_APP_CONSOLE_DOMAIN=localhost
|
||||
_APP_DOMAIN_FUNCTIONS=functions.localhost
|
||||
_APP_DOMAIN_SITES=sites.localhost
|
||||
_APP_DOMAIN_TARGET_CNAME=test.localhost
|
||||
_APP_DOMAIN_TARGET_A=127.0.0.1
|
||||
_APP_DOMAIN_TARGET_CNAME=cname.localhost
|
||||
_APP_DOMAIN_TARGET_A=203.0.0.1
|
||||
_APP_DOMAIN_TARGET_AAAA=::1
|
||||
_APP_DOMAIN_TARGET_CAA=digicert.com
|
||||
_APP_RULES_FORMAT=md5
|
||||
@@ -69,8 +69,8 @@ _APP_STORAGE_ANTIVIRUS_PORT=3310
|
||||
_APP_SMTP_HOST=maildev
|
||||
_APP_SMTP_PORT=1025
|
||||
_APP_SMTP_SECURE=
|
||||
_APP_SMTP_USERNAME=
|
||||
_APP_SMTP_PASSWORD=
|
||||
_APP_SMTP_USERNAME=user
|
||||
_APP_SMTP_PASSWORD=password
|
||||
_APP_SMS_PROVIDER=sms://username:password@mock
|
||||
_APP_SMS_FROM=+123456789
|
||||
_APP_SMS_PROJECTS_DENY_LIST=
|
||||
@@ -101,8 +101,10 @@ _APP_USAGE_AGGREGATION_INTERVAL=30
|
||||
_APP_STATS_RESOURCES_INTERVAL=30
|
||||
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
|
||||
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
|
||||
_APP_INTERVAL_DOMAIN_VERIFICATION=60
|
||||
_APP_USAGE_STATS=enabled
|
||||
_APP_LOGGING_CONFIG=
|
||||
_APP_LOGGING_CONFIG_REALTIME=
|
||||
_APP_GRAPHQL_MAX_BATCH_SIZE=10
|
||||
_APP_GRAPHQL_MAX_COMPLEXITY=250
|
||||
_APP_GRAPHQL_MAX_DEPTH=4
|
||||
@@ -123,4 +125,5 @@ _APP_MESSAGE_PUSH_TEST_DSN=
|
||||
_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
|
||||
_APP_PROJECT_REGIONS=default
|
||||
_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000
|
||||
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
|
||||
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
|
||||
_APP_TRUSTED_HEADERS=x-forwarded-for
|
||||
@@ -5,3 +5,5 @@ src/** linguist-detectable=false
|
||||
tests/** linguist-detectable=false
|
||||
public/scripts/** linguist-detectable=false
|
||||
public/dist/scripts/** linguist-detectable=false
|
||||
|
||||
.github/workflows/*.lock.yml linguist-generated=true merge=ours
|
||||
@@ -0,0 +1,83 @@
|
||||
# Fixes and upgrades for the Appwrite Auth / Users / Teams services.
|
||||
"product / auth":
|
||||
- "(auth|session|login|logout|register|2fa|mfa|users|teams|memberships|invite|oauth|oauth2|sso|jwt)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Realtime API.
|
||||
"api / realtime":
|
||||
- "(realtime|subscribe|websockets)"
|
||||
|
||||
# Console, UI and UX issues
|
||||
"product / console":
|
||||
- "(console)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Storage.
|
||||
"product / storage":
|
||||
- "(storage|bucket|file|image|preview|download)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Database.
|
||||
"product / databases":
|
||||
- "(database|collection|tables|attribute|column|document|row|query|queries|indexes|search|filter|sort|pagination)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Functions.
|
||||
"product / functions":
|
||||
- "(function|runtime|deployment|execution|trigger|cron|schedule)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Docs.
|
||||
# "product / docs":
|
||||
# -
|
||||
|
||||
# Fixes and upgrades for the Appwrite Migrations.
|
||||
"product / migrations":
|
||||
- "(migrate|migration)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Messaging.
|
||||
"product / messaging":
|
||||
- "(messaging|email|sms|push|provider|topic|target|notification)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Platform.
|
||||
# "product / platform":
|
||||
# -
|
||||
|
||||
# Fixes and upgrades for database relationships
|
||||
"feature / relationships":
|
||||
- "(relationship)"
|
||||
|
||||
# Issues found only on Appwrite Cloud
|
||||
# "product / cloud":
|
||||
# -
|
||||
|
||||
# Fixes and upgrades for the Appwrite VCS.
|
||||
"product / vcs":
|
||||
- "(repo|push|vcs|repository)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite GraphQL API.
|
||||
"api / graphql":
|
||||
- "(graphql|gql|mutation)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Assistant.
|
||||
"product / assistant":
|
||||
- "(assistant)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Domains.
|
||||
"product / domains":
|
||||
- "(domain|dns|ssl|certificate)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Locale.
|
||||
"product / locale":
|
||||
- "(locale|i18n|internationalization|localization|l10n|translation|timezone|country)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Avatars.
|
||||
"product / avatars":
|
||||
- "(avatar|initial|flag|icon)"
|
||||
|
||||
# Fixes and upgrades for Appwrite Sites.
|
||||
"product / sites":
|
||||
- "(site|web|hosting|domain|ssl|certificate|nextjs|nuxt|react|angular|vue|svelte|astro)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite CLI.
|
||||
"sdk / cli":
|
||||
- "(cli|command line)"
|
||||
|
||||
# Issues only found when self-hosting Appwrite
|
||||
"product / self-hosted":
|
||||
- "(self-host|self host)"
|
||||
@@ -0,0 +1,32 @@
|
||||
name: AI Moderator
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
pull_request:
|
||||
types: [opened, edited]
|
||||
pull_request_review:
|
||||
types: [submitted, edited]
|
||||
pull_request_review_comment:
|
||||
types: [created, edited]
|
||||
discussion:
|
||||
types: [created, edited]
|
||||
discussion_comment:
|
||||
types: [created, edited]
|
||||
|
||||
permissions:
|
||||
models: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
moderate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: AI Moderator
|
||||
uses: github/ai-moderator@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Auto Label Issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Issue Labeler
|
||||
uses: github/issue-labeler@v3.4
|
||||
with:
|
||||
configuration-path: .github/labeler.yml
|
||||
enable-versioned-regex: false
|
||||
include-title: 1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
Generated
+5178
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,115 @@
|
||||
---
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Run daily at midnight UTC
|
||||
workflow_dispatch: # Enable manual trigger
|
||||
stop-after: +30d # workflow will no longer trigger after 30 days. Remove this and recompile to run indefinitely
|
||||
reaction: eyes
|
||||
|
||||
permissions: read-all
|
||||
|
||||
# Add stricter error-detection patterns so issue text doesn't trigger agent error detection.
|
||||
# After merging this file run `gh aw compile` to regenerate the lock file.
|
||||
env:
|
||||
GH_AW_ERROR_PATTERNS: >-
|
||||
[
|
||||
{"id":"gh-action-error","pattern":"^::(error)(?:\\\\s+[^:]*)?::(.+)","level_group":1,"message_group":2,"description":"GitHub Actions workflow command - error"},
|
||||
{"id":"gh-action-warning","pattern":"^::(warning)(?:\\\\s+[^:]*)?::(.+)","level_group":1,"message_group":2,"description":"GitHub Actions workflow command - warning"},
|
||||
{"id":"bracketed-level","pattern":"^\\[(ERROR|CRITICAL|WARNING|WARN)\\]\\s+(.+)","level_group":1,"message_group":2,"description":"Bracketed log level at start of line"},
|
||||
{"id":"timestamped-copilot","pattern":"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z\\s+\\[(ERROR|WARN|WARNING|CRITICAL)\\]\\s+(.+)","level_group":1,"message_group":3,"description":"Timestamped Copilot CLI messages"}
|
||||
]
|
||||
|
||||
network: defaults
|
||||
|
||||
safe-outputs:
|
||||
add-labels:
|
||||
max: 100
|
||||
target: "*"
|
||||
add-comment:
|
||||
max: 10
|
||||
target: "*"
|
||||
|
||||
tools:
|
||||
web-fetch:
|
||||
web-search:
|
||||
github:
|
||||
toolsets:
|
||||
- default
|
||||
- labels
|
||||
|
||||
timeout-minutes: 10
|
||||
source: githubnext/agentics/workflows/issue-triage.md@0837fb7b24c3b84ee77fb7c8cfa8735c48be347a
|
||||
---
|
||||
# Agentic Triage
|
||||
|
||||
<!-- Note - this file can be customized to your needs. Replace this section directly, or add further instructions here. After editing run 'gh aw compile' -->
|
||||
|
||||
You're a triage assistant for GitHub issues. Your task is to analyze issues that were either created in the last 24 hours or updated (with a new comment) in the last 24 hours, and perform initial triage tasks for each of them.
|
||||
|
||||
1. First, use the `list_issues` tool to retrieve all issues created or updated in the last 24 hours. The `since` parameter filters by the issue's `updated_at` timestamp, which includes both newly created issues and recently commented issues. Calculate the timestamp from 24 hours ago (example: 2025-11-06T20:27:14Z for reference) and use it for the `since` parameter.
|
||||
|
||||
2. For each issue found, perform the following triage tasks:
|
||||
|
||||
3. Use the `get_comments` tool to retrieve all the comments on the issue.
|
||||
|
||||
4. Check for spam and quality issue descriptions and comments first:
|
||||
|
||||
- **Non-English Content**: If the issue is primarily written in a non-English language, add a respectful and appreciative comment explaining that while you appreciate their contribution, the majority of the community communicates in English and kindly ask them to repost in English so everyone can follow along and help. Provide a friendly translation of your message in their language if possible.
|
||||
- **Multiple Topics**: If the issue discusses multiple unrelated topics or problems, add a comment explaining that each issue should focus on one clear topic so the team can effectively solve the right problem. Politely ask them to split it into separate issues.
|
||||
- **Obvious Spam or Bot-Generated Content**: If the issue/comment is obviously spam, generated by a bot, or something that is not an actual issue to be worked on, add an issue comment with a one-sentence analysis and move to the next issue.
|
||||
|
||||
5. Retrieve the issue content using the `get_issue` tool for any issues that pass the spam checks.
|
||||
|
||||
6. Next, use the GitHub tools to gather additional context about the issue:
|
||||
|
||||
- Fetch the list of labels available in this repository using the `list_label` tool with `owner: "appwrite"` and `repo: "appwrite"` parameters. This will give you the labels you can use for triaging issues.
|
||||
- Fetch any comments on the issue using the `get_issue_comments` tool to understand recent activity
|
||||
- **Search for duplicate and related issues (repo first, then org-wide)**:
|
||||
- First search in this repository using the `search_issues` tool with a query like: `repo:appwrite/appwrite is:issue (is:open OR is:closed) <key terms>`.
|
||||
- Then perform an org-wide search across the entire Appwrite organization using: `org:appwrite is:issue (is:open OR is:closed) <key terms>`.
|
||||
- Prefer linking to OPEN issues when identifying potential duplicates; include CLOSED ones as related history when useful.
|
||||
|
||||
7. Analyze the issue content, considering:
|
||||
|
||||
- The issue title and description
|
||||
- The type of issue (bug report, feature request, question, etc.)
|
||||
- Technical areas mentioned
|
||||
- Severity or priority indicators
|
||||
- User impact
|
||||
- Components affected
|
||||
|
||||
8. Write notes, ideas, nudges, resource links, debugging strategies and/or reproduction steps for the team to consider relevant to the issue.
|
||||
|
||||
9. Select appropriate labels from the available labels list:
|
||||
|
||||
- Choose labels that accurately reflect the issue's nature
|
||||
- Be specific but comprehensive
|
||||
- Select priority labels if you can determine urgency (high-priority, med-priority, or low-priority)
|
||||
- Consider platform labels (android, ios) if applicable
|
||||
- Search for similar issues. If you find a duplicate of another OPEN issue in THIS repository, you may use a "duplicate" label (if available) and reference the canonical issue.
|
||||
- If the closest match is in another repository within the Appwrite org, do NOT mark as duplicate here; instead, link it in your comment under a "Cross‑repo related issues" section.
|
||||
- Only select labels from the provided list
|
||||
- Don't apply the `good first issue` or `help wanted` labels
|
||||
- It's okay to not add any labels if none are clearly applicable
|
||||
|
||||
10. Apply the selected labels:
|
||||
|
||||
- Use the `update_issue` tool to apply the labels to the issue
|
||||
- DO NOT communicate directly with users
|
||||
- If no labels are clearly applicable, do not apply any labels
|
||||
|
||||
11. Add an issue comment to the issue with your analysis:
|
||||
- Start with "🎯 Agentic Issue Triage"
|
||||
- Provide a brief summary of the issue
|
||||
- **If duplicate or related issues were found**, add sections listing them with links:
|
||||
- "### 🔗 Potentially Related Issues (this repo)" – bullet list of same-repo issues with titles and links
|
||||
- If applicable: "### 🌐 Cross-repo related issues (org: appwrite)" – bullet list including `owner/repo#number` with titles and links
|
||||
- Mention any relevant details that might help the team understand the issue better
|
||||
- Include any debugging strategies or reproduction steps if applicable
|
||||
- Suggest resources or links that might be helpful for resolving the issue or learning skills related to the issue or the particular area of the codebase affected by it
|
||||
- Mention any nudges or ideas that could help the team in addressing the issue
|
||||
- If appropriate break the issue down to sub-tasks and write a checklist of things to do
|
||||
- Use collapsed-by-default sections in the GitHub markdown to keep the comment tidy. Collapse all sections except the short main summary at the top. For bolded section titles, wrap the text with `<strong>` and `</strong>` to make it bold.
|
||||
- Do not indicate/encourage a community member to submit a PR for the issue.
|
||||
|
||||
12. After processing all issues, provide a summary of how many issues were triaged (created or updated in the last 24 hours). If no issues matched the criteria, simply note that no issues needed triage.
|
||||
@@ -42,6 +42,7 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
target: production
|
||||
build-args: |
|
||||
VERSION=${{ steps.meta.outputs.version }}
|
||||
VITE_APPWRITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
|
||||
|
||||
@@ -20,10 +20,10 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
@@ -46,6 +46,7 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
target: production
|
||||
build-args: |
|
||||
VERSION=${{ steps.meta.outputs.version }}
|
||||
push: true
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# AGENTS.md
|
||||
|
||||
Appwrite is an end-to-end backend server for web, mobile, native, and backend apps. This guide provides context and instructions for AI coding agents working on the Appwrite codebase.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Appwrite is a self-hosted Backend-as-a-Service (BaaS) platform that provides developers with a set of APIs and tools to build secure, scalable applications. The project uses a hybrid monolithic-microservice architecture built with PHP, running on Swoole for high performance.
|
||||
|
||||
**Key Technologies:**
|
||||
- **Backend:** PHP 8.3+, Swoole
|
||||
- **Libraries:** Utopia PHP
|
||||
- **Database:** MariaDB, Redis
|
||||
- **Cache:** Redis
|
||||
- **Queue:** Redis
|
||||
- **Containers:** Docker
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
# Run Appwrite
|
||||
docker compose up -d --force-recreate --build
|
||||
|
||||
# Run specific test
|
||||
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName] --filter=[FunctionName]
|
||||
|
||||
# Format code
|
||||
composer format
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) coding standard
|
||||
- Use PSR-4 autoloading
|
||||
- Strict type declarations where applicable
|
||||
- Comprehensive PHPDoc comments
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
#### `resourceType` Naming Rule
|
||||
|
||||
When a collection has a combination of `resourceType`, `resourceId`, and/or `resourceInternalId`, the value of `resourceType` MUST always be **plural** - for example: `functions`, `sites`, `deployments`.
|
||||
|
||||
Examples:
|
||||
```php
|
||||
'resourceType' => 'functions'
|
||||
'resourceType' => 'sites'
|
||||
'resourceType' => 'deployments'
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Critical Security Practices
|
||||
|
||||
- **Never hardcode credentials** - Use environment variables
|
||||
- **Rate limiting** - Respect abuse prevention mechanisms
|
||||
|
||||
## Dependencies
|
||||
|
||||
Avoid introducing new dependencies other than utopia-php.
|
||||
|
||||
## Adding new endpoints
|
||||
|
||||
When adding new endpoints, make sure to use modules and follow its patterns. Find instruction in [Modules AGENTS.md](src/Appwrite/Platform/AGENTS.md) file.
|
||||
|
||||
## Pull Request Guidelines
|
||||
### Before Submitting
|
||||
|
||||
- Run `composer format`
|
||||
- Update documentation if adding features
|
||||
- Add/update tests for your changes
|
||||
- Check that Docker build succeeds
|
||||
`docs/specs/authentication.drawio.svg`
|
||||
|
||||
## Known Issues and Gotchas
|
||||
|
||||
- **Hot Reload:** Code changes require container restart in some cases
|
||||
- **Logging:** There is no central place for logs, so when debugging, ensure to check all possibly relevant containers
|
||||
+639
-251
@@ -1,266 +1,654 @@
|
||||
# Version 1.8.0
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Notable changes
|
||||
|
||||
* Do not allow full range in [#9847](https://github.com/appwrite/appwrite/pull/9847)
|
||||
* Expose internal id as a part of auto increment id in [#9713](https://github.com/appwrite/appwrite/pull/9713)
|
||||
* Expose sequence in [#9870](https://github.com/appwrite/appwrite/pull/9870)
|
||||
* Add flutter 3.32 and dart 3.8 runtimes in [#9914](https://github.com/appwrite/appwrite/pull/9914)
|
||||
* Shorten commit url and branch url in [#9919](https://github.com/appwrite/appwrite/pull/9919)
|
||||
* Remove powered by from error pages in [#9927](https://github.com/appwrite/appwrite/pull/9927)
|
||||
* Enable resource limits on GIF previews in [#9940](https://github.com/appwrite/appwrite/pull/9940)
|
||||
* Only run maintenance task for projects accessed in last 24 hours in [#9989](https://github.com/appwrite/appwrite/pull/9989)
|
||||
* Add increment + decrement routes in [#9986](https://github.com/appwrite/appwrite/pull/9986)
|
||||
* Only run maintenance task for projects accessed in last 30 days in [#9995](https://github.com/appwrite/appwrite/pull/9995)
|
||||
* Update appwrite-assistant image version to 0.8.3 in [#10003](https://github.com/appwrite/appwrite/pull/10003)
|
||||
* Update emails to use button in [#9590](https://github.com/appwrite/appwrite/pull/9590)
|
||||
* Create commit & branch url for first git deployment when site is linked to repo in [#9969](https://github.com/appwrite/appwrite/pull/9969)
|
||||
* Handle React Native schemes in [#9650](https://github.com/appwrite/appwrite/pull/9650)
|
||||
* Handle origin validation for web extensions in [#10107](https://github.com/appwrite/appwrite/pull/10107)
|
||||
* Preview text for emails in [#10198](https://github.com/appwrite/appwrite/pull/10198)
|
||||
* Create email target when using email OTP registration in [#10224](https://github.com/appwrite/appwrite/pull/10224)
|
||||
* Add CSV imports in [#10231](https://github.com/appwrite/appwrite/pull/10231)
|
||||
* Add support for svg favicons in [#10255](https://github.com/appwrite/appwrite/pull/10255)
|
||||
* Realtime support for bulk api in [#10096](https://github.com/appwrite/appwrite/pull/10096)
|
||||
* Skip redundant subqueries in users list route in [#10297](https://github.com/appwrite/appwrite/pull/10297)
|
||||
* Add native sign in with Apple function template in [#10286](https://github.com/appwrite/appwrite/pull/10286)
|
||||
* Add support for HEAD requests in [#10304](https://github.com/appwrite/appwrite/pull/10304)
|
||||
* Update invite email copy in [#10309](https://github.com/appwrite/appwrite/pull/10309)
|
||||
* Increase dynamic API key expiration in [#10328](https://github.com/appwrite/appwrite/pull/10328)
|
||||
* Add TablesDB service in [#10333](https://github.com/appwrite/appwrite/pull/10333)
|
||||
* Add execution.deploymentId to response model in [#10357](https://github.com/appwrite/appwrite/pull/10357)
|
||||
* Switch Union China Pay to just Union Pay in [#10372](https://github.com/appwrite/appwrite/pull/10372) and [#10382](https://github.com/appwrite/appwrite/pull/10382)
|
||||
* Add execution id and log id to response headers in [#10379](https://github.com/appwrite/appwrite/pull/10379)
|
||||
* Add executionId and client IP to function headers in [#9147](https://github.com/appwrite/appwrite/pull/9147)
|
||||
* Allow HEAD requests in function executions in [#10385](https://github.com/appwrite/appwrite/pull/10385)
|
||||
* Add support for select queries when listing deployments in [#10380](https://github.com/appwrite/appwrite/pull/10380)
|
||||
* Add spatial type attributes in [#10356](https://github.com/appwrite/appwrite/pull/10356) and [#10443](https://github.com/appwrite/appwrite/pull/10443)
|
||||
* Add realtime support for bulk upserts in [#10425](https://github.com/appwrite/appwrite/pull/10425)
|
||||
* Add previewUrl to vcs comment from vcs controller in [#10396](https://github.com/appwrite/appwrite/pull/10396)
|
||||
* Rename verification SDK methods to be more specific in [#10606](https://github.com/appwrite/appwrite/pull/10606)
|
||||
* Add project name in email subject in [#10609](https://github.com/appwrite/appwrite/pull/10609)
|
||||
* Throw error when email is not available for account verification in [#10533](https://github.com/appwrite/appwrite/pull/10533)
|
||||
* Add support for transactions in [#10023](https://github.com/appwrite/appwrite/pull/10023) and [#10624](https://github.com/appwrite/appwrite/pull/10624)
|
||||
* Use bcc only emails for smtp in [#10644](https://github.com/appwrite/appwrite/pull/10644)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix rules on active deployment in [#9902](https://github.com/appwrite/appwrite/pull/9902)
|
||||
* Fix for upserts with differing optional parameter sets in [#9928](https://github.com/appwrite/appwrite/pull/9928)
|
||||
* Fix teams deletion in [#9888](https://github.com/appwrite/appwrite/pull/9888)
|
||||
* Fix deletion logic in [#9938](https://github.com/appwrite/appwrite/pull/9938)
|
||||
* Update database for upsert fix in [#9941](https://github.com/appwrite/appwrite/pull/9941)
|
||||
* Fix expire format in account recovery, verification, phone and mfa in [#9600](https://github.com/appwrite/appwrite/pull/9600)
|
||||
* Fix github comments and deployment creation on branch deletion in [#9949](https://github.com/appwrite/appwrite/pull/9949)
|
||||
* Fix cache issues with proxy for deployment download in [#9971](https://github.com/appwrite/appwrite/pull/9971)
|
||||
* Redirect rule parent resource in [#9982](https://github.com/appwrite/appwrite/pull/9982)
|
||||
* Fix usage queues in [#9946](https://github.com/appwrite/appwrite/pull/9946)
|
||||
* Transfer control for the migration in [#9997](https://github.com/appwrite/appwrite/pull/9997)
|
||||
* Prevent 'Attribute "factors" must be an array' error in [#10004](https://github.com/appwrite/appwrite/pull/10004)
|
||||
* Fix all vcs urls missing region in [#9998](https://github.com/appwrite/appwrite/pull/9998)
|
||||
* Add readable error for csv imports in [#9947](https://github.com/appwrite/appwrite/pull/9947)
|
||||
* Fix missing screenshot logs in [#10024](https://github.com/appwrite/appwrite/pull/10024)
|
||||
* Update executor to fix s3 endpoint bug in [#10036](https://github.com/appwrite/appwrite/pull/10036)
|
||||
* Fix build duration calculation in [#10053](https://github.com/appwrite/appwrite/pull/10053)
|
||||
* Fix logs order in [#10052](https://github.com/appwrite/appwrite/pull/10052)
|
||||
* Fix platform check for Sites with automatic rule in [#10043](https://github.com/appwrite/appwrite/pull/10043)
|
||||
* Increase cache ttl to ensure hits in [#10079](https://github.com/appwrite/appwrite/pull/10079)
|
||||
* Fix connect to existing repo flow in [#10034](https://github.com/appwrite/appwrite/pull/10034)
|
||||
* Fix migrations path and type in [#10090](https://github.com/appwrite/appwrite/pull/10090)
|
||||
* Fix JWT authentication database selection for admin mode in [#10098](https://github.com/appwrite/appwrite/pull/10098)
|
||||
* Use _APP_CONSOLE_DOMAIN, if not found, then use _APP_DOMAIN in [#9999](https://github.com/appwrite/appwrite/pull/9999)
|
||||
* Fix file tokens not working on file-security in [#10120](https://github.com/appwrite/appwrite/pull/10120)
|
||||
* Fix build activation race condition in [#9952](https://github.com/appwrite/appwrite/pull/9952)
|
||||
* Changed the default permission param of upsert document in [#10129](https://github.com/appwrite/appwrite/pull/10129)
|
||||
* Fix success validation in oauth2 redirect in [#10130](https://github.com/appwrite/appwrite/pull/10130)
|
||||
* Update OAuth2 redirect URLs in [#10119](https://github.com/appwrite/appwrite/pull/10119)
|
||||
* Fix specs with new env vars in [#10135](https://github.com/appwrite/appwrite/pull/10135)
|
||||
* Skip deployment when commit is created by us in [#10187](https://github.com/appwrite/appwrite/pull/10187)
|
||||
* Use direct source for file-preview when empty in [#10181](https://github.com/appwrite/appwrite/pull/10181)
|
||||
* Better error message for invalid function scheduled time in [#10201](https://github.com/appwrite/appwrite/pull/10201)
|
||||
* Add defaultBranch in getRepository response in [#10190](https://github.com/appwrite/appwrite/pull/10190)
|
||||
* Filter sequence to int because any models skip rule checks in [#10221](https://github.com/appwrite/appwrite/pull/10221)
|
||||
* Fix 500 errors on robots and humans txt files in [#10248](https://github.com/appwrite/appwrite/pull/10248)
|
||||
* Fix atomic number ops with limit 0 in [#10264](https://github.com/appwrite/appwrite/pull/10264)
|
||||
* Update build command for flutter in [#10288](https://github.com/appwrite/appwrite/pull/10288)
|
||||
* Add a fallback locale in [#10307](https://github.com/appwrite/appwrite/pull/10307)
|
||||
* Fix variables sharing across resources in [#10308](https://github.com/appwrite/appwrite/pull/10308)
|
||||
* Fix uncaught invalid arg in [#10318](https://github.com/appwrite/appwrite/pull/10318)
|
||||
* Add missing upsert event in [#10317](https://github.com/appwrite/appwrite/pull/10317)
|
||||
* Improve font reliability in [#10332](https://github.com/appwrite/appwrite/pull/10332)
|
||||
* Truncate logs in function worker in [#9773](https://github.com/appwrite/appwrite/pull/9773)
|
||||
* Fix event template configuration issues in [#10350](https://github.com/appwrite/appwrite/pull/10350)
|
||||
* Fix users events & missed publisher logic for Functions in [#10348](https://github.com/appwrite/appwrite/pull/10348)
|
||||
* Fix incorrect file token expiry in [#10329](https://github.com/appwrite/appwrite/pull/10329)
|
||||
* Fix upserting that makes no change in [#10363](https://github.com/appwrite/appwrite/pull/10363) and [#10364](https://github.com/appwrite/appwrite/pull/10364)
|
||||
* Fix domain validator in [#10374](https://github.com/appwrite/appwrite/pull/10374)
|
||||
* Apply sequence integer casting and attribute cleanup fixes to Row model, TablesDB tests, and document processing in [#10383](https://github.com/appwrite/appwrite/pull/10383)
|
||||
* Fix domain validator in [#10386](https://github.com/appwrite/appwrite/pull/10386)
|
||||
* Fix sequence removal in [#10388](https://github.com/appwrite/appwrite/pull/10388)
|
||||
* Fix TablesDB scopes in [#10387](https://github.com/appwrite/appwrite/pull/10387)
|
||||
* Fix request filter in [#10389](https://github.com/appwrite/appwrite/pull/10389)
|
||||
* Fix nested filter selects in [#10393](https://github.com/appwrite/appwrite/pull/10393)
|
||||
* Fix readonly attr stripping on write in [#10405](https://github.com/appwrite/appwrite/pull/10405)
|
||||
* Replace %s with mustache placeholder in [#10392](https://github.com/appwrite/appwrite/pull/10392)
|
||||
* Support array headers for set-cookie in [#10427](https://github.com/appwrite/appwrite/pull/10427)
|
||||
* Fix put prefs structure validation in [#10436](https://github.com/appwrite/appwrite/pull/10436)
|
||||
* Fix oauth identity check in [#10460](https://github.com/appwrite/appwrite/pull/10460)
|
||||
* Fix check in [#10489](https://github.com/appwrite/appwrite/pull/10489)
|
||||
* Fix database usage metrics in [#10483](https://github.com/appwrite/appwrite/pull/10483)
|
||||
* Throw appropriate 400s from request filters in [#10502](https://github.com/appwrite/appwrite/pull/10502)
|
||||
* Catch query exception on bucket/file list in [#10505](https://github.com/appwrite/appwrite/pull/10505)
|
||||
* Use outputDirectory attribute from deployment in [#10571](https://github.com/appwrite/appwrite/pull/10571)
|
||||
* Fix buildOutput attribute name in deployment check in [#10572](https://github.com/appwrite/appwrite/pull/10572)
|
||||
* Update database for nested selection fix in [#10577](https://github.com/appwrite/appwrite/pull/10577)
|
||||
* Auto-allow sites domain for OAuth in [#10503](https://github.com/appwrite/appwrite/pull/10503)
|
||||
* Handle OIDC well-known endpoint errors in [#10589](https://github.com/appwrite/appwrite/pull/10589)
|
||||
* Correct invalid template links in Create temporary deployment endpoint in [#10581](https://github.com/appwrite/appwrite/pull/10581)
|
||||
* Update broken create table links in TablesDB docs in [#10592](https://github.com/appwrite/appwrite/pull/10592)
|
||||
* Fix cross API compatibility in [#10626](https://github.com/appwrite/appwrite/pull/10626)
|
||||
* Fix code 0 from databases on realtime in [#10631](https://github.com/appwrite/appwrite/pull/10631)
|
||||
* Throw duplicate error when function id already exists in [#10618](https://github.com/appwrite/appwrite/pull/10618)
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Fix task coroutine hooks in [#9850](https://github.com/appwrite/appwrite/pull/9850)
|
||||
* Feat sync encrypt updates in [#9871](https://github.com/appwrite/appwrite/pull/9871)
|
||||
* Revert "Feat sync encrypt updates" in [#9877](https://github.com/appwrite/appwrite/pull/9877)
|
||||
* Add builds worker group in [#9872](https://github.com/appwrite/appwrite/pull/9872)
|
||||
* Revert encrypted attribute changes in [#9898](https://github.com/appwrite/appwrite/pull/9898)
|
||||
* Update sdk generator and sdks in [#9849](https://github.com/appwrite/appwrite/pull/9849)
|
||||
* Release cli in [#9900](https://github.com/appwrite/appwrite/pull/9900)
|
||||
* Improve how rules are fetched in [#9915](https://github.com/appwrite/appwrite/pull/9915)
|
||||
* Sync 1.6 in [#9920](https://github.com/appwrite/appwrite/pull/9920)
|
||||
* Update messaging library in [#9764](https://github.com/appwrite/appwrite/pull/9764)
|
||||
* Disable TCP hook on stats resources in [#9932](https://github.com/appwrite/appwrite/pull/9932)
|
||||
* Remove JSON index on roles due to MySQL bug in [#9924](https://github.com/appwrite/appwrite/pull/9924)
|
||||
* Update queue in [#9936](https://github.com/appwrite/appwrite/pull/9936)
|
||||
* Fix flaky account tests in [#9954](https://github.com/appwrite/appwrite/pull/9954)
|
||||
* Fix flaky messaging test in [#9957](https://github.com/appwrite/appwrite/pull/9957)
|
||||
* Make usage tests robust in [#9956](https://github.com/appwrite/appwrite/pull/9956)
|
||||
* Increase deployment timeouts in tests in [#9955](https://github.com/appwrite/appwrite/pull/9955)
|
||||
* Graceful shutdown on SIGTERM in [#9890](https://github.com/appwrite/appwrite/pull/9890)
|
||||
* Bring back telemetry for storage in [#9903](https://github.com/appwrite/appwrite/pull/9903)
|
||||
* Update version to 1.7.4 and add experimental warnings in [#9959](https://github.com/appwrite/appwrite/pull/9959)
|
||||
* Return queue pre-fetch results in [#9731](https://github.com/appwrite/appwrite/pull/9731)
|
||||
* Update SDK versions in [#9987](https://github.com/appwrite/appwrite/pull/9987)
|
||||
* Restore unique filename for health check #9842 in [#9993](https://github.com/appwrite/appwrite/pull/9993)
|
||||
* Add after build hook in [#9996](https://github.com/appwrite/appwrite/pull/9996)
|
||||
* Remove endpoint selector in [#10000](https://github.com/appwrite/appwrite/pull/10000)
|
||||
* Use static code instead of astro in tests in [#9966](https://github.com/appwrite/appwrite/pull/9966)
|
||||
* Add ref param to vcs list contents in [#9991](https://github.com/appwrite/appwrite/pull/9991)
|
||||
* Update coderabbit config file in [#10005](https://github.com/appwrite/appwrite/pull/10005)
|
||||
* TAR support in [#10016](https://github.com/appwrite/appwrite/pull/10016)
|
||||
* Update delete project scope in [#10017](https://github.com/appwrite/appwrite/pull/10017)
|
||||
* Lazy-load relationships in [#9669](https://github.com/appwrite/appwrite/pull/9669)
|
||||
* Revert "Feat: Lazy-load relationships" in [#10018](https://github.com/appwrite/appwrite/pull/10018)
|
||||
* Revert "Update delete project scope" in [#10022](https://github.com/appwrite/appwrite/pull/10022)
|
||||
* 1.8.x in [#9985](https://github.com/appwrite/appwrite/pull/9985)
|
||||
* Update cli version and add bulk operation warnings in [#10007](https://github.com/appwrite/appwrite/pull/10007)
|
||||
* Update Appwrite description to include Sites, add MCP to products list in [#9867](https://github.com/appwrite/appwrite/pull/9867)
|
||||
* Update README.md in [#10026](https://github.com/appwrite/appwrite/pull/10026)
|
||||
* Fix duplication of platforms in swagger specs in [#10008](https://github.com/appwrite/appwrite/pull/10008)
|
||||
* Update react native sdk and changelog in [#10025](https://github.com/appwrite/appwrite/pull/10025)
|
||||
* Update delete project signature in [#10028](https://github.com/appwrite/appwrite/pull/10028)
|
||||
* Fix Golang SDK examples for docs in [#10001](https://github.com/appwrite/appwrite/pull/10001)
|
||||
* Revert "worker: Graceful shutdown on SIGTERM" in [#10035](https://github.com/appwrite/appwrite/pull/10035)
|
||||
* Fix benchmark CI in [#10055](https://github.com/appwrite/appwrite/pull/10055)
|
||||
* Use ->action(...)) instead of ->callback([$this, 'action']); in [#9967](https://github.com/appwrite/appwrite/pull/9967)
|
||||
* Override project via custom domains log in [#10011](https://github.com/appwrite/appwrite/pull/10011)
|
||||
* Add database worker job logging in [#10056](https://github.com/appwrite/appwrite/pull/10056)
|
||||
* Add runtimeEntrypoint param in [#10062](https://github.com/appwrite/appwrite/pull/10062)
|
||||
* Add missing injections in [#10061](https://github.com/appwrite/appwrite/pull/10061)
|
||||
* Replace Console loop with Swoole Timer for stats resource m… in [#10054](https://github.com/appwrite/appwrite/pull/10054)
|
||||
* Update README.md in [#10063](https://github.com/appwrite/appwrite/pull/10063)
|
||||
* Fix parameter order in action function for robots.txt route in [#10067](https://github.com/appwrite/appwrite/pull/10067)
|
||||
* Preview endpoint logging in [#10068](https://github.com/appwrite/appwrite/pull/10068)
|
||||
* Fix flakyness of account tests in [#10066](https://github.com/appwrite/appwrite/pull/10066)
|
||||
* Update cli to 8.1.0 and add changelog in [#10070](https://github.com/appwrite/appwrite/pull/10070)
|
||||
* Update composer.json and composer.lock to include appwrite-lab… in [#10051](https://github.com/appwrite/appwrite/pull/10051)
|
||||
* Fix tests, for `Cloud` in [#10085](https://github.com/appwrite/appwrite/pull/10085)
|
||||
* Update README.md in [#10084](https://github.com/appwrite/appwrite/pull/10084)
|
||||
* Revert "chore: update composer.json and composer.lock to include appwrite-lab…" in [#10086](https://github.com/appwrite/appwrite/pull/10086)
|
||||
* Update README to add Bulk API link in [#10095](https://github.com/appwrite/appwrite/pull/10095)
|
||||
* Add redis publisher to schedule base if available in [#10099](https://github.com/appwrite/appwrite/pull/10099)
|
||||
* Fix site template test in [#10104](https://github.com/appwrite/appwrite/pull/10104)
|
||||
* Update nodejs 17.1.0 in [#10088](https://github.com/appwrite/appwrite/pull/10088)
|
||||
* Update README.md to add Upsert announcement in [#10112](https://github.com/appwrite/appwrite/pull/10112)
|
||||
* Reduce delete batch size in [#10128](https://github.com/appwrite/appwrite/pull/10128)
|
||||
* Update README.md in [#10134](https://github.com/appwrite/appwrite/pull/10134)
|
||||
* Speed up tests in [#10127](https://github.com/appwrite/appwrite/pull/10127)
|
||||
* Update cli to 8.2.0 in [#10136](https://github.com/appwrite/appwrite/pull/10136)
|
||||
* Prevent injected $user from being shadowed in [#10150](https://github.com/appwrite/appwrite/pull/10150)
|
||||
* Update react native to 0.10.1 and dotnet to 0.14.0 in [#10138](https://github.com/appwrite/appwrite/pull/10138)
|
||||
* Update README.md in [#10153](https://github.com/appwrite/appwrite/pull/10153)
|
||||
* Update cli 8.2.1 in [#10155](https://github.com/appwrite/appwrite/pull/10155)
|
||||
* Fix build usage specification in [#10157](https://github.com/appwrite/appwrite/pull/10157)
|
||||
* Handle redirect validator in specs + GraphQL type mapper in [#10158](https://github.com/appwrite/appwrite/pull/10158)
|
||||
* Update dart 16.1.0, flutter 17.0.2 and cli 8.2.2 in [#10161](https://github.com/appwrite/appwrite/pull/10161)
|
||||
* Improve invalid scheme error in origin check in [#10164](https://github.com/appwrite/appwrite/pull/10164)
|
||||
* 1.7.x in [#9897](https://github.com/appwrite/appwrite/pull/9897)
|
||||
* Added the cases of null permissions in the upsert route and update th… in [#10179](https://github.com/appwrite/appwrite/pull/10179)
|
||||
* Fix 1.7.x specs in [#10197](https://github.com/appwrite/appwrite/pull/10197)
|
||||
* Suppress git-action exception in deployment worker in [#10199](https://github.com/appwrite/appwrite/pull/10199)
|
||||
* Stats-usage on redis in [#10156](https://github.com/appwrite/appwrite/pull/10156)
|
||||
* Fix templates on `1.7.x`. in [#10203](https://github.com/appwrite/appwrite/pull/10203)
|
||||
* Change preview & body for MFA email in [#10205](https://github.com/appwrite/appwrite/pull/10205)
|
||||
* Add docs for nestedType, encode, from and toMap in [#10204](https://github.com/appwrite/appwrite/pull/10204)
|
||||
* Update sdks 1.7.x in [#10202](https://github.com/appwrite/appwrite/pull/10202)
|
||||
* Update migration release in [#10222](https://github.com/appwrite/appwrite/pull/10222)
|
||||
* Remove sequence on incoming docs in [#10228](https://github.com/appwrite/appwrite/pull/10228)
|
||||
* Filter certificates renewal task in maintenance by region in [#10227](https://github.com/appwrite/appwrite/pull/10227)
|
||||
* Move changelog to sdks platforms array in [#10233](https://github.com/appwrite/appwrite/pull/10233)
|
||||
* Update changelog and sdk gen in [#10247](https://github.com/appwrite/appwrite/pull/10247)
|
||||
* Telemetry for cache hits and misses in [#10240](https://github.com/appwrite/appwrite/pull/10240)
|
||||
* Add model examples + additonal examples to specs in [#10249](https://github.com/appwrite/appwrite/pull/10249)
|
||||
* Update favicons endpoint to fallback to ico instead of throwing error in [#10260](https://github.com/appwrite/appwrite/pull/10260)
|
||||
* Update README.md in [#10259](https://github.com/appwrite/appwrite/pull/10259)
|
||||
* Check CAA record before issuing certificate in [#10258](https://github.com/appwrite/appwrite/pull/10258)
|
||||
* Revert "Check CAA record before issuing certificate" in [#10263](https://github.com/appwrite/appwrite/pull/10263)
|
||||
* Test var id attribute in [#10243](https://github.com/appwrite/appwrite/pull/10243)
|
||||
* Add type attribute to the database creation flow in [#10266](https://github.com/appwrite/appwrite/pull/10266)
|
||||
* Add CAA validator in [#10267](https://github.com/appwrite/appwrite/pull/10267)
|
||||
* Update database type to grids and legacy in [#10273](https://github.com/appwrite/appwrite/pull/10273)
|
||||
* Update README.md in [#10272](https://github.com/appwrite/appwrite/pull/10272)
|
||||
* Upgrade composer for utopia migration in [#10274](https://github.com/appwrite/appwrite/pull/10274)
|
||||
* Update SDK generator and sdks in [#10271](https://github.com/appwrite/appwrite/pull/10271)
|
||||
* Fix wrong resource path for audits in [#10279](https://github.com/appwrite/appwrite/pull/10279)
|
||||
* Update `grid` on resource events in [#10282](https://github.com/appwrite/appwrite/pull/10282)
|
||||
* Add readonly param to sequence, databaseId and collectionId in [#10278](https://github.com/appwrite/appwrite/pull/10278)
|
||||
* Update migrations in [#10283](https://github.com/appwrite/appwrite/pull/10283)
|
||||
* Add placeholder detection in [#10284](https://github.com/appwrite/appwrite/pull/10284)
|
||||
* Update docker base to 0.10.3 in [#10285](https://github.com/appwrite/appwrite/pull/10285)
|
||||
* Make check for adding warning header stricter in [#10293](https://github.com/appwrite/appwrite/pull/10293)
|
||||
* Fix databases worker cache clearing bug in [#10294](https://github.com/appwrite/appwrite/pull/10294)
|
||||
* Reapply Redis functions queue in [#10299](https://github.com/appwrite/appwrite/pull/10299)
|
||||
* Add new database query type tests in [#10296](https://github.com/appwrite/appwrite/pull/10296)
|
||||
* Update package in [#10312](https://github.com/appwrite/appwrite/pull/10312)
|
||||
* Update required attributes in [#10311](https://github.com/appwrite/appwrite/pull/10311)
|
||||
* Remove experiment warnings from bulk methods in [#10310](https://github.com/appwrite/appwrite/pull/10310)
|
||||
* Update README.md in [#10313](https://github.com/appwrite/appwrite/pull/10313)
|
||||
* Added internal file param to handle upload to internal bucket in [#10321](https://github.com/appwrite/appwrite/pull/10321)
|
||||
* Remove temp logging in [#10302](https://github.com/appwrite/appwrite/pull/10302)
|
||||
* Improve sites test for stability in [#10331](https://github.com/appwrite/appwrite/pull/10331)
|
||||
* Database lib bump to 0.71.15 in [#10336](https://github.com/appwrite/appwrite/pull/10336)
|
||||
* Clarify userId param in endpoints that create accounts in [#10117](https://github.com/appwrite/appwrite/pull/10117)
|
||||
* Upgrade HTTP in [#10338](https://github.com/appwrite/appwrite/pull/10338)
|
||||
* Remove unnessessary external dependnecies in [#10343](https://github.com/appwrite/appwrite/pull/10343)
|
||||
* Sync main into 1.7.x in [#10347](https://github.com/appwrite/appwrite/pull/10347)
|
||||
* Fix TablesDB casing in [#10346](https://github.com/appwrite/appwrite/pull/10346)
|
||||
* Add cookies test in [#10352](https://github.com/appwrite/appwrite/pull/10352)
|
||||
* Update token tests with jwt decode in [#10354](https://github.com/appwrite/appwrite/pull/10354)
|
||||
* Utilize assets server for fonts in [#10358](https://github.com/appwrite/appwrite/pull/10358)
|
||||
* Sync main into 1.7.x in [#10359](https://github.com/appwrite/appwrite/pull/10359)
|
||||
* Bump 1.7.x in [#10365](https://github.com/appwrite/appwrite/pull/10365)
|
||||
* Fix queue health in [#10369](https://github.com/appwrite/appwrite/pull/10369)
|
||||
* Allow publisher messaging override in scheduler in [#10370](https://github.com/appwrite/appwrite/pull/10370)
|
||||
* Add replacewith and deprecated since to account methods in [#10377](https://github.com/appwrite/appwrite/pull/10377)
|
||||
* Update README.md in [#10376](https://github.com/appwrite/appwrite/pull/10376)
|
||||
* Update CLI in [#10390](https://github.com/appwrite/appwrite/pull/10390)
|
||||
* Update default method in description in [#10391](https://github.com/appwrite/appwrite/pull/10391)
|
||||
* Rename namespace from tables-db to tablesdb in specs in [#10395](https://github.com/appwrite/appwrite/pull/10395)
|
||||
* Update tables group in specs in [#10394](https://github.com/appwrite/appwrite/pull/10394)
|
||||
* Update description for upsert methods in [#10397](https://github.com/appwrite/appwrite/pull/10397)
|
||||
* Update README.md in [#10401](https://github.com/appwrite/appwrite/pull/10401)
|
||||
* Added handling of database resources after migration in [#10400](https://github.com/appwrite/appwrite/pull/10400)
|
||||
* Revert "Added handling of database resources after migration" in [#10406](https://github.com/appwrite/appwrite/pull/10406)
|
||||
* Remove sdk deprecation warnings in [#10408](https://github.com/appwrite/appwrite/pull/10408)
|
||||
* Mark Row response model's param with readonly in [#10409](https://github.com/appwrite/appwrite/pull/10409)
|
||||
* Update exception thrown when svg sanitization fails in [#10416](https://github.com/appwrite/appwrite/pull/10416)
|
||||
* Fix allow null params in [#10417](https://github.com/appwrite/appwrite/pull/10417)
|
||||
* Allow running tests with specific response format in [#10418](https://github.com/appwrite/appwrite/pull/10418)
|
||||
* Make webhooks publisher overridable in [#10419](https://github.com/appwrite/appwrite/pull/10419)
|
||||
* Check audits logs in [#10414](https://github.com/appwrite/appwrite/pull/10414)
|
||||
* Remove direct publisher calls in [#10420](https://github.com/appwrite/appwrite/pull/10420)
|
||||
* removed spatial type response and will be using the json type for the… in [#10433](https://github.com/appwrite/appwrite/pull/10433)
|
||||
* Add tests for new time helpers in [#10437](https://github.com/appwrite/appwrite/pull/10437)
|
||||
* Move projects.list() to module in [#10441](https://github.com/appwrite/appwrite/pull/10441)
|
||||
* Update cli to 9.1.0 in [#10442](https://github.com/appwrite/appwrite/pull/10442)
|
||||
* Add requestBody param examples in specs in [#10431](https://github.com/appwrite/appwrite/pull/10431)
|
||||
* Fix mysql tests in [#10445](https://github.com/appwrite/appwrite/pull/10445)
|
||||
* Upgrade platform lib to have older queue lib in [#10447](https://github.com/appwrite/appwrite/pull/10447)
|
||||
* Fix router compression in [#10452](https://github.com/appwrite/appwrite/pull/10452)
|
||||
* Upgrade http lib for backwards compatible default param in [#10455](https://github.com/appwrite/appwrite/pull/10455)
|
||||
* Update examples in [#10444](https://github.com/appwrite/appwrite/pull/10444)
|
||||
* Automatic pr creation in sdk release script in [#10457](https://github.com/appwrite/appwrite/pull/10457)
|
||||
* Remove avatars command from cli in [#10454](https://github.com/appwrite/appwrite/pull/10454)
|
||||
* Remove deno from platforms array in [#10453](https://github.com/appwrite/appwrite/pull/10453)
|
||||
* Spatial type attributes sdk updates in [#10463](https://github.com/appwrite/appwrite/pull/10463)
|
||||
* Stats resources try catch in [#10469](https://github.com/appwrite/appwrite/pull/10469)
|
||||
* Move proxy endpoints to modules in [#10470](https://github.com/appwrite/appwrite/pull/10470)
|
||||
* Add certificate validation override in [#10471](https://github.com/appwrite/appwrite/pull/10471)
|
||||
* Generate SDKs in [#10475](https://github.com/appwrite/appwrite/pull/10475)
|
||||
* Spatial test tablesdb updates in [#10473](https://github.com/appwrite/appwrite/pull/10473)
|
||||
* Add colors to certificate logs in [#10438](https://github.com/appwrite/appwrite/pull/10438)
|
||||
* appwrite db bump in [#10479](https://github.com/appwrite/appwrite/pull/10479)
|
||||
* Bump database in [#10480](https://github.com/appwrite/appwrite/pull/10480)
|
||||
* Health db queues in [#10482](https://github.com/appwrite/appwrite/pull/10482)
|
||||
* Attempt small size for website dependency in [#10485](https://github.com/appwrite/appwrite/pull/10485)
|
||||
* Worker stop in [#10498](https://github.com/appwrite/appwrite/pull/10498)
|
||||
* Update database in [#10506](https://github.com/appwrite/appwrite/pull/10506)
|
||||
* Stats resources and usage sorting by unique field in [#10472](https://github.com/appwrite/appwrite/pull/10472)
|
||||
* Add spatial column validation during required mode and tests for exis… in [#10509](https://github.com/appwrite/appwrite/pull/10509)
|
||||
* Sub query variables order by in [#10513](https://github.com/appwrite/appwrite/pull/10513)
|
||||
* Update README.md in [#10514](https://github.com/appwrite/appwrite/pull/10514)
|
||||
* bump database 1.5.0 in [#10515](https://github.com/appwrite/appwrite/pull/10515)
|
||||
* Don't remove required attributes in [#10516](https://github.com/appwrite/appwrite/pull/10516)
|
||||
* Catch query exception on bulk update/delete in [#10517](https://github.com/appwrite/appwrite/pull/10517)
|
||||
* Update cli to 10.0.0 in [#10511](https://github.com/appwrite/appwrite/pull/10511)
|
||||
* Add type_enum support and update docs in [#10496](https://github.com/appwrite/appwrite/pull/10496)
|
||||
* Improve code readability for schedules in [#10522](https://github.com/appwrite/appwrite/pull/10522)
|
||||
* Include response model enum names in [#10538](https://github.com/appwrite/appwrite/pull/10538)
|
||||
* SDK releases in [#10539](https://github.com/appwrite/appwrite/pull/10539)
|
||||
* Fix health status enum in [#10540](https://github.com/appwrite/appwrite/pull/10540)
|
||||
* Update afterbuild fn in [#10541](https://github.com/appwrite/appwrite/pull/10541)
|
||||
* Update afterbuild to also pass adapter in [#10545](https://github.com/appwrite/appwrite/pull/10545)
|
||||
* Update `z-index` to be the highest in [#9874](https://github.com/appwrite/appwrite/pull/9874)
|
||||
* Update framework lib to 0.33.28 in [#10551](https://github.com/appwrite/appwrite/pull/10551)
|
||||
* Fix enum typing for platform in specs in [#10553](https://github.com/appwrite/appwrite/pull/10553)
|
||||
* Add enums for database type and column status in [#10561](https://github.com/appwrite/appwrite/pull/10561)
|
||||
* Fix activities in [#10586](https://github.com/appwrite/appwrite/pull/10586)
|
||||
* Fix logs truncation tests in [#10585](https://github.com/appwrite/appwrite/pull/10585)
|
||||
* Remove related data in realtime payload in [#10590](https://github.com/appwrite/appwrite/pull/10590)
|
||||
* Update composer dependencies in [#10601](https://github.com/appwrite/appwrite/pull/10601)
|
||||
* Update sdks add response models in [#10554](https://github.com/appwrite/appwrite/pull/10554)
|
||||
* Sanitize 5xx errors on realtime in [#10598](https://github.com/appwrite/appwrite/pull/10598)
|
||||
* Update database in [#10596](https://github.com/appwrite/appwrite/pull/10596)
|
||||
* Add both collection and table id in the realtime in [#10608](https://github.com/appwrite/appwrite/pull/10608)
|
||||
* Chore bump db in [#10611](https://github.com/appwrite/appwrite/pull/10611)
|
||||
* Branded email for Console auth flows in [#10501](https://github.com/appwrite/appwrite/pull/10501)
|
||||
* Add minor releases for all SDKs - deprecate createVerification, add createEmailVerification in [#10614](https://github.com/appwrite/appwrite/pull/10614)
|
||||
* Add automatic releases in [#10615](https://github.com/appwrite/appwrite/pull/10615)
|
||||
* Feat txn sdks in [#10621](https://github.com/appwrite/appwrite/pull/10621)
|
||||
* Prevent empty releases in sdk release script in [#10627](https://github.com/appwrite/appwrite/pull/10627)
|
||||
* Update domains lib to 0.8.2 in [#10629](https://github.com/appwrite/appwrite/pull/10629)
|
||||
* Fix txn API scope backwards compat in [#10640](https://github.com/appwrite/appwrite/pull/10640)
|
||||
* Fix block schedules in [#10620](https://github.com/appwrite/appwrite/pull/10620)
|
||||
* Update .NET SDK to 0.21.2 and improve release detection in [#10641](https://github.com/appwrite/appwrite/pull/10641)
|
||||
* Make methods protected for extending in [#10617](https://github.com/appwrite/appwrite/pull/10617)
|
||||
|
||||
# Version 1.7.4
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Notable changes
|
||||
|
||||
* Update console image to version 6.0.13 in [#9891](https://github.com/appwrite/appwrite/pull/9891)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix createDeployment chunk upload in [#9886](https://github.com/appwrite/appwrite/pull/9886)
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Update version from 1.7.3 to 1.7.4 in [#9893](https://github.com/appwrite/appwrite/pull/9893)
|
||||
|
||||
# Version 1.7.3
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Notable changes
|
||||
|
||||
* Allow unlimited deployment size in [#9866](https://github.com/appwrite/appwrite/pull/9866)
|
||||
* Bump console to version 6.0.11 in [#9881](https://github.com/appwrite/appwrite/pull/9881)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Send deploymentResourceType in rules verification in [#9859](https://github.com/appwrite/appwrite/pull/9859)
|
||||
* Fix CNAME validation in [#9861](https://github.com/appwrite/appwrite/pull/9861)
|
||||
* Fix bucket not included in path in [#9864](https://github.com/appwrite/appwrite/pull/9864)
|
||||
* Fix URL for view logs in github comment in [#9875](https://github.com/appwrite/appwrite/pull/9875)
|
||||
* Set owner and region while migrating rules in [#9856](https://github.com/appwrite/appwrite/pull/9856)
|
||||
* Remove _APP_DEFAULT_REGION because it is not a valid env var in [#9883](https://github.com/appwrite/appwrite/pull/9883)
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Only load error page for development mode in [#9860](https://github.com/appwrite/appwrite/pull/9860)
|
||||
* Make max deployment and build size configurable in [#9863](https://github.com/appwrite/appwrite/pull/9863)
|
||||
* Update flutter_web_auth_2 docs to match 4.x in [#9858](https://github.com/appwrite/appwrite/pull/9858)
|
||||
* Use unique filename for health check in [#9842](https://github.com/appwrite/appwrite/pull/9842)
|
||||
* Added encrypt property in the attribute string response model in [#9868](https://github.com/appwrite/appwrite/pull/9868)
|
||||
* Add sequence in [#9865](https://github.com/appwrite/appwrite/pull/9865)
|
||||
* Add builds worker group in [#9873](https://github.com/appwrite/appwrite/pull/9873)
|
||||
* updated errro for the string encryption in [#9878](https://github.com/appwrite/appwrite/pull/9878)
|
||||
* Revert "Add sequence" in [#9879](https://github.com/appwrite/appwrite/pull/9879)
|
||||
* Prepare 1.7.3 release in [#9882](https://github.com/appwrite/appwrite/pull/9882)
|
||||
|
||||
# Version 1.6.2
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Notable changes
|
||||
|
||||
* Delete git folder to reduce build size in [9076](https://github.com/appwrite/appwrite/pull/9076)
|
||||
* Upgrade assistant in [9100](https://github.com/appwrite/appwrite/pull/9100)
|
||||
* Use redis adapter for abuse in [9121](https://github.com/appwrite/appwrite/pull/9121)
|
||||
* Set base specification CPUs to 0.5 again in [9146](https://github.com/appwrite/appwrite/pull/9146)
|
||||
* Add new push message parameters in [9060](https://github.com/appwrite/appwrite/pull/9060)
|
||||
* Update audits to include user type in [9211](https://github.com/appwrite/appwrite/pull/9211)
|
||||
* Enable HEIC in [9251](https://github.com/appwrite/appwrite/pull/9251)
|
||||
* Added teamName to membership redirect url in [9269](https://github.com/appwrite/appwrite/pull/9269)
|
||||
* Add support endpoint url for S3 in [9303](https://github.com/appwrite/appwrite/pull/9303)
|
||||
* Added RuPay Credit Card Icon in Avatars Service in [5046](https://github.com/appwrite/appwrite/pull/5046)
|
||||
* Add figma oauth provider in [9623](https://github.com/appwrite/appwrite/pull/9623)
|
||||
* Update console to version 5.2.58 in [9637](https://github.com/appwrite/appwrite/pull/9637)
|
||||
* Delete git folder to reduce build size in [#9076](https://github.com/appwrite/appwrite/pull/9076)
|
||||
* Upgrade assistant in [#9100](https://github.com/appwrite/appwrite/pull/9100)
|
||||
* Use redis adapter for abuse in [#9121](https://github.com/appwrite/appwrite/pull/9121)
|
||||
* Set base specification CPUs to 0.5 again in [#9146](https://github.com/appwrite/appwrite/pull/9146)
|
||||
* Add new push message parameters in [#9060](https://github.com/appwrite/appwrite/pull/9060)
|
||||
* Update audits to include user type in [#9211](https://github.com/appwrite/appwrite/pull/9211)
|
||||
* Enable HEIC in [#9251](https://github.com/appwrite/appwrite/pull/9251)
|
||||
* Added teamName to membership redirect url in [#9269](https://github.com/appwrite/appwrite/pull/9269)
|
||||
* Add support endpoint url for S3 in [#9303](https://github.com/appwrite/appwrite/pull/9303)
|
||||
* Added RuPay Credit Card Icon in Avatars Service in [#5046](https://github.com/appwrite/appwrite/pull/5046)
|
||||
* Add figma oauth provider in [#9623](https://github.com/appwrite/appwrite/pull/9623)
|
||||
* Update console to version 5.2.58 in [#9637](https://github.com/appwrite/appwrite/pull/9637)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Remove failed attribute in [9032](https://github.com/appwrite/appwrite/pull/9032)
|
||||
* Fix delete notFound attribute in [9038](https://github.com/appwrite/appwrite/pull/9038)
|
||||
* 🇮🇸 Added missing Icelandic translations for email strings. in [4848](https://github.com/appwrite/appwrite/pull/4848)
|
||||
* fix doc comment for filter method in [5769](https://github.com/appwrite/appwrite/pull/5769)
|
||||
* Delete attribute No throwing Exception on not found in [9157](https://github.com/appwrite/appwrite/pull/9157)
|
||||
* Fix VCS identity collision in [9138](https://github.com/appwrite/appwrite/pull/9138)
|
||||
* Fix disabling of email-otp when user wants to in [9200](https://github.com/appwrite/appwrite/pull/9200)
|
||||
* Ensure user can delete session in [9209](https://github.com/appwrite/appwrite/pull/9209)
|
||||
* Fix resend invitation in [9218](https://github.com/appwrite/appwrite/pull/9218)
|
||||
* Fix phone number parsing exception handling in [9246](https://github.com/appwrite/appwrite/pull/9246)
|
||||
* Fix amazon oauth in [9253](https://github.com/appwrite/appwrite/pull/9253)
|
||||
* Fix slack oauth scopes, and updated to v2 in [9228](https://github.com/appwrite/appwrite/pull/9228)
|
||||
* Fix forwarded user agent in [9271](https://github.com/appwrite/appwrite/pull/9271)
|
||||
* Fix WEBP File Preview Rendering Issue in [9321](https://github.com/appwrite/appwrite/pull/9321)
|
||||
* Fix build memory specifications in [9360](https://github.com/appwrite/appwrite/pull/9360)
|
||||
* Fix Self Hosting functions by adding missed config in [9373](https://github.com/appwrite/appwrite/pull/9373)
|
||||
* Fix resend team invite if already accepted in [9348](https://github.com/appwrite/appwrite/pull/9348)
|
||||
* Fix null errors on team invite in [9391](https://github.com/appwrite/appwrite/pull/9391)
|
||||
* Fix email (smtp) to multiple recipients in [9243](https://github.com/appwrite/appwrite/pull/9243)
|
||||
* Fix stats timing by using receivedAt date when available in [9428](https://github.com/appwrite/appwrite/pull/9428)
|
||||
* Make min/max params optional for attribute update in [9387](https://github.com/appwrite/appwrite/pull/9387)
|
||||
* Fix blocking of phone sessions when disabled on console in [9447](https://github.com/appwrite/appwrite/pull/9447)
|
||||
* Fix logging config in [9467](https://github.com/appwrite/appwrite/pull/9467)
|
||||
* Update audit timestamp origin in [9481](https://github.com/appwrite/appwrite/pull/9481)
|
||||
* Fix certificates in deletes worker in [9466](https://github.com/appwrite/appwrite/pull/9466)
|
||||
* Fix console audits delete in [9547](https://github.com/appwrite/appwrite/pull/9547)
|
||||
* Fix migrations in [9633](https://github.com/appwrite/appwrite/pull/9633)
|
||||
* Ensure all 4xx errors in OAuth redirect lead to the failure URL in [9679](https://github.com/appwrite/appwrite/pull/9679)
|
||||
* Treat 0 as unlimited for CPUs and memory in [9638](https://github.com/appwrite/appwrite/pull/9638)
|
||||
* Add contextual dispatch logic to fix high CPU usage in [9687](https://github.com/appwrite/appwrite/pull/9687)
|
||||
* Remove failed attribute in [#9032](https://github.com/appwrite/appwrite/pull/9032)
|
||||
* Fix delete notFound attribute in [#9038](https://github.com/appwrite/appwrite/pull/9038)
|
||||
* 🇮🇸 Added missing Icelandic translations for email strings. in [#4848](https://github.com/appwrite/appwrite/pull/4848)
|
||||
* fix doc comment for filter method in [#5769](https://github.com/appwrite/appwrite/pull/5769)
|
||||
* Delete attribute No throwing Exception on not found in [#9157](https://github.com/appwrite/appwrite/pull/9157)
|
||||
* Fix VCS identity collision in [#9138](https://github.com/appwrite/appwrite/pull/9138)
|
||||
* Fix disabling of email-otp when user wants to in [#9200](https://github.com/appwrite/appwrite/pull/9200)
|
||||
* Ensure user can delete session in [#9209](https://github.com/appwrite/appwrite/pull/9209)
|
||||
* Fix resend invitation in [#9218](https://github.com/appwrite/appwrite/pull/9218)
|
||||
* Fix phone number parsing exception handling in [#9246](https://github.com/appwrite/appwrite/pull/9246)
|
||||
* Fix amazon oauth in [#9253](https://github.com/appwrite/appwrite/pull/9253)
|
||||
* Fix slack oauth scopes, and updated to v2 in [#9228](https://github.com/appwrite/appwrite/pull/9228)
|
||||
* Fix forwarded user agent in [#9271](https://github.com/appwrite/appwrite/pull/9271)
|
||||
* Fix WEBP File Preview Rendering Issue in [#9321](https://github.com/appwrite/appwrite/pull/9321)
|
||||
* Fix build memory specifications in [#9360](https://github.com/appwrite/appwrite/pull/9360)
|
||||
* Fix Self Hosting functions by adding missed config in [#9373](https://github.com/appwrite/appwrite/pull/9373)
|
||||
* Fix resend team invite if already accepted in [#9348](https://github.com/appwrite/appwrite/pull/9348)
|
||||
* Fix null errors on team invite in [#9391](https://github.com/appwrite/appwrite/pull/9391)
|
||||
* Fix email (smtp) to multiple recipients in [#9243](https://github.com/appwrite/appwrite/pull/9243)
|
||||
* Fix stats timing by using receivedAt date when available in [#9428](https://github.com/appwrite/appwrite/pull/9428)
|
||||
* Make min/max params optional for attribute update in [#9387](https://github.com/appwrite/appwrite/pull/9387)
|
||||
* Fix blocking of phone sessions when disabled on console in [#9447](https://github.com/appwrite/appwrite/pull/9447)
|
||||
* Fix logging config in [#9467](https://github.com/appwrite/appwrite/pull/9467)
|
||||
* Update audit timestamp origin in [#9481](https://github.com/appwrite/appwrite/pull/9481)
|
||||
* Fix certificates in deletes worker in [#9466](https://github.com/appwrite/appwrite/pull/9466)
|
||||
* Fix console audits delete in [#9547](https://github.com/appwrite/appwrite/pull/9547)
|
||||
* Fix migrations in [#9633](https://github.com/appwrite/appwrite/pull/9633)
|
||||
* Ensure all 4xx errors in OAuth redirect lead to the failure URL in [#9679](https://github.com/appwrite/appwrite/pull/9679)
|
||||
* Treat 0 as unlimited for CPUs and memory in [#9638](https://github.com/appwrite/appwrite/pull/9638)
|
||||
* Add contextual dispatch logic to fix high CPU usage in [#9687](https://github.com/appwrite/appwrite/pull/9687)
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Merge 1.6.x into feat-custom-cf-hostnames in [8904](https://github.com/appwrite/appwrite/pull/8904)
|
||||
* Improve compression param checks in [8922](https://github.com/appwrite/appwrite/pull/8922)
|
||||
* upgrade utopia storage in [8930](https://github.com/appwrite/appwrite/pull/8930)
|
||||
* Feat migration in [8797](https://github.com/appwrite/appwrite/pull/8797)
|
||||
* feat fix web routes in [8962](https://github.com/appwrite/appwrite/pull/8962)
|
||||
* Fix no pool access in [9027](https://github.com/appwrite/appwrite/pull/9027)
|
||||
* feat: use environment variable to check rules format in [9039](https://github.com/appwrite/appwrite/pull/9039)
|
||||
* Update storage.php in [9037](https://github.com/appwrite/appwrite/pull/9037)
|
||||
* Upgrade db 0.53.200 in [9050](https://github.com/appwrite/appwrite/pull/9050)
|
||||
* Chore: upgrade utopia storage in [9066](https://github.com/appwrite/appwrite/pull/9066)
|
||||
* Update usage-dump payload in [9085](https://github.com/appwrite/appwrite/pull/9085)
|
||||
* GitHub Workflows security hardening in [3728](https://github.com/appwrite/appwrite/pull/3728)
|
||||
* Update add-oauth2-provider.md in [4313](https://github.com/appwrite/appwrite/pull/4313)
|
||||
* update readme-cn some doc in [5278](https://github.com/appwrite/appwrite/pull/5278)
|
||||
* Add accessibility features in [7042](https://github.com/appwrite/appwrite/pull/7042)
|
||||
* Add Appwrite Cloud to read me. in [5445](https://github.com/appwrite/appwrite/pull/5445)
|
||||
* Migration throw error in [9092](https://github.com/appwrite/appwrite/pull/9092)
|
||||
* Fix usage payload bug in [9097](https://github.com/appwrite/appwrite/pull/9097)
|
||||
* chore: replace occurrences of dbForConsole to dbForPlatform in [9096](https://github.com/appwrite/appwrite/pull/9096)
|
||||
* fix(realtime): decrement connectionCounter only if connection is known in [9055](https://github.com/appwrite/appwrite/pull/9055)
|
||||
* payload bug fix in [9098](https://github.com/appwrite/appwrite/pull/9098)
|
||||
* Fix usage payload bug in [9099](https://github.com/appwrite/appwrite/pull/9099)
|
||||
* Usage payload debug in [9101](https://github.com/appwrite/appwrite/pull/9101)
|
||||
* Usage payload debug in [9103](https://github.com/appwrite/appwrite/pull/9103)
|
||||
* Usage payload debug in [9104](https://github.com/appwrite/appwrite/pull/9104)
|
||||
* Feat: createFunction abuse labels in [9102](https://github.com/appwrite/appwrite/pull/9102)
|
||||
* Docs-create-document in [9105](https://github.com/appwrite/appwrite/pull/9105)
|
||||
* Docs: Create document and unknown attribute error messages. in [5427](https://github.com/appwrite/appwrite/pull/5427)
|
||||
* Fix: update project accessed at from router and schedulers in [9109](https://github.com/appwrite/appwrite/pull/9109)
|
||||
* chore: initial commit in [9111](https://github.com/appwrite/appwrite/pull/9111)
|
||||
* chore: optimise webhooks payload in [9115](https://github.com/appwrite/appwrite/pull/9115)
|
||||
* Revert "chore: initial commit" in [9117](https://github.com/appwrite/appwrite/pull/9117)
|
||||
* chore: fix attribute name in [9118](https://github.com/appwrite/appwrite/pull/9118)
|
||||
* Migrate to redis abuse in [9124](https://github.com/appwrite/appwrite/pull/9124)
|
||||
* Added webhooks usage stats in [9125](https://github.com/appwrite/appwrite/pull/9125)
|
||||
* chore remove abuse cleanup in [9137](https://github.com/appwrite/appwrite/pull/9137)
|
||||
* fix: remove abuse delete trigger in [9139](https://github.com/appwrite/appwrite/pull/9139)
|
||||
* Remove firebase OAuth API endpoints in [9144](https://github.com/appwrite/appwrite/pull/9144)
|
||||
* chore: release client sdks in [9112](https://github.com/appwrite/appwrite/pull/9112)
|
||||
* Update general.php in [9155](https://github.com/appwrite/appwrite/pull/9155)
|
||||
* feat(swoole): allow configuration override of available cpus in [9177](https://github.com/appwrite/appwrite/pull/9177)
|
||||
* Usage databases api read writes addition in [9142](https://github.com/appwrite/appwrite/pull/9142)
|
||||
* Fix dead connections in [9190](https://github.com/appwrite/appwrite/pull/9190)
|
||||
* Add hostname to audits in [9165](https://github.com/appwrite/appwrite/pull/9165)
|
||||
* chore: shifted authphone usage tracking to api calls in [9191](https://github.com/appwrite/appwrite/pull/9191)
|
||||
* Revert "Fix dead connections" in [9201](https://github.com/appwrite/appwrite/pull/9201)
|
||||
* Add assertEventually to messaging provider logs test in [9192](https://github.com/appwrite/appwrite/pull/9192)
|
||||
* feat project sms usage in [9198](https://github.com/appwrite/appwrite/pull/9198)
|
||||
* chore: add audit labels to project resources in [9056](https://github.com/appwrite/appwrite/pull/9056)
|
||||
* fix sms usage in [9207](https://github.com/appwrite/appwrite/pull/9207)
|
||||
* Update database in [9202](https://github.com/appwrite/appwrite/pull/9202)
|
||||
* Fix dead connections in [9213](https://github.com/appwrite/appwrite/pull/9213)
|
||||
* Revert "Fix dead connections" in [9214](https://github.com/appwrite/appwrite/pull/9214)
|
||||
* Add logs db init for consistency in [9163](https://github.com/appwrite/appwrite/pull/9163)
|
||||
* Split the collection definitions in [9153](https://github.com/appwrite/appwrite/pull/9153)
|
||||
* Log path with populated parameters in [9220](https://github.com/appwrite/appwrite/pull/9220)
|
||||
* Add missing scope on function template in [9208](https://github.com/appwrite/appwrite/pull/9208)
|
||||
* Add relatedCollection default in [9225](https://github.com/appwrite/appwrite/pull/9225)
|
||||
* fix: function usage in [9235](https://github.com/appwrite/appwrite/pull/9235)
|
||||
* feat: optimise events payloads in [9232](https://github.com/appwrite/appwrite/pull/9232)
|
||||
* Optimise webhook events in [9168](https://github.com/appwrite/appwrite/pull/9168)
|
||||
* fix: maintenance job missing type in [9238](https://github.com/appwrite/appwrite/pull/9238)
|
||||
* Update Fetch to 0.3.0 in [9245](https://github.com/appwrite/appwrite/pull/9245)
|
||||
* Fix maintenance job in [9247](https://github.com/appwrite/appwrite/pull/9247)
|
||||
* chore: add missing case for executions in [9248](https://github.com/appwrite/appwrite/pull/9248)
|
||||
* Add index dependency exception in [9226](https://github.com/appwrite/appwrite/pull/9226)
|
||||
* chore: fix benchmarking test when made from fork in [9233](https://github.com/appwrite/appwrite/pull/9233)
|
||||
* Update SDK Generator versions in [9188](https://github.com/appwrite/appwrite/pull/9188)
|
||||
* chore: skipped job instead of throwing error in [9250](https://github.com/appwrite/appwrite/pull/9250)
|
||||
* Implement new SDK Class on 1.6.x in [9237](https://github.com/appwrite/appwrite/pull/9237)
|
||||
* Delete collection before Appwrite's attributes in [9256](https://github.com/appwrite/appwrite/pull/9256)
|
||||
* Feat batch usage dump in [9255](https://github.com/appwrite/appwrite/pull/9255)
|
||||
* Fix cloud tests in [9261](https://github.com/appwrite/appwrite/pull/9261)
|
||||
* Usage: Databases reads writes in [9260](https://github.com/appwrite/appwrite/pull/9260)
|
||||
* Update: Latest sdk specs in [9274](https://github.com/appwrite/appwrite/pull/9274)
|
||||
* Revert "Feat batch usage dump" in [9276](https://github.com/appwrite/appwrite/pull/9276)
|
||||
* feat: add fast2SMS adapter in [9263](https://github.com/appwrite/appwrite/pull/9263)
|
||||
* Update Sdk Generator dependency in [9280](https://github.com/appwrite/appwrite/pull/9280)
|
||||
* Transformed at addition in [9281](https://github.com/appwrite/appwrite/pull/9281)
|
||||
* Docs: clarify update endpoints only work on draft messages in [9236](https://github.com/appwrite/appwrite/pull/9236)
|
||||
* Update sdk generator dependency in [9282](https://github.com/appwrite/appwrite/pull/9282)
|
||||
* Revert "Transformed at addition" in [9284](https://github.com/appwrite/appwrite/pull/9284)
|
||||
* replaced init for cloud link in [9285](https://github.com/appwrite/appwrite/pull/9285)
|
||||
* Add transformed at in [9289](https://github.com/appwrite/appwrite/pull/9289)
|
||||
* Make migrations use Dynamic keys for destination in [9291](https://github.com/appwrite/appwrite/pull/9291)
|
||||
* Make sessions limit tests assert eventually in [9298](https://github.com/appwrite/appwrite/pull/9298)
|
||||
* Chore update database in [9306](https://github.com/appwrite/appwrite/pull/9306)
|
||||
* feat: add AMQP queues in [9287](https://github.com/appwrite/appwrite/pull/9287)
|
||||
* fix(test): use assertEventually instead of while(true) in [9308](https://github.com/appwrite/appwrite/pull/9308)
|
||||
* fix(certificate worker): events are published without queue name in [9309](https://github.com/appwrite/appwrite/pull/9309)
|
||||
* chore: update utopia-php/queue to 0.8.1 in [9311](https://github.com/appwrite/appwrite/pull/9311)
|
||||
* chore: update utopia-php/queue to 0.8.2 in [9312](https://github.com/appwrite/appwrite/pull/9312)
|
||||
* fix(schedule-tasks): revert back to direct pool usage in [9313](https://github.com/appwrite/appwrite/pull/9313)
|
||||
* feat: custom app schemes in [9262](https://github.com/appwrite/appwrite/pull/9262)
|
||||
* Revert "feat: custom app schemes" in [9319](https://github.com/appwrite/appwrite/pull/9319)
|
||||
* Restore "feat: custom app schemes"" in [9320](https://github.com/appwrite/appwrite/pull/9320)
|
||||
* Revert "Restore "feat: custom app schemes""" in [9323](https://github.com/appwrite/appwrite/pull/9323)
|
||||
* chore: update dependencies in [9330](https://github.com/appwrite/appwrite/pull/9330)
|
||||
* Feat: logs DB in [9272](https://github.com/appwrite/appwrite/pull/9272)
|
||||
* Catch invalid index in [9329](https://github.com/appwrite/appwrite/pull/9329)
|
||||
* Fix: missing call for image transformations counting in [9342](https://github.com/appwrite/appwrite/pull/9342)
|
||||
* Fix drop abuse on shared table project delete in [9346](https://github.com/appwrite/appwrite/pull/9346)
|
||||
* Only run all table mode tests on db update in [9338](https://github.com/appwrite/appwrite/pull/9338)
|
||||
* Fix: missing periodic metric in [9350](https://github.com/appwrite/appwrite/pull/9350)
|
||||
* feat(builds): check if function is blocked before building in [9332](https://github.com/appwrite/appwrite/pull/9332)
|
||||
* feat: batch create audit logs in [9347](https://github.com/appwrite/appwrite/pull/9347)
|
||||
* Chore: Update migrations in [9355](https://github.com/appwrite/appwrite/pull/9355)
|
||||
* Fix: metric time was not being written to DB in [9354](https://github.com/appwrite/appwrite/pull/9354)
|
||||
* Fix patch index validation in [9356](https://github.com/appwrite/appwrite/pull/9356)
|
||||
* Fix image trnasformation metrics in [9370](https://github.com/appwrite/appwrite/pull/9370)
|
||||
* Use batch delete in worker in [9375](https://github.com/appwrite/appwrite/pull/9375)
|
||||
* Fix Model Platform is missing response key: store in [9361](https://github.com/appwrite/appwrite/pull/9361)
|
||||
* Feat key segmented usage in [9336](https://github.com/appwrite/appwrite/pull/9336)
|
||||
* Feat messaging metrics in [9353](https://github.com/appwrite/appwrite/pull/9353)
|
||||
* Fix removed audits for shared v2 in [9388](https://github.com/appwrite/appwrite/pull/9388)
|
||||
* chore: bump utopia-php/image to 0.8.0 in [9390](https://github.com/appwrite/appwrite/pull/9390)
|
||||
* Fix outdated CLI commands in documentation in [9122](https://github.com/appwrite/appwrite/pull/9122)
|
||||
* disable logs display in [9398](https://github.com/appwrite/appwrite/pull/9398)
|
||||
* Log batches per project in [9403](https://github.com/appwrite/appwrite/pull/9403)
|
||||
* Batch per project in [9410](https://github.com/appwrite/appwrite/pull/9410)
|
||||
* Fix: stats resources only queue projects accessed in last 3 hours in [9411](https://github.com/appwrite/appwrite/pull/9411)
|
||||
* Track options requests in [9397](https://github.com/appwrite/appwrite/pull/9397)
|
||||
* chore: bump docker-base in [9406](https://github.com/appwrite/appwrite/pull/9406)
|
||||
* refactor: migrate Realtime::send calls to queueForRealtime in [9325](https://github.com/appwrite/appwrite/pull/9325)
|
||||
* Revert "Fix: stats resources only queue projects accessed in last 3 hours" in [9424](https://github.com/appwrite/appwrite/pull/9424)
|
||||
* Remove usage and usage dump in favor of stats-usage and stats-usage-dump in [9339](https://github.com/appwrite/appwrite/pull/9339)
|
||||
* Fix: disable dual writing in [9429](https://github.com/appwrite/appwrite/pull/9429)
|
||||
* Disable transformedAt update for console users in [9425](https://github.com/appwrite/appwrite/pull/9425)
|
||||
* chore: add image transformation stats to usage endpoint in [9393](https://github.com/appwrite/appwrite/pull/9393)
|
||||
* chore: added timeout to deployment builds in tests in [9426](https://github.com/appwrite/appwrite/pull/9426)
|
||||
* fix: model for image transformations in usage project in [9442](https://github.com/appwrite/appwrite/pull/9442)
|
||||
* Feat: calculate database storage in stats-resources in [9443](https://github.com/appwrite/appwrite/pull/9443)
|
||||
* Activities batch writes in [9438](https://github.com/appwrite/appwrite/pull/9438)
|
||||
* chore: bump cache 0.12.x in [9412](https://github.com/appwrite/appwrite/pull/9412)
|
||||
* chore: queue console project for maintenance delete in [9479](https://github.com/appwrite/appwrite/pull/9479)
|
||||
* chore: added logsdb for deletes worker in [9462](https://github.com/appwrite/appwrite/pull/9462)
|
||||
* Feat: calculate and log time taken for each project in [9491](https://github.com/appwrite/appwrite/pull/9491)
|
||||
* chore: update initializing dbForLogs in [9494](https://github.com/appwrite/appwrite/pull/9494)
|
||||
* Feat bulk audit delete in [9487](https://github.com/appwrite/appwrite/pull/9487)
|
||||
* Prepare 1.6.2 release in [9499](https://github.com/appwrite/appwrite/pull/9499)
|
||||
* Regenerate specs in [9497](https://github.com/appwrite/appwrite/pull/9497)
|
||||
* Regenerate examples in [9498](https://github.com/appwrite/appwrite/pull/9498)
|
||||
* chore: bump sdk in [9414](https://github.com/appwrite/appwrite/pull/9414)
|
||||
* update queue to 0.9.* in [9505](https://github.com/appwrite/appwrite/pull/9505)
|
||||
* Feat improve delete queries in [9507](https://github.com/appwrite/appwrite/pull/9507)
|
||||
* Feat: Add rule attributes in [9508](https://github.com/appwrite/appwrite/pull/9508)
|
||||
* Sync main into 1.6.x in [9496](https://github.com/appwrite/appwrite/pull/9496)
|
||||
* Bump console to version 5.2.53 in [9495](https://github.com/appwrite/appwrite/pull/9495)
|
||||
* Prepare 1.6.1 release in [9294](https://github.com/appwrite/appwrite/pull/9294)
|
||||
* Improve delete ordering in [9512](https://github.com/appwrite/appwrite/pull/9512)
|
||||
* Cleanups in [9511](https://github.com/appwrite/appwrite/pull/9511)
|
||||
* Feat dynamic regions in [9408](https://github.com/appwrite/appwrite/pull/9408)
|
||||
* Feat env vars to system lib in [9515](https://github.com/appwrite/appwrite/pull/9515)
|
||||
* Feat: domains count in [9514](https://github.com/appwrite/appwrite/pull/9514)
|
||||
* Migration read from db in [9529](https://github.com/appwrite/appwrite/pull/9529)
|
||||
* feat: add pool telemetry in [9530](https://github.com/appwrite/appwrite/pull/9530)
|
||||
* Disable PDO persistence since we manage our own pool in [9526](https://github.com/appwrite/appwrite/pull/9526)
|
||||
* chore: set min operations to 1 for reads and writes in [9536](https://github.com/appwrite/appwrite/pull/9536)
|
||||
* Remove default region in [9430](https://github.com/appwrite/appwrite/pull/9430)
|
||||
* Use cursor pagination with bigger limit for maintenance project loop in [9546](https://github.com/appwrite/appwrite/pull/9546)
|
||||
* chore: stop tests on failure in [9525](https://github.com/appwrite/appwrite/pull/9525)
|
||||
* chore: only update total count for privileged users in [9554](https://github.com/appwrite/appwrite/pull/9554)
|
||||
* refactor: initialization of audit retention in [9563](https://github.com/appwrite/appwrite/pull/9563)
|
||||
* Delete worker queries fixes in [9523](https://github.com/appwrite/appwrite/pull/9523)
|
||||
* Bump database 0.62.x in [9568](https://github.com/appwrite/appwrite/pull/9568)
|
||||
* Fix: schedules region filtering in [9577](https://github.com/appwrite/appwrite/pull/9577)
|
||||
* Deletes worker fix selects for pagination in [9578](https://github.com/appwrite/appwrite/pull/9578)
|
||||
* Add $permissions for delete documents selects in [9579](https://github.com/appwrite/appwrite/pull/9579)
|
||||
* chore(audits): return queue pre-fetch results in [9533](https://github.com/appwrite/appwrite/pull/9533)
|
||||
* Revert "chore(audits): return queue pre-fetch results" in [9586](https://github.com/appwrite/appwrite/pull/9586)
|
||||
* Feat multi tenant insert in [9573](https://github.com/appwrite/appwrite/pull/9573)
|
||||
* Add order by for cursor in [9588](https://github.com/appwrite/appwrite/pull/9588)
|
||||
* Feat update fetch in [9592](https://github.com/appwrite/appwrite/pull/9592)
|
||||
* Fix tenant casting in [9598](https://github.com/appwrite/appwrite/pull/9598)
|
||||
* Feat update ws in [9602](https://github.com/appwrite/appwrite/pull/9602)
|
||||
* Update database in [9603](https://github.com/appwrite/appwrite/pull/9603)
|
||||
* Fix: image transformation cache in [9608](https://github.com/appwrite/appwrite/pull/9608)
|
||||
* Remove audit payload in [9610](https://github.com/appwrite/appwrite/pull/9610)
|
||||
* Sample rate from DSN in [9559](https://github.com/appwrite/appwrite/pull/9559)
|
||||
* Restrict role change for sole org owner in [9615](https://github.com/appwrite/appwrite/pull/9615)
|
||||
* chore: update php image to 0.8.1 in [9616](https://github.com/appwrite/appwrite/pull/9616)
|
||||
* feat: refactor executor setup in [9420](https://github.com/appwrite/appwrite/pull/9420)
|
||||
* chore: update gitpod.yml config in [9561](https://github.com/appwrite/appwrite/pull/9561)
|
||||
* chore: update dependencies in [9625](https://github.com/appwrite/appwrite/pull/9625)
|
||||
* Update migrations lib in [9628](https://github.com/appwrite/appwrite/pull/9628)
|
||||
* feat: cache telemetry in [9624](https://github.com/appwrite/appwrite/pull/9624)
|
||||
* Bump console to version 5.2.56 in [9631](https://github.com/appwrite/appwrite/pull/9631)
|
||||
* Multi region support in [8667](https://github.com/appwrite/appwrite/pull/8667)
|
||||
* Revert "Multi region support" in [9632](https://github.com/appwrite/appwrite/pull/9632)
|
||||
* Revert "Revert "Multi region support"" in [9636](https://github.com/appwrite/appwrite/pull/9636)
|
||||
* Fix tasks in [9644](https://github.com/appwrite/appwrite/pull/9644)
|
||||
* chore: updated the migration version to 8.6 in [9646](https://github.com/appwrite/appwrite/pull/9646)
|
||||
* Fix: merge the working of StatsUsage and StatsUsageDump in [9585](https://github.com/appwrite/appwrite/pull/9585)
|
||||
* Update database in [9643](https://github.com/appwrite/appwrite/pull/9643)
|
||||
* chore: fix error logging for CLI tasks in [9651](https://github.com/appwrite/appwrite/pull/9651)
|
||||
* fix: usage test assertion in [9653](https://github.com/appwrite/appwrite/pull/9653)
|
||||
* Fix keys in [9656](https://github.com/appwrite/appwrite/pull/9656)
|
||||
* Feat: multi tenant dual writing in [9583](https://github.com/appwrite/appwrite/pull/9583)
|
||||
* Fix/throwing 400 for null order attributes in [9657](https://github.com/appwrite/appwrite/pull/9657)
|
||||
* feat: sdk group attribute in [9596](https://github.com/appwrite/appwrite/pull/9596)
|
||||
* Add configurable function and build size in [9648](https://github.com/appwrite/appwrite/pull/9648)
|
||||
* feat: update API endpoint in the code examples in [8933](https://github.com/appwrite/appwrite/pull/8933)
|
||||
* chore: abstract token secret hiding to response model in [9574](https://github.com/appwrite/appwrite/pull/9574)
|
||||
* chore: update sdks in [9655](https://github.com/appwrite/appwrite/pull/9655)
|
||||
* feat: allow non-critical events to ignore exceptions when enqueuing the event in [9680](https://github.com/appwrite/appwrite/pull/9680)
|
||||
* Revert "Add configurable function and build size" in [9681](https://github.com/appwrite/appwrite/pull/9681)
|
||||
* core: introduce endpoint.docs in specs in [9685](https://github.com/appwrite/appwrite/pull/9685)
|
||||
* fix: remove content-type header from get request specs in [9666](https://github.com/appwrite/appwrite/pull/9666)
|
||||
* chore: update flutter sdk in [9691](https://github.com/appwrite/appwrite/pull/9691)
|
||||
* Merge 1.6.x into feat-custom-cf-hostnames in [#8904](https://github.com/appwrite/appwrite/pull/8904)
|
||||
* Improve compression param checks in [#8922](https://github.com/appwrite/appwrite/pull/8922)
|
||||
* upgrade utopia storage in [#8930](https://github.com/appwrite/appwrite/pull/8930)
|
||||
* Feat migration in [#8797](https://github.com/appwrite/appwrite/pull/8797)
|
||||
* feat fix web routes in [#8962](https://github.com/appwrite/appwrite/pull/8962)
|
||||
* Fix no pool access in [#9027](https://github.com/appwrite/appwrite/pull/9027)
|
||||
* feat: use environment variable to check rules format in [#9039](https://github.com/appwrite/appwrite/pull/9039)
|
||||
* Update storage.php in [#9037](https://github.com/appwrite/appwrite/pull/9037)
|
||||
* Upgrade db 0.53.200 in [#9050](https://github.com/appwrite/appwrite/pull/9050)
|
||||
* Chore: upgrade utopia storage in [#9066](https://github.com/appwrite/appwrite/pull/9066)
|
||||
* Update usage-dump payload in [#9085](https://github.com/appwrite/appwrite/pull/9085)
|
||||
* GitHub Workflows security hardening in [#3728](https://github.com/appwrite/appwrite/pull/3728)
|
||||
* Update add-oauth2-provider.md in [#4313](https://github.com/appwrite/appwrite/pull/4313)
|
||||
* update readme-cn some doc in [#5278](https://github.com/appwrite/appwrite/pull/5278)
|
||||
* Add accessibility features in [#7042](https://github.com/appwrite/appwrite/pull/7042)
|
||||
* Add Appwrite Cloud to read me. in [#5445](https://github.com/appwrite/appwrite/pull/5445)
|
||||
* Migration throw error in [#9092](https://github.com/appwrite/appwrite/pull/9092)
|
||||
* Fix usage payload bug in [#9097](https://github.com/appwrite/appwrite/pull/9097)
|
||||
* chore: replace occurrences of dbForConsole to dbForPlatform in [#9096](https://github.com/appwrite/appwrite/pull/9096)
|
||||
* fix(realtime): decrement connectionCounter only if connection is known in [#9055](https://github.com/appwrite/appwrite/pull/9055)
|
||||
* payload bug fix in [#9098](https://github.com/appwrite/appwrite/pull/9098)
|
||||
* Fix usage payload bug in [#9099](https://github.com/appwrite/appwrite/pull/9099)
|
||||
* Usage payload debug in [#9101](https://github.com/appwrite/appwrite/pull/9101)
|
||||
* Usage payload debug in [#9103](https://github.com/appwrite/appwrite/pull/9103)
|
||||
* Usage payload debug in [#9104](https://github.com/appwrite/appwrite/pull/9104)
|
||||
* Feat: createFunction abuse labels in [#9102](https://github.com/appwrite/appwrite/pull/9102)
|
||||
* Docs-create-document in [#9105](https://github.com/appwrite/appwrite/pull/9105)
|
||||
* Docs: Create document and unknown attribute error messages. in [#5427](https://github.com/appwrite/appwrite/pull/5427)
|
||||
* Fix: update project accessed at from router and schedulers in [#9109](https://github.com/appwrite/appwrite/pull/9109)
|
||||
* chore: initial commit in [#9111](https://github.com/appwrite/appwrite/pull/9111)
|
||||
* chore: optimise webhooks payload in [#9115](https://github.com/appwrite/appwrite/pull/9115)
|
||||
* Revert "chore: initial commit" in [#9117](https://github.com/appwrite/appwrite/pull/9117)
|
||||
* chore: fix attribute name in [#9118](https://github.com/appwrite/appwrite/pull/9118)
|
||||
* Migrate to redis abuse in [#9124](https://github.com/appwrite/appwrite/pull/9124)
|
||||
* Added webhooks usage stats in [#9125](https://github.com/appwrite/appwrite/pull/9125)
|
||||
* chore remove abuse cleanup in [#9137](https://github.com/appwrite/appwrite/pull/9137)
|
||||
* fix: remove abuse delete trigger in [#9139](https://github.com/appwrite/appwrite/pull/9139)
|
||||
* Remove firebase OAuth API endpoints in [#9144](https://github.com/appwrite/appwrite/pull/9144)
|
||||
* chore: release client sdks in [#9112](https://github.com/appwrite/appwrite/pull/9112)
|
||||
* Update general.php in [#9155](https://github.com/appwrite/appwrite/pull/9155)
|
||||
* feat(swoole): allow configuration override of available cpus in [#9177](https://github.com/appwrite/appwrite/pull/9177)
|
||||
* Usage databases api read writes addition in [#9142](https://github.com/appwrite/appwrite/pull/9142)
|
||||
* Fix dead connections in [#9190](https://github.com/appwrite/appwrite/pull/9190)
|
||||
* Add hostname to audits in [#9165](https://github.com/appwrite/appwrite/pull/9165)
|
||||
* chore: shifted authphone usage tracking to api calls in [#9191](https://github.com/appwrite/appwrite/pull/9191)
|
||||
* Revert "Fix dead connections" in [#9201](https://github.com/appwrite/appwrite/pull/9201)
|
||||
* Add assertEventually to messaging provider logs test in [#9192](https://github.com/appwrite/appwrite/pull/9192)
|
||||
* feat project sms usage in [#9198](https://github.com/appwrite/appwrite/pull/9198)
|
||||
* chore: add audit labels to project resources in [#9056](https://github.com/appwrite/appwrite/pull/9056)
|
||||
* fix sms usage in [#9207](https://github.com/appwrite/appwrite/pull/9207)
|
||||
* Update database in [#9202](https://github.com/appwrite/appwrite/pull/9202)
|
||||
* Fix dead connections in [#9213](https://github.com/appwrite/appwrite/pull/9213)
|
||||
* Revert "Fix dead connections" in [#9214](https://github.com/appwrite/appwrite/pull/9214)
|
||||
* Add logs db init for consistency in [#9163](https://github.com/appwrite/appwrite/pull/9163)
|
||||
* Split the collection definitions in [#9153](https://github.com/appwrite/appwrite/pull/9153)
|
||||
* Log path with populated parameters in [#9220](https://github.com/appwrite/appwrite/pull/9220)
|
||||
* Add missing scope on function template in [#9208](https://github.com/appwrite/appwrite/pull/9208)
|
||||
* Add relatedCollection default in [#9225](https://github.com/appwrite/appwrite/pull/9225)
|
||||
* fix: function usage in [#9235](https://github.com/appwrite/appwrite/pull/9235)
|
||||
* feat: optimise events payloads in [#9232](https://github.com/appwrite/appwrite/pull/9232)
|
||||
* Optimise webhook events in [#9168](https://github.com/appwrite/appwrite/pull/9168)
|
||||
* fix: maintenance job missing type in [#9238](https://github.com/appwrite/appwrite/pull/9238)
|
||||
* Update Fetch to 0.3.0 in [#9245](https://github.com/appwrite/appwrite/pull/9245)
|
||||
* Fix maintenance job in [#9247](https://github.com/appwrite/appwrite/pull/9247)
|
||||
* chore: add missing case for executions in [#9248](https://github.com/appwrite/appwrite/pull/9248)
|
||||
* Add index dependency exception in [#9226](https://github.com/appwrite/appwrite/pull/9226)
|
||||
* chore: fix benchmarking test when made from fork in [#9233](https://github.com/appwrite/appwrite/pull/9233)
|
||||
* Update SDK Generator versions in [#9188](https://github.com/appwrite/appwrite/pull/9188)
|
||||
* chore: skipped job instead of throwing error in [#9250](https://github.com/appwrite/appwrite/pull/9250)
|
||||
* Implement new SDK Class on 1.6.x in [#9237](https://github.com/appwrite/appwrite/pull/9237)
|
||||
* Delete collection before Appwrite's attributes in [#9256](https://github.com/appwrite/appwrite/pull/9256)
|
||||
* Feat batch usage dump in [#9255](https://github.com/appwrite/appwrite/pull/9255)
|
||||
* Fix cloud tests in [#9261](https://github.com/appwrite/appwrite/pull/9261)
|
||||
* Usage: Databases reads writes in [#9260](https://github.com/appwrite/appwrite/pull/9260)
|
||||
* Update: Latest sdk specs in [#9274](https://github.com/appwrite/appwrite/pull/9274)
|
||||
* Revert "Feat batch usage dump" in [#9276](https://github.com/appwrite/appwrite/pull/9276)
|
||||
* feat: add fast2SMS adapter in [#9263](https://github.com/appwrite/appwrite/pull/9263)
|
||||
* Update Sdk Generator dependency in [#9280](https://github.com/appwrite/appwrite/pull/9280)
|
||||
* Transformed at addition in [#9281](https://github.com/appwrite/appwrite/pull/9281)
|
||||
* Docs: clarify update endpoints only work on draft messages in [#9236](https://github.com/appwrite/appwrite/pull/9236)
|
||||
* Update sdk generator dependency in [#9282](https://github.com/appwrite/appwrite/pull/9282)
|
||||
* Revert "Transformed at addition" in [#9284](https://github.com/appwrite/appwrite/pull/9284)
|
||||
* replaced init for cloud link in [#9285](https://github.com/appwrite/appwrite/pull/9285)
|
||||
* Add transformed at in [#9289](https://github.com/appwrite/appwrite/pull/9289)
|
||||
* Make migrations use Dynamic keys for destination in [#9291](https://github.com/appwrite/appwrite/pull/9291)
|
||||
* Make sessions limit tests assert eventually in [#9298](https://github.com/appwrite/appwrite/pull/9298)
|
||||
* Chore update database in [#9306](https://github.com/appwrite/appwrite/pull/9306)
|
||||
* feat: add AMQP queues in [#9287](https://github.com/appwrite/appwrite/pull/9287)
|
||||
* fix(test): use assertEventually instead of while(true) in [#9308](https://github.com/appwrite/appwrite/pull/9308)
|
||||
* fix(certificate worker): events are published without queue name in [#9309](https://github.com/appwrite/appwrite/pull/9309)
|
||||
* chore: update utopia-php/queue to 0.8.1 in [#9311](https://github.com/appwrite/appwrite/pull/9311)
|
||||
* chore: update utopia-php/queue to 0.8.2 in [#9312](https://github.com/appwrite/appwrite/pull/9312)
|
||||
* fix(schedule-tasks): revert back to direct pool usage in [#9313](https://github.com/appwrite/appwrite/pull/9313)
|
||||
* feat: custom app schemes in [#9262](https://github.com/appwrite/appwrite/pull/9262)
|
||||
* Revert "feat: custom app schemes" in [#9319](https://github.com/appwrite/appwrite/pull/9319)
|
||||
* Restore "feat: custom app schemes"" in [#9320](https://github.com/appwrite/appwrite/pull/9320)
|
||||
* Revert "Restore "feat: custom app schemes""" in [#9323](https://github.com/appwrite/appwrite/pull/9323)
|
||||
* chore: update dependencies in [#9330](https://github.com/appwrite/appwrite/pull/9330)
|
||||
* Feat: logs DB in [#9272](https://github.com/appwrite/appwrite/pull/9272)
|
||||
* Catch invalid index in [#9329](https://github.com/appwrite/appwrite/pull/9329)
|
||||
* Fix: missing call for image transformations counting in [#9342](https://github.com/appwrite/appwrite/pull/9342)
|
||||
* Fix drop abuse on shared table project delete in [#9346](https://github.com/appwrite/appwrite/pull/9346)
|
||||
* Only run all table mode tests on db update in [#9338](https://github.com/appwrite/appwrite/pull/9338)
|
||||
* Fix: missing periodic metric in [#9350](https://github.com/appwrite/appwrite/pull/9350)
|
||||
* feat(builds): check if function is blocked before building in [#9332](https://github.com/appwrite/appwrite/pull/9332)
|
||||
* feat: batch create audit logs in [#9347](https://github.com/appwrite/appwrite/pull/9347)
|
||||
* Chore: Update migrations in [#9355](https://github.com/appwrite/appwrite/pull/9355)
|
||||
* Fix: metric time was not being written to DB in [#9354](https://github.com/appwrite/appwrite/pull/9354)
|
||||
* Fix patch index validation in [#9356](https://github.com/appwrite/appwrite/pull/9356)
|
||||
* Fix image trnasformation metrics in [#9370](https://github.com/appwrite/appwrite/pull/9370)
|
||||
* Use batch delete in worker in [#9375](https://github.com/appwrite/appwrite/pull/9375)
|
||||
* Fix Model Platform is missing response key: store in [#9361](https://github.com/appwrite/appwrite/pull/9361)
|
||||
* Feat key segmented usage in [#9336](https://github.com/appwrite/appwrite/pull/9336)
|
||||
* Feat messaging metrics in [#9353](https://github.com/appwrite/appwrite/pull/9353)
|
||||
* Fix removed audits for shared v2 in [#9388](https://github.com/appwrite/appwrite/pull/9388)
|
||||
* chore: bump utopia-php/image to 0.8.0 in [#9390](https://github.com/appwrite/appwrite/pull/9390)
|
||||
* Fix outdated CLI commands in documentation in [#9122](https://github.com/appwrite/appwrite/pull/9122)
|
||||
* disable logs display in [#9398](https://github.com/appwrite/appwrite/pull/9398)
|
||||
* Log batches per project in [#9403](https://github.com/appwrite/appwrite/pull/9403)
|
||||
* Batch per project in [#9410](https://github.com/appwrite/appwrite/pull/9410)
|
||||
* Fix: stats resources only queue projects accessed in last 3 hours in [#9411](https://github.com/appwrite/appwrite/pull/9411)
|
||||
* Track options requests in [#9397](https://github.com/appwrite/appwrite/pull/9397)
|
||||
* chore: bump docker-base in [#9406](https://github.com/appwrite/appwrite/pull/9406)
|
||||
* refactor: migrate Realtime::send calls to queueForRealtime in [#9325](https://github.com/appwrite/appwrite/pull/9325)
|
||||
* Revert "Fix: stats resources only queue projects accessed in last 3 hours" in [#9424](https://github.com/appwrite/appwrite/pull/9424)
|
||||
* Remove usage and usage dump in favor of stats-usage and stats-usage-dump in [#9339](https://github.com/appwrite/appwrite/pull/9339)
|
||||
* Fix: disable dual writing in [#9429](https://github.com/appwrite/appwrite/pull/9429)
|
||||
* Disable transformedAt update for console users in [#9425](https://github.com/appwrite/appwrite/pull/9425)
|
||||
* chore: add image transformation stats to usage endpoint in [#9393](https://github.com/appwrite/appwrite/pull/9393)
|
||||
* chore: added timeout to deployment builds in tests in [#9426](https://github.com/appwrite/appwrite/pull/9426)
|
||||
* fix: model for image transformations in usage project in [#9442](https://github.com/appwrite/appwrite/pull/9442)
|
||||
* Feat: calculate database storage in stats-resources in [#9443](https://github.com/appwrite/appwrite/pull/9443)
|
||||
* Activities batch writes in [#9438](https://github.com/appwrite/appwrite/pull/9438)
|
||||
* chore: bump cache 0.12.x in [#9412](https://github.com/appwrite/appwrite/pull/9412)
|
||||
* chore: queue console project for maintenance delete in [#9479](https://github.com/appwrite/appwrite/pull/9479)
|
||||
* chore: added logsdb for deletes worker in [#9462](https://github.com/appwrite/appwrite/pull/9462)
|
||||
* Feat: calculate and log time taken for each project in [#9491](https://github.com/appwrite/appwrite/pull/9491)
|
||||
* chore: update initializing dbForLogs in [#9494](https://github.com/appwrite/appwrite/pull/9494)
|
||||
* Feat bulk audit delete in [#9487](https://github.com/appwrite/appwrite/pull/9487)
|
||||
* Prepare 1.6.2 release in [#9499](https://github.com/appwrite/appwrite/pull/9499)
|
||||
* Regenerate specs in [#9497](https://github.com/appwrite/appwrite/pull/9497)
|
||||
* Regenerate examples in [#9498](https://github.com/appwrite/appwrite/pull/9498)
|
||||
* chore: bump sdk in [#9414](https://github.com/appwrite/appwrite/pull/9414)
|
||||
* update queue to 0.9.* in [#9505](https://github.com/appwrite/appwrite/pull/9505)
|
||||
* Feat improve delete queries in [#9507](https://github.com/appwrite/appwrite/pull/9507)
|
||||
* Feat: Add rule attributes in [#9508](https://github.com/appwrite/appwrite/pull/9508)
|
||||
* Sync main into 1.6.x in [#9496](https://github.com/appwrite/appwrite/pull/9496)
|
||||
* Bump console to version 5.2.53 in [#9495](https://github.com/appwrite/appwrite/pull/9495)
|
||||
* Prepare 1.6.1 release in [#9294](https://github.com/appwrite/appwrite/pull/9294)
|
||||
* Improve delete ordering in [#9512](https://github.com/appwrite/appwrite/pull/9512)
|
||||
* Cleanups in [#9511](https://github.com/appwrite/appwrite/pull/9511)
|
||||
* Feat dynamic regions in [#9408](https://github.com/appwrite/appwrite/pull/9408)
|
||||
* Feat env vars to system lib in [#9515](https://github.com/appwrite/appwrite/pull/9515)
|
||||
* Feat: domains count in [#9514](https://github.com/appwrite/appwrite/pull/9514)
|
||||
* Migration read from db in [#9529](https://github.com/appwrite/appwrite/pull/9529)
|
||||
* feat: add pool telemetry in [#9530](https://github.com/appwrite/appwrite/pull/9530)
|
||||
* Disable PDO persistence since we manage our own pool in [#9526](https://github.com/appwrite/appwrite/pull/9526)
|
||||
* chore: set min operations to 1 for reads and writes in [#9536](https://github.com/appwrite/appwrite/pull/9536)
|
||||
* Remove default region in [#9430](https://github.com/appwrite/appwrite/pull/9430)
|
||||
* Use cursor pagination with bigger limit for maintenance project loop in [#9546](https://github.com/appwrite/appwrite/pull/9546)
|
||||
* chore: stop tests on failure in [#9525](https://github.com/appwrite/appwrite/pull/9525)
|
||||
* chore: only update total count for privileged users in [#9554](https://github.com/appwrite/appwrite/pull/9554)
|
||||
* refactor: initialization of audit retention in [#9563](https://github.com/appwrite/appwrite/pull/9563)
|
||||
* Delete worker queries fixes in [#9523](https://github.com/appwrite/appwrite/pull/9523)
|
||||
* Bump database 0.62.x in [#9568](https://github.com/appwrite/appwrite/pull/9568)
|
||||
* Fix: schedules region filtering in [#9577](https://github.com/appwrite/appwrite/pull/9577)
|
||||
* Deletes worker fix selects for pagination in [#9578](https://github.com/appwrite/appwrite/pull/9578)
|
||||
* Add $permissions for delete documents selects in [#9579](https://github.com/appwrite/appwrite/pull/9579)
|
||||
* chore(audits): return queue pre-fetch results in [#9533](https://github.com/appwrite/appwrite/pull/9533)
|
||||
* Revert "chore(audits): return queue pre-fetch results" in [#9586](https://github.com/appwrite/appwrite/pull/9586)
|
||||
* Feat multi tenant insert in [#9573](https://github.com/appwrite/appwrite/pull/9573)
|
||||
* Add order by for cursor in [#9588](https://github.com/appwrite/appwrite/pull/9588)
|
||||
* Feat update fetch in [#9592](https://github.com/appwrite/appwrite/pull/9592)
|
||||
* Fix tenant casting in [#9598](https://github.com/appwrite/appwrite/pull/9598)
|
||||
* Feat update ws in [#9602](https://github.com/appwrite/appwrite/pull/9602)
|
||||
* Update database in [#9603](https://github.com/appwrite/appwrite/pull/9603)
|
||||
* Fix: image transformation cache in [#9608](https://github.com/appwrite/appwrite/pull/9608)
|
||||
* Remove audit payload in [#9610](https://github.com/appwrite/appwrite/pull/9610)
|
||||
* Sample rate from DSN in [#9559](https://github.com/appwrite/appwrite/pull/9559)
|
||||
* Restrict role change for sole org owner in [#9615](https://github.com/appwrite/appwrite/pull/9615)
|
||||
* chore: update php image to 0.8.1 in [#9616](https://github.com/appwrite/appwrite/pull/9616)
|
||||
* feat: refactor executor setup in [#9420](https://github.com/appwrite/appwrite/pull/9420)
|
||||
* chore: update gitpod.yml config in [#9561](https://github.com/appwrite/appwrite/pull/9561)
|
||||
* chore: update dependencies in [#9625](https://github.com/appwrite/appwrite/pull/9625)
|
||||
* Update migrations lib in [#9628](https://github.com/appwrite/appwrite/pull/9628)
|
||||
* feat: cache telemetry in [#9624](https://github.com/appwrite/appwrite/pull/9624)
|
||||
* Bump console to version 5.2.56 in [#9631](https://github.com/appwrite/appwrite/pull/9631)
|
||||
* Multi region support in [#8667](https://github.com/appwrite/appwrite/pull/8667)
|
||||
* Revert "Multi region support" in [#9632](https://github.com/appwrite/appwrite/pull/9632)
|
||||
* Revert "Revert "Multi region support"" in [#9636](https://github.com/appwrite/appwrite/pull/9636)
|
||||
* Fix tasks in [#9644](https://github.com/appwrite/appwrite/pull/9644)
|
||||
* chore: updated the migration version to 8.6 in [#9646](https://github.com/appwrite/appwrite/pull/9646)
|
||||
* Fix: merge the working of StatsUsage and StatsUsageDump in [#9585](https://github.com/appwrite/appwrite/pull/9585)
|
||||
* Update database in [#9643](https://github.com/appwrite/appwrite/pull/9643)
|
||||
* chore: fix error logging for CLI tasks in [#9651](https://github.com/appwrite/appwrite/pull/9651)
|
||||
* fix: usage test assertion in [#9653](https://github.com/appwrite/appwrite/pull/9653)
|
||||
* Fix keys in [#9656](https://github.com/appwrite/appwrite/pull/9656)
|
||||
* Feat: multi tenant dual writing in [#9583](https://github.com/appwrite/appwrite/pull/9583)
|
||||
* Fix/throwing 400 for null order attributes in [#9657](https://github.com/appwrite/appwrite/pull/9657)
|
||||
* feat: sdk group attribute in [#9596](https://github.com/appwrite/appwrite/pull/9596)
|
||||
* Add configurable function and build size in [#9648](https://github.com/appwrite/appwrite/pull/9648)
|
||||
* feat: update API endpoint in the code examples in [#8933](https://github.com/appwrite/appwrite/pull/8933)
|
||||
* chore: abstract token secret hiding to response model in [#9574](https://github.com/appwrite/appwrite/pull/9574)
|
||||
* chore: update sdks in [#9655](https://github.com/appwrite/appwrite/pull/9655)
|
||||
* feat: allow non-critical events to ignore exceptions when enqueuing the event in [#9680](https://github.com/appwrite/appwrite/pull/9680)
|
||||
* Revert "Add configurable function and build size" in [#9681](https://github.com/appwrite/appwrite/pull/9681)
|
||||
* core: introduce endpoint.docs in specs in [#9685](https://github.com/appwrite/appwrite/pull/9685)
|
||||
* fix: remove content-type header from get request specs in [#9666](https://github.com/appwrite/appwrite/pull/9666)
|
||||
* chore: update flutter sdk in [#9691](https://github.com/appwrite/appwrite/pull/9691)
|
||||
|
||||
# Version 1.6.1
|
||||
|
||||
|
||||
+26
-13
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
|
||||
--no-plugins --no-scripts --prefer-dist \
|
||||
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
|
||||
|
||||
FROM appwrite/base:0.10.4 AS final
|
||||
FROM appwrite/base:0.10.6 AS base
|
||||
|
||||
LABEL maintainer="team@appwrite.io"
|
||||
|
||||
@@ -24,11 +24,9 @@ ENV _APP_VERSION=$VERSION \
|
||||
_APP_HOME=https://appwrite.io
|
||||
|
||||
RUN \
|
||||
if [ "$DEBUG" == "true" ]; then \
|
||||
if [ "$DEBUG" == "true" ]; then \
|
||||
apk add boost boost-dev; \
|
||||
fi
|
||||
|
||||
RUN apk add libwebp
|
||||
fi
|
||||
|
||||
WORKDIR /usr/src/code
|
||||
|
||||
@@ -38,9 +36,7 @@ COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor
|
||||
COPY ./app /usr/src/code/app
|
||||
COPY ./public /usr/src/code/public
|
||||
COPY ./bin /usr/local/bin
|
||||
COPY ./docs /usr/src/code/docs
|
||||
COPY ./src /usr/src/code/src
|
||||
COPY ./dev /usr/src/code/dev
|
||||
|
||||
# Set Volumes
|
||||
RUN mkdir -p /storage/uploads && \
|
||||
@@ -61,6 +57,7 @@ RUN mkdir -p /storage/uploads && \
|
||||
# Executables
|
||||
RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/install && \
|
||||
chmod +x /usr/local/bin/interval && \
|
||||
chmod +x /usr/local/bin/maintenance && \
|
||||
chmod +x /usr/local/bin/migrate && \
|
||||
chmod +x /usr/local/bin/realtime && \
|
||||
@@ -92,14 +89,30 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/stats-resources && \
|
||||
chmod +x /usr/local/bin/worker-stats-resources
|
||||
|
||||
# Letsencrypt Permissions
|
||||
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
||||
|
||||
# Enable Extensions
|
||||
RUN if [ "$DEBUG" = "true" ]; then cp /usr/src/code/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini; fi
|
||||
RUN if [ "$DEBUG" = "true" ]; then mkdir -p /tmp/xdebug; fi
|
||||
RUN if [ "$DEBUG" = "false" ]; then rm -rf /usr/src/code/dev; fi
|
||||
RUN if [ "$DEBUG" = "false" ]; then rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20230831/xdebug.so; fi
|
||||
FROM base AS production
|
||||
|
||||
RUN rm -rf /usr/src/code/app/config/specs && \
|
||||
rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20240924/xdebug.so && \
|
||||
find /usr -name '*.a' -delete 2>/dev/null || true && \
|
||||
find /usr -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true && \
|
||||
find /usr -name '*.pyc' -delete 2>/dev/null || true
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD [ "php", "app/http.php" ]
|
||||
|
||||
FROM base AS development
|
||||
|
||||
COPY ./docs /usr/src/code/docs
|
||||
COPY ./dev /usr/src/code/dev
|
||||
|
||||
RUN if [ "$DEBUG" = "true" ]; then \
|
||||
cp /usr/src/code/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini && \
|
||||
mkdir -p /tmp/xdebug && \
|
||||
apk add --update --no-cache openssh-client github-cli; \
|
||||
fi
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
|
||||
+23
-26
@@ -2,7 +2,7 @@
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite banner with logo and slogan build like a team of hundreds""></a>
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite banner, with logo and text saying "The Developer's Cloud""></a>
|
||||
<br />
|
||||
<br />
|
||||
<b>适用于[Flutter/Vue/Angular/React/iOS/Android/* 等等平台 *]的完整后端服务</b>
|
||||
@@ -36,7 +36,6 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
|
||||
|
||||
内容:
|
||||
|
||||
|
||||
- [开始](#开始)
|
||||
- [安装](#安装)
|
||||
- [Unix](#unix)
|
||||
@@ -57,7 +56,8 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
|
||||
- [版权说明](#版权说明)
|
||||
|
||||
## 开始
|
||||
要轻松开始使用Appwrite,您可以[**免费注册Appwrite Cloud**](https://cloud.appwrite.io/)。在Appwrite Cloud公开测试版期间,您可以完全免费使用Appwrite,而且我们不会收集您的信用卡信息。
|
||||
|
||||
要轻松开始使用 Appwrite,您可以[**免费注册 Appwrite Cloud**](https://cloud.appwrite.io/)。在 Appwrite Cloud 公开测试版期间,您可以完全免费使用 Appwrite,而且我们不会收集您的信用卡信息。
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -119,22 +119,16 @@ docker run -it --rm `
|
||||
<br /><sub><b>DigitalOcean</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://gitpod.io/#https://github.com/appwrite/integration-for-gitpod">
|
||||
<img width="50" height="39" src="public/images/integrations/gitpod-logo.svg" alt="Gitpod Logo" />
|
||||
<br /><sub><b>Gitpod</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://www.linode.com/marketplace/apps/appwrite/appwrite/">
|
||||
<img width="50" height="39" src="public/images/integrations/akamai-logo.svg" alt="Akamai Logo" />
|
||||
<br /><sub><b>Akamai Compute</b></sub></a>
|
||||
<br /><sub><b>Akamai Compute</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://aws.amazon.com/marketplace/pp/prodview-2hiaeo2px4md6">
|
||||
<img width="50" height="39" src="public/images/integrations/aws-logo.svg" alt="AWS Logo" />
|
||||
<br /><sub><b>AWS Marketplace</b></sub></a>
|
||||
<br /><sub><b>AWS Marketplace</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -188,25 +182,28 @@ docker run -it --rm `
|
||||
以下是当前支持的平台和语言列表。如果您想帮助我们为您选择的平台添加支持,您可以访问我们的 [SDK 生成器](https://github.com/appwrite/sdk-generator) 项目并查看我们的 [贡献指南](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md)。
|
||||
|
||||
#### 客户端
|
||||
* :white_check_mark: [Web](https://github.com/appwrite/sdk-for-web) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Flutter](https://github.com/appwrite/sdk-for-flutter) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Android](https://github.com/appwrite/sdk-for-android) (由 Appwrite 团队维护)
|
||||
|
||||
- :white_check_mark: [Web](https://github.com/appwrite/sdk-for-web) (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Flutter](https://github.com/appwrite/sdk-for-flutter) (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Android](https://github.com/appwrite/sdk-for-android) (由 Appwrite 团队维护)
|
||||
|
||||
#### 服务器
|
||||
* :white_check_mark: [NodeJS](https://github.com/appwrite/sdk-for-node) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [PHP](https://github.com/appwrite/sdk-for-php) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Dart](https://github.com/appwrite/sdk-for-dart) - (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Deno](https://github.com/appwrite/sdk-for-deno) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Ruby](https://github.com/appwrite/sdk-for-ruby) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Python](https://github.com/appwrite/sdk-for-python) (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Kotlin](https://github.com/appwrite/sdk-for-kotlin) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
|
||||
* :white_check_mark: [.NET](https://github.com/appwrite/sdk-for-dotnet) - **公测** (由 Appwrite 团队维护)
|
||||
|
||||
- :white_check_mark: [NodeJS](https://github.com/appwrite/sdk-for-node) (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [PHP](https://github.com/appwrite/sdk-for-php) (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Dart](https://github.com/appwrite/sdk-for-dart) - (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Deno](https://github.com/appwrite/sdk-for-deno) - **公测** (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Ruby](https://github.com/appwrite/sdk-for-ruby) (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Python](https://github.com/appwrite/sdk-for-python) (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Kotlin](https://github.com/appwrite/sdk-for-kotlin) - **公测** (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
|
||||
- :white_check_mark: [.NET](https://github.com/appwrite/sdk-for-dotnet) - **公测** (由 Appwrite 团队维护)
|
||||
|
||||
#### 开发者社区
|
||||
* :white_check_mark: [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (维护者 [Michael Gangolf](https://github.com/m1ga/))
|
||||
* :white_check_mark: [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (维护者 [fenix-hub @GodotNuts](https://github.com/fenix-hub))
|
||||
|
||||
- :white_check_mark: [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (维护者 [Michael Gangolf](https://github.com/m1ga/))
|
||||
- :white_check_mark: [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (维护者 [fenix-hub @GodotNuts](https://github.com/fenix-hub))
|
||||
|
||||
找不到需要的的 SDK? - 欢迎通过发起 PR 来帮助我们完善 Appwrite 的软件生态环境 [SDK 生成器](https://github.com/appwrite/sdk-generator)!
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
> We just announced Timestamp Overrides for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-timestamp-overrides)
|
||||
> We just announced DB operators for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-db-operators)
|
||||
|
||||
> Appwrite Cloud is now Generally Available - [Learn more](https://appwrite.io/cloud-ga)
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite banner, with logo and text saying "Build Like a Team of Hundreds"></a>
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite banner, with logo and text saying "The Developer's Cloud"></a>
|
||||
<br />
|
||||
<br />
|
||||
<b>Appwrite is an all-in-one development platform for Web, Mobile, and Flutter applications. Use built-in backend infrastructure and web hosting, all from a single place. Built with the open source community and optimized for developer experience in the coding languages you love.</b>
|
||||
<b>Appwrite is a best-in-class, developer-first platform that gives builders everything they need to create scalable, stable, and production-ready software, fast.</b>
|
||||
<br />
|
||||
<br />
|
||||
</p>
|
||||
@@ -32,13 +32,6 @@ Appwrite is an end-to-end platform for building Web, Mobile, Native, or Backend
|
||||
|
||||
Using Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, messaging, and [more services](https://appwrite.io/docs).
|
||||
|
||||
<p align="center">
|
||||
<br />
|
||||
<a href="https://www.producthunt.com/posts/appwrite-2?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-appwrite-2" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=360315&theme=light&period=daily" alt="Appwrite - 100% open source alternative for Firebase | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
<br />
|
||||
<br />
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
Find out more at: [https://appwrite.io](https://appwrite.io).
|
||||
@@ -129,12 +122,6 @@ Choose from one of the providers below:
|
||||
<br /><sub><b>DigitalOcean</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://gitpod.io/#https://github.com/appwrite/integration-for-gitpod">
|
||||
<img width="50" height="39" src="public/images/integrations/gitpod-logo.svg" alt="Gitpod Logo" />
|
||||
<br /><sub><b>Gitpod</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://www.linode.com/marketplace/apps/appwrite/appwrite/">
|
||||
<img width="50" height="39" src="public/images/integrations/akamai-logo.svg" alt="Akamai Logo" />
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+15
@@ -9,6 +9,7 @@ use Appwrite\Event\StatsResources;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\Runtimes\Runtimes;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Executor\Executor;
|
||||
use Swoole\Runtime;
|
||||
use Swoole\Timer;
|
||||
@@ -76,6 +77,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console');
|
||||
$dbForPlatform->setDocumentType('users', User::class);
|
||||
|
||||
// Ensure tables exist
|
||||
$collections = Config::getParam('collections', [])['console'];
|
||||
@@ -103,6 +105,11 @@ CLI::setResource('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
}, []);
|
||||
|
||||
CLI::setResource(
|
||||
'isResourceBlocked',
|
||||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
|
||||
);
|
||||
|
||||
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
@@ -250,6 +257,14 @@ CLI::setResource('logError', function (Registry $register) {
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $error->getTrace());
|
||||
|
||||
if ($error->getPrevious() !== null) {
|
||||
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
|
||||
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
|
||||
}
|
||||
$log->addExtra('previousFile', $error->getPrevious()->getFile());
|
||||
$log->addExtra('previousLine', $error->getPrevious()->getLine());
|
||||
}
|
||||
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Utopia\Auth\Hashes\Argon2;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
@@ -173,7 +173,7 @@ return [
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => Auth::DEFAULT_ALGO,
|
||||
'default' => (new Argon2())->getName(),
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
@@ -184,7 +184,7 @@ return [
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
'default' => (new Argon2())->getOptions(),
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
@@ -364,6 +364,61 @@ return [
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('emailCanonical'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 320,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('emailIsFree'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('emailIsDisposable'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('emailIsCorporate'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('emailIsCanonical'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -1527,6 +1582,17 @@ return [
|
||||
'required' => true,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('transformations'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
'default' => true,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
|
||||
@@ -6,7 +6,7 @@ use Utopia\Database\Helpers\ID;
|
||||
|
||||
$providers = Config::getParam('oAuthProviders', []);
|
||||
|
||||
return [
|
||||
$platformCollections = [
|
||||
'projects' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('projects'),
|
||||
@@ -643,6 +643,39 @@ return [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'resourceType',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'resourceId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'resourceInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -718,6 +751,13 @@ return [
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_resource',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['resourceType', 'resourceInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_accessedAt',
|
||||
'type' => Database::INDEX_KEY,
|
||||
@@ -1317,6 +1357,17 @@ return [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('logs'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 1000000,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -1892,3 +1943,31 @@ return [
|
||||
'indexes' => []
|
||||
],
|
||||
];
|
||||
|
||||
// Organization API keys subquery
|
||||
$platformCollections['teams']['attributes'][] = [
|
||||
'$id' => ID::custom('keys'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['subQueryOrganizationKeys'],
|
||||
];
|
||||
|
||||
// Account API keys subquery
|
||||
$platformCollections['users']['attributes'][] = [
|
||||
'$id' => ID::custom('keys'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['subQueryAccountKeys'],
|
||||
];
|
||||
|
||||
return $platformCollections;
|
||||
|
||||
@@ -2345,7 +2345,7 @@ return [
|
||||
'$id' => ID::custom('errors'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'size' => 1_000_000,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
@@ -2521,4 +2521,128 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'transactions' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('transactions'),
|
||||
'name' => 'Transactions',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('status'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => 16, // pending | committing | committed | failed
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 'pending',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('operations'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => true,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('expiresAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_expiresAt'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['expiresAt'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_DESC],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'transactionLogs' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('transactionLogs'),
|
||||
'name' => 'Transaction Logs',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('transactionInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('databaseInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('collectionInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('documentId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('action'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => 32, // create | update | upsert | increment | decrement | delete | bulkCreate | bulkUpdate | bulkUpsert | bulkDelete
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('data'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => 5_000_000, // Allow large payloads for bulk operations
|
||||
'signed' => false,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_transaction'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['transactionInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* Initializes console project document.
|
||||
*/
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Network\Platform;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\System\System;
|
||||
@@ -38,7 +37,7 @@ $console = [
|
||||
'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
|
||||
'duration' => TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
|
||||
'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled',
|
||||
'invalidateSessions' => true
|
||||
],
|
||||
@@ -49,6 +48,7 @@ $console = [
|
||||
'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
|
||||
'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
|
||||
],
|
||||
'smtpBaseTemplate' => APP_BRANDED_EMAIL_BASE_TEMPLATE,
|
||||
];
|
||||
|
||||
return $console;
|
||||
|
||||
+102
-35
@@ -211,6 +211,11 @@ return [
|
||||
'description' => 'User with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::USER_EMAIL_NOT_FOUND => [
|
||||
'name' => Exception::USER_EMAIL_NOT_FOUND,
|
||||
'description' => 'User email could not be found.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::USER_EMAIL_ALREADY_EXISTS => [
|
||||
'name' => Exception::USER_EMAIL_ALREADY_EXISTS,
|
||||
'description' => 'A user with the same email already exists in the current project.',
|
||||
@@ -312,11 +317,21 @@ return [
|
||||
'description' => 'OAuth2 provider returned some error.',
|
||||
'code' => 424,
|
||||
],
|
||||
Exception::USER_EMAIL_NOT_VERIFIED => [
|
||||
'name' => Exception::USER_EMAIL_NOT_VERIFIED,
|
||||
'description' => 'User email is not verified',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::USER_EMAIL_ALREADY_VERIFIED => [
|
||||
'name' => Exception::USER_EMAIL_ALREADY_VERIFIED,
|
||||
'description' => 'User email is already verified',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::USER_PHONE_NOT_VERIFIED => [
|
||||
'name' => Exception::USER_PHONE_NOT_VERIFIED,
|
||||
'description' => 'User phone is not verified',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::USER_PHONE_ALREADY_VERIFIED => [
|
||||
'name' => Exception::USER_PHONE_ALREADY_VERIFIED,
|
||||
'description' => 'User phone is already verified',
|
||||
@@ -507,6 +522,11 @@ return [
|
||||
'description' => 'The requested file is not publicly readable.',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::STORAGE_BUCKET_TRANSFORMATIONS_DISABLED => [
|
||||
'name' => Exception::STORAGE_BUCKET_TRANSFORMATIONS_DISABLED,
|
||||
'description' => 'Image transformations are disabled for the requested bucket.',
|
||||
'code' => 403,
|
||||
],
|
||||
|
||||
/** Tokens */
|
||||
Exception::TOKEN_NOT_FOUND => [
|
||||
@@ -563,6 +583,11 @@ return [
|
||||
'description' => 'The requested runtime is either inactive or unsupported. Please check the value of the _APP_FUNCTIONS_RUNTIMES environment variable.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::FUNCTION_ALREADY_EXISTS => [
|
||||
'name' => Exception::FUNCTION_ALREADY_EXISTS,
|
||||
'description' => 'Function with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::FUNCTION_ENTRYPOINT_MISSING => [
|
||||
'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".',
|
||||
@@ -663,12 +688,12 @@ return [
|
||||
/** Databases */
|
||||
Exception::DATABASE_NOT_FOUND => [
|
||||
'name' => Exception::DATABASE_NOT_FOUND,
|
||||
'description' => 'Database not found',
|
||||
'description' => 'Database with the requested ID \'%s\' could not be found.',
|
||||
'code' => 404
|
||||
],
|
||||
Exception::DATABASE_ALREADY_EXISTS => [
|
||||
'name' => Exception::DATABASE_ALREADY_EXISTS,
|
||||
'description' => 'Database already exists',
|
||||
'description' => 'Database with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409
|
||||
],
|
||||
Exception::DATABASE_TIMEOUT => [
|
||||
@@ -685,41 +710,41 @@ return [
|
||||
/** Collections */
|
||||
Exception::COLLECTION_NOT_FOUND => [
|
||||
'name' => Exception::COLLECTION_NOT_FOUND,
|
||||
'description' => 'Collection with the requested ID could not be found.',
|
||||
'description' => 'Collection with the requested ID \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::COLLECTION_ALREADY_EXISTS => [
|
||||
'name' => Exception::COLLECTION_ALREADY_EXISTS,
|
||||
'description' => 'A collection with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'description' => 'A collection with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::COLLECTION_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::COLLECTION_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of collections has been reached.',
|
||||
'description' => 'The maximum number of collections for database \'%s\' has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Tables */
|
||||
Exception::TABLE_NOT_FOUND => [
|
||||
'name' => Exception::TABLE_NOT_FOUND,
|
||||
'description' => 'Table with the requested ID could not be found.',
|
||||
'description' => 'Table with the requested ID \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::TABLE_ALREADY_EXISTS => [
|
||||
'name' => Exception::TABLE_ALREADY_EXISTS,
|
||||
'description' => 'A table with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'description' => 'A table with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::TABLE_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::TABLE_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of tables has been reached.',
|
||||
'description' => 'The maximum number of tables for database \'%s\' has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Documents */
|
||||
Exception::DOCUMENT_NOT_FOUND => [
|
||||
'name' => Exception::DOCUMENT_NOT_FOUND,
|
||||
'description' => 'Document with the requested ID could not be found.',
|
||||
'description' => 'Document with the requested ID \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::DOCUMENT_INVALID_STRUCTURE => [
|
||||
@@ -739,7 +764,7 @@ return [
|
||||
],
|
||||
Exception::DOCUMENT_ALREADY_EXISTS => [
|
||||
'name' => Exception::DOCUMENT_ALREADY_EXISTS,
|
||||
'description' => 'Document with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'description' => 'Document with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::DOCUMENT_UPDATE_CONFLICT => [
|
||||
@@ -756,7 +781,7 @@ return [
|
||||
/** Rows */
|
||||
Exception::ROW_NOT_FOUND => [
|
||||
'name' => Exception::ROW_NOT_FOUND,
|
||||
'description' => 'Row with the requested ID could not be found.',
|
||||
'description' => 'Row with the requested ID \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::ROW_INVALID_STRUCTURE => [
|
||||
@@ -766,7 +791,7 @@ return [
|
||||
],
|
||||
Exception::ROW_MISSING_DATA => [
|
||||
'name' => Exception::ROW_MISSING_DATA,
|
||||
'description' => 'The row data is missing. Try again with row data populated',
|
||||
'description' => 'The row data is missing. Try again with row data populated.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ROW_MISSING_PAYLOAD => [
|
||||
@@ -776,7 +801,7 @@ return [
|
||||
],
|
||||
Exception::ROW_ALREADY_EXISTS => [
|
||||
'name' => Exception::ROW_ALREADY_EXISTS,
|
||||
'description' => 'Row with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'description' => 'Row with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::ROW_UPDATE_CONFLICT => [
|
||||
@@ -793,17 +818,17 @@ return [
|
||||
/** Attributes */
|
||||
Exception::ATTRIBUTE_NOT_FOUND => [
|
||||
'name' => Exception::ATTRIBUTE_NOT_FOUND,
|
||||
'description' => 'Attribute with the requested ID could not be found.',
|
||||
'description' => 'Attribute with the requested key \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::ATTRIBUTE_UNKNOWN => [
|
||||
'name' => Exception::ATTRIBUTE_UNKNOWN,
|
||||
'description' => 'The attribute required for the index could not be found. Please confirm all your attributes are in the available state.',
|
||||
'description' => 'The attribute \'%s\' required for the index could not be found. Please confirm all your attributes are in the available state.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_NOT_AVAILABLE => [
|
||||
'name' => Exception::ATTRIBUTE_NOT_AVAILABLE,
|
||||
'description' => 'The requested attribute is not yet available. Please try again later.',
|
||||
'description' => 'The requested attribute \'%s\' is not yet available. Please try again later.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_FORMAT_UNSUPPORTED => [
|
||||
@@ -818,12 +843,12 @@ return [
|
||||
],
|
||||
Exception::ATTRIBUTE_ALREADY_EXISTS => [
|
||||
'name' => Exception::ATTRIBUTE_ALREADY_EXISTS,
|
||||
'description' => 'Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.',
|
||||
'description' => 'Attribute with the requested key \'%s\' already exists. Attribute keys must be unique, try again with a different key.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::ATTRIBUTE_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::ATTRIBUTE_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number or size of attributes for this collection has been reached.',
|
||||
'description' => 'The maximum number or size of attributes for collection \'%s\' has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_VALUE_INVALID => [
|
||||
@@ -833,7 +858,7 @@ return [
|
||||
],
|
||||
Exception::ATTRIBUTE_TYPE_INVALID => [
|
||||
'name' => Exception::ATTRIBUTE_TYPE_INVALID,
|
||||
'description' => 'The attribute type is invalid.',
|
||||
'description' => 'The attribute \'%s\' type is invalid.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_INVALID_RESIZE => [
|
||||
@@ -844,7 +869,7 @@ return [
|
||||
|
||||
Exception::ATTRIBUTE_TYPE_NOT_SUPPORTED => [
|
||||
'name' => Exception::ATTRIBUTE_TYPE_NOT_SUPPORTED,
|
||||
'description' => 'Attribute type is not supported.',
|
||||
'description' => 'Attribute type \'%s\' is not supported.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
@@ -858,17 +883,17 @@ return [
|
||||
/** Columns */
|
||||
Exception::COLUMN_NOT_FOUND => [
|
||||
'name' => Exception::COLUMN_NOT_FOUND,
|
||||
'description' => 'Column with the requested ID could not be found.',
|
||||
'description' => 'Column with the requested key \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::COLUMN_UNKNOWN => [
|
||||
'name' => Exception::COLUMN_UNKNOWN,
|
||||
'description' => 'The column required for the index could not be found. Please confirm all your columns are in the available state.',
|
||||
'description' => 'The column \'%s\' required for the index could not be found. Please confirm all your columns are in the available state.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::COLUMN_NOT_AVAILABLE => [
|
||||
'name' => Exception::COLUMN_NOT_AVAILABLE,
|
||||
'description' => 'The requested column is not yet available. Please try again later.',
|
||||
'description' => 'The requested column \'%s\' is not yet available. Please try again later.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::COLUMN_FORMAT_UNSUPPORTED => [
|
||||
@@ -883,12 +908,12 @@ return [
|
||||
],
|
||||
Exception::COLUMN_ALREADY_EXISTS => [
|
||||
'name' => Exception::COLUMN_ALREADY_EXISTS,
|
||||
'description' => 'Column with the requested key already exists. Column keys must be unique, try again with a different key.',
|
||||
'description' => 'Column with the requested key \'%s\' already exists. Column keys must be unique, try again with a different key.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::COLUMN_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::COLUMN_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number or size of columns for this table has been reached.',
|
||||
'description' => 'The maximum number or size of columns for table \'%s\' has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::COLUMN_VALUE_INVALID => [
|
||||
@@ -898,7 +923,7 @@ return [
|
||||
],
|
||||
Exception::COLUMN_TYPE_INVALID => [
|
||||
'name' => Exception::COLUMN_TYPE_INVALID,
|
||||
'description' => 'The column type is invalid.',
|
||||
'description' => 'The column \'%s\' type is invalid.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::COLUMN_INVALID_RESIZE => [
|
||||
@@ -908,24 +933,24 @@ return [
|
||||
],
|
||||
Exception::COLUMN_TYPE_NOT_SUPPORTED => [
|
||||
'name' => Exception::COLUMN_TYPE_NOT_SUPPORTED,
|
||||
'description' => 'Column type is not supported.',
|
||||
'description' => 'Column type \'%s\' is not supported.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Indexes */
|
||||
Exception::INDEX_NOT_FOUND => [
|
||||
'name' => Exception::INDEX_NOT_FOUND,
|
||||
'description' => 'Index with the requested ID could not be found.',
|
||||
'description' => 'Index with the requested key \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::INDEX_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::INDEX_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of indexes has been reached.',
|
||||
'description' => 'The maximum number of indexes for collection \'%s\' has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::INDEX_ALREADY_EXISTS => [
|
||||
'name' => Exception::INDEX_ALREADY_EXISTS,
|
||||
'description' => 'Index with the requested key already exists. Try again with a different key.',
|
||||
'description' => 'Index with the requested key \'%s\' already exists. Try again with a different key.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::INDEX_INVALID => [
|
||||
@@ -935,24 +960,24 @@ return [
|
||||
],
|
||||
Exception::INDEX_DEPENDENCY => [
|
||||
'name' => Exception::INDEX_DEPENDENCY,
|
||||
'description' => 'Attribute cannot be renamed or deleted. Please remove the associated index first.',
|
||||
'description' => 'Attribute \'%s\' cannot be renamed or deleted. Please remove the associated index first.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Column Indexes, same as Indexes but with different type */
|
||||
Exception::COLUMN_INDEX_NOT_FOUND => [
|
||||
'name' => Exception::COLUMN_INDEX_NOT_FOUND,
|
||||
'description' => 'Index with the requested ID could not be found.',
|
||||
'description' => 'Index with the requested key \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::COLUMN_INDEX_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::COLUMN_INDEX_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of indexes has been reached.',
|
||||
'description' => 'The maximum number of indexes for table \'%s\' has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::COLUMN_INDEX_ALREADY_EXISTS => [
|
||||
'name' => Exception::COLUMN_INDEX_ALREADY_EXISTS,
|
||||
'description' => 'Index with the requested key already exists. Try again with a different key.',
|
||||
'description' => 'Index with the requested key \'%s\' already exists. Try again with a different key.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::COLUMN_INDEX_INVALID => [
|
||||
@@ -962,10 +987,52 @@ return [
|
||||
],
|
||||
Exception::COLUMN_INDEX_DEPENDENCY => [
|
||||
'name' => Exception::COLUMN_INDEX_DEPENDENCY,
|
||||
'description' => 'Column cannot be renamed or deleted. Please remove the associated index first.',
|
||||
'description' => 'Column \'%s\' cannot be renamed or deleted. Please remove the associated index first.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Transactions */
|
||||
Exception::TRANSACTION_NOT_FOUND => [
|
||||
'name' => Exception::TRANSACTION_NOT_FOUND,
|
||||
'description' => 'Transaction with the requested ID \'%s\' could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::TRANSACTION_ALREADY_EXISTS => [
|
||||
'name' => Exception::TRANSACTION_ALREADY_EXISTS,
|
||||
'description' => 'Transaction with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::TRANSACTION_INVALID => [
|
||||
'name' => Exception::TRANSACTION_INVALID,
|
||||
'description' => 'The transaction is invalid. Please check the transaction state and try again.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::TRANSACTION_FAILED => [
|
||||
'name' => Exception::TRANSACTION_FAILED,
|
||||
'description' => 'The transaction has errored. Please check the transaction data and try again.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::TRANSACTION_EXPIRED => [
|
||||
'name' => Exception::TRANSACTION_EXPIRED,
|
||||
'description' => 'The transaction has expired. Please create a new transaction and try again.',
|
||||
'code' => 410,
|
||||
],
|
||||
Exception::TRANSACTION_CONFLICT => [
|
||||
'name' => Exception::TRANSACTION_CONFLICT,
|
||||
'description' => 'The transaction has a conflict. Please resolve the conflict and try again.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::TRANSACTION_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::TRANSACTION_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of operations per transaction has been exceeded.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::TRANSACTION_NOT_READY => [
|
||||
'name' => Exception::TRANSACTION_NOT_READY,
|
||||
'description' => 'The transaction is not ready yet. Please try again later.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Project Errors */
|
||||
Exception::PROJECT_NOT_FOUND => [
|
||||
'name' => Exception::PROJECT_NOT_FOUND,
|
||||
|
||||
+41
-22
@@ -8,20 +8,13 @@ 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'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/analog/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/analog/env.sh',
|
||||
'adapters' => [
|
||||
@@ -47,7 +40,7 @@ return [
|
||||
'name' => 'Angular',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/angular/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/angular/env.sh',
|
||||
'adapters' => [
|
||||
@@ -73,7 +66,7 @@ return [
|
||||
'name' => 'Next.js',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/next-js/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/next-js/env.sh',
|
||||
'adapters' => [
|
||||
@@ -98,7 +91,7 @@ return [
|
||||
'name' => 'React',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -115,7 +108,7 @@ return [
|
||||
'name' => 'Nuxt',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/nuxt/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/nuxt/env.sh',
|
||||
'adapters' => [
|
||||
@@ -140,7 +133,7 @@ return [
|
||||
'name' => 'Vue.js',
|
||||
'screenshotSleep' => 5000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -157,7 +150,7 @@ return [
|
||||
'name' => 'SvelteKit',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/sveltekit/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/sveltekit/env.sh',
|
||||
'adapters' => [
|
||||
@@ -182,7 +175,7 @@ return [
|
||||
'name' => 'Astro',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/astro/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/astro/env.sh',
|
||||
'adapters' => [
|
||||
@@ -202,12 +195,37 @@ return [
|
||||
]
|
||||
]
|
||||
],
|
||||
'tanstack-start' => [
|
||||
'key' => 'tanstack-start',
|
||||
'name' => 'TanStack Start',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/tanstack-start/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/tanstack-start/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './.output',
|
||||
'startCommand' => 'bash helpers/tanstack-start/server.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist/client',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
]
|
||||
]
|
||||
],
|
||||
'remix' => [
|
||||
'key' => 'remix',
|
||||
'name' => 'Remix',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/remix/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/remix/env.sh',
|
||||
'adapters' => [
|
||||
@@ -232,7 +250,7 @@ return [
|
||||
'name' => 'Lynx',
|
||||
'screenshotSleep' => 5000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -248,8 +266,8 @@ return [
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'screenshotSleep' => 5000,
|
||||
'buildRuntime' => 'flutter-3.29',
|
||||
'runtimes' => getVersions($templateRuntimes['FLUTTER']['versions'], 'flutter'),
|
||||
'buildRuntime' => 'flutter-3.35',
|
||||
'runtimes' => $templateRuntimes['FLUTTER'],
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -257,6 +275,7 @@ return [
|
||||
'installCommand' => 'flutter pub get',
|
||||
'outputDirectory' => './build/web',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
'fallbackFile' => 'index.html'
|
||||
],
|
||||
],
|
||||
],
|
||||
@@ -265,7 +284,7 @@ return [
|
||||
'name' => 'React Native',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -282,7 +301,7 @@ return [
|
||||
'name' => 'Vite',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -298,7 +317,7 @@ return [
|
||||
'name' => 'Other',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
|
||||
@@ -2,6 +2,38 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="preconnect" href="https://assets.appwrite.io/" crossorigin>
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="supported-color-schemes" content="light dark">
|
||||
<style type="text/css">
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
supported-color-schemes: light dark;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark ) {
|
||||
body {
|
||||
color: #616b7c !important;
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
a {
|
||||
color: currentColor !important;
|
||||
}
|
||||
a.button {
|
||||
color: #ffffff !important;
|
||||
background-color: {{accentColor}} !important;
|
||||
border-color: {{accentColor}} !important;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
color: #373b4d !important;
|
||||
}
|
||||
h4 {
|
||||
color: #4f5769 !important;
|
||||
}
|
||||
p.security-phrase:not(:empty), hr {
|
||||
border-color: #e8e9f0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
@@ -37,7 +69,6 @@
|
||||
font-family: "Inter", sans-serif;
|
||||
background-color: #ffffff;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
a {
|
||||
color: currentColor;
|
||||
@@ -98,6 +129,7 @@
|
||||
color: #ffffff;
|
||||
border-radius: 8px;
|
||||
height: 48px;
|
||||
line-height: 24px;
|
||||
padding: 12px 20px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
@@ -131,6 +163,14 @@
|
||||
.social-icon > img {
|
||||
margin: auto;
|
||||
}
|
||||
p.security-phrase:not(:empty) {
|
||||
opacity: 0.7;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-top: 32px;
|
||||
padding-top: 32px;
|
||||
border-top: 1px solid #e8e9f0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -145,8 +185,9 @@
|
||||
<tr>
|
||||
<td>
|
||||
<img
|
||||
height="32px"
|
||||
height="26px"
|
||||
src="{{logoUrl}}"
|
||||
alt="{{platform}} logo"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -155,12 +196,12 @@
|
||||
<table style="margin-top: 32px">
|
||||
<tr>
|
||||
<td>
|
||||
<h1>{{subject}}</h1>
|
||||
<h1>{{heading}}</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table style="margin-top: 32px">
|
||||
<table style="margin-top: 16px">
|
||||
<tr>
|
||||
<td>
|
||||
{{body}}
|
||||
@@ -184,7 +225,7 @@
|
||||
<tr>
|
||||
<td style="padding-left: 4px; padding-right: 4px">
|
||||
<a
|
||||
href="{{twitterUrl}}"
|
||||
href="{{twitter}}"
|
||||
class="social-icon"
|
||||
title="Twitter"
|
||||
>
|
||||
@@ -193,7 +234,7 @@
|
||||
</td>
|
||||
<td style="padding-left: 4px; padding-right: 4px">
|
||||
<a
|
||||
href="{{discordUrl}}"
|
||||
href="{{discord}}"
|
||||
class="social-icon"
|
||||
>
|
||||
<img src="https://cloud.appwrite.io/images/mails/discord.png" height="24" width="24" />
|
||||
@@ -201,7 +242,7 @@
|
||||
</td>
|
||||
<td style="padding-left: 4px; padding-right: 4px">
|
||||
<a
|
||||
href="{{githubUrl}}"
|
||||
href="{{github}}"
|
||||
class="social-icon"
|
||||
>
|
||||
<img src="https://cloud.appwrite.io/images/mails/github.png" height="24" width="24" />
|
||||
@@ -211,15 +252,15 @@
|
||||
</table>
|
||||
<table style="width: auto; margin: 0 auto; margin-top: 60px">
|
||||
<tr>
|
||||
<td><a href="{{termsUrl}}">Terms</a></td>
|
||||
<td><a href="{{terms}}">Terms</a></td>
|
||||
<td style="color: #e8e9f0">
|
||||
<div style="margin: 0 8px">|</div>
|
||||
</td>
|
||||
<td><a href="{{privacyUrl}}">Privacy</a></td>
|
||||
<td><a href="{{privacy}}">Privacy</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="text-align: center" align="center">
|
||||
© {{year}} Appwrite | 251 Little Falls Drive, Wilmington 19808,
|
||||
© {{year}} {{platform}} | 251 Little Falls Drive, Wilmington 19808,
|
||||
Delaware, United States
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,38 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="preconnect" href="https://assets.appwrite.io/" crossorigin>
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="supported-color-schemes" content="light dark">
|
||||
<style type="text/css">
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
supported-color-schemes: light dark;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark ) {
|
||||
body {
|
||||
color: #616b7c !important;
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
a {
|
||||
color: currentColor !important;
|
||||
}
|
||||
a.button {
|
||||
color: #ffffff !important;
|
||||
background-color: #2D2D31 !important;
|
||||
border-color: #414146 !important;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
color: #373b4d !important;
|
||||
}
|
||||
h4 {
|
||||
color: #4f5769 !important;
|
||||
}
|
||||
p.security-phrase:not(:empty), hr {
|
||||
border-color: #e8e9f0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
@@ -44,6 +76,21 @@
|
||||
color: currentColor;
|
||||
word-break: break-all;
|
||||
}
|
||||
a.button {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
padding: 9px 14px;
|
||||
color: #ffffff;
|
||||
background-color: #2D2D31;
|
||||
border: 1px solid #414146;
|
||||
border-radius: 8px;
|
||||
}
|
||||
a.button:hover,
|
||||
a.button:focus {
|
||||
opacity: 0.8;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-spacing: 0 !important;
|
||||
@@ -94,10 +141,15 @@
|
||||
h* {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
p.security-phrase:not(:empty) {
|
||||
opacity: 0.7;
|
||||
margin-top: 32px;
|
||||
padding-top: 32px;
|
||||
border-top: 1px solid #e8e9f0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<p>{{hello}}</p>
|
||||
<p>{{body}}</p>
|
||||
<p>{{footer}}</p>
|
||||
<p style="margin-bottom: 32px">
|
||||
{{thanks}}
|
||||
<br/>
|
||||
{{signature}}
|
||||
</p>
|
||||
@@ -1,6 +1,6 @@
|
||||
<p>{{hello}}</p>
|
||||
<p>{{body}}</p>
|
||||
<p><a href="{{redirect}}" target="_blank" style="font-size: 14px; font-family: Inter, sans-serif; color: #ffffff; text-decoration: none; background-color: #2D2D31; border-radius: 8px; padding: 9px 14px; border: 1px solid #414146; display: inline-block; text-align:center; box-sizing: border-box;">{{buttonText}}</a></p>
|
||||
<p><a href="{{redirect}}" target="_blank" class="button">{{buttonText}}</a></p>
|
||||
<p>{{footer}}</p>
|
||||
<p style="margin-bottom: 32px">
|
||||
{{thanks}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<table border="0" cellspacing="0" cellpadding="0" style="padding-top: 10px; padding-bottom: 10px; display: inline-block;">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 8px; background-color: #19191D;">
|
||||
<a rel="noopener" target="_blank" href="{{redirect}}" style="font-size: 14px; font-family: Inter; color: #ffffff; text-decoration: none; border-radius: 8px; padding: 9px 14px; border: 1px solid #19191D; display: inline-block;">{{buttonText}}</a>
|
||||
<a rel="noopener" target="_blank" href="{{redirect}}" class="button">{{buttonText}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<table border="0" cellspacing="0" cellpadding="0" style="padding-top: 10px; padding-bottom: 10px; display: inline-block;">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 8px; background-color: #ffffff;">
|
||||
<p style="font-size: 24px; text-indent: 18px; letter-spacing: 18px; font-family: 'Inter', sans-serif; color: #414146; text-decoration: none; border-radius: 8px; padding: 24px 12px; border: 1px solid #EDEDF0; display: inline-block; font-weight: bold; ">{{otp}}</p>
|
||||
<p style="font-size: 24px; text-indent: 18px; letter-spacing: 18px; font-family: 'Inter', sans-serif; color: #414146; text-decoration: none; border-radius: 8px; margin-top: 0px; margin-bottom: 0px; padding: 24px 12px; border: 1px solid #EDEDF0; display: inline-block; font-weight: bold;">{{otp}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<table border="0" cellspacing="0" cellpadding="0" style="padding-top: 10px; padding-bottom: 10px; display: inline-block;">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 8px; background-color: #ffffff;">
|
||||
<p style="font-size: 24px; text-indent: 18px; letter-spacing: 18px; font-family: Inter; color: #414146; text-decoration: none; border-radius: 8px; padding: 24px 12px; border: 1px solid #EDEDF0; display: inline-block; font-weight: bold; ">{{otp}}</p>
|
||||
<p style="font-size: 24px; text-indent: 18px; letter-spacing: 18px; font-family: 'Inter', sans-serif; color: #414146; text-decoration: none; border-radius: 8px; margin-top: 0px; margin-bottom: 0px; padding: 24px 12px; border: 1px solid #EDEDF0; display: inline-block; font-weight: bold; ">{{otp}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -15,6 +15,4 @@
|
||||
<p style="margin-bottom: 0px;">{{thanks}}</p>
|
||||
<p style="margin-top: 0px;">{{signature}}</p>
|
||||
|
||||
<hr style="margin-block-start: 1rem; margin-block-end: 1rem;">
|
||||
|
||||
<p style="opacity: 0.7;">{{securityPhrase}}</p>
|
||||
<p class="security-phrase">{{securityPhrase}}</p>
|
||||
@@ -3,8 +3,9 @@
|
||||
"settings.locale": "en",
|
||||
"settings.direction": "ltr",
|
||||
"emails.sender": "{{project}} Team",
|
||||
"emails.verification.subject": "Account Verification",
|
||||
"emails.verification.subject": "Account Verification for {{project}}",
|
||||
"emails.verification.preview": "Verify your email to activate your {{project}} account.",
|
||||
"emails.verification.heading": "Verify your email to start using {{project}}",
|
||||
"emails.verification.hello": "Hello {{user}},",
|
||||
"emails.verification.body": "Follow this link to verify your email address to your {{b}}{{project}}{{/b}} account.",
|
||||
"emails.verification.footer": "If you didn’t ask to verify this address, you can ignore this message.",
|
||||
@@ -33,6 +34,7 @@
|
||||
"emails.sessionAlert.signature": "{{project}} team",
|
||||
"emails.otpSession.subject": "OTP for {{project}} Login",
|
||||
"emails.otpSession.preview": "Use OTP {{otp}} to sign in to {{project}}. Expires in 15 minutes.",
|
||||
"emails.otpSession.heading": "Login with OTP to use {{project}}",
|
||||
"emails.otpSession.hello": "Hello {{user}},",
|
||||
"emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.",
|
||||
"emails.otpSession.clientInfo": "This sign in was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the sign in, you can safely ignore this email.",
|
||||
@@ -41,12 +43,13 @@
|
||||
"emails.otpSession.signature": "{{project}} team",
|
||||
"emails.mfaChallenge.subject": "Verification Code for {{project}}",
|
||||
"emails.mfaChallenge.preview": "Use code {{otp}} for two-step verification in {{project}}. Expires in 15 minutes.",
|
||||
"emails.mfaChallenge.heading": "Complete two-step verification to use {{project}}",
|
||||
"emails.mfaChallenge.hello": "Hello {{user}},",
|
||||
"emails.mfaChallenge.description": "Enter the following code to confirm your two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.",
|
||||
"emails.mfaChallenge.clientInfo": "This verification code was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the verification code, you can safely ignore this email.",
|
||||
"emails.mfaChallenge.thanks": "Thanks,",
|
||||
"emails.mfaChallenge.signature": "{{project}} team",
|
||||
"emails.recovery.subject": "Password Reset",
|
||||
"emails.recovery.subject": "Password Reset for {{project}}",
|
||||
"emails.recovery.preview": "Reset your {{project}} password using the link.",
|
||||
"emails.recovery.hello": "Hello {{user}},",
|
||||
"emails.recovery.body": "Follow this link to reset your {{b}}{{project}}{{/b}} password.",
|
||||
@@ -54,6 +57,21 @@
|
||||
"emails.recovery.thanks": "Thanks,",
|
||||
"emails.recovery.buttonText": "Reset password",
|
||||
"emails.recovery.signature": "{{project}} team",
|
||||
"emails.csvExport.success.subject": "Your CSV export is ready",
|
||||
"emails.csvExport.success.preview": "Your data export has been completed successfully.",
|
||||
"emails.csvExport.success.hello": "Hello {{user}},",
|
||||
"emails.csvExport.success.body": "Your CSV export is ready to download. Click the button below to download your data export.",
|
||||
"emails.csvExport.success.footer": "This download link will expire in 1 hour.",
|
||||
"emails.csvExport.success.thanks": "Thanks,",
|
||||
"emails.csvExport.success.buttonText": "Download CSV",
|
||||
"emails.csvExport.success.signature": "Appwrite team",
|
||||
"emails.csvExport.failure.subject": "Your CSV export failed - file too large",
|
||||
"emails.csvExport.failure.preview": "Your data export failed because the file size exceeds your plan limit.",
|
||||
"emails.csvExport.failure.hello": "Hello {{user}},",
|
||||
"emails.csvExport.failure.body": "Your CSV export could not be completed because the export file size ({{size}}MB) exceeds your plan limit. Please consider upgrading your plan or exporting a smaller dataset.",
|
||||
"emails.csvExport.failure.footer": "If you have any questions, please contact our support team.",
|
||||
"emails.csvExport.failure.thanks": "Thanks,",
|
||||
"emails.csvExport.failure.signature": "{{project}} team",
|
||||
"emails.invitation.subject": "Invitation to {{team}} Team at {{project}}",
|
||||
"emails.invitation.preview": "{{owner}} invited you to join {{team}} at {{project}}",
|
||||
"emails.invitation.hello": "Hello {{user}},",
|
||||
|
||||
@@ -462,4 +462,15 @@ return [
|
||||
'mock' => true,
|
||||
'class' => 'Appwrite\\Auth\\OAuth2\\Mock',
|
||||
],
|
||||
'mock-unverified' => [
|
||||
'name' => 'MockUnverified',
|
||||
'developers' => 'https://appwrite.io',
|
||||
'icon' => 'icon-appwrite',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => true,
|
||||
'class' => 'Appwrite\\Auth\\OAuth2\\MockUnverified',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Utopia\System\System;
|
||||
|
||||
/**
|
||||
* Platform configuration
|
||||
*/
|
||||
return [
|
||||
'apiHostname' => System::getEnv('_APP_DOMAIN', 'localhost'),
|
||||
'consoleHostname' => System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', 'localhost')),
|
||||
'hostnames' => array_filter(array_unique([
|
||||
System::getEnv('_APP_DOMAIN', 'localhost'),
|
||||
System::getEnv('_APP_CONSOLE_DOMAIN', 'localhost'),
|
||||
])),
|
||||
'platformName' => APP_EMAIL_PLATFORM_NAME,
|
||||
'logoUrl' => APP_EMAIL_LOGO_URL,
|
||||
'accentColor' => APP_EMAIL_ACCENT_COLOR,
|
||||
'footerImageUrl' => APP_EMAIL_FOOTER_IMAGE_URL,
|
||||
'twitterUrl' => APP_SOCIAL_TWITTER,
|
||||
'discordUrl' => APP_SOCIAL_DISCORD,
|
||||
'githubUrl' => APP_SOCIAL_GITHUB,
|
||||
'termsUrl' => APP_EMAIL_TERMS_URL,
|
||||
'privacyUrl' => APP_EMAIL_PRIVACY_URL,
|
||||
'websiteUrl' => 'https://' . APP_DOMAIN,
|
||||
'emailSenderName' => APP_EMAIL_PLATFORM_NAME,
|
||||
];
|
||||
+10
-8
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
|
||||
$member = [
|
||||
'global',
|
||||
@@ -28,7 +28,7 @@ $member = [
|
||||
'subscribers.write',
|
||||
'subscribers.read',
|
||||
'assistant.read',
|
||||
'rules.read'
|
||||
'rules.read',
|
||||
];
|
||||
|
||||
$admins = [
|
||||
@@ -58,6 +58,8 @@ $admins = [
|
||||
'projects.write',
|
||||
'keys.read',
|
||||
'keys.write',
|
||||
'devKeys.read',
|
||||
'devKeys.write',
|
||||
'webhooks.read',
|
||||
'webhooks.write',
|
||||
'locale.read',
|
||||
@@ -92,7 +94,7 @@ $admins = [
|
||||
];
|
||||
|
||||
return [
|
||||
Auth::USER_ROLE_GUESTS => [
|
||||
User::ROLE_GUESTS => [
|
||||
'label' => 'Guests',
|
||||
'scopes' => [
|
||||
'global',
|
||||
@@ -112,23 +114,23 @@ return [
|
||||
'execution.write',
|
||||
],
|
||||
],
|
||||
Auth::USER_ROLE_USERS => [
|
||||
User::ROLE_USERS => [
|
||||
'label' => 'Users',
|
||||
'scopes' => \array_merge($member),
|
||||
],
|
||||
Auth::USER_ROLE_ADMIN => [
|
||||
User::ROLE_ADMIN => [
|
||||
'label' => 'Admin',
|
||||
'scopes' => \array_merge($admins),
|
||||
],
|
||||
Auth::USER_ROLE_DEVELOPER => [
|
||||
User::ROLE_DEVELOPER => [
|
||||
'label' => 'Developer',
|
||||
'scopes' => \array_merge($admins),
|
||||
],
|
||||
Auth::USER_ROLE_OWNER => [
|
||||
User::ROLE_OWNER => [
|
||||
'label' => 'Owner',
|
||||
'scopes' => \array_merge($member, $admins),
|
||||
],
|
||||
Auth::USER_ROLE_APPS => [
|
||||
User::ROLE_APPS => [
|
||||
'label' => 'Applications',
|
||||
'scopes' => ['global', 'health.read', 'graphql'],
|
||||
],
|
||||
|
||||
@@ -47,10 +47,10 @@ return [ // List of publicly visible scopes
|
||||
'description' => 'Access to create, update, and delete your project\'s database table\'s columns',
|
||||
],
|
||||
'indexes.read' => [
|
||||
'description' => 'Access to read your project\'s database collection\'s indexes',
|
||||
'description' => 'Access to read your project\'s database table\'s indexes',
|
||||
],
|
||||
'indexes.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s database collection\'s indexes',
|
||||
'description' => 'Access to create, update, and delete your project\'s database table\'s indexes',
|
||||
],
|
||||
'documents.read' => [
|
||||
'description' => 'Access to read your project\'s database documents',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
APP_PLATFORM_CLIENT => [
|
||||
'key' => APP_PLATFORM_CLIENT,
|
||||
APP_SDK_PLATFORM_CLIENT => [
|
||||
'key' => APP_SDK_PLATFORM_CLIENT,
|
||||
'name' => 'Client',
|
||||
'description' => 'Client libraries for integrating with Appwrite to build client-based applications and websites. Read the [getting started for web](https://appwrite.io/docs/getting-started-for-web) or [getting started for Flutter](https://appwrite.io/docs/getting-started-for-flutter) tutorials to start building your first application.',
|
||||
'enabled' => true,
|
||||
@@ -11,14 +11,14 @@ return [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '20.1.0',
|
||||
'version' => '21.5.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-web',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-web'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git',
|
||||
@@ -60,14 +60,14 @@ return [
|
||||
[
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'version' => '19.1.0',
|
||||
'version' => '20.3.2',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-flutter',
|
||||
'package' => 'https://pub.dev/packages/appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'dart',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-flutter'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-flutter.git',
|
||||
@@ -79,14 +79,14 @@ return [
|
||||
[
|
||||
'key' => 'apple',
|
||||
'name' => 'Apple',
|
||||
'version' => '12.1.0',
|
||||
'version' => '13.5.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'swift',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-apple'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-apple.git',
|
||||
@@ -104,7 +104,7 @@ return [
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => '',
|
||||
'source' => false,
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-objective-c.git',
|
||||
@@ -116,14 +116,15 @@ return [
|
||||
[
|
||||
'key' => 'android',
|
||||
'name' => 'Android',
|
||||
'version' => '10.1.0',
|
||||
'namespace' => 'io.appwrite',
|
||||
'version' => '11.4.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-android',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'kotlin',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-android'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-android.git',
|
||||
@@ -139,14 +140,14 @@ return [
|
||||
[
|
||||
'key' => 'react-native',
|
||||
'name' => 'React Native',
|
||||
'version' => '0.14.0',
|
||||
'version' => '0.19.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-react-native',
|
||||
'package' => 'https://npmjs.com/package/react-native-appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-react-native'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-react-native.git',
|
||||
@@ -165,7 +166,7 @@ return [
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'graphql',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-graphql'),
|
||||
'gitUrl' => '',
|
||||
@@ -185,7 +186,7 @@ return [
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'family' => APP_SDK_PLATFORM_CLIENT,
|
||||
'prism' => 'http',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-rest'),
|
||||
'gitUrl' => '',
|
||||
@@ -198,8 +199,8 @@ return [
|
||||
],
|
||||
],
|
||||
|
||||
APP_PLATFORM_CONSOLE => [
|
||||
'key' => APP_PLATFORM_CONSOLE,
|
||||
APP_SDK_PLATFORM_CONSOLE => [
|
||||
'key' => APP_SDK_PLATFORM_CONSOLE,
|
||||
'name' => 'Console',
|
||||
'enabled' => false,
|
||||
'beta' => false,
|
||||
@@ -207,14 +208,14 @@ return [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Console',
|
||||
'version' => '0.1.0',
|
||||
'version' => '0.2.0',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CONSOLE,
|
||||
'family' => APP_SDK_PLATFORM_CONSOLE,
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
|
||||
'gitUrl' => '',
|
||||
@@ -226,14 +227,14 @@ return [
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '10.0.0',
|
||||
'version' => '12.0.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CONSOLE,
|
||||
'family' => APP_SDK_PLATFORM_CONSOLE,
|
||||
'prism' => 'bash',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-cli'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-cli.git',
|
||||
@@ -252,8 +253,8 @@ return [
|
||||
],
|
||||
],
|
||||
|
||||
APP_PLATFORM_SERVER => [
|
||||
'key' => APP_PLATFORM_SERVER,
|
||||
APP_SDK_PLATFORM_SERVER => [
|
||||
'key' => APP_SDK_PLATFORM_SERVER,
|
||||
'name' => 'Server',
|
||||
'description' => 'Libraries for integrating with Appwrite to build server side integrations. Read the [getting started for server](https://appwrite.io/docs/getting-started-for-server) tutorial to start building your first server integration.',
|
||||
'enabled' => true,
|
||||
@@ -262,14 +263,14 @@ return [
|
||||
[
|
||||
'key' => 'nodejs',
|
||||
'name' => 'Node.js',
|
||||
'version' => '19.1.0',
|
||||
'version' => '21.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-node',
|
||||
'package' => 'https://www.npmjs.com/package/node-appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-nodejs'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-node.git',
|
||||
@@ -281,14 +282,14 @@ return [
|
||||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '17.1.0',
|
||||
'version' => '19.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'php',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-php'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-php.git',
|
||||
@@ -300,14 +301,14 @@ return [
|
||||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '13.1.0',
|
||||
'version' => '14.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'python',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-python'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-python.git',
|
||||
@@ -319,14 +320,14 @@ return [
|
||||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '18.1.0',
|
||||
'version' => '20.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'ruby',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-ruby'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-ruby.git',
|
||||
@@ -338,14 +339,14 @@ return [
|
||||
[
|
||||
'key' => 'go',
|
||||
'name' => 'Go',
|
||||
'version' => '0.12.0',
|
||||
'version' => 'v0.16.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'go',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-go'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-go.git',
|
||||
@@ -357,14 +358,14 @@ return [
|
||||
[
|
||||
'key' => 'dotnet',
|
||||
'name' => '.NET',
|
||||
'version' => '0.18.0',
|
||||
'version' => '0.24.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
|
||||
'package' => 'https://www.nuget.org/packages/Appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'csharp',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-dotnet'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-dotnet.git',
|
||||
@@ -376,14 +377,14 @@ return [
|
||||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '18.1.0',
|
||||
'version' => '20.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_appwrite',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'dart',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-dart'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-dart.git',
|
||||
@@ -395,14 +396,15 @@ return [
|
||||
[
|
||||
'key' => 'kotlin',
|
||||
'name' => 'Kotlin',
|
||||
'version' => '11.1.0',
|
||||
'namespace' => 'io.appwrite',
|
||||
'version' => '13.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'kotlin',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-kotlin'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-kotlin.git',
|
||||
@@ -418,14 +420,14 @@ return [
|
||||
[
|
||||
'key' => 'swift',
|
||||
'name' => 'Swift',
|
||||
'version' => '12.1.0',
|
||||
'version' => '14.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'swift',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-swift'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-swift.git',
|
||||
@@ -444,7 +446,7 @@ return [
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'graphql',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-graphql'),
|
||||
'gitUrl' => '',
|
||||
@@ -464,7 +466,7 @@ return [
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'family' => APP_SDK_PLATFORM_SERVER,
|
||||
'prism' => 'http',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-rest'),
|
||||
'gitUrl' => '',
|
||||
+26
-4
@@ -13,19 +13,21 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'web/console' => [
|
||||
'key' => 'web/console',
|
||||
'name' => 'Console',
|
||||
'subtitle' => '',
|
||||
'description' => '',
|
||||
'controller' => 'web/console.php',
|
||||
'controller' => '', // Uses modules
|
||||
'sdk' => false,
|
||||
'docs' => false,
|
||||
'docsUrl' => '',
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'account' => [
|
||||
'key' => 'account',
|
||||
@@ -39,6 +41,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/account.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'avatars' => [
|
||||
'key' => 'avatars',
|
||||
@@ -52,6 +55,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/avatars.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'databases' => [
|
||||
'key' => 'databases',
|
||||
@@ -65,6 +69,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/databases.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'tablesdb' => [
|
||||
'key' => 'tablesdb',
|
||||
@@ -78,6 +83,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/databases.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'locale' => [
|
||||
'key' => 'locale',
|
||||
@@ -91,6 +97,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/locale.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'health' => [
|
||||
'key' => 'health',
|
||||
@@ -104,6 +111,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/health.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'projects' => [
|
||||
'key' => 'projects',
|
||||
@@ -117,6 +125,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'project' => [
|
||||
'key' => 'project',
|
||||
@@ -130,19 +139,21 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'storage' => [
|
||||
'key' => 'storage',
|
||||
'name' => 'Storage',
|
||||
'subtitle' => 'The Storage service allows you to manage your project files.',
|
||||
'description' => '/docs/services/storage.md',
|
||||
'controller' => 'api/storage.php',
|
||||
'controller' => '',
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/client/storage',
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/storage.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'teams' => [
|
||||
'key' => 'teams',
|
||||
@@ -156,6 +167,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/teams.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'users' => [
|
||||
'key' => 'users',
|
||||
@@ -169,6 +181,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/users.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'vcs' => [
|
||||
'key' => 'vcs',
|
||||
@@ -182,6 +195,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'sites' => [
|
||||
'key' => 'sites',
|
||||
@@ -195,6 +209,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/sites.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'functions' => [
|
||||
'key' => 'functions',
|
||||
@@ -208,6 +223,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/functions.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'proxy' => [
|
||||
'key' => 'proxy',
|
||||
@@ -221,6 +237,7 @@ return [
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '/images/services/proxy.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'mock' => [
|
||||
'key' => 'mock',
|
||||
@@ -234,6 +251,7 @@ return [
|
||||
'tests' => true,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'graphql' => [
|
||||
'key' => 'graphql',
|
||||
@@ -247,19 +265,21 @@ return [
|
||||
'tests' => true,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/graphql.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'console' => [
|
||||
'key' => 'console',
|
||||
'name' => 'Console',
|
||||
'subtitle' => 'The Console service allows you to interact with console relevant informations.',
|
||||
'subtitle' => 'The Console service allows you to interact with console relevant information.',
|
||||
'description' => '',
|
||||
'controller' => 'api/console.php',
|
||||
'controller' => '', // Uses modules
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => '',
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'migrations' => [
|
||||
'key' => 'migrations',
|
||||
@@ -273,6 +293,7 @@ return [
|
||||
'tests' => true,
|
||||
'optional' => false,
|
||||
'icon' => '/images/services/migrations.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
'messaging' => [
|
||||
'key' => 'messaging',
|
||||
@@ -286,5 +307,6 @@ return [
|
||||
'tests' => true,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/messaging.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
]
|
||||
];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,43 +1,12 @@
|
||||
<?php
|
||||
|
||||
// TODO: Remove, replace with runtimes.php directly
|
||||
// Used in function templates and site frameworks
|
||||
use Utopia\Config\Config;
|
||||
|
||||
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.8', '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.32', '3.24']
|
||||
],
|
||||
];
|
||||
$runtimes = Config::getParam('runtimes');
|
||||
|
||||
$mappedRuntimes = \array_reduce($runtimes, function ($acc, $runtime) {
|
||||
$acc[strtoupper($runtime['key'])][] = $runtime['key'] . '-' . $runtime['version'];
|
||||
return $acc;
|
||||
}, []);
|
||||
|
||||
return $mappedRuntimes;
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\System\System;
|
||||
|
||||
$templateRuntimes = Config::getParam('template-runtimes');
|
||||
$allowList = \array_map('trim', \explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
|
||||
|
||||
function getRuntimes($runtime, $commands, $entrypoint, $providerRootDirectory, $versionsDenyList = [])
|
||||
function getRuntimes($runtimes, $commands, $entrypoint, $providerRootDirectory, $allowList)
|
||||
{
|
||||
return array_map(function ($version) use ($runtime, $commands, $entrypoint, $providerRootDirectory) {
|
||||
return array_map(function ($runtime) use ($commands, $entrypoint, $providerRootDirectory) {
|
||||
return [
|
||||
'name' => $runtime['name'] . '-' . $version,
|
||||
'name' => $runtime,
|
||||
'commands' => $commands,
|
||||
'entrypoint' => $entrypoint,
|
||||
'providerRootDirectory' => $providerRootDirectory
|
||||
];
|
||||
}, array_filter($runtime['versions'], function ($version) use ($versionsDenyList) {
|
||||
return !in_array($version, $versionsDenyList);
|
||||
}, array_filter($runtimes, function ($runtime) use ($allowList) {
|
||||
return in_array($runtime, $allowList);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -32,24 +34,26 @@ return [
|
||||
'timeout' => 15,
|
||||
'useCases' => ['starter'],
|
||||
'runtimes' => [
|
||||
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/starter'),
|
||||
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/starter', $allowList),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/starter'
|
||||
'python/starter',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/starter'),
|
||||
...getRuntimes($templateRuntimes['GO'], '', 'main.go', 'go/starter'),
|
||||
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/starter', $allowList),
|
||||
...getRuntimes($templateRuntimes['GO'], '', 'main.go', 'go/starter', $allowList),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PHP'],
|
||||
'composer install',
|
||||
'src/index.php',
|
||||
'php/starter'
|
||||
'php/starter',
|
||||
$allowList
|
||||
),
|
||||
...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'),
|
||||
...getRuntimes($templateRuntimes['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter', $allowList),
|
||||
...getRuntimes($templateRuntimes['BUN'], 'bun install', 'src/main.ts', 'bun/starter', $allowList),
|
||||
...getRuntimes($templateRuntimes['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter', $allowList),
|
||||
],
|
||||
'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',
|
||||
@@ -75,7 +79,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/query-upstash-vector'
|
||||
'node/query-upstash-vector',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/query-upstash-vector">file</a>.',
|
||||
@@ -120,7 +125,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/query-redis-labs'
|
||||
'node/query-redis-labs',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/query-redis-labs">file</a>.',
|
||||
@@ -164,7 +170,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/query-neo4j-auradb'
|
||||
'node/query-neo4j-auradb',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/query-neo4j-auradb">file</a>.',
|
||||
@@ -217,7 +224,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/query-mongo-atlas'
|
||||
'node/query-mongo-atlas',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/query-mongo-atlas">file</a>.',
|
||||
@@ -255,7 +263,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/query-neon-postgres'
|
||||
'node/query-neon-postgres',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/query-neon-postgres">file</a>.',
|
||||
@@ -323,25 +332,29 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/prompt-chatgpt'
|
||||
'node/prompt-chatgpt',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/prompt_chatgpt'
|
||||
'python/prompt_chatgpt',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PHP'],
|
||||
'composer install',
|
||||
'src/index.php',
|
||||
'php/prompt-chatgpt'
|
||||
'php/prompt-chatgpt',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['DART'],
|
||||
'dart pub get',
|
||||
'lib/main.dart',
|
||||
'dart/prompt_chatgpt'
|
||||
'dart/prompt_chatgpt',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/prompt-chatgpt">file</a>.',
|
||||
@@ -385,19 +398,22 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/discord-command-bot'
|
||||
'node/discord-command-bot',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt && python src/setup.py',
|
||||
'src/main.py',
|
||||
'python/discord_command_bot'
|
||||
'python/discord_command_bot',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['GO'],
|
||||
'',
|
||||
'main.go',
|
||||
'go/discord-command-bot'
|
||||
'go/discord-command-bot',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/discord-command-bot">file</a>.',
|
||||
@@ -449,7 +465,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/analyze-with-perspectiveapi'
|
||||
'node/analyze-with-perspectiveapi',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/analyze-with-perspectiveapi">file</a>.',
|
||||
@@ -486,19 +503,22 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/censor-with-redact'
|
||||
'node/censor-with-redact',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/censor_with_redact'
|
||||
'python/censor_with_redact',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['DART'],
|
||||
'dart pub get',
|
||||
'lib/main.dart',
|
||||
'dart/censor_with_redact'
|
||||
'dart/censor_with_redact',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/censor-with-redact">file</a>.',
|
||||
@@ -530,7 +550,7 @@ return [
|
||||
'timeout' => 15,
|
||||
'useCases' => ['utilities'],
|
||||
'runtimes' => [
|
||||
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf')
|
||||
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf', $allowList)
|
||||
],
|
||||
'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',
|
||||
@@ -557,7 +577,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/github-issue-bot'
|
||||
'node/github-issue-bot',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/github-issue-bot">file</a>.',
|
||||
@@ -601,7 +622,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/url-shortener'
|
||||
'node/url-shortener',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/url-shortener">file</a>.',
|
||||
@@ -653,19 +675,22 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/sync-with-algolia'
|
||||
'node/sync-with-algolia',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/sync_with_algolia'
|
||||
'python/sync_with_algolia',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PHP'],
|
||||
'composer install',
|
||||
'src/index.php',
|
||||
'php/sync-with-algolia'
|
||||
'php/sync-with-algolia',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/sync-with-algolia">file</a>.',
|
||||
@@ -735,31 +760,36 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/sync-with-meilisearch'
|
||||
'node/sync-with-meilisearch',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/sync-with-meilisearch'
|
||||
'python/sync-with-meilisearch',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PHP'],
|
||||
'composer install',
|
||||
'src/index.php',
|
||||
'php/sync-with-meilisearch'
|
||||
'php/sync-with-meilisearch',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['BUN'],
|
||||
'bun install',
|
||||
'src/main.ts',
|
||||
'bun/sync-with-meilisearch'
|
||||
'bun/sync-with-meilisearch',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['RUBY'],
|
||||
'bundle install',
|
||||
'lib/main.rb',
|
||||
'ruby/sync-with-meilisearch'
|
||||
'ruby/sync-with-meilisearch',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/sync-with-meilisearch">file</a>.',
|
||||
@@ -829,37 +859,43 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/whatsapp-with-vonage'
|
||||
'node/whatsapp-with-vonage',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/whatsapp_with_vonage'
|
||||
'python/whatsapp_with_vonage',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['DART'],
|
||||
'dart pub get',
|
||||
'lib/main.dart',
|
||||
'dart/whatsapp-with-vonage'
|
||||
'dart/whatsapp-with-vonage',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PHP'],
|
||||
'composer install',
|
||||
'src/index.php',
|
||||
'php/whatsapp-with-vonage'
|
||||
'php/whatsapp-with-vonage',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['BUN'],
|
||||
'bun install',
|
||||
'src/main.ts',
|
||||
'bun/whatsapp-with-vonage'
|
||||
'bun/whatsapp-with-vonage',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['RUBY'],
|
||||
'bundle install',
|
||||
'lib/main.rb',
|
||||
'ruby/whatsapp-with-vonage'
|
||||
'ruby/whatsapp-with-vonage',
|
||||
$allowList
|
||||
),
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/whatsapp-with-vonage">file</a>.',
|
||||
@@ -916,7 +952,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/push-notification-with-fcm'
|
||||
'node/push-notification-with-fcm',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/push-notification-with-fcm">file</a>.',
|
||||
@@ -973,19 +1010,22 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/email-contact-form'
|
||||
'node/email-contact-form',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PYTHON'],
|
||||
'pip install -r requirements.txt',
|
||||
'src/main.py',
|
||||
'python/email_contact_form'
|
||||
'python/email_contact_form',
|
||||
$allowList
|
||||
),
|
||||
...getRuntimes(
|
||||
$templateRuntimes['PHP'],
|
||||
'composer install',
|
||||
'src/index.php',
|
||||
'php/email-contact-form'
|
||||
'php/email-contact-form',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/email-contact-form">file</a>.',
|
||||
@@ -1057,7 +1097,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/subscriptions-with-stripe'
|
||||
'node/subscriptions-with-stripe',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/subscriptions-with-stripe">file</a>.',
|
||||
@@ -1099,7 +1140,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/payments-with-stripe'
|
||||
'node/payments-with-stripe',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/payments-with-stripe">file</a>.',
|
||||
@@ -1157,7 +1199,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/text-generation-with-huggingface'
|
||||
'node/text-generation-with-huggingface',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/text-generation-with-huggingface">file</a>.',
|
||||
@@ -1192,7 +1235,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/language-translation-with-huggingface'
|
||||
'node/language-translation-with-huggingface',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/language-translation-with-huggingface">file</a>.',
|
||||
@@ -1227,7 +1271,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/image-classification-with-huggingface'
|
||||
'node/image-classification-with-huggingface',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/image-classification-with-huggingface">file</a>.',
|
||||
@@ -1286,7 +1331,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/object-detection-with-huggingface'
|
||||
'node/object-detection-with-huggingface',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/object-detection-with-huggingface">file</a>.',
|
||||
@@ -1345,7 +1391,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/speech-recognition-with-huggingface'
|
||||
'node/speech-recognition-with-huggingface',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/speech-recognition-with-huggingface">file</a>.',
|
||||
@@ -1407,7 +1454,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/text-to-speech-with-huggingface'
|
||||
'node/text-to-speech-with-huggingface',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/text-to-speech-with-huggingface">file</a>.',
|
||||
@@ -1466,7 +1514,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/generate-with-replicate'
|
||||
'node/generate-with-replicate',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'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-with-replicate">file</a>.',
|
||||
@@ -1502,7 +1551,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/generate-with-together-ai'
|
||||
'node/generate-with-together-ai',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'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-with-together-ai">file</a>.',
|
||||
@@ -1545,7 +1595,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/chat-with-perplexity-ai'
|
||||
'node/chat-with-perplexity-ai',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/chat-with-perplexity-ai">file</a>.',
|
||||
@@ -1587,7 +1638,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/generate-with-replicate'
|
||||
'node/generate-with-replicate',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'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-with-replicate">file</a>.',
|
||||
@@ -1623,7 +1675,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/sync-with-pinecone'
|
||||
'node/sync-with-pinecone',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/sync-with-pinecone">file</a>.',
|
||||
@@ -1687,7 +1740,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/rag-with-langchain'
|
||||
'node/rag-with-langchain',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/rag-with-langchain">file</a>.',
|
||||
@@ -1751,7 +1805,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/speak-with-elevenlabs'
|
||||
'node/speak-with-elevenlabs',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/speak-with-elevenlabs">file</a>.',
|
||||
@@ -1807,7 +1862,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/speak-with-lmnt'
|
||||
'node/speak-with-lmnt',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/speak-with-lmnt">file</a>.',
|
||||
@@ -1849,7 +1905,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/chat-with-anyscale'
|
||||
'node/chat-with-anyscale',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/chat-with-anyscale">file</a>.',
|
||||
@@ -1891,7 +1948,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/music-generation-with-huggingface'
|
||||
'node/music-generation-with-huggingface',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/music-generation-with-huggingface">file</a>.',
|
||||
@@ -1934,7 +1992,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/generate-with-fal-ai'
|
||||
'node/generate-with-fal-ai',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'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-with-fal-ai">file</a>.',
|
||||
@@ -1970,7 +2029,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/subscriptions-with-lemon-squeezy'
|
||||
'node/subscriptions-with-lemon-squeezy',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/subscriptions-with-lemon-squeezy">file</a>.',
|
||||
@@ -2026,7 +2086,8 @@ return [
|
||||
$templateRuntimes['NODE'],
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/payments-with-lemon-squeezy'
|
||||
'node/payments-with-lemon-squeezy',
|
||||
$allowList
|
||||
)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/payments-with-lemon-squeezy">file</a>.',
|
||||
@@ -2094,7 +2155,7 @@ return [
|
||||
'timeout' => 15,
|
||||
'useCases' => ['auth'],
|
||||
'runtimes' => [
|
||||
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/sign_in_with_apple')
|
||||
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/sign_in_with_apple', $allowList)
|
||||
],
|
||||
'instructions' => 'For documentation and instructions, check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/dart/sign_in_with_apple">file</a>.',
|
||||
'vcsProvider' => 'github',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\System\System;
|
||||
|
||||
/**
|
||||
@@ -7,7 +8,8 @@ use Utopia\System\System;
|
||||
*/
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN', '');
|
||||
$platform = Config::getParam('platform', []);
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
|
||||
$url = $protocol . '://' . $hostname;
|
||||
|
||||
@@ -19,6 +21,9 @@ class UseCases
|
||||
public const ECOMMERCE = 'ecommerce';
|
||||
public const DOCUMENTATION = 'documentation';
|
||||
public const BLOG = 'blog';
|
||||
public const AI = 'artificial intelligence';
|
||||
public const FORMS = 'forms';
|
||||
public const DASHBOARD = 'dashboard';
|
||||
}
|
||||
|
||||
const TEMPLATE_FRAMEWORKS = [
|
||||
@@ -78,7 +83,7 @@ const TEMPLATE_FRAMEWORKS = [
|
||||
'installCommand' => '',
|
||||
'buildCommand' => 'flutter build web',
|
||||
'outputDirectory' => './build/web',
|
||||
'buildRuntime' => 'flutter-3.29',
|
||||
'buildRuntime' => 'flutter-3.35',
|
||||
'adapter' => 'static',
|
||||
'fallbackFile' => '',
|
||||
],
|
||||
@@ -111,6 +116,16 @@ const TEMPLATE_FRAMEWORKS = [
|
||||
'outputDirectory' => './dist',
|
||||
'fallbackFile' => '+not-found.html',
|
||||
],
|
||||
'TANSTACK_START' => [
|
||||
'key' => 'tanstack-start',
|
||||
'name' => 'TanStack Start',
|
||||
'installCommand' => 'npm install',
|
||||
'buildCommand' => 'npm run build',
|
||||
'outputDirectory' => './dist',
|
||||
'buildRuntime' => 'node-22',
|
||||
'adapter' => 'ssr',
|
||||
'fallbackFile' => '',
|
||||
],
|
||||
'ANGULAR' => [
|
||||
'key' => 'angular',
|
||||
'name' => 'Angular',
|
||||
@@ -950,6 +965,50 @@ return [
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'starter-for-tanstack-start',
|
||||
'name' => 'TanStack Start starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'tagline' => 'Simple TanStack Start application integrated with Appwrite SDK.',
|
||||
'score' => 9, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-tanstack-start-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/starter-for-tanstack-start-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('TANSTACK_START', [
|
||||
'providerRootDirectory' => './',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'starter-for-tanstack-start',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.1.*',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_ENDPOINT',
|
||||
'description' => 'Endpoint of Appwrite server',
|
||||
'value' => '{apiEndpoint}',
|
||||
'placeholder' => '{apiEndpoint}',
|
||||
'required' => true,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_PROJECT_ID',
|
||||
'description' => 'Your Appwrite project ID',
|
||||
'value' => '{projectId}',
|
||||
'placeholder' => '{projectId}',
|
||||
'required' => true,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_PROJECT_NAME',
|
||||
'description' => 'Your Appwrite project name',
|
||||
'value' => '{projectName}',
|
||||
'placeholder' => '{projectName}',
|
||||
'required' => true,
|
||||
'type' => 'text'
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'starter-for-nuxt',
|
||||
'name' => 'Nuxt starter',
|
||||
@@ -1327,6 +1386,25 @@ return [
|
||||
'providerVersion' => '0.3.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
'key' => 'playground-for-tanstack-start',
|
||||
'name' => 'TanStack Start playground',
|
||||
'tagline' => 'A basic TanStack Start website without Appwrite SDK integration.',
|
||||
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'screenshotDark' => $url . '/images/sites/templates/playground-for-tanstack-start-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/playground-for-tanstack-start-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('TANSTACK_START', [
|
||||
'providerRootDirectory' => './tanstack-start/starter',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.5.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
'key' => 'playground-for-react-native',
|
||||
'name' => 'React Native playground',
|
||||
@@ -1365,4 +1443,163 @@ return [
|
||||
'providerVersion' => '0.3.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
'key' => 'text-to-speech',
|
||||
'name' => 'Text-to-speech with ElevenLabs',
|
||||
'tagline' => 'Next.js app that transforms text into natural, human-like speech using ElevenLabs',
|
||||
'score' => 10, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::AI],
|
||||
'screenshotDark' => $url . '/images/sites/templates/text-to-speech-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/text-to-speech-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('NEXTJS', [
|
||||
'providerRootDirectory' => './nextjs/text-to-speech',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.6.*',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => 'ELEVENLABS_API_KEY',
|
||||
'description' => 'Your ElevenLabs API key',
|
||||
'value' => '',
|
||||
'placeholder' => 'sk_.....',
|
||||
'required' => true,
|
||||
'type' => 'password'
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'crm-dashboard-react-admin',
|
||||
'name' => 'CRM dashboard with React Admin',
|
||||
'tagline' => 'A React-based admin dashboard template with CRM features.',
|
||||
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::DASHBOARD],
|
||||
'screenshotDark' => $url . '/images/sites/templates/crm-dashboard-react-admin-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/crm-dashboard-react-admin-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('REACT', [
|
||||
'providerRootDirectory' => './react/react-admin',
|
||||
'installCommand' => 'pnpm install',
|
||||
'buildCommand' => 'pnpm build && pnpm db-seed',
|
||||
'outputDirectory' => './dist',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_ENDPOINT',
|
||||
'description' => 'Endpoint of Appwrite server',
|
||||
'value' => '{apiEndpoint}',
|
||||
'placeholder' => '{apiEndpoint}',
|
||||
'required' => true,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_PROJECT_ID',
|
||||
'description' => 'Your Appwrite project ID',
|
||||
'value' => '{projectId}',
|
||||
'placeholder' => '{projectId}',
|
||||
'required' => true,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'APPWRITE_API_KEY',
|
||||
'description' => 'Your Appwrite API key (for seeding only)',
|
||||
'value' => '',
|
||||
'placeholder' => 'a0b1...',
|
||||
'required' => true,
|
||||
'type' => 'password'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_DATABASE_ID',
|
||||
'description' => 'Database ID (default: admin)',
|
||||
'value' => 'admin',
|
||||
'placeholder' => 'admin',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_REVIEWS',
|
||||
'description' => 'Table ID for reviews table',
|
||||
'value' => 'reviews',
|
||||
'placeholder' => 'reviews',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_INVOICES',
|
||||
'description' => 'Table ID for invoices table',
|
||||
'value' => 'invoices',
|
||||
'placeholder' => 'invoices',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_ORDERS',
|
||||
'description' => 'Table ID for orders table',
|
||||
'value' => 'orders',
|
||||
'placeholder' => 'orders',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_PRODUCTS',
|
||||
'description' => 'Table ID for products table',
|
||||
'value' => 'products',
|
||||
'placeholder' => 'products',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_CATEGORIES',
|
||||
'description' => 'Table ID for categories table',
|
||||
'value' => 'categories',
|
||||
'placeholder' => 'categories',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_CUSTOMERS',
|
||||
'description' => 'Table ID for customers table',
|
||||
'value' => 'customers',
|
||||
'placeholder' => 'customers',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'job-applications-formspree',
|
||||
'name' => 'Job applications form with Formspree',
|
||||
'tagline' => 'A simple form submission template using Formspree.',
|
||||
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::FORMS],
|
||||
'screenshotDark' => $url . '/images/sites/templates/job-applications-formspree-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/job-applications-formspree-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('REACT', [
|
||||
'providerRootDirectory' => './react/formspree',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => 'VITE_FORMSPREE_FORM_ID',
|
||||
'description' => 'Your Formspree form ID',
|
||||
'value' => '',
|
||||
'placeholder' => 'xrgkpqld',
|
||||
'required' => true,
|
||||
'type' => 'text'
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
@@ -81,7 +81,7 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_DOMAIN',
|
||||
'description' => 'Your Appwrite domain address. When setting a public suffix domain, Appwrite will attempt to issue a valid SSL certificate automatically. When used with a dev domain, Appwrite will assign a self-signed SSL certificate. The default value is \'localhost\'.',
|
||||
'description' => 'Your Appwrite domain address. When setting a public suffix domain, Appwrite will attempt to issue a valid SSL certificate automatically. When used with a dev domain, Appwrite will assign a self-signed SSL certificate. The default value is \'localhost\'. Multiple domains can be separated by commas.',
|
||||
'introduction' => '',
|
||||
'default' => 'localhost',
|
||||
'required' => true,
|
||||
@@ -357,6 +357,15 @@ return [
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_TRUSTED_HEADERS',
|
||||
'description' => 'This option allows you to set the list of trusted headers, the value is a comma‑separated list of HTTP header names, evaluated left-to-right for the first valid IP. Header names are treated case-insensitively.',
|
||||
'introduction' => '1.8.0',
|
||||
'default' => 'x-forwarded-for',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
]
|
||||
],
|
||||
],
|
||||
@@ -952,6 +961,16 @@ return [
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_BROWSER_HOST',
|
||||
'description' => 'The host used by Appwrite to communicate with the browser service for screenshots.',
|
||||
'introduction' => '1.8.0',
|
||||
'default' => 'http://appwrite-browser:3000/v1',
|
||||
'required' => false,
|
||||
'overwrite' => true,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_EXECUTOR_RUNTIME_NETWORK',
|
||||
'description' => 'Deprecated with 0.14.0, use \'OPEN_RUNTIMES_NETWORK\' instead.',
|
||||
|
||||
+715
-1254
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -23,6 +24,8 @@ use Utopia\Fetch\Client;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\HexColor;
|
||||
use Utopia\Validator\Range;
|
||||
@@ -93,8 +96,8 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
|
||||
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
|
||||
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
|
||||
|
||||
$oAuthProviders = Config::getParam('oAuthProviders');
|
||||
$className = $oAuthProviders[$provider]['class'];
|
||||
if (!\class_exists($className)) {
|
||||
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
|
||||
}
|
||||
@@ -175,7 +178,7 @@ App::get('/v1/avatars/credit-cards/:code')
|
||||
group: null,
|
||||
name: 'getCreditCard',
|
||||
description: '/docs/references/avatars/get-credit-card.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
@@ -203,7 +206,7 @@ App::get('/v1/avatars/browsers/:code')
|
||||
group: null,
|
||||
name: 'getBrowser',
|
||||
description: '/docs/references/avatars/get-browser.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
@@ -231,7 +234,7 @@ App::get('/v1/avatars/flags/:code')
|
||||
group: null,
|
||||
name: 'getFlag',
|
||||
description: '/docs/references/avatars/get-flag.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
@@ -259,7 +262,7 @@ App::get('/v1/avatars/image')
|
||||
group: null,
|
||||
name: 'getImage',
|
||||
description: '/docs/references/avatars/get-image.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
@@ -330,7 +333,7 @@ App::get('/v1/avatars/favicon')
|
||||
group: null,
|
||||
name: 'getFavicon',
|
||||
description: '/docs/references/avatars/get-favicon.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
@@ -504,7 +507,7 @@ App::get('/v1/avatars/qr')
|
||||
group: null,
|
||||
name: 'getQR',
|
||||
description: '/docs/references/avatars/get-qr.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
@@ -554,7 +557,7 @@ App::get('/v1/avatars/initials')
|
||||
group: null,
|
||||
name: 'getInitials',
|
||||
description: '/docs/references/avatars/get-initials.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
@@ -635,6 +638,187 @@ App::get('/v1/avatars/initials')
|
||||
->file($image->getImageBlob());
|
||||
});
|
||||
|
||||
App::get('/v1/avatars/screenshots')
|
||||
->desc('Get webpage screenshot')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('usage.metric', METRIC_AVATARS_SCREENSHOTS_GENERATED)
|
||||
->label('abuse-limit', 60)
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'avatar/screenshot')
|
||||
->label('cache.resource', 'screenshot/{request.url}/{request.width}/{request.height}/{request.scale}/{request.theme}/{request.userAgent}/{request.fullpage}/{request.locale}/{request.timezone}/{request.latitude}/{request.longitude}/{request.accuracy}/{request.touch}/{request.permissions}/{request.sleep}/{request.quality}/{request.output}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getScreenshot',
|
||||
description: '/docs/references/avatars/get-screenshot.md',
|
||||
auth: [AuthType::ADMIN, 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('url', '', new URL(['http', 'https']), 'Website URL which you want to capture.', example: 'https://example.com')
|
||||
->param('headers', [], new Assoc(), 'HTTP headers to send with the browser request. Defaults to empty.', true, example: '{"Authorization":"Bearer token123","X-Custom-Header":"value"}')
|
||||
->param('viewportWidth', 1280, new Range(1, 1920), 'Browser viewport width. Pass an integer between 1 to 1920. Defaults to 1280.', true, example: '1920')
|
||||
->param('viewportHeight', 720, new Range(1, 1080), 'Browser viewport height. Pass an integer between 1 to 1080. Defaults to 720.', true, example: '1080')
|
||||
->param('scale', 1, new Range(0.1, 3, Range::TYPE_FLOAT), 'Browser scale factor. Pass a number between 0.1 to 3. Defaults to 1.', true, example: '2')
|
||||
->param('theme', 'light', new WhiteList(['light', 'dark']), 'Browser theme. Pass "light" or "dark". Defaults to "light".', true, example: 'dark')
|
||||
->param('userAgent', '', new Text(512), 'Custom user agent string. Defaults to browser default.', true, example: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15')
|
||||
->param('fullpage', false, new Boolean(true), 'Capture full page scroll. Pass 0 for viewport only, or 1 for full page. Defaults to 0.', true, example: 'true')
|
||||
->param('locale', '', new Text(10), 'Browser locale (e.g., "en-US", "fr-FR"). Defaults to browser default.', true, example: 'en-US')
|
||||
->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true, example: 'america/new_york')
|
||||
->param('latitude', 0, new Range(-90, 90, Range::TYPE_FLOAT), 'Geolocation latitude. Pass a number between -90 to 90. Defaults to 0.', true, example: '37.7749')
|
||||
->param('longitude', 0, new Range(-180, 180, Range::TYPE_FLOAT), 'Geolocation longitude. Pass a number between -180 to 180. Defaults to 0.', true, example: '-122.4194')
|
||||
->param('accuracy', 0, new Range(0, 100000, Range::TYPE_FLOAT), 'Geolocation accuracy in meters. Pass a number between 0 to 100000. Defaults to 0.', true, example: '100')
|
||||
->param('touch', false, new Boolean(true), 'Enable touch support. Pass 0 for no touch, or 1 for touch enabled. Defaults to 0.', true, example: 'true')
|
||||
->param('permissions', [], new ArrayList(new WhiteList(['geolocation', 'camera', 'microphone', 'notifications', 'midi', 'push', 'clipboard-read', 'clipboard-write', 'payment-handler', 'usb', 'bluetooth', 'accelerometer', 'gyroscope', 'magnetometer', 'ambient-light-sensor', 'background-sync', 'persistent-storage', 'screen-wake-lock', 'web-share', 'xr-spatial-tracking'])), 'Browser permissions to grant. Pass an array of permission names like ["geolocation", "camera", "microphone"]. Defaults to empty.', true, example: '["geolocation","notifications"]')
|
||||
->param('sleep', 0, new Range(0, 10), 'Wait time in seconds before taking the screenshot. Pass an integer between 0 to 10. Defaults to 0.', true, example: '3')
|
||||
->param('width', 0, new Range(0, 2000), 'Output image width. Pass 0 to use original width, or an integer between 1 to 2000. Defaults to 0 (original width).', true, example: '800')
|
||||
->param('height', 0, new Range(0, 2000), 'Output image height. Pass 0 to use original height, or an integer between 1 to 2000. Defaults to 0 (original height).', true, example: '600')
|
||||
->param('quality', -1, new Range(-1, 100), 'Screenshot quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true, example: '85')
|
||||
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true, example: 'jpeg')
|
||||
->inject('response')
|
||||
->inject('queueForStatsUsage')
|
||||
->action(function (string $url, array $headers, int $viewportWidth, int $viewportHeight, float $scale, string $theme, string $userAgent, bool $fullpage, string $locale, string $timezone, float $latitude, float $longitude, float $accuracy, bool $touch, array $permissions, int $sleep, int $width, int $height, int $quality, string $output, Response $response, StatsUsage $queueForStatsUsage) {
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
$domain = new Domain(\parse_url($url, PHP_URL_HOST));
|
||||
|
||||
if (!$domain->isKnown()) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$client = new Client();
|
||||
$client->setTimeout(30);
|
||||
$client->addHeader('content-type', Client::CONTENT_TYPE_APPLICATION_JSON);
|
||||
|
||||
// Convert indexed array to empty array (should not happen due to Assoc validator)
|
||||
if (is_array($headers) && count($headers) > 0 && array_keys($headers) === range(0, count($headers) - 1)) {
|
||||
$headers = [];
|
||||
}
|
||||
|
||||
// Create a new object to ensure proper JSON serialization
|
||||
$headersObject = new \stdClass();
|
||||
foreach ($headers as $key => $value) {
|
||||
$headersObject->$key = $value;
|
||||
}
|
||||
|
||||
// Create the config with headers as an object
|
||||
// The custom browser service accepts: url, theme, headers, sleep, viewport, userAgent, fullPage, locale, timezoneId, geolocation, hasTouch, scale
|
||||
$config = [
|
||||
'url' => $url,
|
||||
'theme' => $theme,
|
||||
'headers' => $headersObject,
|
||||
'sleep' => $sleep * 1000, // Convert seconds to milliseconds
|
||||
'waitUntil' => 'load',
|
||||
'viewport' => [
|
||||
'width' => $viewportWidth,
|
||||
'height' => $viewportHeight
|
||||
]
|
||||
];
|
||||
|
||||
// Add scale if not default
|
||||
if ($scale != 1) {
|
||||
$config['deviceScaleFactor'] = $scale;
|
||||
}
|
||||
|
||||
// Add optional parameters that were set, preserving arrays as arrays
|
||||
if (!empty($userAgent)) {
|
||||
$config['userAgent'] = $userAgent;
|
||||
}
|
||||
|
||||
if ($fullpage) {
|
||||
$config['fullPage'] = true;
|
||||
}
|
||||
|
||||
if (!empty($locale)) {
|
||||
$config['locale'] = $locale;
|
||||
}
|
||||
|
||||
if (!empty($timezone)) {
|
||||
$config['timezoneId'] = $timezone;
|
||||
}
|
||||
|
||||
// Add geolocation if any coordinates are provided
|
||||
if ($latitude != 0 || $longitude != 0) {
|
||||
$config['geolocation'] = [
|
||||
'latitude' => $latitude,
|
||||
'longitude' => $longitude,
|
||||
'accuracy' => $accuracy
|
||||
];
|
||||
}
|
||||
|
||||
if ($touch) {
|
||||
$config['hasTouch'] = true;
|
||||
}
|
||||
|
||||
// Add permissions if provided (preserve as array)
|
||||
if (!empty($permissions)) {
|
||||
$config['permissions'] = $permissions; // Keep as array
|
||||
}
|
||||
|
||||
try {
|
||||
$browserEndpoint = System::getEnv('_APP_BROWSER_HOST', 'http://appwrite-browser:3000/v1');
|
||||
|
||||
$fetchResponse = $client->fetch(
|
||||
url: $browserEndpoint . '/screenshots',
|
||||
method: 'POST',
|
||||
body: $config
|
||||
);
|
||||
|
||||
if ($fetchResponse->getStatusCode() >= 400) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED, 'Screenshot service failed: ' . $fetchResponse->getBody());
|
||||
}
|
||||
|
||||
$screenshot = $fetchResponse->getBody();
|
||||
|
||||
if (empty($screenshot)) {
|
||||
throw new Exception(Exception::AVATAR_IMAGE_NOT_FOUND, 'Screenshot not generated');
|
||||
}
|
||||
|
||||
// Determine if image processing is needed
|
||||
$needsProcessing = ($width > 0 || $height > 0) || $quality !== -1 || !empty($output);
|
||||
|
||||
if ($needsProcessing) {
|
||||
// Process image with cropping, quality adjustment, or format conversion
|
||||
$image = new Image($screenshot);
|
||||
|
||||
$image->crop($width, $height);
|
||||
|
||||
$output = $output ?: 'png'; // Default to PNG if not specified
|
||||
$resizedScreenshot = $image->output($output, $quality);
|
||||
unset($image);
|
||||
} else {
|
||||
// Return original screenshot without processing
|
||||
$resizedScreenshot = $screenshot;
|
||||
$output = 'png'; // Screenshots are typically PNG by default
|
||||
}
|
||||
|
||||
// Set content type based on output format
|
||||
$outputs = Config::getParam('storage-outputs');
|
||||
$contentType = $outputs[$output] ?? $outputs['png'];
|
||||
|
||||
$queueForStatsUsage->addMetric(METRIC_AVATARS_SCREENSHOTS_GENERATED, 1);
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType($contentType)
|
||||
->file($resizedScreenshot);
|
||||
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED, 'Screenshot generation failed: ' . $th->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
App::get('/v1/cards/cloud')
|
||||
->desc('Get front Of Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
App::init()
|
||||
->groups(['console'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
if ($project->getId() !== 'console') {
|
||||
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
App::get('/v1/console/variables')
|
||||
->desc('Get variables')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'console',
|
||||
group: 'console',
|
||||
name: 'variables',
|
||||
description: '/docs/references/console/variables.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_CONSOLE_VARIABLES,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$validator = new Domain(System::getEnv('_APP_DOMAIN'));
|
||||
$isDomainValid = !empty(System::getEnv('_APP_DOMAIN', '')) && $validator->isKnown() && !$validator->isTest();
|
||||
|
||||
$validator = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME'));
|
||||
$isCNAMEValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')) && $validator->isKnown() && !$validator->isTest();
|
||||
|
||||
$validator = new IP(IP::V4);
|
||||
$isAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_A', '')) && ($validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_A')));
|
||||
|
||||
$validator = new IP(IP::V6);
|
||||
$isAAAAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_AAAA', '')) && $validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA'));
|
||||
|
||||
$isDomainEnabled = $isDomainValid && (
|
||||
$isAAAAValid || $isAValid || $isCNAMEValid
|
||||
);
|
||||
|
||||
$isVcsEnabled = !empty(System::getEnv('_APP_VCS_GITHUB_APP_NAME', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_APP_ID', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''));
|
||||
|
||||
$isAssistantEnabled = !empty(System::getEnv('_APP_ASSISTANT_OPENAI_API_KEY', ''));
|
||||
|
||||
$variables = new Document([
|
||||
'_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'),
|
||||
'_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'),
|
||||
'_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'),
|
||||
// Combine CAA domain with most common flags and tag (no parameters)
|
||||
'_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"',
|
||||
'_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'),
|
||||
'_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'),
|
||||
'_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'),
|
||||
'_APP_VCS_ENABLED' => $isVcsEnabled,
|
||||
'_APP_DOMAIN_ENABLED' => $isDomainEnabled,
|
||||
'_APP_ASSISTANT_ENABLED' => $isAssistantEnabled,
|
||||
'_APP_DOMAIN_SITES' => System::getEnv('_APP_DOMAIN_SITES'),
|
||||
'_APP_DOMAIN_FUNCTIONS' => System::getEnv('_APP_DOMAIN_FUNCTIONS'),
|
||||
'_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS'),
|
||||
'_APP_DOMAINS_NAMESERVERS' => System::getEnv('_APP_DOMAINS_NAMESERVERS'),
|
||||
]);
|
||||
|
||||
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
|
||||
});
|
||||
|
||||
App::post('/v1/console/assistant')
|
||||
->desc('Create assistant query')
|
||||
->groups(['api', 'assistant'])
|
||||
->label('scope', 'assistant.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'assistant',
|
||||
group: 'console',
|
||||
name: 'chat',
|
||||
description: '/docs/references/assistant/chat.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::TEXT
|
||||
))
|
||||
->label('abuse-limit', 15)
|
||||
->label('abuse-key', 'userId:{userId}')
|
||||
->param('prompt', '', new Text(2000), 'Prompt. A string containing questions asked to the AI assistant.')
|
||||
->inject('response')
|
||||
->action(function (string $prompt, Response $response) {
|
||||
$ch = curl_init('http://appwrite-assistant:3003/v1/models/assistant/prompt');
|
||||
$responseHeaders = [];
|
||||
$query = json_encode(['prompt' => $prompt]);
|
||||
$headers = ['accept: text/event-stream'];
|
||||
$handleEvent = function ($ch, $data) use ($response) {
|
||||
$response->chunk($data);
|
||||
|
||||
return \strlen($data);
|
||||
};
|
||||
|
||||
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $handleEvent);
|
||||
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 9000);
|
||||
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
|
||||
$len = strlen($header);
|
||||
$header = explode(':', $header, 2);
|
||||
|
||||
if (count($header) < 2) { // ignore invalid headers
|
||||
return $len;
|
||||
}
|
||||
|
||||
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
|
||||
|
||||
return $len;
|
||||
});
|
||||
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
|
||||
|
||||
curl_exec($ch);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
$response->chunk('', true);
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\GraphQL\Promises\Adapter;
|
||||
@@ -9,6 +8,7 @@ use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use GraphQL\Error\DebugFlag;
|
||||
@@ -32,7 +32,7 @@ App::init()
|
||||
if (
|
||||
array_key_exists('graphql', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['graphql']
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
@@ -46,7 +46,7 @@ App::get('/v1/graphql')
|
||||
namespace: 'graphql',
|
||||
group: 'graphql',
|
||||
name: 'get',
|
||||
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
hide: true,
|
||||
description: '/docs/references/graphql/get.md',
|
||||
responses: [
|
||||
@@ -93,7 +93,7 @@ App::post('/v1/graphql/mutation')
|
||||
namespace: 'graphql',
|
||||
group: 'graphql',
|
||||
name: 'mutation',
|
||||
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
description: '/docs/references/graphql/post.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
@@ -144,7 +144,7 @@ App::post('/v1/graphql')
|
||||
namespace: 'graphql',
|
||||
group: 'graphql',
|
||||
name: 'query',
|
||||
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
description: '/docs/references/graphql/post.md',
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
|
||||
@@ -50,7 +50,7 @@ App::get('/v1/health')
|
||||
group: 'health',
|
||||
name: 'get',
|
||||
description: '/docs/references/health/get.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -89,7 +89,7 @@ App::get('/v1/health/db')
|
||||
group: 'health',
|
||||
name: 'getDB',
|
||||
description: '/docs/references/health/get-db.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -150,7 +150,7 @@ App::get('/v1/health/cache')
|
||||
group: 'health',
|
||||
name: 'getCache',
|
||||
description: '/docs/references/health/get-cache.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -210,7 +210,7 @@ App::get('/v1/health/pubsub')
|
||||
group: 'health',
|
||||
name: 'getPubSub',
|
||||
description: '/docs/references/health/get-pubsub.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -270,7 +270,7 @@ App::get('/v1/health/time')
|
||||
group: 'health',
|
||||
name: 'getTime',
|
||||
description: '/docs/references/health/get-time.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -334,7 +334,7 @@ App::get('/v1/health/queue/webhooks')
|
||||
group: 'queue',
|
||||
name: 'getQueueWebhooks',
|
||||
description: '/docs/references/health/get-queue-webhooks.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -367,7 +367,7 @@ App::get('/v1/health/queue/logs')
|
||||
group: 'queue',
|
||||
name: 'getQueueLogs',
|
||||
description: '/docs/references/health/get-queue-logs.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -400,7 +400,7 @@ App::get('/v1/health/certificate')
|
||||
group: 'health',
|
||||
name: 'getCertificate',
|
||||
description: '/docs/references/health/get-certificate.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -457,7 +457,7 @@ App::get('/v1/health/queue/certificates')
|
||||
group: 'queue',
|
||||
name: 'getQueueCertificates',
|
||||
description: '/docs/references/health/get-queue-certificates.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -490,7 +490,7 @@ App::get('/v1/health/queue/builds')
|
||||
group: 'queue',
|
||||
name: 'getQueueBuilds',
|
||||
description: '/docs/references/health/get-queue-builds.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -523,7 +523,7 @@ App::get('/v1/health/queue/databases')
|
||||
group: 'queue',
|
||||
name: 'getQueueDatabases',
|
||||
description: '/docs/references/health/get-queue-databases.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -556,7 +556,7 @@ App::get('/v1/health/queue/deletes')
|
||||
group: 'queue',
|
||||
name: 'getQueueDeletes',
|
||||
description: '/docs/references/health/get-queue-deletes.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -589,7 +589,7 @@ App::get('/v1/health/queue/mails')
|
||||
group: 'queue',
|
||||
name: 'getQueueMails',
|
||||
description: '/docs/references/health/get-queue-mails.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -622,7 +622,7 @@ App::get('/v1/health/queue/messaging')
|
||||
group: 'queue',
|
||||
name: 'getQueueMessaging',
|
||||
description: '/docs/references/health/get-queue-messaging.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -655,7 +655,7 @@ App::get('/v1/health/queue/migrations')
|
||||
group: 'queue',
|
||||
name: 'getQueueMigrations',
|
||||
description: '/docs/references/health/get-queue-migrations.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -688,7 +688,7 @@ App::get('/v1/health/queue/functions')
|
||||
group: 'queue',
|
||||
name: 'getQueueFunctions',
|
||||
description: '/docs/references/health/get-queue-functions.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -721,7 +721,7 @@ App::get('/v1/health/queue/stats-resources')
|
||||
group: 'queue',
|
||||
name: 'getQueueStatsResources',
|
||||
description: '/docs/references/health/get-queue-stats-resources.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -754,7 +754,7 @@ App::get('/v1/health/queue/stats-usage')
|
||||
group: 'queue',
|
||||
name: 'getQueueUsage',
|
||||
description: '/docs/references/health/get-queue-stats-usage.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -787,7 +787,7 @@ App::get('/v1/health/storage/local')
|
||||
group: 'storage',
|
||||
name: 'getStorageLocal',
|
||||
description: '/docs/references/health/get-storage-local.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -837,7 +837,7 @@ App::get('/v1/health/storage')
|
||||
group: 'storage',
|
||||
name: 'getStorage',
|
||||
description: '/docs/references/health/get-storage.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -889,7 +889,7 @@ App::get('/v1/health/anti-virus')
|
||||
group: 'health',
|
||||
name: 'getAntivirus',
|
||||
description: '/docs/references/health/get-storage-anti-virus.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -935,7 +935,7 @@ App::get('/v1/health/queue/failed/:name')
|
||||
group: 'queue',
|
||||
name: 'getFailedJobs',
|
||||
description: '/docs/references/health/get-failed-queue-jobs.md',
|
||||
auth: [AuthType::KEY],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -945,18 +945,18 @@ App::get('/v1/health/queue/failed/:name')
|
||||
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::STATS_RESOURCES_QUEUE_NAME,
|
||||
Event::STATS_USAGE_QUEUE_NAME,
|
||||
Event::WEBHOOK_QUEUE_NAME,
|
||||
Event::CERTIFICATES_QUEUE_NAME,
|
||||
Event::BUILDS_QUEUE_NAME,
|
||||
Event::MESSAGING_QUEUE_NAME,
|
||||
Event::MIGRATIONS_QUEUE_NAME
|
||||
System::getEnv('_APP_DATABASE_QUEUE_NAME', Event::DATABASE_QUEUE_NAME),
|
||||
System::getEnv('_APP_DELETE_QUEUE_NAME', Event::DELETE_QUEUE_NAME),
|
||||
System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME),
|
||||
System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME),
|
||||
System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME),
|
||||
System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME),
|
||||
System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME),
|
||||
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME),
|
||||
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME),
|
||||
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME),
|
||||
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME),
|
||||
System::getEnv('_APP_MIGRATIONS_QUEUE_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)
|
||||
->inject('response')
|
||||
@@ -993,18 +993,18 @@ App::get('/v1/health/queue/failed/:name')
|
||||
|
||||
/** @var Event $queue */
|
||||
$queue = match ($name) {
|
||||
Event::DATABASE_QUEUE_NAME => $queueForDatabase,
|
||||
Event::DELETE_QUEUE_NAME => $queueForDeletes,
|
||||
Event::AUDITS_QUEUE_NAME => $queueForAudits,
|
||||
Event::MAILS_QUEUE_NAME => $queueForMails,
|
||||
Event::FUNCTIONS_QUEUE_NAME => $queueForFunctions,
|
||||
Event::STATS_RESOURCES_QUEUE_NAME => $queueForStatsResources,
|
||||
Event::STATS_USAGE_QUEUE_NAME => $queueForStatsUsage,
|
||||
Event::WEBHOOK_QUEUE_NAME => $queueForWebhooks,
|
||||
Event::CERTIFICATES_QUEUE_NAME => $queueForCertificates,
|
||||
Event::BUILDS_QUEUE_NAME => $queueForBuilds,
|
||||
Event::MESSAGING_QUEUE_NAME => $queueForMessaging,
|
||||
Event::MIGRATIONS_QUEUE_NAME => $queueForMigrations,
|
||||
System::getEnv('_APP_DATABASE_QUEUE_NAME', Event::DATABASE_QUEUE_NAME) => $queueForDatabase,
|
||||
System::getEnv('_APP_DELETE_QUEUE_NAME', Event::DELETE_QUEUE_NAME) => $queueForDeletes,
|
||||
System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME) => $queueForAudits,
|
||||
System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME) => $queueForMails,
|
||||
System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME) => $queueForFunctions,
|
||||
System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME) => $queueForStatsResources,
|
||||
System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME) => $queueForStatsUsage,
|
||||
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME) => $queueForWebhooks,
|
||||
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME) => $queueForCertificates,
|
||||
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME) => $queueForBuilds,
|
||||
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME) => $queueForMessaging,
|
||||
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME) => $queueForMigrations,
|
||||
};
|
||||
$failed = $queue->getSize(failed: true);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ App::get('/v1/locale')
|
||||
group: null,
|
||||
name: 'get',
|
||||
description: '/docs/references/locale/get-locale.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -37,7 +37,6 @@ App::get('/v1/locale')
|
||||
$currencies = Config::getParam('locale-currencies');
|
||||
$output = [];
|
||||
$ip = $request->getIP();
|
||||
$time = (60 * 60 * 24 * 45); // 45 days cache
|
||||
|
||||
$output['ip'] = $ip;
|
||||
|
||||
@@ -68,10 +67,6 @@ App::get('/v1/locale')
|
||||
$output['currency'] = $currency;
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'public, max-age=' . $time)
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
;
|
||||
$response->dynamic(new Document($output), Response::MODEL_LOCALE);
|
||||
});
|
||||
|
||||
@@ -84,7 +79,7 @@ App::get('/v1/locale/codes')
|
||||
group: null,
|
||||
name: 'listCodes',
|
||||
description: '/docs/references/locale/list-locale-codes.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -110,7 +105,7 @@ App::get('/v1/locale/countries')
|
||||
group: null,
|
||||
name: 'listCountries',
|
||||
description: '/docs/references/locale/list-countries.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -147,7 +142,7 @@ App::get('/v1/locale/countries/eu')
|
||||
group: null,
|
||||
name: 'listCountriesEU',
|
||||
description: '/docs/references/locale/list-countries-eu.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -186,7 +181,7 @@ App::get('/v1/locale/countries/phones')
|
||||
group: null,
|
||||
name: 'listCountriesPhones',
|
||||
description: '/docs/references/locale/list-countries-phones.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -224,7 +219,7 @@ App::get('/v1/locale/continents')
|
||||
group: null,
|
||||
name: 'listContinents',
|
||||
description: '/docs/references/locale/list-continents.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -260,7 +255,7 @@ App::get('/v1/locale/currencies')
|
||||
group: null,
|
||||
name: 'listCurrencies',
|
||||
description: '/docs/references/locale/list-currencies.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -287,7 +282,7 @@ App::get('/v1/locale/languages')
|
||||
group: null,
|
||||
name: 'listLanguages',
|
||||
description: '/docs/references/locale/list-languages.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
|
||||
+315
-123
@@ -49,6 +49,7 @@ use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\JSON;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -80,12 +81,12 @@ App::post('/v1/messaging/providers/mailgun')
|
||||
->param('name', '', new Text(128), 'Provider name.')
|
||||
->param('apiKey', '', new Text(0), 'Mailgun API Key.', true)
|
||||
->param('domain', '', new Text(0), 'Mailgun Domain.', true)
|
||||
->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true)
|
||||
->param('isEuRegion', null, new Nullable(new Boolean()), 'Set as EU region.', true)
|
||||
->param('fromName', '', new Text(128, 0), 'Sender Name.', true)
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128, 0), 'Name set in the reply to field for the mail. Default value is sender name. Reply to name must have reply to email as well.', true)
|
||||
->param('replyToEmail', '', new Email(), 'Email set in the reply to field for the mail. Default value is sender email. Reply to email must have reply to name as well.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -177,7 +178,7 @@ App::post('/v1/messaging/providers/sendgrid')
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128, 0), 'Name set in the reply to field for the mail. Default value is sender name.', true)
|
||||
->param('replyToEmail', '', new Email(), 'Email set in the reply to field for the mail. Default value is sender email.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -231,6 +232,88 @@ App::post('/v1/messaging/providers/sendgrid')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/providers/resend')
|
||||
->desc('Create Resend provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
->label('audits.resource', 'provider/{response.$id}')
|
||||
->label('event', 'providers.[providerId].create')
|
||||
->label('scope', 'providers.write')
|
||||
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'messaging',
|
||||
group: 'providers',
|
||||
name: 'createResendProvider',
|
||||
description: '/docs/references/messaging/create-resend-provider.md',
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_PROVIDER,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('providerId', '', new CustomId(), 'Provider 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), 'Provider name.')
|
||||
->param('apiKey', '', new Text(0), 'Resend API key.', true)
|
||||
->param('fromName', '', new Text(128, 0), 'Sender Name.', true)
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128, 0), 'Name set in the reply to field for the mail. Default value is sender name.', true)
|
||||
->param('replyToEmail', '', new Email(), 'Email set in the reply to field for the mail. Default value is sender email.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (string $providerId, string $name, string $apiKey, string $fromName, string $fromEmail, string $replyToName, string $replyToEmail, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
$providerId = $providerId == 'unique()' ? ID::unique() : $providerId;
|
||||
|
||||
$credentials = [];
|
||||
|
||||
if (!empty($apiKey)) {
|
||||
$credentials['apiKey'] = $apiKey;
|
||||
}
|
||||
|
||||
$options = [
|
||||
'fromName' => $fromName,
|
||||
'fromEmail' => $fromEmail,
|
||||
'replyToName' => $replyToName,
|
||||
'replyToEmail' => $replyToEmail,
|
||||
];
|
||||
|
||||
if (
|
||||
$enabled === true
|
||||
&& !empty($fromEmail)
|
||||
&& \array_key_exists('apiKey', $credentials)
|
||||
) {
|
||||
$enabled = true;
|
||||
} else {
|
||||
$enabled = false;
|
||||
}
|
||||
|
||||
$provider = new Document([
|
||||
'$id' => $providerId,
|
||||
'name' => $name,
|
||||
'provider' => 'resend',
|
||||
'type' => MESSAGE_TYPE_EMAIL,
|
||||
'enabled' => $enabled,
|
||||
'credentials' => $credentials,
|
||||
'options' => $options,
|
||||
]);
|
||||
|
||||
try {
|
||||
$provider = $dbForProject->createDocument('providers', $provider);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::PROVIDER_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('providerId', $provider->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::post('/v1/messaging/providers/smtp')
|
||||
->desc('Create SMTP provider')
|
||||
->groups(['api', 'messaging'])
|
||||
@@ -256,6 +339,7 @@ App::post('/v1/messaging/providers/smtp')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.createSMTPProvider',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -284,7 +368,7 @@ App::post('/v1/messaging/providers/smtp')
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128, 0), 'Name set in the reply to field for the mail. Default value is sender name.', true)
|
||||
->param('replyToEmail', '', new Email(), 'Email set in the reply to field for the mail. Default value is sender email.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -371,7 +455,7 @@ App::post('/v1/messaging/providers/msg91')
|
||||
->param('templateId', '', new Text(0), 'Msg91 template ID', true)
|
||||
->param('senderId', '', new Text(0), 'Msg91 sender ID.', true)
|
||||
->param('authKey', '', new Text(0), 'Msg91 auth key.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -454,7 +538,7 @@ App::post('/v1/messaging/providers/telesign')
|
||||
->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('customerId', '', new Text(0), 'Telesign customer ID.', true)
|
||||
->param('apiKey', '', new Text(0), 'Telesign API key.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -538,7 +622,7 @@ App::post('/v1/messaging/providers/textmagic')
|
||||
->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('username', '', new Text(0), 'Textmagic username.', true)
|
||||
->param('apiKey', '', new Text(0), 'Textmagic apiKey.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -622,7 +706,7 @@ App::post('/v1/messaging/providers/twilio')
|
||||
->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('accountSid', '', new Text(0), 'Twilio account secret ID.', true)
|
||||
->param('authToken', '', new Text(0), 'Twilio authentication token.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -706,7 +790,7 @@ App::post('/v1/messaging/providers/vonage')
|
||||
->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('apiKey', '', new Text(0), 'Vonage API key.', true)
|
||||
->param('apiSecret', '', new Text(0), 'Vonage API secret.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -789,6 +873,7 @@ App::post('/v1/messaging/providers/fcm')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.createFCMProvider',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -806,8 +891,8 @@ App::post('/v1/messaging/providers/fcm')
|
||||
])
|
||||
->param('providerId', '', new CustomId(), 'Provider 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), 'Provider name.')
|
||||
->param('serviceAccountJSON', null, new JSON(), 'FCM service account JSON.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('serviceAccountJSON', null, new Nullable(new JSON()), 'FCM service account JSON.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -878,6 +963,7 @@ App::post('/v1/messaging/providers/apns')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.createAPNSProvider',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -900,7 +986,7 @@ App::post('/v1/messaging/providers/apns')
|
||||
->param('teamId', '', new Text(0), 'APNS team ID.', true)
|
||||
->param('bundleId', '', new Text(0), 'APNS bundle ID.', true)
|
||||
->param('sandbox', false, new Boolean(), 'Use APNS sandbox environment.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -985,9 +1071,10 @@ App::get('/v1/messaging/providers')
|
||||
))
|
||||
->param('queries', [], new Providers(), '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(', ', Providers::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, Database $dbForProject, Response $response) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -1023,7 +1110,7 @@ App::get('/v1/messaging/providers')
|
||||
}
|
||||
try {
|
||||
$providers = $dbForProject->find('providers', $queries);
|
||||
$total = $dbForProject->count('providers', $queries, APP_LIMIT_COUNT);
|
||||
$total = $includeTotal ? $dbForProject->count('providers', $queries, APP_LIMIT_COUNT) : 0;
|
||||
} 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.");
|
||||
}
|
||||
@@ -1053,11 +1140,13 @@ App::get('/v1/messaging/providers/:providerId/logs')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider 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)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $providerId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->inject('audit')
|
||||
->action(function (string $providerId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
|
||||
$provider = $dbForProject->getDocument('providers', $providerId);
|
||||
|
||||
if ($provider->isEmpty()) {
|
||||
@@ -1070,15 +1159,12 @@ App::get('/v1/messaging/providers/:providerId/logs')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'provider/' . $providerId;
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
|
||||
$output = [];
|
||||
|
||||
foreach ($logs as $i => &$log) {
|
||||
@@ -1125,7 +1211,7 @@ App::get('/v1/messaging/providers/:providerId/logs')
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $audit->countLogsByResource($resource, $queries),
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
@@ -1186,8 +1272,8 @@ App::patch('/v1/messaging/providers/mailgun/:providerId')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('apiKey', '', new Text(0), 'Mailgun API Key.', true)
|
||||
->param('domain', '', new Text(0), 'Mailgun Domain.', true)
|
||||
->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('isEuRegion', null, new Nullable(new Boolean()), 'Set as EU region.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('fromName', '', new Text(128), 'Sender Name.', true)
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128), 'Name set in the reply to field for the mail. Default value is sender name.', true)
|
||||
@@ -1297,7 +1383,7 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('apiKey', '', new Text(0), 'Sendgrid API key.', true)
|
||||
->param('fromName', '', new Text(128), 'Sender Name.', true)
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
@@ -1372,6 +1458,104 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/providers/resend/:providerId')
|
||||
->desc('Update Resend provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.update')
|
||||
->label('audits.resource', 'provider/{response.$id}')
|
||||
->label('event', 'providers.[providerId].update')
|
||||
->label('scope', 'providers.write')
|
||||
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'messaging',
|
||||
group: 'providers',
|
||||
name: 'updateResendProvider',
|
||||
description: '/docs/references/messaging/update-resend-provider.md',
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROVIDER,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('apiKey', '', new Text(0), 'Resend API key.', true)
|
||||
->param('fromName', '', new Text(128), 'Sender Name.', true)
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128), 'Name set in the Reply To field for the mail. Default value is Sender Name.', true)
|
||||
->param('replyToEmail', '', new Text(128), 'Email set in the Reply To field for the mail. Default value is Sender Email.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (string $providerId, string $name, ?bool $enabled, string $apiKey, string $fromName, string $fromEmail, string $replyToName, string $replyToEmail, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
$provider = $dbForProject->getDocument('providers', $providerId);
|
||||
|
||||
if ($provider->isEmpty()) {
|
||||
throw new Exception(Exception::PROVIDER_NOT_FOUND);
|
||||
}
|
||||
$providerAttr = $provider->getAttribute('provider');
|
||||
|
||||
if ($providerAttr !== 'resend') {
|
||||
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
|
||||
}
|
||||
|
||||
if (!empty($name)) {
|
||||
$provider->setAttribute('name', $name);
|
||||
}
|
||||
|
||||
$options = $provider->getAttribute('options');
|
||||
|
||||
if (!empty($fromName)) {
|
||||
$options['fromName'] = $fromName;
|
||||
}
|
||||
|
||||
if (!empty($fromEmail)) {
|
||||
$options['fromEmail'] = $fromEmail;
|
||||
}
|
||||
|
||||
if (!empty($replyToName)) {
|
||||
$options['replyToName'] = $replyToName;
|
||||
}
|
||||
|
||||
if (!empty($replyToEmail)) {
|
||||
$options['replyToEmail'] = $replyToEmail;
|
||||
}
|
||||
|
||||
$provider->setAttribute('options', $options);
|
||||
|
||||
if (!empty($apiKey)) {
|
||||
$provider->setAttribute('credentials', [
|
||||
'apiKey' => $apiKey,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!\is_null($enabled)) {
|
||||
if ($enabled) {
|
||||
if (
|
||||
\array_key_exists('apiKey', $provider->getAttribute('credentials')) &&
|
||||
\array_key_exists('fromEmail', $provider->getAttribute('options'))
|
||||
) {
|
||||
$provider->setAttribute('enabled', true);
|
||||
} else {
|
||||
throw new Exception(Exception::PROVIDER_MISSING_CREDENTIALS);
|
||||
}
|
||||
} else {
|
||||
$provider->setAttribute('enabled', false);
|
||||
}
|
||||
}
|
||||
|
||||
$provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('providerId', $provider->getId());
|
||||
|
||||
$response
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
App::patch('/v1/messaging/providers/smtp/:providerId')
|
||||
->desc('Update SMTP provider')
|
||||
->groups(['api', 'messaging'])
|
||||
@@ -1397,6 +1581,7 @@ App::patch('/v1/messaging/providers/smtp/:providerId')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.updateSMTPProvider',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -1415,17 +1600,17 @@ App::patch('/v1/messaging/providers/smtp/:providerId')
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('host', '', new Text(0), 'SMTP hosts. Either a single hostname or multiple semicolon-delimited hostnames. You can also specify a different port for each host such as `smtp1.example.com:25;smtp2.example.com`. You can also specify encryption type, for example: `tls://smtp1.example.com:587;ssl://smtp2.example.com:465"`. Hosts will be tried in order.', true)
|
||||
->param('port', null, new Range(1, 65535), 'SMTP port.', true)
|
||||
->param('port', null, new Nullable(new Range(1, 65535)), 'SMTP port.', true)
|
||||
->param('username', '', new Text(0), 'Authentication username.', true)
|
||||
->param('password', '', new Text(0), 'Authentication password.', true)
|
||||
->param('encryption', '', new WhiteList(['none', 'ssl', 'tls']), 'Encryption type. Can be \'ssl\' or \'tls\'', true)
|
||||
->param('autoTLS', null, new Boolean(), 'Enable SMTP AutoTLS feature.', true)
|
||||
->param('autoTLS', null, new Nullable(new Boolean()), 'Enable SMTP AutoTLS feature.', true)
|
||||
->param('mailer', '', new Text(0), 'The value to use for the X-Mailer header.', true)
|
||||
->param('fromName', '', new Text(128), 'Sender Name.', true)
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128), 'Name set in the Reply To field for the mail. Default value is Sender Name.', true)
|
||||
->param('replyToEmail', '', new Text(128), 'Email set in the Reply To field for the mail. Default value is Sender Email.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -1543,7 +1728,7 @@ App::patch('/v1/messaging/providers/msg91/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('templateId', '', new Text(0), 'Msg91 template ID.', true)
|
||||
->param('senderId', '', new Text(0), 'Msg91 sender ID.', true)
|
||||
->param('authKey', '', new Text(0), 'Msg91 auth key.', true)
|
||||
@@ -1630,7 +1815,7 @@ App::patch('/v1/messaging/providers/telesign/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('customerId', '', new Text(0), 'Telesign customer ID.', true)
|
||||
->param('apiKey', '', new Text(0), 'Telesign API key.', true)
|
||||
->param('from', '', new Text(256), 'Sender number.', true)
|
||||
@@ -1719,7 +1904,7 @@ App::patch('/v1/messaging/providers/textmagic/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('username', '', new Text(0), 'Textmagic username.', true)
|
||||
->param('apiKey', '', new Text(0), 'Textmagic apiKey.', true)
|
||||
->param('from', '', new Text(256), 'Sender number.', true)
|
||||
@@ -1808,7 +1993,7 @@ App::patch('/v1/messaging/providers/twilio/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('accountSid', '', new Text(0), 'Twilio account secret ID.', true)
|
||||
->param('authToken', '', new Text(0), 'Twilio authentication token.', true)
|
||||
->param('from', '', new Text(256), 'Sender number.', true)
|
||||
@@ -1897,7 +2082,7 @@ App::patch('/v1/messaging/providers/vonage/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('apiKey', '', new Text(0), 'Vonage API key.', true)
|
||||
->param('apiSecret', '', new Text(0), 'Vonage API secret.', true)
|
||||
->param('from', '', new Text(256), 'Sender number.', true)
|
||||
@@ -1988,6 +2173,7 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.updateFCMProvider',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -2005,8 +2191,8 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
|
||||
])
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('serviceAccountJSON', null, new JSON(), 'FCM service account JSON.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('serviceAccountJSON', null, new Nullable(new JSON()), 'FCM service account JSON.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -2083,6 +2269,7 @@ App::patch('/v1/messaging/providers/apns/:providerId')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.updateAPNSProvider',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -2100,12 +2287,12 @@ App::patch('/v1/messaging/providers/apns/:providerId')
|
||||
])
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('authKey', '', new Text(0), 'APNS authentication key.', true)
|
||||
->param('authKeyId', '', new Text(0), 'APNS authentication key ID.', true)
|
||||
->param('teamId', '', new Text(0), 'APNS team ID.', true)
|
||||
->param('bundleId', '', new Text(0), 'APNS bundle ID.', true)
|
||||
->param('sandbox', null, new Boolean(), 'Use APNS sandbox environment.', true)
|
||||
->param('sandbox', null, new Nullable(new Boolean()), 'Use APNS sandbox environment.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -2292,9 +2479,10 @@ App::get('/v1/messaging/topics')
|
||||
))
|
||||
->param('queries', [], new Topics(), '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(', ', Topics::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, Database $dbForProject, Response $response) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -2330,7 +2518,7 @@ App::get('/v1/messaging/topics')
|
||||
}
|
||||
try {
|
||||
$topics = $dbForProject->find('topics', $queries);
|
||||
$total = $dbForProject->count('topics', $queries, APP_LIMIT_COUNT);
|
||||
$total = $includeTotal ? $dbForProject->count('topics', $queries, APP_LIMIT_COUNT) : 0;
|
||||
} 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.");
|
||||
}
|
||||
@@ -2360,11 +2548,13 @@ App::get('/v1/messaging/topics/:topicId/logs')
|
||||
))
|
||||
->param('topicId', '', new UID(), 'Topic 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)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $topicId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->inject('audit')
|
||||
->action(function (string $topicId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
|
||||
$topic = $dbForProject->getDocument('topics', $topicId);
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
@@ -2377,15 +2567,12 @@ App::get('/v1/messaging/topics/:topicId/logs')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'topic/' . $topicId;
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
|
||||
|
||||
$output = [];
|
||||
|
||||
@@ -2433,7 +2620,7 @@ App::get('/v1/messaging/topics/:topicId/logs')
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $audit->countLogsByResource($resource, $queries),
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
@@ -2492,8 +2679,8 @@ App::patch('/v1/messaging/topics/:topicId')
|
||||
]
|
||||
))
|
||||
->param('topicId', '', new UID(), 'Topic ID.')
|
||||
->param('name', null, new Text(128), 'Topic Name.', true)
|
||||
->param('subscribe', null, new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with subscribe permission. By default all users are granted with any subscribe permission. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
|
||||
->param('name', null, new Nullable(new Text(128)), 'Topic Name.', true)
|
||||
->param('subscribe', null, new Nullable(new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE)), 'An array of role strings with subscribe permission. By default all users are granted with any subscribe permission. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -2693,9 +2880,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.')
|
||||
->param('queries', [], new Subscribers(), '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(', ', Providers::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, array $queries, string $search, Database $dbForProject, Response $response) {
|
||||
->action(function (string $topicId, array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -2757,7 +2945,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
$response
|
||||
->dynamic(new Document([
|
||||
'subscribers' => $subscribers,
|
||||
'total' => $dbForProject->count('subscribers', $queries, APP_LIMIT_COUNT),
|
||||
'total' => $includeTotal ? $dbForProject->count('subscribers', $queries, APP_LIMIT_COUNT) : 0,
|
||||
]), Response::MODEL_SUBSCRIBER_LIST);
|
||||
});
|
||||
|
||||
@@ -2781,11 +2969,13 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
|
||||
))
|
||||
->param('subscriberId', '', new UID(), 'Subscriber 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)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $subscriberId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->inject('audit')
|
||||
->action(function (string $subscriberId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
|
||||
$subscriber = $dbForProject->getDocument('subscribers', $subscriberId);
|
||||
|
||||
if ($subscriber->isEmpty()) {
|
||||
@@ -2798,15 +2988,12 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'subscriber/' . $subscriberId;
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
|
||||
|
||||
$output = [];
|
||||
|
||||
@@ -2854,7 +3041,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $audit->countLogsByResource($resource, $queries),
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
@@ -3004,7 +3191,7 @@ App::post('/v1/messaging/messages/email')
|
||||
->param('attachments', [], new ArrayList(new CompoundUID()), 'Array of compound ID strings of bucket IDs and file IDs to be attached to the email. They should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->param('draft', false, new Boolean(), 'Is message a draft', true)
|
||||
->param('html', false, new Boolean(), 'Is content of type HTML', true)
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
@@ -3156,6 +3343,7 @@ App::post('/v1/messaging/messages/sms')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.createSMS',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -3177,7 +3365,7 @@ App::post('/v1/messaging/messages/sms')
|
||||
->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
|
||||
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
|
||||
->param('draft', false, new Boolean(), 'Is message a draft', true)
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
@@ -3300,7 +3488,7 @@ App::post('/v1/messaging/messages/push')
|
||||
->param('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true)
|
||||
->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
|
||||
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
|
||||
->param('data', null, new JSON(), 'Additional key-value pair data for push notification.', true)
|
||||
->param('data', null, new Nullable(new JSON()), 'Additional key-value pair data for push notification.', true)
|
||||
->param('action', '', new Text(256), 'Action for push notification.', true)
|
||||
->param('image', '', new CompoundUID(), 'Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->param('icon', '', new Text(256), 'Icon for push notification. Available only for Android and Web Platform.', true)
|
||||
@@ -3309,7 +3497,7 @@ App::post('/v1/messaging/messages/push')
|
||||
->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true)
|
||||
->param('badge', -1, new Integer(), 'Badge for push notification. Available only for iOS Platform.', true)
|
||||
->param('draft', false, new Boolean(), 'Is message a draft', true)
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->param('contentAvailable', false, new Boolean(), 'If set to true, the notification will be delivered in the background. Available only for iOS Platform.', true)
|
||||
->param('critical', false, new Boolean(), 'If set to true, the notification will be marked as critical. This requires the app to have the critical notification entitlement. Available only for iOS Platform.', true)
|
||||
->param('priority', 'high', new WhiteList(['normal', 'high']), 'Set the notification priority. "normal" will consider device state and may not deliver notifications immediately. "high" will always attempt to immediately deliver the notification.', true)
|
||||
@@ -3319,7 +3507,8 @@ App::post('/v1/messaging/messages/push')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, string $title, string $body, ?array $topics, ?array $users, ?array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, int $badge, bool $draft, ?string $scheduledAt, bool $contentAvailable, bool $critical, string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->inject('platform')
|
||||
->action(function (string $messageId, string $title, string $body, ?array $topics, ?array $users, ?array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, int $badge, bool $draft, ?string $scheduledAt, bool $contentAvailable, bool $critical, string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response, array $platform) {
|
||||
$messageId = $messageId == 'unique()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
@@ -3375,8 +3564,8 @@ App::post('/v1/messaging/messages/push')
|
||||
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$host = System::getEnv('_APP_DOMAIN', 'localhost');
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$endpoint = "$protocol://{$platform['apiHostname']}/v1";
|
||||
|
||||
$scheduleTime = $currentScheduledAt ?? $scheduledAt;
|
||||
if (!\is_null($scheduleTime)) {
|
||||
@@ -3396,7 +3585,7 @@ App::post('/v1/messaging/messages/push')
|
||||
$image = [
|
||||
'bucketId' => $bucket->getId(),
|
||||
'fileId' => $file->getId(),
|
||||
'url' => "{$protocol}://{$host}/v1/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
|
||||
'url' => "{$endpoint}/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -3511,9 +3700,10 @@ App::get('/v1/messaging/messages')
|
||||
))
|
||||
->param('queries', [], new Messages(), '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(', ', Messages::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, Database $dbForProject, Response $response) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -3549,7 +3739,7 @@ App::get('/v1/messaging/messages')
|
||||
}
|
||||
try {
|
||||
$messages = $dbForProject->find('messages', $queries);
|
||||
$total = $dbForProject->count('messages', $queries, APP_LIMIT_COUNT);
|
||||
$total = $includeTotal ? $dbForProject->count('messages', $queries, APP_LIMIT_COUNT) : 0;
|
||||
} 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.");
|
||||
}
|
||||
@@ -3579,11 +3769,13 @@ App::get('/v1/messaging/messages/:messageId/logs')
|
||||
))
|
||||
->param('messageId', '', new UID(), 'Message 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)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $messageId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->inject('audit')
|
||||
->action(function (string $messageId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -3596,15 +3788,12 @@ App::get('/v1/messaging/messages/:messageId/logs')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'message/' . $messageId;
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
|
||||
|
||||
$output = [];
|
||||
|
||||
@@ -3652,7 +3841,7 @@ App::get('/v1/messaging/messages/:messageId/logs')
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $audit->countLogsByResource($resource, $queries),
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
@@ -3677,9 +3866,10 @@ App::get('/v1/messaging/messages/:messageId/targets')
|
||||
))
|
||||
->param('messageId', '', new UID(), 'Message ID.')
|
||||
->param('queries', [], new Targets(), '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(', ', Targets::ALLOWED_ATTRIBUTES), true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $messageId, array $queries, Response $response, Database $dbForProject) {
|
||||
->action(function (string $messageId, array $queries, bool $includeTotal, Response $response, Database $dbForProject) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -3729,7 +3919,7 @@ App::get('/v1/messaging/messages/:messageId/targets')
|
||||
}
|
||||
try {
|
||||
$targets = $dbForProject->find('targets', $queries);
|
||||
$total = $dbForProject->count('targets', $queries, APP_LIMIT_COUNT);
|
||||
$total = $includeTotal ? $dbForProject->count('targets', $queries, APP_LIMIT_COUNT) : 0;
|
||||
} 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.");
|
||||
}
|
||||
@@ -3792,17 +3982,17 @@ App::patch('/v1/messaging/messages/email/:messageId')
|
||||
]
|
||||
))
|
||||
->param('messageId', '', new UID(), 'Message ID.')
|
||||
->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true)
|
||||
->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true)
|
||||
->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true)
|
||||
->param('subject', null, new Text(998), 'Email Subject.', true)
|
||||
->param('content', null, new Text(64230), 'Email Content.', true)
|
||||
->param('draft', null, new Boolean(), 'Is message a draft', true)
|
||||
->param('html', null, new Boolean(), 'Is content of type HTML', true)
|
||||
->param('cc', null, new ArrayList(new UID()), 'Array of target IDs to be added as CC.', true)
|
||||
->param('bcc', null, new ArrayList(new UID()), 'Array of target IDs to be added as BCC.', true)
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->param('attachments', null, new ArrayList(new CompoundUID()), 'Array of compound ID strings of bucket IDs and file IDs to be attached to the email. They should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->param('topics', null, new Nullable(new ArrayList(new UID())), 'List of Topic IDs.', true)
|
||||
->param('users', null, new Nullable(new ArrayList(new UID())), 'List of User IDs.', true)
|
||||
->param('targets', null, new Nullable(new ArrayList(new UID())), 'List of Targets IDs.', true)
|
||||
->param('subject', null, new Nullable(new Text(998)), 'Email Subject.', true)
|
||||
->param('content', null, new Nullable(new Text(64230)), 'Email Content.', true)
|
||||
->param('draft', null, new Nullable(new Boolean()), 'Is message a draft', true)
|
||||
->param('html', null, new Nullable(new Boolean()), 'Is content of type HTML', true)
|
||||
->param('cc', null, new Nullable(new ArrayList(new UID())), 'Array of target IDs to be added as CC.', true)
|
||||
->param('bcc', null, new Nullable(new ArrayList(new UID())), 'Array of target IDs to be added as BCC.', true)
|
||||
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->param('attachments', null, new Nullable(new ArrayList(new CompoundUID())), 'Array of compound ID strings of bucket IDs and file IDs to be attached to the email. They should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
@@ -4002,6 +4192,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'messaging.updateSMS',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'messaging',
|
||||
@@ -4018,12 +4209,12 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
)
|
||||
])
|
||||
->param('messageId', '', new UID(), 'Message ID.')
|
||||
->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true)
|
||||
->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true)
|
||||
->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true)
|
||||
->param('content', null, new Text(64230), 'Email Content.', true)
|
||||
->param('draft', null, new Boolean(), 'Is message a draft', true)
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->param('topics', null, new Nullable(new ArrayList(new UID())), 'List of Topic IDs.', true)
|
||||
->param('users', null, new Nullable(new ArrayList(new UID())), 'List of User IDs.', true)
|
||||
->param('targets', null, new Nullable(new ArrayList(new UID())), 'List of Targets IDs.', true)
|
||||
->param('content', null, new Nullable(new Text(64230)), 'Email Content.', true)
|
||||
->param('draft', null, new Nullable(new Boolean()), 'Is message a draft', true)
|
||||
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
@@ -4180,31 +4371,32 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
]
|
||||
))
|
||||
->param('messageId', '', new UID(), 'Message ID.')
|
||||
->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true)
|
||||
->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true)
|
||||
->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true)
|
||||
->param('title', null, new Text(256), 'Title for push notification.', true)
|
||||
->param('body', null, new Text(64230), 'Body for push notification.', true)
|
||||
->param('data', null, new JSON(), 'Additional Data for push notification.', true)
|
||||
->param('action', null, new Text(256), 'Action for push notification.', true)
|
||||
->param('image', null, new CompoundUID(), 'Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->param('icon', null, new Text(256), 'Icon for push notification. Available only for Android and Web platforms.', true)
|
||||
->param('sound', null, new Text(256), 'Sound for push notification. Available only for Android and iOS platforms.', true)
|
||||
->param('color', null, new Text(256), 'Color for push notification. Available only for Android platforms.', true)
|
||||
->param('tag', null, new Text(256), 'Tag for push notification. Available only for Android platforms.', true)
|
||||
->param('badge', null, new Integer(), 'Badge for push notification. Available only for iOS platforms.', true)
|
||||
->param('draft', null, new Boolean(), 'Is message a draft', true)
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->param('contentAvailable', null, new Boolean(), 'If set to true, the notification will be delivered in the background. Available only for iOS Platform.', true)
|
||||
->param('critical', null, new Boolean(), 'If set to true, the notification will be marked as critical. This requires the app to have the critical notification entitlement. Available only for iOS Platform.', true)
|
||||
->param('priority', null, new WhiteList(['normal', 'high']), 'Set the notification priority. "normal" will consider device battery state and may send notifications later. "high" will always attempt to immediately deliver the notification.', true)
|
||||
->param('topics', null, new Nullable(new ArrayList(new UID())), 'List of Topic IDs.', true)
|
||||
->param('users', null, new Nullable(new ArrayList(new UID())), 'List of User IDs.', true)
|
||||
->param('targets', null, new Nullable(new ArrayList(new UID())), 'List of Targets IDs.', true)
|
||||
->param('title', null, new Nullable(new Text(256)), 'Title for push notification.', true)
|
||||
->param('body', null, new Nullable(new Text(64230)), 'Body for push notification.', true)
|
||||
->param('data', null, new Nullable(new JSON()), 'Additional Data for push notification.', true)
|
||||
->param('action', null, new Nullable(new Text(256)), 'Action for push notification.', true)
|
||||
->param('image', null, new Nullable(new CompoundUID()), 'Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->param('icon', null, new Nullable(new Text(256)), 'Icon for push notification. Available only for Android and Web platforms.', true)
|
||||
->param('sound', null, new Nullable(new Text(256)), 'Sound for push notification. Available only for Android and iOS platforms.', true)
|
||||
->param('color', null, new Nullable(new Text(256)), 'Color for push notification. Available only for Android platforms.', true)
|
||||
->param('tag', null, new Nullable(new Text(256)), 'Tag for push notification. Available only for Android platforms.', true)
|
||||
->param('badge', null, new Nullable(new Integer()), 'Badge for push notification. Available only for iOS platforms.', true)
|
||||
->param('draft', null, new Nullable(new Boolean()), 'Is message a draft', true)
|
||||
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->param('contentAvailable', null, new Nullable(new Boolean()), 'If set to true, the notification will be delivered in the background. Available only for iOS Platform.', true)
|
||||
->param('critical', null, new Nullable(new Boolean()), 'If set to true, the notification will be marked as critical. This requires the app to have the critical notification entitlement. Available only for iOS Platform.', true)
|
||||
->param('priority', null, new Nullable(new WhiteList(['normal', 'high'])), 'Set the notification priority. "normal" will consider device battery state and may send notifications later. "high" will always attempt to immediately deliver the notification.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, ?bool $contentAvailable, ?bool $critical, ?string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->inject('platform')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, ?bool $contentAvailable, ?bool $critical, ?string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response, array $platform) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -4372,9 +4564,6 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$host = System::getEnv('_APP_DOMAIN', 'localhost');
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
|
||||
$scheduleTime = $currentScheduledAt ?? $scheduledAt;
|
||||
if (!\is_null($scheduleTime)) {
|
||||
$expiry = (new \DateTime($scheduleTime))->add(new \DateInterval('P15D'))->format('U');
|
||||
@@ -4390,10 +4579,13 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
'projectId' => $project->getId(),
|
||||
]);
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$endpoint = "$protocol://{$platform['apiHostname']}/v1";
|
||||
|
||||
$pushData['image'] = [
|
||||
'bucketId' => $bucket->getId(),
|
||||
'fileId' => $file->getId(),
|
||||
'url' => "{$protocol}://{$host}/v1/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}"
|
||||
'url' => "{$endpoint}/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Extend\Exception;
|
||||
@@ -20,6 +19,7 @@ use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Queries\Documents;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Migration\Resource;
|
||||
@@ -69,10 +69,11 @@ App::post('/v1/migrations/appwrite')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $endpoint, string $projectId, string $apiKey, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
->action(function (array $resources, string $endpoint, string $projectId, string $apiKey, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
@@ -96,6 +97,7 @@ App::post('/v1/migrations/appwrite')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
|
||||
@@ -128,10 +130,11 @@ App::post('/v1/migrations/firebase')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$serviceAccountData = json_decode($serviceAccount, true);
|
||||
|
||||
if (empty($serviceAccountData)) {
|
||||
@@ -163,6 +166,7 @@ App::post('/v1/migrations/firebase')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
|
||||
@@ -200,10 +204,11 @@ App::post('/v1/migrations/supabase')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
@@ -230,6 +235,7 @@ App::post('/v1/migrations/supabase')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
|
||||
@@ -268,10 +274,11 @@ App::post('/v1/migrations/nhost')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
@@ -299,6 +306,7 @@ App::post('/v1/migrations/nhost')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
|
||||
@@ -307,7 +315,8 @@ App::post('/v1/migrations/nhost')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
App::post('/v1/migrations/csv')
|
||||
App::post('/v1/migrations/csv/imports')
|
||||
->alias('/v1/migrations/csv')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Import documents from a CSV')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -316,8 +325,8 @@ App::post('/v1/migrations/csv')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'migrations',
|
||||
group: null,
|
||||
name: 'createCsvMigration',
|
||||
description: '/docs/references/migrations/migration-csv.md',
|
||||
name: 'createCSVImport',
|
||||
description: '/docs/references/migrations/migration-csv-import.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
@@ -334,16 +343,26 @@ App::post('/v1/migrations/csv')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForImports')
|
||||
->inject('deviceForMigrations')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (string $bucketId, string $fileId, string $resourceId, bool $internalFile, Response $response, Database $dbForProject, Database $dbForPlatform, Document $project, Device $deviceForFiles, Device $deviceForImports, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
if ($internalFile && !$isPrivilegedUser) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
->action(function (
|
||||
string $bucketId,
|
||||
string $fileId,
|
||||
string $resourceId,
|
||||
bool $internalFile,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Document $project,
|
||||
array $platform,
|
||||
Device $deviceForFiles,
|
||||
Device $deviceForMigrations,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
) {
|
||||
$bucket = Authorization::skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
|
||||
if ($internalFile) {
|
||||
return $dbForPlatform->getDocument('buckets', 'default');
|
||||
@@ -351,7 +370,7 @@ App::post('/v1/migrations/csv')
|
||||
return $dbForProject->getDocument('buckets', $bucketId);
|
||||
});
|
||||
|
||||
if ($bucket->isEmpty() || (!$isAPIKey && !$isPrivilegedUser)) {
|
||||
if ($bucket->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -365,18 +384,17 @@ App::post('/v1/migrations/csv')
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
|
||||
}
|
||||
|
||||
// no encryption, compression on files above 20MB.
|
||||
// No encryption or compression on files above 20MB.
|
||||
$hasEncryption = !empty($file->getAttribute('openSSLCipher'));
|
||||
$compression = $file->getAttribute('algorithm', Compression::NONE);
|
||||
$hasCompression = $compression !== Compression::NONE;
|
||||
|
||||
$migrationId = ID::unique();
|
||||
$newPath = $deviceForImports->getPath($migrationId . '_' . $fileId . '.csv');
|
||||
$newPath = $deviceForMigrations->getPath($migrationId . '_' . $fileId . '.csv');
|
||||
|
||||
if ($hasEncryption || $hasCompression) {
|
||||
$source = $deviceForFiles->read($path);
|
||||
|
||||
// 1. decrypt
|
||||
if ($hasEncryption) {
|
||||
$source = OpenSSL::decrypt(
|
||||
$source,
|
||||
@@ -388,7 +406,6 @@ App::post('/v1/migrations/csv')
|
||||
);
|
||||
}
|
||||
|
||||
// 2. decompress
|
||||
if ($hasCompression) {
|
||||
switch ($compression) {
|
||||
case Compression::ZSTD:
|
||||
@@ -400,15 +417,15 @@ App::post('/v1/migrations/csv')
|
||||
}
|
||||
}
|
||||
|
||||
// manual write after decryption and/or decompression
|
||||
if (! $deviceForImports->write($newPath, $source, 'text/csv')) {
|
||||
throw new \Exception("Unable to copy file");
|
||||
// Manual write after decryption and/or decompression
|
||||
if (!$deviceForMigrations->write($newPath, $source, 'text/csv')) {
|
||||
throw new \Exception('Unable to copy file');
|
||||
}
|
||||
} elseif (! $deviceForFiles->transfer($path, $newPath, $deviceForImports)) {
|
||||
throw new \Exception("Unable to copy file");
|
||||
} elseif (!$deviceForFiles->transfer($path, $newPath, $deviceForMigrations)) {
|
||||
throw new \Exception('Unable to copy file');
|
||||
}
|
||||
|
||||
$fileSize = $deviceForImports->getFileSize($newPath);
|
||||
$fileSize = $deviceForMigrations->getFileSize($newPath);
|
||||
$resources = Transfer::extractServices([Transfer::GROUP_DATABASES]);
|
||||
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
@@ -434,6 +451,140 @@ App::post('/v1/migrations/csv')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
App::post('/v1/migrations/csv/exports')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Export documents to CSV')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('event', 'migrations.[migrationId].create')
|
||||
->label('audits.event', 'migration.create')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'migrations',
|
||||
group: null,
|
||||
name: 'createCSVExport',
|
||||
description: '/docs/references/migrations/migration-csv-export.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_ACCEPTED,
|
||||
model: Response::MODEL_MIGRATION,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database to export.')
|
||||
->param('filename', '', new Text(255), 'The name of the file to be created for the export, excluding the .csv extension.')
|
||||
->param('columns', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of attributes to export. If empty, all attributes will be exported. You can use the `*` wildcard to export all attributes from the collection.', true)
|
||||
->param('queries', [], new ArrayList(new Text(0)), 'Array of query strings generated using the Query class provided by the SDK to filter documents to export. [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.', true)
|
||||
->param('delimiter', ',', new Text(1), 'The character that separates each column value. Default is comma.', true)
|
||||
->param('enclosure', '"', new Text(1), 'The character that encloses each column value. Default is double quotes.', true)
|
||||
->param('escape', '"', new Text(1), 'The escape character for the enclosure character. Default is double quotes.', true)
|
||||
->param('header', true, new Boolean(), 'Whether to include the header row with column names. Default is true.', true)
|
||||
->param('notify', true, new Boolean(), 'Set to true to receive an email when the export is complete. Default is true.', true)
|
||||
->inject('user')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (
|
||||
string $resourceId,
|
||||
string $filename,
|
||||
array $columns,
|
||||
array $queries,
|
||||
string $delimiter,
|
||||
string $enclosure,
|
||||
string $escape,
|
||||
bool $header,
|
||||
bool $notify,
|
||||
Document $user,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Document $project,
|
||||
array $platform,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
) {
|
||||
try {
|
||||
$parsedQueries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
|
||||
if ($bucket->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
[$databaseId, $collectionId] = \explode(':', $resourceId, 2);
|
||||
if (empty($databaseId)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
if (empty($collectionId)) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$validator = new Documents(
|
||||
attributes: $collection->getAttribute('attributes', []),
|
||||
indexes: $collection->getAttribute('indexes', []),
|
||||
idAttributeType: $dbForProject->getAdapter()->getIdAttributeType(),
|
||||
);
|
||||
|
||||
if (!$validator->isValid($parsedQueries)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => Appwrite::getName(),
|
||||
'destination' => CSV::getName(),
|
||||
'resources' => Transfer::extractServices([Transfer::GROUP_DATABASES]),
|
||||
'resourceId' => $resourceId,
|
||||
'resourceType' => Resource::TYPE_DATABASE,
|
||||
'statusCounters' => '{}',
|
||||
'resourceData' => '{}',
|
||||
'errors' => [],
|
||||
'options' => [
|
||||
'bucketId' => 'default', // Always use internal bucket
|
||||
'filename' => $filename,
|
||||
'columns' => $columns,
|
||||
'queries' => $queries,
|
||||
'delimiter' => $delimiter,
|
||||
'enclosure' => $enclosure,
|
||||
'escape' => $escape,
|
||||
'header' => $header,
|
||||
'notify' => $notify,
|
||||
'userInternalId' => $user->getSequence(),
|
||||
],
|
||||
]));
|
||||
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->trigger();
|
||||
|
||||
$response
|
||||
@@ -460,9 +611,10 @@ App::get('/v1/migrations')
|
||||
))
|
||||
->param('queries', [], new Migrations(), '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(', ', Migrations::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -501,7 +653,7 @@ App::get('/v1/migrations')
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
try {
|
||||
$migrations = $dbForProject->find('migrations', $queries);
|
||||
$total = $dbForProject->count('migrations', $filterQueries, APP_LIMIT_COUNT);
|
||||
$total = $includeTotal ? $dbForProject->count('migrations', $filterQueries, APP_LIMIT_COUNT) : 0;
|
||||
} 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.");
|
||||
}
|
||||
@@ -765,9 +917,10 @@ App::patch('/v1/migrations/:migrationId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (string $migrationId, Response $response, Database $dbForProject, Document $project, Document $user, Migration $queueForMigrations) {
|
||||
->action(function (string $migrationId, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Migration $queueForMigrations) {
|
||||
$migration = $dbForProject->getDocument('migrations', $migrationId);
|
||||
|
||||
if ($migration->isEmpty()) {
|
||||
@@ -786,6 +939,7 @@ App::patch('/v1/migrations/:migrationId')
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Datetime as DateTimeValidator;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -526,8 +527,8 @@ App::put('/v1/project/variables/:variableId')
|
||||
))
|
||||
->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)
|
||||
->param('value', null, new Nullable(new Text(8192, 0)), 'Variable value. Max length: 8192 chars.', true)
|
||||
->param('secret', null, new Nullable(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')
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\MockNumber;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Mail;
|
||||
@@ -22,6 +21,7 @@ use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Adapter\Database as AdapterDatabase;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
@@ -46,6 +46,7 @@ use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Multiple;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
@@ -118,7 +119,7 @@ App::post('/v1/projects')
|
||||
'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT,
|
||||
'passwordHistory' => 0,
|
||||
'passwordDictionary' => false,
|
||||
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG,
|
||||
'duration' => TOKEN_EXPIRATION_LOGIN_LONG,
|
||||
'personalDataCheck' => false,
|
||||
'mockNumbers' => [],
|
||||
'sessionAlerts' => false,
|
||||
@@ -247,13 +248,15 @@ App::post('/v1/projects')
|
||||
}
|
||||
|
||||
if ($create || $projectTables) {
|
||||
$audit = new Audit($dbForProject);
|
||||
$adapter = new AdapterDatabase($dbForProject);
|
||||
$audit = new Audit($adapter);
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
if (!$create && $sharedTablesV1) {
|
||||
$attributes = \array_map(fn ($attribute) => new Document($attribute), Audit::ATTRIBUTES);
|
||||
$indexes = \array_map(fn (array $index) => new Document($index), Audit::INDEXES);
|
||||
$adapter = new AdapterDatabase($dbForProject);
|
||||
$attributes = $adapter->getAttributeDocuments();
|
||||
$indexes = $adapter->getIndexDocuments();
|
||||
$dbForProject->createDocument(Database::METADATA, new Document([
|
||||
'$id' => ID::custom('audit'),
|
||||
'$permissions' => [Permission::create(Role::any())],
|
||||
@@ -294,7 +297,7 @@ App::post('/v1/projects')
|
||||
|
||||
// Hook allowing instant project mirroring during migration
|
||||
// Outside of migration, hook is not registered and has no effect
|
||||
$hooks->trigger('afterProjectCreation', [ $project, $pools, $cache ]);
|
||||
$hooks->trigger('afterProjectCreation', [$project, $pools, $cache]);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -563,6 +566,7 @@ App::patch('/v1/projects/:projectId/api')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.updateAPIStatus',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -620,6 +624,7 @@ App::patch('/v1/projects/:projectId/api/all')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.updateAPIStatusAll',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -678,9 +683,9 @@ App::patch('/v1/projects/:projectId/oauth2')
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'Provider Name')
|
||||
->param('appId', null, new Text(256), 'Provider app ID. Max length: 256 chars.', true)
|
||||
->param('secret', null, new text(512), 'Provider secret key. Max length: 512 chars.', true)
|
||||
->param('enabled', null, new Boolean(), 'Provider status. Set to \'false\' to disable new session creation.', true)
|
||||
->param('appId', null, new Nullable(new Text(256)), 'Provider app ID. Max length: 256 chars.', true)
|
||||
->param('secret', null, new Nullable(new text(512)), 'Provider secret key. Max length: 512 chars.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Provider status. Set to \'false\' to disable new session creation.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $provider, ?string $appId, ?string $secret, ?bool $enabled, Response $response, Database $dbForPlatform) {
|
||||
@@ -1234,9 +1239,10 @@ App::get('/v1/projects/:projectId/webhooks')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform) {
|
||||
->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
@@ -1251,7 +1257,7 @@ App::get('/v1/projects/:projectId/webhooks')
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'webhooks' => $webhooks,
|
||||
'total' => count($webhooks),
|
||||
'total' => $includeTotal ? count($webhooks) : 0,
|
||||
]), Response::MODEL_WEBHOOK_LIST);
|
||||
});
|
||||
|
||||
@@ -1475,8 +1481,8 @@ App::post('/v1/projects/:projectId/keys')
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) {
|
||||
@@ -1496,6 +1502,9 @@ App::post('/v1/projects/:projectId/keys')
|
||||
],
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $project->getId(),
|
||||
'resourceInternalId' => $project->getSequence(),
|
||||
'resourceId' => $project->getId(),
|
||||
'resourceType' => 'projects',
|
||||
'name' => $name,
|
||||
'scopes' => $scopes,
|
||||
'expire' => $expire,
|
||||
@@ -1531,9 +1540,10 @@ App::get('/v1/projects/:projectId/keys')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform) {
|
||||
->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
@@ -1542,13 +1552,19 @@ App::get('/v1/projects/:projectId/keys')
|
||||
}
|
||||
|
||||
$keys = $dbForPlatform->find('keys', [
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::or([
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::and([
|
||||
Query::equal('resourceType', ['projects']),
|
||||
Query::equal('resourceInternalId', [$project->getSequence()]),
|
||||
])
|
||||
]),
|
||||
Query::limit(5000),
|
||||
]);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'keys' => $keys,
|
||||
'total' => count($keys),
|
||||
'total' => $includeTotal ? count($keys) : 0,
|
||||
]), Response::MODEL_KEY_LIST);
|
||||
});
|
||||
|
||||
@@ -1583,7 +1599,13 @@ App::get('/v1/projects/:projectId/keys/:keyId')
|
||||
|
||||
$key = $dbForPlatform->findOne('keys', [
|
||||
Query::equal('$id', [$keyId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::or([
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::and([
|
||||
Query::equal('resourceType', ['projects']),
|
||||
Query::equal('resourceInternalId', [$project->getSequence()]),
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
if ($key->isEmpty()) {
|
||||
@@ -1613,8 +1635,8 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('keyId', '', new UID(), 'Key unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) {
|
||||
@@ -1627,7 +1649,13 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
|
||||
$key = $dbForPlatform->findOne('keys', [
|
||||
Query::equal('$id', [$keyId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::or([
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::and([
|
||||
Query::equal('resourceType', ['projects']),
|
||||
Query::equal('resourceInternalId', [$project->getSequence()]),
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
if ($key->isEmpty()) {
|
||||
@@ -1678,7 +1706,13 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
|
||||
|
||||
$key = $dbForPlatform->findOne('keys', [
|
||||
Query::equal('$id', [$keyId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::or([
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::and([
|
||||
Query::equal('resourceType', ['projects']),
|
||||
Query::equal('resourceInternalId', [$project->getSequence()]),
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
if ($key->isEmpty()) {
|
||||
@@ -1834,9 +1868,10 @@ App::get('/v1/projects/:projectId/platforms')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform) {
|
||||
->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
@@ -1851,7 +1886,7 @@ App::get('/v1/projects/:projectId/platforms')
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'platforms' => $platforms,
|
||||
'total' => count($platforms),
|
||||
'total' => $includeTotal ? count($platforms) : 0,
|
||||
]), Response::MODEL_PLATFORM_LIST);
|
||||
});
|
||||
|
||||
@@ -2021,6 +2056,7 @@ App::patch('/v1/projects/:projectId/smtp')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.updateSMTP',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -2073,6 +2109,7 @@ App::patch('/v1/projects/:projectId/smtp')
|
||||
if ($enabled) {
|
||||
$mail = new PHPMailer(true);
|
||||
$mail->isSMTP();
|
||||
$mail->SMTPAuth = (!empty($username) && !empty($password));
|
||||
$mail->Username = $username;
|
||||
$mail->Password = $password;
|
||||
$mail->Host = $host;
|
||||
@@ -2088,7 +2125,7 @@ App::patch('/v1/projects/:projectId/smtp')
|
||||
throw new Exception('Connection is not valid.');
|
||||
}
|
||||
} catch (Throwable $error) {
|
||||
throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, 'Could not connect to SMTP server: ' . $error->getMessage());
|
||||
throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2137,6 +2174,7 @@ App::post('/v1/projects/:projectId/smtp/tests')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.createSMTPTest',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -2231,6 +2269,7 @@ App::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.getSMSTemplate',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -2397,6 +2436,7 @@ App::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.updateSMSTemplate',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -2521,6 +2561,7 @@ App::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.deleteSMSTemplate',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
@@ -2655,7 +2696,7 @@ App::patch('/v1/projects/:projectId/auth/session-invalidation')
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['invalidateSessions'] = $enabled;
|
||||
$dbForPlatform->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+123
-95
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
@@ -10,7 +9,7 @@ use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Email as EmailValidator;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use Appwrite\Platform\Workers\Deletes;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -18,6 +17,7 @@ use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Memberships;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Teams;
|
||||
@@ -28,6 +28,9 @@ use MaxMind\Db\Reader;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Auth\Proofs\Password;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -48,12 +51,13 @@ use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Emails\Email;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
App::post('/v1/teams')
|
||||
@@ -68,7 +72,7 @@ App::post('/v1/teams')
|
||||
group: 'teams',
|
||||
name: 'create',
|
||||
description: '/docs/references/teams/create-team.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -85,8 +89,8 @@ App::post('/v1/teams')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
|
||||
$teamId = $teamId == 'unique()' ? ID::unique() : $teamId;
|
||||
|
||||
@@ -159,7 +163,7 @@ App::get('/v1/teams')
|
||||
group: 'teams',
|
||||
name: 'list',
|
||||
description: '/docs/references/teams/list-teams.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -169,9 +173,11 @@ App::get('/v1/teams')
|
||||
))
|
||||
->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)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject) {
|
||||
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
@@ -211,7 +217,7 @@ App::get('/v1/teams')
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
try {
|
||||
$results = $dbForProject->find('teams', $queries);
|
||||
$total = $dbForProject->count('teams', $filterQueries, APP_LIMIT_COUNT);
|
||||
$total = $includeTotal ? $dbForProject->count('teams', $filterQueries, APP_LIMIT_COUNT) : 0;
|
||||
} 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.");
|
||||
}
|
||||
@@ -231,7 +237,7 @@ App::get('/v1/teams/:teamId')
|
||||
group: 'teams',
|
||||
name: 'get',
|
||||
description: '/docs/references/teams/get-team.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -262,7 +268,7 @@ App::get('/v1/teams/:teamId/prefs')
|
||||
group: 'teams',
|
||||
name: 'getPrefs',
|
||||
description: '/docs/references/teams/get-team-prefs.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -304,7 +310,7 @@ App::put('/v1/teams/:teamId')
|
||||
group: 'teams',
|
||||
name: 'updateName',
|
||||
description: '/docs/references/teams/update-team-name.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -350,7 +356,7 @@ App::put('/v1/teams/:teamId/prefs')
|
||||
group: 'teams',
|
||||
name: 'updatePrefs',
|
||||
description: '/docs/references/teams/update-team-prefs.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -397,7 +403,7 @@ App::delete('/v1/teams/:teamId')
|
||||
group: 'teams',
|
||||
name: 'delete',
|
||||
description: '/docs/references/teams/delete-team.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
@@ -456,7 +462,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
group: 'memberships',
|
||||
name: 'createMembership',
|
||||
description: '/docs/references/teams/create-team-membership.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
@@ -466,21 +472,20 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
))
|
||||
->label('abuse-limit', 10)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('email', '', new Email(), 'Email of the new team member.', true)
|
||||
->param('email', '', new EmailValidator(), 'Email of the new team member.', true)
|
||||
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('roles', [], function (Document $project) {
|
||||
if ($project->getId() === 'console') {
|
||||
;
|
||||
$roles = array_keys(Config::getParam('roles', []));
|
||||
array_filter($roles, function ($role) {
|
||||
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
|
||||
$roles = array_filter($roles, function ($role) {
|
||||
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
|
||||
});
|
||||
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}
|
||||
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
|
||||
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey']) // TODO add our own built-in confirm page
|
||||
->param('url', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator']) // TODO add our own built-in confirm page
|
||||
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
@@ -493,9 +498,11 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->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());
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
|
||||
$url = htmlentities($url);
|
||||
if (empty($url)) {
|
||||
@@ -566,37 +573,53 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
|
||||
try {
|
||||
$userId = ID::unique();
|
||||
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::read(Role::user($userId)),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
'email' => empty($email) ? null : $email,
|
||||
'phone' => empty($phone) ? null : $phone,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
// TODO: Set password empty?
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
/**
|
||||
* Set the password update time to 0 for users created using
|
||||
* team invite and OAuth to allow password updates without an
|
||||
* old password
|
||||
*/
|
||||
'passwordUpdate' => null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
'prefs' => new \stdClass(),
|
||||
'sessions' => null,
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email, $name]),
|
||||
])));
|
||||
$hash = $proofForPassword->hash($proofForPassword->generate());
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
}
|
||||
|
||||
$userId = ID::unique();
|
||||
|
||||
$userDocument = new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::read(Role::user($userId)),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
'email' => empty($email) ? null : $email,
|
||||
'phone' => empty($phone) ? null : $phone,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
// TODO: Set password empty?
|
||||
'password' => $hash,
|
||||
'hash' => $proofForPassword->getHash()->getName(),
|
||||
'hashOptions' => $proofForPassword->getHash()->getOptions(),
|
||||
/**
|
||||
* Set the password update time to 0 for users created using
|
||||
* team invite and OAuth to allow password updates without an
|
||||
* old password
|
||||
*/
|
||||
'passwordUpdate' => null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
'prefs' => new \stdClass(),
|
||||
'sessions' => null,
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email, $name]),
|
||||
'emailCanonical' => $emailCanonical?->getCanonical(),
|
||||
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
|
||||
'emailIsCorporate' => $emailCanonical?->isCorporate(),
|
||||
'emailIsDisposable' => $emailCanonical?->isDisposable(),
|
||||
'emailIsFree' => $emailCanonical?->isFree(),
|
||||
]);
|
||||
|
||||
try {
|
||||
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', $userDocument));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
@@ -613,7 +636,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
Query::equal('teamInternalId', [$team->getSequence()]),
|
||||
]);
|
||||
|
||||
$secret = Auth::tokenGenerator();
|
||||
$secret = $proofForToken->generate();
|
||||
if ($membership->isEmpty()) {
|
||||
$membershipId = ID::unique();
|
||||
$membership = new Document([
|
||||
@@ -633,7 +656,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
'invited' => DateTime::now(),
|
||||
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
|
||||
'confirm' => ($isPrivilegedUser || $isAppUser),
|
||||
'secret' => Auth::hash($secret),
|
||||
'secret' => $proofForToken->hash($secret),
|
||||
'search' => implode(' ', [$membershipId, $invitee->getId()])
|
||||
]);
|
||||
|
||||
@@ -644,9 +667,8 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
if ($isPrivilegedUser || $isAppUser) {
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
}
|
||||
|
||||
} elseif ($membership->getAttribute('confirm') === false) {
|
||||
$membership->setAttribute('secret', Auth::hash($secret));
|
||||
$membership->setAttribute('secret', $proofForToken->hash($secret));
|
||||
$membership->setAttribute('invited', DateTime::now());
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) {
|
||||
@@ -749,7 +771,6 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->setName($invitee->getAttribute('name', ''))
|
||||
->setVariables($emailVariables)
|
||||
->trigger();
|
||||
|
||||
} elseif (!empty($phone)) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
@@ -827,7 +848,7 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
group: 'memberships',
|
||||
name: 'listMemberships',
|
||||
description: '/docs/references/teams/list-team-members.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -838,10 +859,11 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
->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)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $teamId, array $queries, string $search, Response $response, Document $project, Database $dbForProject) {
|
||||
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject) {
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
@@ -893,11 +915,11 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
collection: 'memberships',
|
||||
queries: $queries,
|
||||
);
|
||||
$total = $dbForProject->count(
|
||||
$total = $includeTotal ? $dbForProject->count(
|
||||
collection: 'memberships',
|
||||
queries: $filterQueries,
|
||||
max: APP_LIMIT_COUNT
|
||||
);
|
||||
) : 0;
|
||||
} 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.");
|
||||
}
|
||||
@@ -912,8 +934,8 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
];
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
$isPrivilegedUser = User::isPrivileged($roles);
|
||||
$isAppUser = User::isApp($roles);
|
||||
|
||||
$membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) {
|
||||
return $privacy || $isPrivilegedUser || $isAppUser;
|
||||
@@ -969,7 +991,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
group: 'memberships',
|
||||
name: 'getMembership',
|
||||
description: '/docs/references/teams/get-team-member.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1003,8 +1025,8 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
];
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
$isPrivilegedUser = User::isPrivileged($roles);
|
||||
$isAppUser = User::isApp($roles);
|
||||
|
||||
$membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) {
|
||||
return $privacy || $isPrivilegedUser || $isAppUser;
|
||||
@@ -1056,7 +1078,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
group: 'memberships',
|
||||
name: 'updateMembership',
|
||||
description: '/docs/references/teams/update-team-membership.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1069,8 +1091,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->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]);
|
||||
$roles = array_filter($roles, function ($role) {
|
||||
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
|
||||
});
|
||||
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}
|
||||
@@ -1099,8 +1121,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
@@ -1166,7 +1188,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
group: 'memberships',
|
||||
name: 'updateMembershipStatus',
|
||||
description: '/docs/references/teams/update-team-membership-status.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
@@ -1185,7 +1207,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents) {
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
@@ -1204,7 +1228,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
|
||||
}
|
||||
|
||||
if (Auth::hash($secret) !== $membership->getAttribute('secret')) {
|
||||
if (!$proofForToken->verify($secret, $membership->getAttribute('secret'))) {
|
||||
throw new Exception(Exception::TEAM_INVALID_SECRET);
|
||||
}
|
||||
|
||||
@@ -1238,9 +1262,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
|
||||
$secret = Auth::tokenGenerator();
|
||||
$secret = $proofForToken->generate();
|
||||
$session = new Document(array_merge([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
@@ -1250,9 +1274,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
],
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getSequence(),
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'provider' => SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => $user->getAttribute('email'),
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'factors' => ['email'],
|
||||
@@ -1264,14 +1288,19 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
Authorization::setRole(Role::user($userId)->toString());
|
||||
|
||||
$encoded = $store
|
||||
->setProperty('id', $user->getId())
|
||||
->setProperty('secret', $secret)
|
||||
->encode();
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded]));
|
||||
}
|
||||
|
||||
$response
|
||||
->addCookie(
|
||||
name: Auth::$cookieName . '_legacy',
|
||||
value: Auth::encodeSession($user->getId(), $secret),
|
||||
name: $store->getKey() . '_legacy',
|
||||
value: $encoded,
|
||||
expire: (new \DateTime($expire))->getTimestamp(),
|
||||
path: '/',
|
||||
domain: Config::getParam('cookieDomain'),
|
||||
@@ -1279,8 +1308,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
httponly: true
|
||||
)
|
||||
->addCookie(
|
||||
name: Auth::$cookieName,
|
||||
value: Auth::encodeSession($user->getId(), $secret),
|
||||
name: $store->getKey(),
|
||||
value: $encoded,
|
||||
expire: (new \DateTime($expire))->getTimestamp(),
|
||||
path: '/',
|
||||
domain: Config::getParam('cookieDomain'),
|
||||
@@ -1324,7 +1353,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
group: 'memberships',
|
||||
name: 'deleteMembership',
|
||||
description: '/docs/references/teams/delete-team-membership.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
@@ -1430,11 +1459,13 @@ App::get('/v1/teams/:teamId/logs')
|
||||
))
|
||||
->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)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->inject('audit')
|
||||
->action(function (string $teamId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
@@ -1448,15 +1479,12 @@ App::get('/v1/teams/:teamId/logs')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'team/' . $team->getId();
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
|
||||
|
||||
$output = [];
|
||||
|
||||
@@ -1503,7 +1531,7 @@ App::get('/v1/teams/:teamId/logs')
|
||||
}
|
||||
}
|
||||
$response->dynamic(new Document([
|
||||
'total' => $audit->countLogsByResource($resource, $queries),
|
||||
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
+212
-152
File diff suppressed because it is too large
Load Diff
+228
-47
@@ -4,7 +4,6 @@ use Appwrite\Auth\OAuth2\Github as OAuth2Github;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
@@ -14,9 +13,12 @@ use Appwrite\Utopia\Database\Validator\Queries\Installations;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\Comment;
|
||||
use Swoole\Coroutine\WaitGroup;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Adapters\Dotenv as ConfigDotenv;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Config\Exceptions\Parse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
@@ -28,13 +30,24 @@ use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Detector\Detection\Framework\Analog;
|
||||
use Utopia\Detector\Detection\Framework\Angular;
|
||||
use Utopia\Detector\Detection\Framework\Astro;
|
||||
use Utopia\Detector\Detection\Framework\Flutter;
|
||||
use Utopia\Detector\Detection\Framework\Lynx;
|
||||
use Utopia\Detector\Detection\Framework\NextJs;
|
||||
use Utopia\Detector\Detection\Framework\Nuxt;
|
||||
use Utopia\Detector\Detection\Framework\React;
|
||||
use Utopia\Detector\Detection\Framework\ReactNative;
|
||||
use Utopia\Detector\Detection\Framework\Remix;
|
||||
use Utopia\Detector\Detection\Framework\Svelte;
|
||||
use Utopia\Detector\Detection\Framework\SvelteKit;
|
||||
use Utopia\Detector\Detection\Framework\TanStackStart;
|
||||
use Utopia\Detector\Detection\Framework\Vue;
|
||||
use Utopia\Detector\Detection\Packager\NPM;
|
||||
use Utopia\Detector\Detection\Packager\PNPM;
|
||||
use Utopia\Detector\Detection\Packager\Yarn;
|
||||
@@ -58,11 +71,12 @@ use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Exception\FileNotFound;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
use function Swoole\Coroutine\batch;
|
||||
|
||||
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForPlatform, Build $queueForBuilds, callable $getProjectDB, Request $request) {
|
||||
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForPlatform, Build $queueForBuilds, callable $getProjectDB, array $platform) {
|
||||
$errors = [];
|
||||
foreach ($repositories as $repository) {
|
||||
try {
|
||||
@@ -118,7 +132,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
|
||||
$commentStatus = $isAuthorized ? 'waiting' : 'failed';
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
|
||||
$authorizeUrl = $protocol . '://' . $hostname . "/console/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
|
||||
|
||||
@@ -160,17 +174,17 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
if ($lockAcquired) {
|
||||
// Wrap in try/finally to ensure lock file gets deleted
|
||||
try {
|
||||
$comment = new Comment();
|
||||
$comment = new Comment($platform);
|
||||
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
|
||||
|
||||
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
|
||||
} finally {
|
||||
$dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId);
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$comment = new Comment();
|
||||
$comment = new Comment($platform);
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
|
||||
$latestCommentId = \strval($github->createComment($owner, $repositoryName, $providerPullRequestId, $comment->generateComment()));
|
||||
|
||||
@@ -231,13 +245,13 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
if ($lockAcquired) {
|
||||
// Wrap in try/finally to ensure lock file gets deleted
|
||||
try {
|
||||
$comment = new Comment();
|
||||
$comment = new Comment($platform);
|
||||
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
|
||||
|
||||
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
|
||||
} finally {
|
||||
$dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId);
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,13 +466,13 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$previewUrl = !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '';
|
||||
|
||||
if (!empty($previewUrl)) {
|
||||
$comment = new Comment();
|
||||
$comment = new Comment($platform);
|
||||
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, $previewUrl);
|
||||
$github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment());
|
||||
}
|
||||
} finally {
|
||||
$dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId);
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -527,12 +541,12 @@ App::get('/v1/vcs/github/authorize')
|
||||
type: MethodType::WEBAUTH,
|
||||
hide: true,
|
||||
))
|
||||
->param('success', '', fn ($platforms) => new Redirect($platforms), 'URL to redirect back to console after a successful installation attempt.', true, ['platforms'])
|
||||
->param('failure', '', fn ($platforms) => new Redirect($platforms), 'URL to redirect back to console after a failed installation attempt.', true, ['platforms'])
|
||||
->inject('request')
|
||||
->param('success', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to console after a successful installation attempt.', true, ['redirectValidator'])
|
||||
->param('failure', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to console after a failed installation attempt.', true, ['redirectValidator'])
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->action(function (string $success, string $failure, Request $request, Response $response, Document $project) {
|
||||
->inject('platform')
|
||||
->action(function (string $success, string $failure, Response $response, Document $project, array $platform) {
|
||||
$state = \json_encode([
|
||||
'projectId' => $project->getId(),
|
||||
'success' => $success,
|
||||
@@ -541,7 +555,7 @@ App::get('/v1/vcs/github/authorize')
|
||||
|
||||
$appName = System::getEnv('_APP_VCS_GITHUB_APP_NAME');
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
|
||||
if (empty($appName)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'GitHub App name is not configured. Please configure VCS (Version Control System) variables in .env file.');
|
||||
@@ -570,10 +584,10 @@ App::get('/v1/vcs/github/callback')
|
||||
->inject('gitHub')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $providerInstallationId, string $setupAction, string $state, string $code, GitHub $github, Document $user, Document $project, Request $request, Response $response, Database $dbForPlatform) {
|
||||
->inject('platform')
|
||||
->action(function (string $providerInstallationId, string $setupAction, string $state, string $code, GitHub $github, Document $user, Document $project, Response $response, Database $dbForPlatform, array $platform) {
|
||||
if (empty($state)) {
|
||||
$error = 'Installation requests from organisation members for the Appwrite GitHub App are currently unsupported. To proceed with the installation, login to the Appwrite Console and install the GitHub App.';
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $error);
|
||||
@@ -600,7 +614,7 @@ App::get('/v1/vcs/github/callback')
|
||||
|
||||
$region = $project->getAttribute('region', 'default');
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
|
||||
$defaultState = [
|
||||
'success' => $protocol . '://' . $hostname . "/console/project-$region-$projectId/settings/git-installations",
|
||||
@@ -818,7 +832,10 @@ App::post('/v1/vcs/github/installations/:installationId/detections')
|
||||
$files = \array_column($files, 'name');
|
||||
$languages = $github->listRepositoryLanguages($owner, $repositoryName);
|
||||
|
||||
$detector = new Packager($files);
|
||||
$detector = new Packager();
|
||||
foreach ($files as $file) {
|
||||
$detector->addInput($file);
|
||||
}
|
||||
$detector
|
||||
->addOption(new Yarn())
|
||||
->addOption(new PNPM())
|
||||
@@ -828,6 +845,14 @@ App::post('/v1/vcs/github/installations/:installationId/detections')
|
||||
$packager = !\is_null($detection) ? $detection->getName() : 'npm';
|
||||
|
||||
if ($type === 'framework') {
|
||||
$packages = '';
|
||||
try {
|
||||
$contentResponse = $github->getRepositoryContent($owner, $repositoryName, \rtrim($providerRootDirectory, '/') . '/package.json');
|
||||
$packages = $contentResponse['content'] ?? '';
|
||||
} catch (FileNotFound $e) {
|
||||
// Continue detection without package.json
|
||||
}
|
||||
|
||||
$output = new Document([
|
||||
'framework' => '',
|
||||
'installCommand' => '',
|
||||
@@ -835,14 +860,27 @@ App::post('/v1/vcs/github/installations/:installationId/detections')
|
||||
'outputDirectory' => '',
|
||||
]);
|
||||
|
||||
$detector = new Framework($files, $packager);
|
||||
$detector = new Framework($packager);
|
||||
$detector->addInput($packages, Framework::INPUT_PACKAGES);
|
||||
foreach ($files as $file) {
|
||||
$detector->addInput($file, Framework::INPUT_FILE);
|
||||
}
|
||||
|
||||
$detector
|
||||
->addOption(new Flutter())
|
||||
->addOption(new Nuxt())
|
||||
->addOption(new Analog())
|
||||
->addOption(new Angular())
|
||||
->addOption(new Astro())
|
||||
->addOption(new SvelteKit())
|
||||
->addOption(new Flutter())
|
||||
->addOption(new Lynx())
|
||||
->addOption(new NextJs())
|
||||
->addOption(new Remix());
|
||||
->addOption(new Nuxt())
|
||||
->addOption(new React())
|
||||
->addOption(new ReactNative())
|
||||
->addOption(new Remix())
|
||||
->addOption(new Svelte())
|
||||
->addOption(new SvelteKit())
|
||||
->addOption(new TanStackStart())
|
||||
->addOption(new Vue());
|
||||
|
||||
$framework = $detector->detect();
|
||||
|
||||
@@ -877,7 +915,18 @@ App::post('/v1/vcs/github/installations/:installationId/detections')
|
||||
];
|
||||
|
||||
foreach ($strategies as $strategy) {
|
||||
$detector = new Runtime($strategy === Strategy::LANGUAGES ? $languages : $files, $strategy, $packager);
|
||||
$detector = new Runtime($strategy, $packager);
|
||||
|
||||
if ($strategy === Strategy::LANGUAGES) {
|
||||
foreach ($languages as $language) {
|
||||
$detector->addInput($language);
|
||||
}
|
||||
} else {
|
||||
foreach ($files as $file) {
|
||||
$detector->addInput($file);
|
||||
}
|
||||
}
|
||||
|
||||
$detector
|
||||
->addOption(new Node())
|
||||
->addOption(new Bun())
|
||||
@@ -919,6 +968,46 @@ App::post('/v1/vcs/github/installations/:installationId/detections')
|
||||
throw new Exception(Exception::FUNCTION_RUNTIME_NOT_DETECTED);
|
||||
}
|
||||
}
|
||||
|
||||
$wg = new WaitGroup();
|
||||
$envs = [];
|
||||
foreach ($files as $file) {
|
||||
if (!(\str_starts_with($file, '.env'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$wg->add();
|
||||
go(function () use ($github, $owner, $repositoryName, $providerRootDirectory, $file, $wg, &$envs) {
|
||||
try {
|
||||
$contentResponse = $github->getRepositoryContent($owner, $repositoryName, \rtrim($providerRootDirectory, '/') . '/' . $file);
|
||||
$envFile = $contentResponse['content'] ?? '';
|
||||
|
||||
$configAdapter = new ConfigDotenv();
|
||||
try {
|
||||
$envObject = $configAdapter->parse($envFile);
|
||||
foreach ($envObject as $envName => $envValue) {
|
||||
$envs[$envName] = $envValue;
|
||||
}
|
||||
} catch (Parse $err) {
|
||||
// Silence error, so rest of endpoint can return
|
||||
}
|
||||
} finally {
|
||||
$wg->done();
|
||||
}
|
||||
});
|
||||
}
|
||||
$wg->wait();
|
||||
|
||||
$variables = [];
|
||||
foreach ($envs as $key => $value) {
|
||||
$variables[] = [
|
||||
'name' => $key,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
|
||||
$output->setAttribute('variables', $variables);
|
||||
|
||||
$response->dynamic($output, $type === 'framework' ? Response::MODEL_DETECTION_FRAMEWORK : Response::MODEL_DETECTION_RUNTIME);
|
||||
});
|
||||
|
||||
@@ -946,10 +1035,11 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
->param('installationId', '', new Text(256), 'Installation Id')
|
||||
->param('type', '', new WhiteList(['runtime', 'framework']), 'Detector type. Must be one of the following: runtime, framework')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->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('gitHub')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $installationId, string $type, string $search, GitHub $github, Response $response, Database $dbForPlatform) {
|
||||
->action(function (string $installationId, string $type, string $search, array $queries, GitHub $github, Response $response, Database $dbForPlatform) {
|
||||
if (empty($search)) {
|
||||
$search = "";
|
||||
}
|
||||
@@ -965,11 +1055,20 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
$page = 1;
|
||||
$perPage = 4;
|
||||
$queries = Query::parseQueries($queries);
|
||||
$limitQuery = current(array_filter($queries, fn ($query) => $query->getMethod() === Query::TYPE_LIMIT));
|
||||
$offsetQuery = current(array_filter($queries, fn ($query) => $query->getMethod() === Query::TYPE_OFFSET));
|
||||
|
||||
$limit = !empty($limitQuery) ? $limitQuery->getValue() : 4;
|
||||
$offset = !empty($offsetQuery) ? $offsetQuery->getValue() : 0;
|
||||
|
||||
if ($offset % $limit !== 0) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'offset must be a multiple of the limit');
|
||||
}
|
||||
|
||||
$page = ($offset / $limit) + 1;
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$repos = $github->searchRepositories($owner, $page, $perPage, $search);
|
||||
['items' => $repos, 'total' => $total] = $github->searchRepositories($owner, $page, $limit, $search);
|
||||
|
||||
$repos = \array_map(function ($repo) use ($installation) {
|
||||
$repo['id'] = \strval($repo['id'] ?? '');
|
||||
@@ -984,7 +1083,10 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
$files = $github->listRepositoryContents($repo['organization'], $repo['name'], '');
|
||||
$files = \array_column($files, 'name');
|
||||
|
||||
$detector = new Packager($files);
|
||||
$detector = new Packager();
|
||||
foreach ($files as $file) {
|
||||
$detector->addInput($file);
|
||||
}
|
||||
$detector
|
||||
->addOption(new Yarn())
|
||||
->addOption(new PNPM())
|
||||
@@ -994,14 +1096,35 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
$packager = !\is_null($detection) ? $detection->getName() : 'npm';
|
||||
|
||||
if ($type === 'framework') {
|
||||
$frameworkDetector = new Framework($files, $packager);
|
||||
$packages = '';
|
||||
try {
|
||||
$contentResponse = $github->getRepositoryContent($repo['organization'], $repo['name'], 'package.json');
|
||||
$packages = $contentResponse['content'] ?? '';
|
||||
} catch (FileNotFound $e) {
|
||||
// Continue detection without package.json
|
||||
}
|
||||
|
||||
$frameworkDetector = new Framework($packager);
|
||||
$frameworkDetector->addInput($packages, Framework::INPUT_PACKAGES);
|
||||
foreach ($files as $file) {
|
||||
$frameworkDetector->addInput($file, Framework::INPUT_FILE);
|
||||
}
|
||||
|
||||
$frameworkDetector
|
||||
->addOption(new Flutter())
|
||||
->addOption(new Nuxt())
|
||||
->addOption(new Analog())
|
||||
->addOption(new Angular())
|
||||
->addOption(new Astro())
|
||||
->addOption(new SvelteKit())
|
||||
->addOption(new Flutter())
|
||||
->addOption(new Lynx())
|
||||
->addOption(new NextJs())
|
||||
->addOption(new Remix());
|
||||
->addOption(new Nuxt())
|
||||
->addOption(new React())
|
||||
->addOption(new ReactNative())
|
||||
->addOption(new Remix())
|
||||
->addOption(new Svelte())
|
||||
->addOption(new SvelteKit())
|
||||
->addOption(new TanStackStart())
|
||||
->addOption(new Vue());
|
||||
|
||||
$detectedFramework = $frameworkDetector->detect();
|
||||
|
||||
@@ -1026,7 +1149,16 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
];
|
||||
|
||||
foreach ($strategies as $strategy) {
|
||||
$detector = new Runtime($strategy === Strategy::LANGUAGES ? $languages : $files, $strategy, $packager);
|
||||
$detector = new Runtime($strategy, $packager);
|
||||
if ($strategy === Strategy::LANGUAGES) {
|
||||
foreach ($languages as $language) {
|
||||
$detector->addInput($language);
|
||||
}
|
||||
} else {
|
||||
foreach ($files as $file) {
|
||||
$detector->addInput($file);
|
||||
}
|
||||
}
|
||||
$detector
|
||||
->addOption(new Node())
|
||||
->addOption(new Bun())
|
||||
@@ -1060,6 +1192,44 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
$repo['runtime'] = $runtimeWithVersion ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
$wg = new WaitGroup();
|
||||
$envs = [];
|
||||
foreach ($files as $file) {
|
||||
if (!(\str_starts_with($file, '.env'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$wg->add();
|
||||
go(function () use ($github, $repo, $file, $wg, &$envs) {
|
||||
try {
|
||||
$contentResponse = $github->getRepositoryContent($repo['organization'], $repo['name'], $file);
|
||||
$envFile = $contentResponse['content'] ?? '';
|
||||
|
||||
$configAdapter = new ConfigDotenv();
|
||||
try {
|
||||
$envObject = $configAdapter->parse($envFile);
|
||||
foreach ($envObject as $envName => $envValue) {
|
||||
$envs[$envName] = $envValue;
|
||||
}
|
||||
} catch (Parse) {
|
||||
// Silence error, so rest of endpoint can return
|
||||
}
|
||||
} finally {
|
||||
$wg->done();
|
||||
}
|
||||
});
|
||||
}
|
||||
$wg->wait();
|
||||
|
||||
$repo['variables'] = [];
|
||||
foreach ($envs as $key => $value) {
|
||||
$repo['variables'][] = [
|
||||
'name' => $key,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
|
||||
return $repo;
|
||||
};
|
||||
}, $repos));
|
||||
@@ -1070,7 +1240,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
|
||||
$response->dynamic(new Document([
|
||||
$type === 'framework' ? 'frameworkProviderRepositories' : 'runtimeProviderRepositories' => $repos,
|
||||
'total' => \count($repos),
|
||||
'total' => $total,
|
||||
]), ($type === 'framework') ? Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST : Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST);
|
||||
});
|
||||
|
||||
@@ -1308,8 +1478,9 @@ App::post('/v1/vcs/github/events')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->inject('platform')
|
||||
->action(
|
||||
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
|
||||
$payload = $request->getRawPayload();
|
||||
$signatureRemote = $request->getHeader('x-hub-signature-256', '');
|
||||
$signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', '');
|
||||
@@ -1352,7 +1523,7 @@ App::post('/v1/vcs/github/events')
|
||||
|
||||
// create new deployment only on push (not committed by us) and not when branch is created or deleted
|
||||
if ($providerCommitAuthorEmail !== APP_VCS_GITHUB_EMAIL && !$providerBranchCreated && !$providerBranchDeleted) {
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
|
||||
}
|
||||
} elseif ($event == $github::EVENT_INSTALLATION) {
|
||||
if ($parsedPayload["action"] == "deleted") {
|
||||
@@ -1374,7 +1545,7 @@ App::post('/v1/vcs/github/events')
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('repositories', $repository->getId()));
|
||||
}
|
||||
|
||||
$dbForPlatform->deleteDocument('installations', $installation->getId());
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('installations', $installation->getId()));
|
||||
}
|
||||
}
|
||||
} elseif ($event == $github::EVENT_PULL_REQUEST) {
|
||||
@@ -1408,7 +1579,7 @@ App::post('/v1/vcs/github/events')
|
||||
Query::orderDesc('$createdAt')
|
||||
]));
|
||||
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
|
||||
} elseif ($parsedPayload["action"] == "closed") {
|
||||
// Allowed external contributions cleanup
|
||||
|
||||
@@ -1458,11 +1629,12 @@ App::get('/v1/vcs/installations')
|
||||
))
|
||||
->param('queries', [], new Installations(), '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(', ', Installations::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForProject, Database $dbForPlatform) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject, Database $dbForPlatform) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -1503,7 +1675,7 @@ App::get('/v1/vcs/installations')
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
try {
|
||||
$results = $dbForPlatform->find('installations', $queries);
|
||||
$total = $dbForPlatform->count('installations', $filterQueries, APP_LIMIT_COUNT);
|
||||
$total = $includeTotal ? $dbForPlatform->count('installations', $filterQueries, APP_LIMIT_COUNT) : 0;
|
||||
} 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.");
|
||||
}
|
||||
@@ -1611,20 +1783,21 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
->param('repositoryId', '', new Text(256), 'VCS Repository Id')
|
||||
->param('providerPullRequestId', '', new Text(256), 'GitHub Pull Request Id')
|
||||
->inject('gitHub')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
->inject('platform')
|
||||
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Response $response, Document $project, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$repository = Authorization::skip(fn () => $dbForPlatform->getDocument('repositories', $repositoryId, [
|
||||
$repository = Authorization::skip(fn () => $dbForPlatform->findOne('repositories', [
|
||||
Query::equal('$id', [$repositoryId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()])
|
||||
]));
|
||||
|
||||
@@ -1664,8 +1837,16 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
|
||||
$providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? '';
|
||||
$providerCommitHash = $pullRequestResponse['head']['sha'] ?? '';
|
||||
$providerBranchUrl = $pullRequestResponse['head']['repo']['html_url'] ?? '';
|
||||
$providerRepositoryName = $pullRequestResponse['head']['repo']['name'] ?? '';
|
||||
$providerRepositoryUrl = $pullRequestResponse['head']['repo']['html_url'] ?? '';
|
||||
$providerRepositoryOwner = $pullRequestResponse['head']['repo']['owner']['login'] ?? '';
|
||||
$providerCommitAuthor = $pullRequestResponse['head']['user']['login'] ?? '';
|
||||
$providerCommitAuthorUrl = $pullRequestResponse['head']['user']['html_url'] ?? '';
|
||||
$providerCommitMessage = $pullRequestResponse['title'] ?? '';
|
||||
$providerCommitUrl = $pullRequestResponse['html_url'] ?? '';
|
||||
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, true, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
+197
-192
@@ -4,25 +4,26 @@ require_once __DIR__ . '/../init.php';
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Network\Cors;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Transformation\Adapter\Preview;
|
||||
use Appwrite\Transformation\Transformation;
|
||||
use Appwrite\Utopia\Database\Documents\User as DBUser;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
|
||||
use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
|
||||
use Appwrite\Utopia\Request\Filters\V18 as RequestV18;
|
||||
use Appwrite\Utopia\Request\Filters\V19 as RequestV19;
|
||||
use Appwrite\Utopia\Request\Filters\V20 as RequestV20;
|
||||
use Appwrite\Utopia\Request\Filters\V21 as RequestV21;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filters\V16 as ResponseV16;
|
||||
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17;
|
||||
@@ -38,6 +39,7 @@ 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\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
@@ -50,33 +52,36 @@ use Utopia\Logger\Log\User;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Service;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
Config::setParam('domainVerification', false);
|
||||
Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey)
|
||||
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey)
|
||||
{
|
||||
$host = $request->getHostname() ?? '';
|
||||
if (!empty($previewHostname)) {
|
||||
$host = $previewHostname;
|
||||
}
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($host)));
|
||||
} else {
|
||||
$rule = Authorization::skip(
|
||||
fn () => $dbForPlatform->find('rules', [
|
||||
Query::equal('domain', [$host]),
|
||||
Query::limit(1)
|
||||
])
|
||||
)[0] ?? new Document();
|
||||
}
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$rule = Authorization::skip(function () use ($dbForPlatform, $host, $isMd5) {
|
||||
if ($isMd5) {
|
||||
return $dbForPlatform->getDocument('rules', md5($host));
|
||||
}
|
||||
|
||||
return $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$host]),
|
||||
]) ?? new Document();
|
||||
});
|
||||
|
||||
$errorView = __DIR__ . '/../views/general/error.phtml';
|
||||
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . '://' . System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$url = $protocol . '://' . $platform['consoleHostname'];
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
|
||||
if ($rule->isEmpty()) {
|
||||
$appDomainFunctionsFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
|
||||
@@ -97,10 +102,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if (System::getEnv('_APP_OPTIONS_ROUTER_PROTECTION', 'disabled') === 'enabled') {
|
||||
if ($host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL && $host !== System::getEnv('_APP_CONSOLE_DOMAIN', '')) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.', view: $errorView);
|
||||
}
|
||||
if (!in_array($host, $platformHostnames)) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.', view: $errorView);
|
||||
}
|
||||
|
||||
// Act as API - no Proxy logic
|
||||
@@ -142,7 +145,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
|
||||
if ($type === 'deployment') {
|
||||
if (System::getEnv('_APP_OPTIONS_ROUTER_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
|
||||
if ($request->getProtocol() !== 'https' && $request->getHostname() !== APP_HOSTNAME_INTERNAL) {
|
||||
if ($request->getProtocol() !== 'https') {
|
||||
if ($request->getMethod() !== Request::METHOD_GET) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.', view: $errorView);
|
||||
}
|
||||
@@ -222,7 +225,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
*/
|
||||
$requirePreview = \is_null($apiKey) || !$apiKey->isPreviewAuthDisabled();
|
||||
if ($isPreview && $requirePreview) {
|
||||
$cookie = $request->getCookie(Auth::$cookieNamePreview, '');
|
||||
$cookie = $request->getCookie(COOKIE_NAME_PREVIEW, '');
|
||||
$authorized = false;
|
||||
|
||||
// Security checks to mark authorized true
|
||||
@@ -267,7 +270,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
}
|
||||
|
||||
if (!$authorized) {
|
||||
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . "://" . System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$url = $protocol . "://" . $platform['consoleHostname'];
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
@@ -455,9 +458,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
|
||||
}
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN');
|
||||
$endpoint = $protocol . '://' . $hostname . "/v1";
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$endpoint = "$protocol://{$platform['apiHostname']}/v1";
|
||||
|
||||
// Appwrite vars
|
||||
if ($type === 'function') {
|
||||
@@ -844,34 +846,31 @@ App::init()
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('log')
|
||||
->inject('console')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('locale')
|
||||
->inject('localeCodes')
|
||||
->inject('platforms')
|
||||
->inject('geodb')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForCertificates')
|
||||
->inject('queueForFunctions')
|
||||
->inject('executor')
|
||||
->inject('platform')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('previewHostname')
|
||||
->inject('devKey')
|
||||
->inject('apiKey')
|
||||
->inject('httpReferrer')
|
||||
->inject('httpReferrerSafe')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $platforms, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, string $httpReferrer, string $httpReferrerSafe) {
|
||||
->inject('cors')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Func $queueForFunctions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$hostname = $request->getHostname() ?? '';
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
if (!in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -906,92 +905,9 @@ App::init()
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$request->addFilter(new RequestV20($dbForProject, $route->getPathValues($request)));
|
||||
}
|
||||
}
|
||||
|
||||
$domain = $request->getHostname();
|
||||
$domains = Config::getParam('domains', []);
|
||||
if (!array_key_exists($domain, $domains)) {
|
||||
$domain = new Domain(!empty($domain) ? $domain : '');
|
||||
|
||||
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
|
||||
$domains[$domain->get()] = false;
|
||||
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
|
||||
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
|
||||
Console::warning('Skipping SSL certificates generation on ACME challenge.');
|
||||
} else {
|
||||
Authorization::disable();
|
||||
|
||||
$envDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$mainDomain = null;
|
||||
if (!empty($envDomain) && $envDomain !== 'localhost') {
|
||||
$mainDomain = $envDomain;
|
||||
} else {
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$domainDocument = $dbForPlatform->getDocument('rules', md5($envDomain));
|
||||
} else {
|
||||
$domainDocument = $dbForPlatform->findOne('rules', [Query::orderAsc('$id')]);
|
||||
}
|
||||
$mainDomain = !$domainDocument->isEmpty() ? $domainDocument->getAttribute('domain') : $domain->get();
|
||||
}
|
||||
|
||||
if ($mainDomain !== $domain->get()) {
|
||||
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
|
||||
} else {
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$domainDocument = $dbForPlatform->getDocument('rules', md5($domain->get()));
|
||||
} else {
|
||||
$domainDocument = $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain->get()])
|
||||
]);
|
||||
}
|
||||
|
||||
$owner = '';
|
||||
$functionsDomainFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
$siteDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
if (!empty($functionsDomainFallback) && \str_ends_with($host, $functionsDomainFallback)) {
|
||||
$functionsDomain = $functionsDomainFallback;
|
||||
}
|
||||
|
||||
if (
|
||||
(!empty($functionsDomain) && \str_ends_with($domain->get(), $functionsDomain)) ||
|
||||
(!empty($siteDomain) && \str_ends_with($domain->get(), $siteDomain))
|
||||
) {
|
||||
$owner = 'Appwrite';
|
||||
}
|
||||
|
||||
if ($domainDocument->isEmpty()) {
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
|
||||
$domainDocument = new Document([
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
'$id' => $ruleId,
|
||||
'domain' => $domain->get(),
|
||||
'type' => 'api',
|
||||
'status' => 'verifying',
|
||||
'projectId' => $console->getId(),
|
||||
'projectInternalId' => $console->getSequence(),
|
||||
'search' => implode(' ', [$ruleId, $domain->get()]),
|
||||
'owner' => $owner,
|
||||
'region' => $console->getAttribute('region')
|
||||
]);
|
||||
|
||||
$domainDocument = $dbForPlatform->createDocument('rules', $domainDocument);
|
||||
|
||||
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
|
||||
|
||||
$queueForCertificates
|
||||
->setDomain($domainDocument)
|
||||
->setSkipRenewCheck(true)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
$domains[$domain->get()] = true;
|
||||
|
||||
Authorization::reset(); // ensure authorization is re-enabled
|
||||
if (version_compare($requestFormat, '1.9.0', '<')) {
|
||||
$request->addFilter(new RequestV21());
|
||||
}
|
||||
Config::setParam('domains', $domains);
|
||||
}
|
||||
|
||||
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
|
||||
@@ -999,7 +915,7 @@ App::init()
|
||||
$locale->setDefault($localeParam);
|
||||
}
|
||||
|
||||
$origin = \parse_url($request->getOrigin($httpReferrer), PHP_URL_HOST);
|
||||
$origin = \parse_url($request->getOrigin($request->getReferer('')), PHP_URL_HOST);
|
||||
$selfDomain = new Domain($request->getHostname());
|
||||
$endDomain = new Domain((string)$origin);
|
||||
Config::setParam(
|
||||
@@ -1028,8 +944,8 @@ App::init()
|
||||
$warnings = [];
|
||||
|
||||
/*
|
||||
* Response format
|
||||
*/
|
||||
* Response format
|
||||
*/
|
||||
$responseFormat = $request->getHeader('x-appwrite-response-format', System::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($responseFormat) {
|
||||
if (version_compare($responseFormat, '1.4.0', '<')) {
|
||||
@@ -1049,14 +965,13 @@ App::init()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Security Headers
|
||||
*
|
||||
* As recommended at:
|
||||
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
|
||||
*/
|
||||
// Add Appwrite warning headers
|
||||
if (!empty($warnings)) {
|
||||
$response->addHeader('X-Appwrite-Warning', implode(';', $warnings));
|
||||
}
|
||||
|
||||
if (System::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
|
||||
if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost' && ($swooleRequest->header['host'] ?? '') !== APP_HOSTNAME_INTERNAL) { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
|
||||
if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost') { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
|
||||
if ($request->getMethod() !== Request::METHOD_GET) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.');
|
||||
}
|
||||
@@ -1064,49 +979,148 @@ App::init()
|
||||
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->getProtocol() === 'https') {
|
||||
$response->addHeader('Strict-Transport-Security', 'max-age=' . (60 * 60 * 24 * 126)); // 126 days
|
||||
/**
|
||||
* Security headers
|
||||
*
|
||||
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
|
||||
*/
|
||||
App::init()
|
||||
->groups(['api', 'web'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('cors')
|
||||
->inject('devKey')
|
||||
->inject('originValidator')
|
||||
->action(function (Request $request, Response $response, Cors $cors, Document $devKey, Validator $originValidator) {
|
||||
// CORS headers
|
||||
foreach ($cors->headers($request->getOrigin()) as $name => $value) {
|
||||
$response->addHeader($name, $value);
|
||||
}
|
||||
|
||||
// Security headers
|
||||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('X-Content-Type-Options', 'nosniff')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $httpReferrerSafe)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true');
|
||||
->addHeader('X-Content-Type-Options', 'nosniff');
|
||||
|
||||
if (!$devKey->isEmpty()) {
|
||||
$response->addHeader('Access-Control-Allow-Origin', '*');
|
||||
if ($request->getProtocol() === 'https') {
|
||||
$maxAge = 60 * 60 * 24 * 126; // 126 days
|
||||
$response->addHeader('Strict-Transport-Security', "max-age=$maxAge");
|
||||
}
|
||||
|
||||
if (!empty($warnings)) {
|
||||
$response->addHeader('X-Appwrite-Warning', implode(';', $warnings));
|
||||
// Application level CSRF protection
|
||||
$origin = $request->getOrigin();
|
||||
if (empty($origin) || !$devKey->isEmpty() || !empty($request->getHeader('x-appwrite-key'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate Client Domain - Check to avoid CSRF attack
|
||||
* Adding Appwrite API domains to allow XDOMAIN communication
|
||||
* Skip this check for non-web platforms which are not required to send an origin header
|
||||
*/
|
||||
$origin = $request->getOrigin($request->getReferer(''));
|
||||
$originValidator = new Origin($platforms);
|
||||
|
||||
if (
|
||||
$devKey->isEmpty()
|
||||
&& !empty($origin)
|
||||
&& !$originValidator->isValid($origin)
|
||||
&& \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE])
|
||||
&& $route->getLabel('origin', false) !== '*'
|
||||
&& empty($request->getHeader('x-appwrite-key', ''))
|
||||
&& \parse_url($httpReferrerSafe, PHP_URL_HOST) === 'localhost'
|
||||
) {
|
||||
$route = $request->getRoute();
|
||||
if ($route->getLabel('origin', false) === '*') {
|
||||
return;
|
||||
}
|
||||
if (!$originValidator->isValid($origin)) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_UNKNOWN_ORIGIN, $originValidator->getDescription());
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Automatic certificate generation
|
||||
*/
|
||||
App::init()
|
||||
->groups(['api', 'web'])
|
||||
->inject('request')
|
||||
->inject('console')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForCertificates')
|
||||
->inject('platform')
|
||||
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform) {
|
||||
$hostname = $request->getHostname();
|
||||
$cache = Config::getParam('hostnames', []);
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
|
||||
// 1. Cache hit
|
||||
if (array_key_exists($hostname, $cache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Domain validation
|
||||
$domain = new Domain(!empty($hostname) ? $hostname : '');
|
||||
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
|
||||
$cache[$domain->get()] = false;
|
||||
Config::setParam('hostnames', $cache);
|
||||
return;
|
||||
}
|
||||
|
||||
if (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Check if domain is a main domain
|
||||
if (!in_array($domain->get(), $platformHostnames)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Check/create rule (requires DB access)
|
||||
Authorization::disable();
|
||||
try {
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$document = $isMd5
|
||||
? $dbForPlatform->getDocument('rules', md5($domain->get()))
|
||||
: $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain->get()]),
|
||||
]);
|
||||
|
||||
if (!$document->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Create new rule
|
||||
$owner = '';
|
||||
$fallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
|
||||
$funcDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
$siteDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
|
||||
if (!empty($fallback) && \str_ends_with($domain->get(), $fallback)) {
|
||||
$funcDomain = $fallback;
|
||||
}
|
||||
|
||||
if (
|
||||
(!empty($funcDomain) && \str_ends_with($domain->get(), $funcDomain)) ||
|
||||
(!empty($siteDomain) && \str_ends_with($domain->get(), $siteDomain))
|
||||
) {
|
||||
$owner = 'Appwrite';
|
||||
}
|
||||
|
||||
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
|
||||
$document = new Document([
|
||||
'$id' => $ruleId,
|
||||
'domain' => $domain->get(),
|
||||
'type' => 'api',
|
||||
'status' => 'verifying',
|
||||
'projectId' => $console->getId(),
|
||||
'projectInternalId' => $console->getSequence(),
|
||||
'search' => implode(' ', [$ruleId, $domain->get()]),
|
||||
'owner' => $owner,
|
||||
'region' => $console->getAttribute('region')
|
||||
]);
|
||||
|
||||
$dbForPlatform->createDocument('rules', $document);
|
||||
|
||||
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
|
||||
$queueForCertificates
|
||||
->setDomain($document)
|
||||
->setSkipRenewCheck(true)
|
||||
->trigger();
|
||||
} catch (Duplicate $e) {
|
||||
Console::info('Certificate already exists');
|
||||
} finally {
|
||||
$cache[$domain->get()] = true;
|
||||
Config::setParam('hostnames', $cache);
|
||||
Authorization::reset();
|
||||
}
|
||||
});
|
||||
|
||||
App::options()
|
||||
->inject('utopia')
|
||||
->inject('swooleRequest')
|
||||
@@ -1121,38 +1135,32 @@ App::options()
|
||||
->inject('executor')
|
||||
->inject('geodb')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('platform')
|
||||
->inject('previewHostname')
|
||||
->inject('project')
|
||||
->inject('devKey')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey) {
|
||||
->inject('cors')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
if (!in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
|
||||
$origin = $request->getOrigin();
|
||||
foreach ($cors->headers($request->getOrigin()) as $name => $value) {
|
||||
$response->addHeader($name, $value);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $origin)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
->noContent();
|
||||
|
||||
if (!$devKey->isEmpty()) {
|
||||
$response->addHeader('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
|
||||
/** OPTIONS requests in utopia do not execute shutdown handlers, as a result we need to track the OPTIONS requests explicitly
|
||||
* @see https://github.com/utopia-php/http/blob/0.33.16/src/App.php#L825-L855
|
||||
*/
|
||||
@@ -1253,10 +1261,10 @@ App::error()
|
||||
}
|
||||
|
||||
/**
|
||||
* If its not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php
|
||||
* If not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php
|
||||
*/
|
||||
if (!$publish && $project->getId() !== 'console') {
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
if (!DBUser::isPrivileged(Authorization::getRoles())) {
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
if (!empty($file)) {
|
||||
@@ -1355,6 +1363,7 @@ App::error()
|
||||
case 409: // Error allowed publicly
|
||||
case 412: // Error allowed publicly
|
||||
case 416: // Error allowed publicly
|
||||
case 422: // Error allowed publicly
|
||||
case 429: // Error allowed publicly
|
||||
case 451: // Error allowed publicly
|
||||
case 501: // Error allowed publicly
|
||||
@@ -1438,18 +1447,16 @@ App::get('/robots.txt')
|
||||
->inject('executor')
|
||||
->inject('geodb')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('platform')
|
||||
->inject('previewHostname')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
|
||||
$host = $request->getHostname() ?? '';
|
||||
$consoleDomain = System::getEnv('_APP_CONSOLE_DOMAIN', '');
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
if (($host === $consoleDomain || $host === $mainDomain || $host === 'localhost') && empty($previewHostname)) {
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey) {
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1472,18 +1479,16 @@ App::get('/humans.txt')
|
||||
->inject('executor')
|
||||
->inject('geodb')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('platform')
|
||||
->inject('previewHostname')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
|
||||
$host = $request->getHostname() ?? '';
|
||||
$consoleDomain = System::getEnv('_APP_CONSOLE_DOMAIN', '');
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
if (($host === $consoleDomain || $host === $mainDomain || $host === 'localhost') && empty($previewHostname)) {
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey) {
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1612,7 +1617,7 @@ App::get('/_appwrite/authorize')
|
||||
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
|
||||
|
||||
$response
|
||||
->addCookie(Auth::$cookieNamePreview, $jwt, (new \DateTime($expire))->getTimestamp(), '/', $host, ('https' === $protocol), true, null)
|
||||
->addCookie(COOKIE_NAME_PREVIEW, $jwt, (new \DateTime($expire))->getTimestamp(), '/', $host, ('https' === $protocol), true, null)
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->redirect($protocol . '://' . $host . $path);
|
||||
|
||||
@@ -15,7 +15,6 @@ use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
@@ -27,7 +26,7 @@ App::get('/v1/mock/tests/general/oauth2')
|
||||
->label('docs', false)
|
||||
->label('mock', true)
|
||||
->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
|
||||
->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.') // Important to deny an open redirect attack
|
||||
->param('redirect_uri', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator']) // Important to deny an open redirect attack
|
||||
->param('scope', '', new Text(100), 'OAuth2 scope list.')
|
||||
->param('state', '', new Text(1024), 'OAuth2 state.')
|
||||
->inject('response')
|
||||
@@ -64,7 +63,7 @@ App::get('/v1/mock/tests/general/oauth2/token')
|
||||
->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
|
||||
->param('client_secret', '', new Text(100), 'OAuth2 scope list.')
|
||||
->param('grant_type', 'authorization_code', new WhiteList(['refresh_token', 'authorization_code']), 'OAuth2 Grant Type.', true)
|
||||
->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.', true)
|
||||
->param('redirect_uri', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
|
||||
->param('code', '', new Text(100), 'OAuth2 state.', true)
|
||||
->param('refresh_token', '', new Text(100), 'OAuth2 refresh token.', true)
|
||||
->inject('response')
|
||||
@@ -118,6 +117,28 @@ App::get('/v1/mock/tests/general/oauth2/user')
|
||||
'id' => 1,
|
||||
'name' => 'User Name',
|
||||
'email' => 'useroauth@localhost.test',
|
||||
'verified' => true,
|
||||
]);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/oauth2/user-unverified')
|
||||
->desc('OAuth2 User Unverified')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->param('token', '', new Text(100), 'OAuth2 Access Token.')
|
||||
->inject('response')
|
||||
->action(function (string $token, Response $response) {
|
||||
|
||||
if ($token != '123456') {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid token');
|
||||
}
|
||||
|
||||
$response->json([
|
||||
'id' => 2,
|
||||
'name' => 'User Name Unverified',
|
||||
'email' => 'useroauthunverified@localhost.test',
|
||||
'verified' => false,
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
+125
-29
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Event\Audit;
|
||||
@@ -9,20 +8,22 @@ use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Realtime;
|
||||
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\Database\Documents\User;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -34,7 +35,7 @@ use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
$parseLabel = function (string $label, array $responsePayload, array $requestParams, Document $user) {
|
||||
$parseLabel = function (string $label, array $responsePayload, array $requestParams, User $user) {
|
||||
preg_match_all('/{(.*?)}/', $label, $matches);
|
||||
foreach ($matches[1] ?? [] as $pos => $match) {
|
||||
$find = $matches[0][$pos];
|
||||
@@ -54,7 +55,20 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
|
||||
};
|
||||
|
||||
if (array_key_exists($replace, $params)) {
|
||||
$label = \str_replace($find, $params[$replace], $label);
|
||||
$replacement = $params[$replace];
|
||||
// Convert to string if it's not already a string
|
||||
if (!is_string($replacement)) {
|
||||
if (is_array($replacement)) {
|
||||
$replacement = json_encode($replacement);
|
||||
} elseif (is_object($replacement) && method_exists($replacement, '__toString')) {
|
||||
$replacement = (string)$replacement;
|
||||
} elseif (is_scalar($replacement)) {
|
||||
$replacement = (string)$replacement;
|
||||
} else {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, "The server encountered an error while parsing the label: $label. Please create an issue on GitHub to allow us to investigate further https://github.com/appwrite/appwrite/issues/new/choose");
|
||||
}
|
||||
}
|
||||
$label = \str_replace($find, $replacement, $label);
|
||||
}
|
||||
}
|
||||
return $label;
|
||||
@@ -219,42 +233,97 @@ App::init()
|
||||
->inject('mode')
|
||||
->inject('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) {
|
||||
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey) {
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
/**
|
||||
* Handle user authentication and session validation.
|
||||
*
|
||||
* This function follows a series of steps to determine the appropriate user session
|
||||
* based on cookies, headers, and JWT tokens.
|
||||
*
|
||||
* Process:
|
||||
*
|
||||
* Project & Role Validation:
|
||||
* 1. Check if the project is empty. If so, throw an exception.
|
||||
* 2. Get the roles configuration.
|
||||
* 3. Determine the role for the user based on the user document.
|
||||
* 4. Get the scopes for the role.
|
||||
*
|
||||
* API Key Authentication:
|
||||
* 5. If there is an API key:
|
||||
* - Verify no user session exists simultaneously
|
||||
* - Check if key is expired
|
||||
* - Set role and scopes from API key
|
||||
* - Handle special app role case
|
||||
* - For standard keys, update last accessed time
|
||||
*
|
||||
* User Activity:
|
||||
* 6. If the project is not the console and user is not admin:
|
||||
* - Update user's last activity timestamp
|
||||
*
|
||||
* Access Control:
|
||||
* 7. Get the method from the route
|
||||
* 8. Validate namespace permissions
|
||||
* 9. Validate scope permissions
|
||||
* 10. Check if user is blocked
|
||||
*
|
||||
* Security Checks:
|
||||
* 11. Verify password status (check if reset required)
|
||||
* 12. Validate MFA requirements:
|
||||
* - Check if MFA is enabled
|
||||
* - Verify email status
|
||||
* - Verify phone status
|
||||
* - Verify authenticator status
|
||||
* 13. Handle Multi-Factor Authentication:
|
||||
* - Check remaining required factors
|
||||
* - Validate factor completion
|
||||
* - Throw exception if factors incomplete
|
||||
*/
|
||||
|
||||
// Step 1: Check if project is empty
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Step 2: Get roles configuration
|
||||
$roles = Config::getParam('roles', []);
|
||||
|
||||
// Step 3: Determine role for user
|
||||
// TODO get scopes from the identity instead of the user roles config. The identity will containn the scopes the user authorized for the access token.
|
||||
|
||||
$role = $user->isEmpty()
|
||||
? Role::guests()->toString()
|
||||
: Role::users()->toString();
|
||||
|
||||
// Step 4: Get scopes for the role
|
||||
$scopes = $roles[$role]['scopes'];
|
||||
|
||||
// API Key authentication
|
||||
// Step 5: API Key Authentication
|
||||
if (!empty($apiKey)) {
|
||||
// Verify no user session exists simultaneously
|
||||
if (!$user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
|
||||
}
|
||||
// Check if key is expired
|
||||
if ($apiKey->isExpired()) {
|
||||
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
// Set role and scopes from API key
|
||||
$role = $apiKey->getRole();
|
||||
$scopes = $apiKey->getScopes();
|
||||
|
||||
|
||||
if ($apiKey->getRole() === Auth::USER_ROLE_APPS) {
|
||||
// Handle special app role case
|
||||
if ($apiKey->getRole() === User::ROLE_APPS) {
|
||||
// Disable authorization checks for API keys
|
||||
Authorization::setDefaultStatus(false);
|
||||
|
||||
$user = new Document([
|
||||
$user = new User([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'type' => Auth::ACTIVITY_TYPE_APP,
|
||||
'type' => ACTIVITY_TYPE_APP,
|
||||
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => $apiKey->getName(),
|
||||
@@ -263,6 +332,7 @@ App::init()
|
||||
$queueForAudits->setUser($user);
|
||||
}
|
||||
|
||||
// For standard keys, update last accessed time
|
||||
if ($apiKey->getType() === API_KEY_STANDARD) {
|
||||
$dbKey = $project->find(
|
||||
key: 'secret',
|
||||
@@ -328,11 +398,11 @@ App::init()
|
||||
$scopes = \array_unique($scopes);
|
||||
|
||||
Authorization::setRole($role);
|
||||
foreach (Auth::getRoles($user) as $authRole) {
|
||||
foreach ($user->getRoles() as $authRole) {
|
||||
Authorization::setRole($authRole);
|
||||
}
|
||||
|
||||
// Update project last activity
|
||||
// Step 6: Update project and user last activity
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$accessedAt = $project->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
@@ -341,7 +411,6 @@ App::init()
|
||||
}
|
||||
}
|
||||
|
||||
// Update user last activity
|
||||
if (!empty($user->getId())) {
|
||||
$accessedAt = $user->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
|
||||
@@ -355,6 +424,7 @@ App::init()
|
||||
}
|
||||
}
|
||||
|
||||
// Steps 7-9: Access Control - Method, Namespace and Scope Validation
|
||||
/**
|
||||
* @var ?Method $method
|
||||
*/
|
||||
@@ -372,27 +442,29 @@ App::init()
|
||||
if (
|
||||
array_key_exists($namespace, $project->getAttribute('services', []))
|
||||
&& !$project->getAttribute('services', [])[$namespace]
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
|
||||
) {
|
||||
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
// Do now allow access if scope is not allowed
|
||||
// Step 9: Validate scope permissions
|
||||
$allowed = (array)$route->getLabel('scope', 'none');
|
||||
if (empty(\array_intersect($allowed, $scopes))) {
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scopes (' . \json_encode($allowed) . ')');
|
||||
}
|
||||
|
||||
// Do not allow access to blocked accounts
|
||||
// Step 10: Check if user is blocked
|
||||
if (false === $user->getAttribute('status')) { // Account is blocked
|
||||
throw new Exception(Exception::USER_BLOCKED);
|
||||
}
|
||||
|
||||
// Step 11: Verify password status
|
||||
if ($user->getAttribute('reset')) {
|
||||
throw new Exception(Exception::USER_PASSWORD_RESET_REQUIRED);
|
||||
}
|
||||
|
||||
// Step 12: Validate MFA requirements
|
||||
$mfaEnabled = $user->getAttribute('mfa', false);
|
||||
$hasVerifiedEmail = $user->getAttribute('emailVerification', false);
|
||||
$hasVerifiedPhone = $user->getAttribute('phoneVerification', false);
|
||||
@@ -400,6 +472,7 @@ App::init()
|
||||
$hasMoreFactors = $hasVerifiedEmail || $hasVerifiedPhone || $hasVerifiedAuthenticator;
|
||||
$minimumFactors = ($mfaEnabled && $hasMoreFactors) ? 2 : 1;
|
||||
|
||||
// Step 13: Handle Multi-Factor Authentication
|
||||
if (!in_array('mfa', $route->getGroups())) {
|
||||
if ($session && \count($session->getAttribute('factors', [])) < $minimumFactors) {
|
||||
throw new Exception(Exception::USER_MORE_FACTORS_REQUIRED);
|
||||
@@ -424,6 +497,9 @@ App::init()
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForBuilds')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForMigrations')
|
||||
->inject('dbForProject')
|
||||
->inject('timelimit')
|
||||
->inject('resourceToken')
|
||||
@@ -432,14 +508,15 @@ App::init()
|
||||
->inject('plan')
|
||||
->inject('devKey')
|
||||
->inject('telemetry')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
->inject('platform')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Migration $queueForMigrations, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
if (
|
||||
array_key_exists('rest', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['rest']
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
@@ -470,8 +547,8 @@ App::init()
|
||||
$closestLimit = null;
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
$isPrivilegedUser = User::isPrivileged($roles);
|
||||
$isAppUser = User::isApp($roles);
|
||||
|
||||
foreach ($timeLimitArray as $timeLimit) {
|
||||
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
|
||||
@@ -506,6 +583,10 @@ App::init()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: (@loks0n)
|
||||
* Avoid mutating the message across file boundaries - it's difficult to reason about at scale.
|
||||
*/
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
@@ -526,7 +607,7 @@ App::init()
|
||||
if (!$user->isEmpty()) {
|
||||
$userClone = clone $user;
|
||||
// $user doesn't support `type` and can cause unintended effects.
|
||||
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
|
||||
$userClone->setAttribute('type', ACTIVITY_TYPE_USER);
|
||||
$queueForAudits->setUser($userClone);
|
||||
}
|
||||
|
||||
@@ -536,10 +617,17 @@ App::init()
|
||||
}
|
||||
}
|
||||
|
||||
/* Auto-set projects */
|
||||
$queueForDeletes->setProject($project);
|
||||
$queueForDatabase->setProject($project);
|
||||
$queueForBuilds->setProject($project);
|
||||
$queueForMessaging->setProject($project);
|
||||
$queueForFunctions->setProject($project);
|
||||
$queueForBuilds->setProject($project);
|
||||
|
||||
/* Auto-set platforms */
|
||||
$queueForFunctions->setPlatform($platform);
|
||||
$queueForBuilds->setPlatform($platform);
|
||||
$queueForMails->setPlatform($platform);
|
||||
|
||||
// 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.
|
||||
@@ -569,7 +657,7 @@ App::init()
|
||||
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());
|
||||
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !User::isPrivileged(Authorization::getRoles());
|
||||
|
||||
$key = $request->cacheIdentifier();
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
@@ -580,6 +668,10 @@ App::init()
|
||||
$data = $cache->load($key, $timestamp);
|
||||
|
||||
if (!empty($data) && !$cacheLog->isEmpty()) {
|
||||
$usageMetric = $route->getLabel('usage.metric', null);
|
||||
if ($usageMetric === METRIC_AVATARS_SCREENSHOTS_GENERATED) {
|
||||
$queueForStatsUsage->disableMetric(METRIC_AVATARS_SCREENSHOTS_GENERATED);
|
||||
}
|
||||
$parts = explode('/', $cacheLog->getAttribute('resourceType', ''));
|
||||
$type = $parts[0] ?? null;
|
||||
|
||||
@@ -588,12 +680,16 @@ App::init()
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$bucket->getAttribute('transformations', true) && !$isAppUser && !$isPrivilegedUser) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_TRANSFORMATIONS_DISABLED);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
@@ -618,7 +714,7 @@ App::init()
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
//Do not update transformedAt if it's a console user
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
if (!User::isPrivileged(Authorization::getRoles())) {
|
||||
$transformedAt = $file->getAttribute('transformedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
|
||||
$file->setAttribute('transformedAt', DateTime::now());
|
||||
@@ -718,7 +814,7 @@ App::shutdown()
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForRealtime')
|
||||
->inject('dbForProject')
|
||||
->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) {
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject) use ($parseLabel) {
|
||||
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
@@ -766,7 +862,7 @@ 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);
|
||||
$userClone->setAttribute('type', ACTIVITY_TYPE_USER);
|
||||
$queueForAudits->setUser($userClone);
|
||||
} elseif ($queueForAudits->getUser() === null || $queueForAudits->getUser()->isEmpty()) {
|
||||
/**
|
||||
@@ -777,10 +873,10 @@ App::shutdown()
|
||||
*
|
||||
* Therefore, we consider this an anonymous request and create a relevant user.
|
||||
*/
|
||||
$user = new Document([
|
||||
$user = new User([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'type' => Auth::ACTIVITY_TYPE_GUEST,
|
||||
'type' => ACTIVITY_TYPE_GUEST,
|
||||
'email' => 'guest.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => 'Guest',
|
||||
@@ -870,7 +966,7 @@ App::shutdown()
|
||||
}
|
||||
|
||||
if ($project->getId() !== 'console') {
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
if (!User::isPrivileged(Authorization::getRoles())) {
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
if (!empty($file)) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Request;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
@@ -20,7 +20,7 @@ App::init()
|
||||
$lastUpdate = $session->getAttribute('mfaUpdatedAt');
|
||||
if (!empty($lastUpdate)) {
|
||||
$now = DateTime::now();
|
||||
$maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), Auth::MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge
|
||||
$maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge
|
||||
|
||||
$isSessionFresh = DateTime::formatTz($maxAllowedDate) >= DateTime::formatTz($now);
|
||||
}
|
||||
@@ -49,8 +49,8 @@ App::init()
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
|
||||
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||
return;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
|
||||
App::init()
|
||||
->groups(['web'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
$response
|
||||
->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
|
||||
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
|
||||
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
|
||||
;
|
||||
});
|
||||
|
||||
App::get('/')
|
||||
->alias('auth/*')
|
||||
->alias('/invite')
|
||||
->alias('/login')
|
||||
->alias('/mfa')
|
||||
->alias('/card/*')
|
||||
->alias('/recover')
|
||||
->alias('/register/*')
|
||||
->groups(['web'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
$url = parse_url($request->getURI());
|
||||
$target = "/console{$url['path']}";
|
||||
$params = $request->getParams();
|
||||
if (!empty($params)) {
|
||||
$target .= "?" . \http_build_query($params);
|
||||
}
|
||||
if ($url['fragment'] ?? false) {
|
||||
$target .= "#{$url['fragment']}";
|
||||
}
|
||||
$response->redirect($target);
|
||||
});
|
||||
@@ -10,7 +10,7 @@ App::get('/versions')
|
||||
->label('scope', 'public')
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$platforms = Config::getParam('platforms');
|
||||
$platforms = Config::getParam('sdks');
|
||||
|
||||
$versions = [
|
||||
'server' => APP_VERSION_STABLE,
|
||||
|
||||
+8
-4
@@ -12,6 +12,8 @@ use Swoole\Process;
|
||||
use Swoole\Table;
|
||||
use Swoole\Timer;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Adapter\Database as AdapterDatabase;
|
||||
use Utopia\Audit\Adapter\SQL as AuditAdapterSQL;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Compression\Compression;
|
||||
@@ -260,8 +262,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
|
||||
// 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);
|
||||
if ($dbForPlatform->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
|
||||
$adapter = new AdapterDatabase($dbForPlatform);
|
||||
$audit = new Audit($adapter);
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
@@ -389,8 +392,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
}
|
||||
|
||||
if ($dbForProject->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($dbForProject);
|
||||
if ($dbForProject->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
|
||||
$adapter = new AdapterDatabase($dbForProject);
|
||||
$audit = new Audit($adapter);
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
|
||||
+40
-36
@@ -1,42 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Utopia\Config\Adapters\PHP;
|
||||
use Utopia\Config\Config;
|
||||
|
||||
require_once __DIR__ . '/../config/storage/resource_limits.php';
|
||||
|
||||
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');
|
||||
$configAdapter = new PHP();
|
||||
|
||||
Config::load('runtimes', __DIR__ . '/../config/runtimes.php', $configAdapter);
|
||||
Config::load('runtimes-v2', __DIR__ . '/../config/runtimes-v2.php', $configAdapter);
|
||||
Config::load('template-runtimes', __DIR__ . '/../config/template-runtimes.php', $configAdapter);
|
||||
Config::load('events', __DIR__ . '/../config/events.php', $configAdapter);
|
||||
Config::load('auth', __DIR__ . '/../config/auth.php', $configAdapter);
|
||||
Config::load('apis', __DIR__ . '/../config/apis.php', $configAdapter); // List of APIs
|
||||
Config::load('errors', __DIR__ . '/../config/errors.php', $configAdapter);
|
||||
Config::load('oAuthProviders', __DIR__ . '/../config/oAuthProviders.php', $configAdapter);
|
||||
Config::load('sdks', __DIR__ . '/../config/sdks.php', $configAdapter);
|
||||
Config::load('platform', __DIR__ . '/../config/platform.php', $configAdapter);
|
||||
Config::load('console', __DIR__ . '/../config/console.php', $configAdapter);
|
||||
Config::load('collections', __DIR__ . '/../config/collections.php', $configAdapter);
|
||||
Config::load('frameworks', __DIR__ . '/../config/frameworks.php', $configAdapter);
|
||||
Config::load('usage', __DIR__ . '/../config/usage.php', $configAdapter);
|
||||
Config::load('roles', __DIR__ . '/../config/roles.php', $configAdapter); // User roles and scopes
|
||||
Config::load('scopes', __DIR__ . '/../config/scopes.php', $configAdapter); // User roles and scopes
|
||||
Config::load('services', __DIR__ . '/../config/services.php', $configAdapter); // List of services
|
||||
Config::load('variables', __DIR__ . '/../config/variables.php', $configAdapter); // List of env variables
|
||||
Config::load('regions', __DIR__ . '/../config/regions.php', $configAdapter); // List of available regions
|
||||
Config::load('avatar-browsers', __DIR__ . '/../config/avatars/browsers.php', $configAdapter);
|
||||
Config::load('avatar-credit-cards', __DIR__ . '/../config/avatars/credit-cards.php', $configAdapter);
|
||||
Config::load('avatar-flags', __DIR__ . '/../config/avatars/flags.php', $configAdapter);
|
||||
Config::load('locale-codes', __DIR__ . '/../config/locale/codes.php', $configAdapter);
|
||||
Config::load('locale-currencies', __DIR__ . '/../config/locale/currencies.php', $configAdapter);
|
||||
Config::load('locale-eu', __DIR__ . '/../config/locale/eu.php', $configAdapter);
|
||||
Config::load('locale-languages', __DIR__ . '/../config/locale/languages.php', $configAdapter);
|
||||
Config::load('locale-phones', __DIR__ . '/../config/locale/phones.php', $configAdapter);
|
||||
Config::load('locale-countries', __DIR__ . '/../config/locale/countries.php', $configAdapter);
|
||||
Config::load('locale-continents', __DIR__ . '/../config/locale/continents.php', $configAdapter);
|
||||
Config::load('locale-templates', __DIR__ . '/../config/locale/templates.php', $configAdapter);
|
||||
Config::load('storage-logos', __DIR__ . '/../config/storage/logos.php', $configAdapter);
|
||||
Config::load('storage-mimes', __DIR__ . '/../config/storage/mimes.php', $configAdapter);
|
||||
Config::load('storage-inputs', __DIR__ . '/../config/storage/inputs.php', $configAdapter);
|
||||
Config::load('storage-outputs', __DIR__ . '/../config/storage/outputs.php', $configAdapter);
|
||||
Config::load('specifications', __DIR__ . '/../config/specifications.php', $configAdapter);
|
||||
Config::load('templates-function', __DIR__ . '/../config/templates/function.php', $configAdapter);
|
||||
Config::load('templates-site', __DIR__ . '/../config/templates/site.php', $configAdapter);
|
||||
|
||||
+95
-4
@@ -4,12 +4,17 @@ use Appwrite\Platform\Modules\Compute\Specification;
|
||||
|
||||
const APP_NAME = 'Appwrite';
|
||||
const APP_DOMAIN = 'appwrite.io';
|
||||
|
||||
// Email
|
||||
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
|
||||
const APP_EMAIL_SECURITY = ''; // Default security email address
|
||||
const APP_EMAIL_LOGO_URL = 'https://cloud.appwrite.io/images/mails/logo.png';
|
||||
const APP_EMAIL_ACCENT_COLOR = '#fd366e';
|
||||
const APP_EMAIL_TERMS_URL = 'https://appwrite.io/terms';
|
||||
const APP_EMAIL_PRIVACY_URL = 'https://appwrite.io/privacy';
|
||||
const APP_EMAIL_PLATFORM_NAME = 'Appwrite';
|
||||
const APP_EMAIL_FOOTER_IMAGE_URL = 'https://appwrite.io/email/footer.png';
|
||||
|
||||
const APP_USERAGENT = APP_NAME . '-Server v%s. Please report abuse at %s';
|
||||
const APP_MODE_DEFAULT = 'default';
|
||||
const APP_MODE_ADMIN = 'admin';
|
||||
@@ -31,6 +36,7 @@ const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate
|
||||
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_LIMIT_DATABASE_BATCH = 100; // Default maximum batch size for database operations
|
||||
const APP_LIMIT_DATABASE_TRANSACTION = 100; // Default maximum operations per transaction
|
||||
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
|
||||
@@ -54,7 +60,12 @@ const APP_DATABASE_TIMEOUT_MILLISECONDS_API = 15 * 1000; // 15 seconds
|
||||
const APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER = 300 * 1000; // 5 minutes
|
||||
const APP_DATABASE_TIMEOUT_MILLISECONDS_TASK = 300 * 1000; // 5 minutes
|
||||
const APP_DATABASE_QUERY_MAX_VALUES = 500;
|
||||
const APP_DATABASE_QUERY_MAX_VALUES_WORKER = 5000;
|
||||
const APP_DATABASE_ENCRYPT_SIZE_MIN = 150;
|
||||
const APP_DATABASE_TXN_TTL_MIN = 60; // 1 minute
|
||||
const APP_DATABASE_TXN_TTL_MAX = 3600; // 1 hour
|
||||
const APP_DATABASE_TXN_TTL_DEFAULT = 300; // 5 minutes
|
||||
const APP_DATABASE_TXN_MAX_OPERATIONS = 100; // Maximum operations per transaction
|
||||
const APP_STORAGE_UPLOADS = '/storage/uploads';
|
||||
const APP_STORAGE_SITES = '/storage/sites';
|
||||
const APP_STORAGE_FUNCTIONS = '/storage/functions';
|
||||
@@ -76,15 +87,79 @@ const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
|
||||
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
|
||||
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
|
||||
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
|
||||
const APP_HOSTNAME_INTERNAL = 'appwrite';
|
||||
const APP_COMPUTE_CPUS_DEFAULT = 0.5;
|
||||
const APP_COMPUTE_MEMORY_DEFAULT = 512;
|
||||
const APP_COMPUTE_SPECIFICATION_DEFAULT = Specification::S_1VCPU_512MB;
|
||||
const APP_PLATFORM_SERVER = 'server';
|
||||
const APP_PLATFORM_CLIENT = 'client';
|
||||
const APP_PLATFORM_CONSOLE = 'console';
|
||||
const APP_SDK_PLATFORM_SERVER = 'server';
|
||||
const APP_SDK_PLATFORM_CLIENT = 'client';
|
||||
const APP_SDK_PLATFORM_CONSOLE = 'console';
|
||||
const APP_VCS_GITHUB_USERNAME = 'Appwrite';
|
||||
const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io';
|
||||
const APP_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite';
|
||||
const APP_BRANDED_EMAIL_BASE_TEMPLATE = 'email-base-styled';
|
||||
|
||||
/**
|
||||
* JWT for Resource Tokens.
|
||||
*/
|
||||
const RESOURCE_TOKEN_ALGORITHM = 'HS256';
|
||||
const RESOURCE_TOKEN_MAX_AGE = 86400 * 365 * 10; /* 10 years */
|
||||
const RESOURCE_TOKEN_LEEWAY = 10; // 10 seconds
|
||||
|
||||
/**
|
||||
* Token Expiration times.
|
||||
*/
|
||||
const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */
|
||||
const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */
|
||||
const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */
|
||||
const TOKEN_EXPIRATION_CONFIRM = 3600 * 1; /* 1 hour */
|
||||
const TOKEN_EXPIRATION_OTP = 60 * 15; /* 15 minutes */
|
||||
const TOKEN_EXPIRATION_GENERIC = 60 * 15; /* 15 minutes */
|
||||
|
||||
/**
|
||||
* Token Lengths.
|
||||
*/
|
||||
const TOKEN_LENGTH_MAGIC_URL = 64;
|
||||
const TOKEN_LENGTH_VERIFICATION = 256;
|
||||
const TOKEN_LENGTH_RECOVERY = 256;
|
||||
const TOKEN_LENGTH_OAUTH2 = 64;
|
||||
const TOKEN_LENGTH_SESSION = 256;
|
||||
|
||||
/**
|
||||
* Token Types.
|
||||
*/
|
||||
const TOKEN_TYPE_LOGIN = 1; // Deprecated
|
||||
const TOKEN_TYPE_VERIFICATION = 2;
|
||||
const TOKEN_TYPE_RECOVERY = 3;
|
||||
const TOKEN_TYPE_INVITE = 4;
|
||||
const TOKEN_TYPE_MAGIC_URL = 5;
|
||||
const TOKEN_TYPE_PHONE = 6;
|
||||
const TOKEN_TYPE_OAUTH2 = 7;
|
||||
const TOKEN_TYPE_GENERIC = 8;
|
||||
const TOKEN_TYPE_EMAIL = 9; // OTP
|
||||
|
||||
/**
|
||||
* Session Providers.
|
||||
*/
|
||||
const SESSION_PROVIDER_EMAIL = 'email';
|
||||
const SESSION_PROVIDER_ANONYMOUS = 'anonymous';
|
||||
const SESSION_PROVIDER_MAGIC_URL = 'magic-url';
|
||||
const SESSION_PROVIDER_PHONE = 'phone';
|
||||
const SESSION_PROVIDER_OAUTH2 = 'oauth2';
|
||||
const SESSION_PROVIDER_TOKEN = 'token';
|
||||
const SESSION_PROVIDER_SERVER = 'server';
|
||||
|
||||
/**
|
||||
* Activity associated with user or the app.
|
||||
*/
|
||||
const ACTIVITY_TYPE_APP = 'app';
|
||||
const ACTIVITY_TYPE_USER = 'user';
|
||||
const ACTIVITY_TYPE_GUEST = 'guest';
|
||||
|
||||
/**
|
||||
* MFA
|
||||
*/
|
||||
const MFA_RECENT_DURATION = 1800; // 30 mins
|
||||
|
||||
|
||||
// Database Reconnect
|
||||
const DATABASE_RECONNECT_SLEEP = 2;
|
||||
@@ -104,8 +179,11 @@ 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_TRANSACTION = 'transaction';
|
||||
const DELETE_TYPE_EXPIRED_TRANSACTIONS = 'expired_transactions';
|
||||
const DELETE_TYPE_PROJECTS = 'projects';
|
||||
const DELETE_TYPE_SITES = 'sites';
|
||||
const DELETE_TYPE_FUNCTIONS = 'functions';
|
||||
@@ -128,8 +206,15 @@ 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_CSV_EXPORTS = 'csv_exports';
|
||||
const DELETE_TYPE_MAINTENANCE = 'maintenance';
|
||||
|
||||
// Rule statuses
|
||||
const RULE_STATUS_CREATED = 'created'; // This is also the status when domain DNS verification fails.
|
||||
const RULE_STATUS_CERTIFICATE_GENERATING = 'verifying';
|
||||
const RULE_STATUS_CERTIFICATE_GENERATION_FAILED = 'unverified';
|
||||
const RULE_STATUS_VERIFIED = 'verified';
|
||||
|
||||
// Message types
|
||||
const MESSAGE_SEND_TYPE_INTERNAL = 'internal';
|
||||
const MESSAGE_SEND_TYPE_EXTERNAL = 'external';
|
||||
@@ -260,6 +345,9 @@ 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';
|
||||
const METRIC_AVATARS_SCREENSHOTS_GENERATED = 'avatars.screenshotsGenerated';
|
||||
const METRIC_FUNCTIONS_RUNTIME = 'functions.runtimes.{runtime}';
|
||||
const METRIC_SITES_FRAMEWORK = 'sites.frameworks.{framework}';
|
||||
|
||||
// Resource types
|
||||
const RESOURCE_TYPE_PROJECTS = 'projects';
|
||||
@@ -283,3 +371,6 @@ const TOKENS_RESOURCE_TYPE_DATABASES = 'databases';
|
||||
const SCHEDULE_RESOURCE_TYPE_EXECUTION = 'execution';
|
||||
const SCHEDULE_RESOURCE_TYPE_FUNCTION = 'function';
|
||||
const SCHEDULE_RESOURCE_TYPE_MESSAGE = 'message';
|
||||
|
||||
/** Preview cookie */
|
||||
const COOKIE_NAME_PREVIEW = 'a_jwt_console';
|
||||
|
||||
@@ -136,7 +136,13 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('keys', [
|
||||
Query::equal('projectInternalId', [$document->getSequence()]),
|
||||
Query::or([
|
||||
Query::equal('projectInternalId', [$document->getSequence()]),
|
||||
Query::and([
|
||||
Query::equal('resourceType', ['projects']),
|
||||
Query::equal('resourceInternalId', [$document->getSequence()]),
|
||||
])
|
||||
]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
|
||||
+47
-1
@@ -39,6 +39,7 @@ if (!App::isProduction()) {
|
||||
PublicDomain::allow(['request-catcher-sms']);
|
||||
PublicDomain::allow(['request-catcher-webhook']);
|
||||
}
|
||||
|
||||
$register->set('logger', function () {
|
||||
// Register error logger
|
||||
$providerName = System::getEnv('_APP_LOGGING_PROVIDER', '');
|
||||
@@ -97,6 +98,51 @@ $register->set('logger', function () {
|
||||
return new Logger($adapter);
|
||||
});
|
||||
|
||||
$register->set('realtimeLogger', function () {
|
||||
// Register error logger for realtime, falls back to default logging config
|
||||
$providerConfig = System::getEnv('_APP_LOGGING_CONFIG_REALTIME', '')
|
||||
?: System::getEnv('_APP_LOGGING_CONFIG', '');
|
||||
|
||||
if (empty($providerConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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()],
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
@@ -326,7 +372,7 @@ $register->set('smtp', function () {
|
||||
return $mail;
|
||||
});
|
||||
$register->set('geodb', function () {
|
||||
return new Reader(__DIR__ . '/../assets/dbip/dbip-country-lite-2024-09.mmdb');
|
||||
return new Reader(__DIR__ . '/../assets/dbip/dbip-country-lite-2025-12.mmdb');
|
||||
});
|
||||
$register->set('passwordsDictionary', function () {
|
||||
$content = \file_get_contents(__DIR__ . '/../assets/security/10k-common-passwords');
|
||||
|
||||
+275
-167
@@ -2,9 +2,9 @@
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Databases\TransactionState;
|
||||
use Appwrite\Event\Audit as AuditEvent;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
@@ -20,12 +20,24 @@ use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Network\Cors;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Executor\Executor;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Adapter\Database as AdapterDatabase;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Auth\Hashes\Argon2;
|
||||
use Utopia\Auth\Hashes\Sha;
|
||||
use Utopia\Auth\Proofs\Code;
|
||||
use Utopia\Auth\Proofs\Password;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\Cache\Adapter\Pool as CachePool;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
@@ -35,7 +47,6 @@ use Utopia\Database\Adapter\Pool as DatabasePool;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime as DatabaseDateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DSN\DSN;
|
||||
@@ -56,7 +67,7 @@ use Utopia\Storage\Storage;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
use Utopia\Telemetry\Adapter\None as NoTelemetry;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub as VcsGitHub;
|
||||
|
||||
@@ -137,7 +148,7 @@ App::setResource('queueForStatsUsage', function (Publisher $publisher) {
|
||||
return new StatsUsage($publisher);
|
||||
}, ['publisher']);
|
||||
App::setResource('queueForAudits', function (Publisher $publisher) {
|
||||
return new Audit($publisher);
|
||||
return new AuditEvent($publisher);
|
||||
}, ['publisher']);
|
||||
App::setResource('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
@@ -151,121 +162,246 @@ App::setResource('queueForMigrations', function (Publisher $publisher) {
|
||||
App::setResource('queueForStatsResources', function (Publisher $publisher) {
|
||||
return new StatsResources($publisher);
|
||||
}, ['publisher']);
|
||||
App::setResource('platforms', function (Request $request, Document $console, Document $project) {
|
||||
$console->setAttribute('platforms', [ // Always allow current host
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Current Host',
|
||||
'type' => Platform::TYPE_WEB,
|
||||
'hostname' => $request->getHostname(),
|
||||
], Document::SET_TYPE_APPEND);
|
||||
|
||||
$hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', ''));
|
||||
$validator = new Hostname();
|
||||
foreach ($hostnames as $hostname) {
|
||||
$hostname = trim($hostname);
|
||||
if (!$validator->isValid($hostname)) {
|
||||
continue;
|
||||
}
|
||||
$console->setAttribute('platforms', [
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'type' => Platform::TYPE_WEB,
|
||||
'name' => $hostname,
|
||||
'hostname' => $hostname,
|
||||
], Document::SET_TYPE_APPEND);
|
||||
}
|
||||
/**
|
||||
* Platform configuration
|
||||
*/
|
||||
App::setResource('platform', function () {
|
||||
return Config::getParam('platform', []);
|
||||
}, []);
|
||||
|
||||
// Add `exp` and `appwrite-callback-{projectId}` schemes
|
||||
/**
|
||||
* List of allowed request hostnames for the request.
|
||||
*/
|
||||
App::setResource('allowedHostnames', function (array $platform, Document $project, Document $rule, Document $devKey, Request $request) {
|
||||
$allowed = [...($platform['hostnames'] ?? [])];
|
||||
|
||||
/* Add platform configured hostnames */
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$project->setAttribute('platforms', [
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'type' => Platform::TYPE_SCHEME,
|
||||
'name' => 'Expo',
|
||||
'key' => 'exp',
|
||||
], Document::SET_TYPE_APPEND);
|
||||
$project->setAttribute('platforms', [
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'type' => Platform::TYPE_SCHEME,
|
||||
'name' => 'Appwrite Callback',
|
||||
'key' => 'appwrite-callback-' . $project->getId(),
|
||||
], Document::SET_TYPE_APPEND);
|
||||
$platforms = $project->getAttribute('platforms', []);
|
||||
$hostnames = Platform::getHostnames($platforms);
|
||||
$allowed = [...$allowed, ...$hostnames];
|
||||
}
|
||||
|
||||
return [
|
||||
...$console->getAttribute('platforms', []),
|
||||
...$project->getAttribute('platforms', []),
|
||||
];
|
||||
}, ['request', 'console', 'project']);
|
||||
/* Add the request hostname if a dev key is found */
|
||||
if (!$devKey->isEmpty()) {
|
||||
$allowed[] = $request->getHostname();
|
||||
}
|
||||
|
||||
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
/** @var string $mode */
|
||||
$originHostname = parse_url($request->getOrigin(), PHP_URL_HOST);
|
||||
|
||||
/* Add request hostname for preflight requests */
|
||||
if ($request->getMethod() === 'OPTIONS') {
|
||||
$allowed[] = $originHostname;
|
||||
}
|
||||
|
||||
/* Allow the request origin if a dev key or rule is found */
|
||||
if ((!$rule->isEmpty() || !$devKey->isEmpty()) && !empty($originHostname)) {
|
||||
$allowed[] = $originHostname;
|
||||
}
|
||||
|
||||
return array_unique($allowed);
|
||||
}, ['platform', 'project', 'rule', 'devKey', 'request']);
|
||||
|
||||
/**
|
||||
* List of allowed request schemes for the request.
|
||||
*/
|
||||
App::setResource('allowedSchemes', function (Document $project) {
|
||||
$allowed = [];
|
||||
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
/* Add hardcoded schemes */
|
||||
$allowed[] = 'exp';
|
||||
$allowed[] = 'appwrite-callback-' . $project->getId();
|
||||
|
||||
/* Add platform configured schemes */
|
||||
$platforms = $project->getAttribute('platforms', []);
|
||||
$schemes = Platform::getSchemes($platforms);
|
||||
$allowed = [...$allowed, ...$schemes];
|
||||
}
|
||||
|
||||
return array_unique($allowed);
|
||||
}, ['project']);
|
||||
|
||||
/**
|
||||
* Rule associated with a request origin.
|
||||
*/
|
||||
App::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project) {
|
||||
$domain = \parse_url($request->getOrigin(), PHP_URL_HOST);
|
||||
if (empty($domain)) {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$rule = Authorization::skip(function () use ($dbForPlatform, $domain, $isMd5) {
|
||||
if ($isMd5) {
|
||||
return $dbForPlatform->getDocument('rules', md5($domain));
|
||||
}
|
||||
|
||||
return $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain]),
|
||||
]) ?? new Document();
|
||||
});
|
||||
|
||||
if ($rule->getAttribute('projectInternalId') !== $project->getSequence()) {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
return $rule;
|
||||
}, ['request', 'dbForPlatform', 'project']);
|
||||
|
||||
/**
|
||||
* CORS service
|
||||
*/
|
||||
App::setResource('cors', fn (array $allowedHostnames) => new Cors(
|
||||
$allowedHostnames,
|
||||
allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
||||
allowedHeaders: [
|
||||
'Accept',
|
||||
'Origin',
|
||||
'Cookie',
|
||||
'Set-Cookie',
|
||||
// Content
|
||||
'Content-Type',
|
||||
'Content-Range',
|
||||
// Appwrite
|
||||
'X-Appwrite-Project',
|
||||
'X-Appwrite-Key',
|
||||
'X-Appwrite-Dev-Key',
|
||||
'X-Appwrite-Locale',
|
||||
'X-Appwrite-Mode',
|
||||
'X-Appwrite-JWT',
|
||||
'X-Appwrite-Response-Format',
|
||||
'X-Appwrite-Timeout',
|
||||
'X-Appwrite-ID',
|
||||
'X-Appwrite-Timestamp',
|
||||
'X-Appwrite-Session',
|
||||
'X-Appwrite-Platform', // for `$platform` injection and SDK generator
|
||||
// SDK generator
|
||||
'X-SDK-Version',
|
||||
'X-SDK-Name',
|
||||
'X-SDK-Language',
|
||||
'X-SDK-Platform',
|
||||
'X-SDK-GraphQL',
|
||||
'X-SDK-Profile',
|
||||
// Caching
|
||||
'Range',
|
||||
'Cache-Control',
|
||||
'Expires',
|
||||
'Pragma',
|
||||
// Server to server
|
||||
'X-Fallback-Cookies',
|
||||
'X-Requested-With',
|
||||
'X-Forwarded-For',
|
||||
'X-Forwarded-User-Agent',
|
||||
],
|
||||
allowCredentials: true,
|
||||
exposedHeaders: [
|
||||
'X-Appwrite-Session',
|
||||
'X-Fallback-Cookies',
|
||||
],
|
||||
), ['allowedHostnames']);
|
||||
|
||||
App::setResource('originValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) {
|
||||
if (!$devKey->isEmpty()) {
|
||||
return new URL();
|
||||
}
|
||||
return new Origin($allowedHostnames, $allowedSchemes);
|
||||
}, ['devKey', 'allowedHostnames', 'allowedSchemes']);
|
||||
|
||||
App::setResource('redirectValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) {
|
||||
if (!$devKey->isEmpty()) {
|
||||
return new URL();
|
||||
}
|
||||
return new Redirect($allowedHostnames, $allowedSchemes);
|
||||
}, ['devKey', 'allowedHostnames', 'allowedSchemes']);
|
||||
|
||||
App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken) {
|
||||
/**
|
||||
* Handles user authentication and session validation.
|
||||
*
|
||||
* This function follows a series of steps to determine the appropriate user session
|
||||
* based on cookies, headers, and JWT tokens.
|
||||
*
|
||||
* Process:
|
||||
* 1. Checks the cookie based on mode:
|
||||
* - If in admin mode, uses console project id for key.
|
||||
* - Otherwise, sets the key using the project ID
|
||||
* 2. If no cookie is found, attempts to retrieve the fallback header `x-fallback-cookies`.
|
||||
* - If this method is used, returns the header: `X-Debug-Fallback: true`.
|
||||
* 3. Fetches the user document from the appropriate database based on the mode.
|
||||
* 4. If the user document is empty or the session key cannot be verified, sets an empty user document.
|
||||
* 5. Regardless of the results from steps 1-4, attempts to fetch the JWT token.
|
||||
* 6. If the JWT user has a valid session ID, updates the user variable with the user from `projectDB`,
|
||||
* overwriting the previous value.
|
||||
*/
|
||||
|
||||
Authorization::setDefaultStatus(true);
|
||||
|
||||
Auth::setCookieName('a_session_' . $project->getId());
|
||||
$store->setKey('a_session_' . $project->getId());
|
||||
|
||||
if (APP_MODE_ADMIN === $mode) {
|
||||
Auth::setCookieName('a_session_' . $console->getId());
|
||||
$store->setKey('a_session_' . $console->getId());
|
||||
}
|
||||
|
||||
$session = Auth::decodeSession(
|
||||
$store->decode(
|
||||
$request->getCookie(
|
||||
Auth::$cookieName, // Get sessions
|
||||
$request->getCookie(Auth::$cookieName . '_legacy', '')
|
||||
$store->getKey(), // Get sessions
|
||||
$request->getCookie($store->getKey() . '_legacy', '')
|
||||
)
|
||||
);
|
||||
|
||||
// Get session from header for SSR clients
|
||||
if (empty($session['id']) && empty($session['secret'])) {
|
||||
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
|
||||
$sessionHeader = $request->getHeader('x-appwrite-session', '');
|
||||
|
||||
if (!empty($sessionHeader)) {
|
||||
$session = Auth::decodeSession($sessionHeader);
|
||||
$store->decode($sessionHeader);
|
||||
}
|
||||
}
|
||||
|
||||
// Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies
|
||||
if ($response) {
|
||||
if ($response) { // if in http context - add debug header
|
||||
$response->addHeader('X-Debug-Fallback', 'false');
|
||||
}
|
||||
|
||||
if (empty($session['id']) && empty($session['secret'])) {
|
||||
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
|
||||
if ($response) {
|
||||
$response->addHeader('X-Debug-Fallback', 'true');
|
||||
}
|
||||
$fallback = $request->getHeader('x-fallback-cookies', '');
|
||||
$fallback = \json_decode($fallback, true);
|
||||
$session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
|
||||
$store->decode(((is_array($fallback) && isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : ''));
|
||||
}
|
||||
|
||||
Auth::$unique = $session['id'] ?? '';
|
||||
Auth::$secret = $session['secret'] ?? '';
|
||||
|
||||
$user = new Document([]);
|
||||
|
||||
if (!empty(Auth::$unique)) {
|
||||
if ($mode === APP_MODE_ADMIN) {
|
||||
$user = $dbForPlatform->getDocument('users', Auth::$unique);
|
||||
} elseif (!$project->isEmpty()) {
|
||||
if ($project->getId() === 'console') {
|
||||
$user = $dbForPlatform->getDocument('users', Auth::$unique);
|
||||
} else {
|
||||
$user = $dbForProject->getDocument('users', Auth::$unique);
|
||||
$user = null;
|
||||
if (APP_MODE_ADMIN === $mode) {
|
||||
/** @var User $user */
|
||||
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
|
||||
} else {
|
||||
if ($project->isEmpty()) {
|
||||
$user = new User([]);
|
||||
} else {
|
||||
if (!empty($store->getProperty('id', ''))) {
|
||||
if ($project->getId() === 'console') {
|
||||
/** @var User $user */
|
||||
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
|
||||
} else {
|
||||
/** @var User $user */
|
||||
$user = $dbForProject->getDocument('users', $store->getProperty('id', ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!$user ||
|
||||
$user->isEmpty() // Check a document has been found in the DB
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
|
||||
|| !$user->sessionVerify($store->getProperty('secret', ''), $proofForToken)
|
||||
) { // Validate user has valid login token
|
||||
$user = new Document([]);
|
||||
$user = new User([]);
|
||||
}
|
||||
|
||||
// if (APP_MODE_ADMIN === $mode) {
|
||||
// if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
|
||||
// Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
@@ -273,18 +409,14 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
// $user = new Document([]);
|
||||
// }
|
||||
// }
|
||||
|
||||
$authJWT = $request->getHeader('x-appwrite-jwt', '');
|
||||
|
||||
if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
|
||||
|
||||
try {
|
||||
$payload = $jwt->decode($authJWT);
|
||||
} catch (JWTException $error) {
|
||||
throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
|
||||
}
|
||||
|
||||
$jwtUserId = $payload['userId'] ?? '';
|
||||
if (!empty($jwtUserId)) {
|
||||
if ($mode === APP_MODE_ADMIN) {
|
||||
@@ -293,20 +425,18 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
$user = $dbForProject->getDocument('users', $jwtUserId);
|
||||
}
|
||||
}
|
||||
|
||||
$jwtSessionId = $payload['sessionId'] ?? '';
|
||||
if (!empty($jwtSessionId)) {
|
||||
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
|
||||
$user = new Document([]);
|
||||
$user = new User([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dbForProject->setMetadata('user', $user->getId());
|
||||
$dbForPlatform->setMetadata('user', $user->getId());
|
||||
|
||||
return $user;
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']);
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken']);
|
||||
|
||||
App::setResource('project', function ($dbForPlatform, $request, $console) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
@@ -324,31 +454,61 @@ App::setResource('project', function ($dbForPlatform, $request, $console) {
|
||||
return $project;
|
||||
}, ['dbForPlatform', 'request', 'console']);
|
||||
|
||||
App::setResource('session', function (Document $user) {
|
||||
App::setResource('session', function (User $user, Store $store, Token $proofForToken) {
|
||||
if ($user->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
$sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret);
|
||||
$sessionId = $user->sessionVerify($store->getProperty('secret', ''), $proofForToken);
|
||||
|
||||
if (!$sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($sessions as $session) {/** @var Document $session */
|
||||
foreach ($sessions as $session) {
|
||||
/** @var Document $session */
|
||||
if ($sessionId === $session->getId()) {
|
||||
return $session;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}, ['user']);
|
||||
}, ['user', 'store', 'proofForToken']);
|
||||
|
||||
App::setResource('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
}, []);
|
||||
|
||||
App::setResource('store', function (): Store {
|
||||
return new Store();
|
||||
});
|
||||
|
||||
App::setResource('proofForPassword', function (): Password {
|
||||
$hash = new Argon2();
|
||||
$hash
|
||||
->setMemoryCost(7168)
|
||||
->setTimeCost(5)
|
||||
->setThreads(1);
|
||||
|
||||
$password = new Password();
|
||||
$password
|
||||
->setHash($hash);
|
||||
|
||||
return $password;
|
||||
});
|
||||
|
||||
App::setResource('proofForToken', function (): Token {
|
||||
$token = new Token();
|
||||
$token->setHash(new Sha());
|
||||
return $token;
|
||||
});
|
||||
|
||||
App::setResource('proofForCode', function (): Code {
|
||||
$code = new Code();
|
||||
$code->setHash(new Sha());
|
||||
return $code;
|
||||
});
|
||||
|
||||
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
@@ -369,13 +529,14 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
|
||||
->setMetadata('project', $project->getId())
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
|
||||
$database->setDocumentType('users', User::class);
|
||||
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant((int)$project->getSequence())
|
||||
->setTenant((int) $project->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
@@ -398,6 +559,8 @@ App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
|
||||
|
||||
$database->setDocumentType('users', User::class);
|
||||
|
||||
return $database;
|
||||
}, ['pools', 'cache']);
|
||||
|
||||
@@ -422,13 +585,14 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
|
||||
->setMetadata('project', $project->getId())
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
|
||||
$database->setDocumentType('users', User::class);
|
||||
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant((int)$project->getSequence())
|
||||
->setTenant((int) $project->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
@@ -458,7 +622,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
|
||||
return function (?Document $project = null) use ($pools, $cache, &$database) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant((int)$project->getSequence());
|
||||
$database->setTenant((int) $project->getSequence());
|
||||
return $database;
|
||||
}
|
||||
|
||||
@@ -473,13 +637,18 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
|
||||
// set tenant
|
||||
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant((int)$project->getSequence());
|
||||
$database->setTenant((int) $project->getSequence());
|
||||
}
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache']);
|
||||
|
||||
App::setResource('audit', function ($dbForProject) {
|
||||
$adapter = new AdapterDatabase($dbForProject);
|
||||
return new Audit($adapter);
|
||||
}, ['dbForProject']);
|
||||
|
||||
App::setResource('telemetry', fn () => new NoTelemetry());
|
||||
|
||||
App::setResource('cache', function (Group $pools, Telemetry $telemetry) {
|
||||
@@ -501,7 +670,7 @@ App::setResource('redis', function () {
|
||||
$pass = System::getEnv('_APP_REDIS_PASS', '');
|
||||
|
||||
$redis = new \Redis();
|
||||
@$redis->pconnect($host, (int)$port);
|
||||
@$redis->pconnect($host, (int) $port);
|
||||
if ($pass) {
|
||||
$redis->auth($pass);
|
||||
}
|
||||
@@ -525,7 +694,7 @@ App::setResource('deviceForFiles', function ($project, Telemetry $telemetry) {
|
||||
App::setResource('deviceForSites', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
App::setResource('deviceForImports', function ($project, Telemetry $telemetry) {
|
||||
App::setResource('deviceForMigrations', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
App::setResource('deviceForFunctions', function ($project, Telemetry $telemetry) {
|
||||
@@ -658,8 +827,8 @@ App::setResource('passwordsDictionary', function ($register) {
|
||||
|
||||
|
||||
App::setResource('servers', function () {
|
||||
$platforms = Config::getParam('platforms');
|
||||
$server = $platforms[APP_PLATFORM_SERVER];
|
||||
$platforms = Config::getParam('sdks');
|
||||
$server = $platforms[APP_SDK_PLATFORM_SERVER];
|
||||
|
||||
$languages = array_map(function ($language) {
|
||||
return strtolower($language['name']);
|
||||
@@ -714,7 +883,7 @@ App::setResource('schema', function ($utopia, $dbForProject) {
|
||||
// NOTE: `params` and `urls` are not used internally in the `Schema::build` function below!
|
||||
$params = [
|
||||
'list' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return [ 'queries' => $args['queries']];
|
||||
return ['queries' => $args['queries']];
|
||||
},
|
||||
'create' => function (string $databaseId, string $collectionId, array $args) {
|
||||
$id = $args['id'] ?? 'unique()';
|
||||
@@ -759,24 +928,6 @@ App::setResource('schema', function ($utopia, $dbForProject) {
|
||||
);
|
||||
}, ['utopia', 'dbForProject']);
|
||||
|
||||
App::setResource('contributors', function () {
|
||||
$path = 'app/config/contributors.json';
|
||||
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
|
||||
return $list;
|
||||
});
|
||||
|
||||
App::setResource('employees', function () {
|
||||
$path = 'app/config/employees.json';
|
||||
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
|
||||
return $list;
|
||||
});
|
||||
|
||||
App::setResource('heroes', function () {
|
||||
$path = 'app/config/heroes.json';
|
||||
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
|
||||
return $list;
|
||||
});
|
||||
|
||||
App::setResource('gitHub', function (Cache $cache) {
|
||||
return new VcsGitHub($cache);
|
||||
}, ['cache']);
|
||||
@@ -843,6 +994,7 @@ App::setResource('devKey', function (Request $request, Document $project, array
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
return $key;
|
||||
}, ['request', 'project', 'servers', 'dbForPlatform']);
|
||||
|
||||
@@ -923,7 +1075,8 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
$tokenJWT = $request->getParam('token');
|
||||
|
||||
if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
// Use a large but reasonable maxAge to avoid auto-exp when token has no expiry
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), RESOURCE_TOKEN_ALGORITHM, RESOURCE_TOKEN_MAX_AGE, RESOURCE_TOKEN_LEEWAY); // Instantiate with key, algo, maxAge and leeway.
|
||||
|
||||
try {
|
||||
$payload = $jwt->decode($tokenJWT);
|
||||
@@ -963,7 +1116,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
}
|
||||
|
||||
$accessedAt = $token->getAttribute('accessedAt', 0);
|
||||
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), - APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) {
|
||||
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) {
|
||||
$token->setAttribute('accessedAt', DatabaseDateTime::now());
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
|
||||
}
|
||||
@@ -982,51 +1135,6 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
return new Document([]);
|
||||
}, ['project', 'dbForProject', 'request']);
|
||||
|
||||
App::setResource('httpReferrer', function (Request $request): string {
|
||||
$referrer = $request->getReferer();
|
||||
return $referrer;
|
||||
}, ['request']);
|
||||
|
||||
App::setResource('httpReferrerSafe', function (Request $request, string $httpReferrer, array $platforms, Database $dbForPlatform, Document $project, App $utopia): string {
|
||||
$origin = \parse_url($request->getOrigin($httpReferrer), PHP_URL_HOST);
|
||||
$protocol = \parse_url($request->getOrigin($httpReferrer), PHP_URL_SCHEME);
|
||||
$port = \parse_url($request->getOrigin($httpReferrer), PHP_URL_PORT);
|
||||
$referrer = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
|
||||
|
||||
// Safe if route is publicly accessible
|
||||
$route = $utopia->getRoute();
|
||||
if ($route->getLabel('origin', false)) {
|
||||
return $referrer;
|
||||
}
|
||||
|
||||
// Safe if added as web platform
|
||||
$originValidator = new Origin($platforms);
|
||||
if ($originValidator->isValid($request->getOrigin($httpReferrer))) {
|
||||
return $referrer;
|
||||
}
|
||||
|
||||
// Safe if rule with same project ID exists
|
||||
if (!empty($origin)) {
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? '')));
|
||||
} else {
|
||||
$rule = Authorization::skip(
|
||||
fn () => $dbForPlatform->find('rules', [
|
||||
Query::equal('domain', [$origin]),
|
||||
Query::limit(1)
|
||||
])
|
||||
)[0] ?? new Document();
|
||||
}
|
||||
|
||||
if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) {
|
||||
return $referrer;
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafe; Localhost is always safe for ease of local development
|
||||
$origin = 'localhost';
|
||||
$protocol = \parse_url($request->getOrigin($httpReferrer), PHP_URL_SCHEME);
|
||||
$port = \parse_url($request->getOrigin($httpReferrer), PHP_URL_PORT);
|
||||
$referrer = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
|
||||
return $referrer;
|
||||
}, ['request', 'httpReferrer', 'platforms', 'dbForPlatform', 'project', 'utopia']);
|
||||
App::setResource('transactionState', function (Database $dbForProject) {
|
||||
return new TransactionState($dbForProject);
|
||||
}, ['dbForProject']);
|
||||
|
||||
+92
-53
@@ -1,13 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\PubSub\Adapter\Pool as PubSubPool;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
use Swoole\Runtime;
|
||||
@@ -16,6 +17,9 @@ use Swoole\Timer;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
use Utopia\App;
|
||||
use Utopia\Auth\Hashes\Sha;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\Cache\Adapter\Pool as CachePool;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
@@ -49,14 +53,14 @@ Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
if (!function_exists('getConsoleDB')) {
|
||||
function getConsoleDB(): Database
|
||||
{
|
||||
global $register;
|
||||
$ctx = Coroutine::getContext();
|
||||
|
||||
static $database = null;
|
||||
|
||||
if ($database !== null) {
|
||||
return $database;
|
||||
if (isset($ctx['dbForPlatform'])) {
|
||||
return $ctx['dbForPlatform'];
|
||||
}
|
||||
|
||||
global $register;
|
||||
|
||||
/** @var Group $pools */
|
||||
$pools = $register->get('pools');
|
||||
|
||||
@@ -66,8 +70,8 @@ if (!function_exists('getConsoleDB')) {
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', '_console');
|
||||
|
||||
return $database;
|
||||
$database->setDocumentType('users', User::class);
|
||||
return $ctx['dbForPlatform'] = $database;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,14 +79,18 @@ if (!function_exists('getConsoleDB')) {
|
||||
if (!function_exists('getProjectDB')) {
|
||||
function getProjectDB(Document $project): Database
|
||||
{
|
||||
global $register;
|
||||
$ctx = Coroutine::getContext();
|
||||
|
||||
static $databases = [];
|
||||
|
||||
if (isset($databases[$project->getSequence()])) {
|
||||
return $databases[$project->getSequence()];
|
||||
if (!isset($ctx['dbForProject'])) {
|
||||
$ctx['dbForProject'] = [];
|
||||
}
|
||||
|
||||
if (isset($ctx['dbForProject'][$project->getSequence()])) {
|
||||
return $ctx['dbForProject'][$project->getSequence()];
|
||||
}
|
||||
|
||||
global $register;
|
||||
|
||||
/** @var Group $pools */
|
||||
$pools = $register->get('pools');
|
||||
|
||||
@@ -118,7 +126,9 @@ if (!function_exists('getProjectDB')) {
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
return $databases[$project->getSequence()] = $database;
|
||||
$database->setDocumentType('users', User::class);
|
||||
|
||||
return $ctx['dbForProject'][$project->getSequence()] = $database;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,14 +136,14 @@ if (!function_exists('getProjectDB')) {
|
||||
if (!function_exists('getCache')) {
|
||||
function getCache(): Cache
|
||||
{
|
||||
global $register;
|
||||
$ctx = Coroutine::getContext();
|
||||
|
||||
static $cache = null;
|
||||
|
||||
if ($cache !== null) {
|
||||
return $cache;
|
||||
if (isset($ctx['cache'])) {
|
||||
return $ctx['cache'];
|
||||
}
|
||||
|
||||
global $register;
|
||||
|
||||
$pools = $register->get('pools'); /** @var Group $pools */
|
||||
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
@@ -143,7 +153,7 @@ if (!function_exists('getCache')) {
|
||||
$adapters[] = new CachePool($pools->get($value));
|
||||
}
|
||||
|
||||
return $cache = new Cache(new Sharding($adapters));
|
||||
return $ctx['cache'] = new Cache(new Sharding($adapters));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,10 +161,10 @@ if (!function_exists('getCache')) {
|
||||
if (!function_exists('getRedis')) {
|
||||
function getRedis(): \Redis
|
||||
{
|
||||
static $redis = null;
|
||||
$ctx = Coroutine::getContext();
|
||||
|
||||
if ($redis !== null) {
|
||||
return $redis;
|
||||
if (isset($ctx['redis'])) {
|
||||
return $ctx['redis'];
|
||||
}
|
||||
|
||||
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
|
||||
@@ -168,46 +178,46 @@ if (!function_exists('getRedis')) {
|
||||
}
|
||||
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
|
||||
|
||||
return $redis;
|
||||
return $ctx['redis'] = $redis;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('getTimelimit')) {
|
||||
function getTimelimit(): TimeLimitRedis
|
||||
function getTimelimit(string $key = "", int $limit = 0, int $seconds = 1): TimeLimitRedis
|
||||
{
|
||||
static $timelimit = null;
|
||||
$ctx = Coroutine::getContext();
|
||||
|
||||
if ($timelimit !== null) {
|
||||
return $timelimit;
|
||||
if (isset($ctx['timelimit'])) {
|
||||
return $ctx['timelimit'];
|
||||
}
|
||||
|
||||
return $timelimit = new TimeLimitRedis("", 0, 1, getRedis());
|
||||
return $ctx['timelimit'] = new TimeLimitRedis($key, $limit, $seconds, getRedis());
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('getRealtime')) {
|
||||
function getRealtime(): Realtime
|
||||
{
|
||||
static $realtime = null;
|
||||
$ctx = Coroutine::getContext();
|
||||
|
||||
if ($realtime !== null) {
|
||||
return $realtime;
|
||||
if (isset($ctx['realtime'])) {
|
||||
return $ctx['realtime'];
|
||||
}
|
||||
|
||||
return $realtime = new Realtime();
|
||||
return $ctx['realtime'] = new Realtime();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('getTelemetry')) {
|
||||
function getTelemetry(int $workerId): Utopia\Telemetry\Adapter
|
||||
{
|
||||
static $telemetry = null;
|
||||
$ctx = Coroutine::getContext();
|
||||
|
||||
if ($telemetry !== null) {
|
||||
return $telemetry;
|
||||
if (isset($ctx['telemetry'])) {
|
||||
return $ctx['telemetry'];
|
||||
}
|
||||
|
||||
return $telemetry = new NoTelemetry();
|
||||
return $ctx['telemetry'] = new NoTelemetry();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +246,7 @@ $adapter
|
||||
$server = new Server($adapter);
|
||||
|
||||
$logError = function (Throwable $error, string $action) use ($register) {
|
||||
$logger = $register->get('logger');
|
||||
$logger = $register->get('realtimeLogger');
|
||||
|
||||
if ($logger && !$error instanceof Exception) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
@@ -457,9 +467,10 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
$project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
|
||||
$database = getProjectDB($project);
|
||||
|
||||
/** @var Appwrite\Utopia\Database\Documents\User $user */
|
||||
$user = $database->getDocument('users', $userId);
|
||||
|
||||
$roles = Auth::getRoles($user);
|
||||
$roles = $user->getRoles();
|
||||
$channels = $realtime->connections[$connection]['channels'];
|
||||
|
||||
$realtime->unsubscribe($connection);
|
||||
@@ -526,14 +537,13 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
if (
|
||||
array_key_exists('realtime', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['realtime']
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
|
||||
$timelimit = $app->getResource('timelimit');
|
||||
$platforms = $app->getResource('platforms');
|
||||
$user = $app->getResource('user'); /** @var Document $user */
|
||||
$user = $app->getResource('user'); /** @var User $user */
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
@@ -557,13 +567,13 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
* Skip this check for non-web platforms which are not required to send an origin header.
|
||||
*/
|
||||
$origin = $request->getOrigin();
|
||||
$originValidator = new Origin($platforms);
|
||||
$originValidator = $app->getResource('originValidator');
|
||||
|
||||
if (!empty($origin) && !$originValidator->isValid($origin) && $project->getId() !== 'console') {
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
|
||||
}
|
||||
|
||||
$roles = Auth::getRoles($user);
|
||||
$roles = $user->getRoles();
|
||||
|
||||
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
|
||||
|
||||
@@ -604,11 +614,18 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$code = 500;
|
||||
}
|
||||
|
||||
$message = $th->getMessage();
|
||||
|
||||
// sanitize 0 && 5xx errors
|
||||
if (($code === 0 || $code >= 500) && !App::isDevelopment()) {
|
||||
$message = 'Error: Server Error';
|
||||
}
|
||||
|
||||
$response = [
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
'code' => $code,
|
||||
'message' => $th->getMessage()
|
||||
'message' => $message
|
||||
]
|
||||
];
|
||||
|
||||
@@ -671,21 +688,31 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
|
||||
}
|
||||
|
||||
$session = Auth::decodeSession($message['data']['session']);
|
||||
Auth::$unique = $session['id'] ?? '';
|
||||
Auth::$secret = $session['secret'] ?? '';
|
||||
$store = new Store();
|
||||
|
||||
$user = $database->getDocument('users', Auth::$unique);
|
||||
$store->decode($message['data']['session']);
|
||||
|
||||
/** @var User $user */
|
||||
$user = $database->getDocument('users', $store->getProperty('id', ''));
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* Moving forward, we should try to use our dependency injection container
|
||||
* to inject the proof for token.
|
||||
* This way we will have one source of truth for the proof for token.
|
||||
*/
|
||||
$proofForToken = new Token();
|
||||
$proofForToken->setHash(new Sha());
|
||||
|
||||
if (
|
||||
empty($user->getId()) // Check a document has been found in the DB
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token
|
||||
|| !$user->sessionVerify($store->getProperty('secret', ''), $proofForToken) // Validate user has valid login token
|
||||
) {
|
||||
// cookie not valid
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
|
||||
}
|
||||
|
||||
$roles = Auth::getRoles($user);
|
||||
$roles = $user->getRoles();
|
||||
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
|
||||
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
|
||||
|
||||
@@ -705,11 +732,23 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message type is not valid.');
|
||||
}
|
||||
} catch (Throwable $th) {
|
||||
$code = $th->getCode();
|
||||
if (!is_int($code)) {
|
||||
$code = 500;
|
||||
}
|
||||
|
||||
$message = $th->getMessage();
|
||||
|
||||
// sanitize 0 && 5xx errors
|
||||
if (($code === 0 || $code >= 500) && !App::isDevelopment()) {
|
||||
$message = 'Error: Server Error';
|
||||
}
|
||||
|
||||
$response = [
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
'code' => $th->getCode(),
|
||||
'message' => $th->getMessage()
|
||||
'code' => $code,
|
||||
'message' => $message
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\System\System;
|
||||
|
||||
$development = $this->getParam('development', false);
|
||||
@@ -15,7 +16,8 @@ $labelClass = '';
|
||||
$buttons = [];
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$platform = Config::getParam('platform', []);
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
// TODO: remove this later
|
||||
if (System::getEnv('_APP_ENV') === 'development') {
|
||||
$hostname = 'localhost';
|
||||
@@ -537,4 +539,4 @@ switch ($type) {
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -11,6 +11,7 @@ $httpsPort = $this->getParam('httpsPort', '');
|
||||
$version = $this->getParam('version', '');
|
||||
$organization = $this->getParam('organization', '');
|
||||
$image = $this->getParam('image', '');
|
||||
$enableAssistant = $this->getParam('enableAssistant', false);
|
||||
?>services:
|
||||
traefik:
|
||||
image: traefik:2.11
|
||||
@@ -179,7 +180,7 @@ $image = $this->getParam('image', '');
|
||||
appwrite-console:
|
||||
<<: *x-logging
|
||||
container_name: appwrite-console
|
||||
image: <?php echo $organization; ?>/console:7.0.2
|
||||
image: <?php echo $organization; ?>/console:7.4.7
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -848,8 +849,9 @@ $image = $this->getParam('image', '');
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
|
||||
<?php if ($enableAssistant): ?>
|
||||
appwrite-assistant:
|
||||
image: appwrite/assistant:0.8.3
|
||||
image: appwrite/assistant:0.8.4
|
||||
container_name: appwrite-assistant
|
||||
<<: *x-logging
|
||||
restart: unless-stopped
|
||||
@@ -857,9 +859,10 @@ $image = $this->getParam('image', '');
|
||||
- appwrite
|
||||
environment:
|
||||
- _APP_ASSISTANT_OPENAI_API_KEY
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
appwrite-browser:
|
||||
image: appwrite/browser:0.2.4
|
||||
image: appwrite/browser:0.3.2
|
||||
container_name: appwrite-browser
|
||||
<<: *x-logging
|
||||
restart: unless-stopped
|
||||
|
||||
+34
-3
@@ -17,9 +17,12 @@ use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Executor\Executor;
|
||||
use Swoole\Runtime;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
use Utopia\Audit\Adapter\Database as AdapterDatabase;
|
||||
use Utopia\Audit\Audit as UtopiaAudit;
|
||||
use Utopia\Cache\Adapter\Pool as CachePool;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
@@ -55,7 +58,7 @@ Server::setResource('dbForPlatform', function (Cache $cache, Registry $register)
|
||||
$adapter = new DatabasePool($pools->get('console'));
|
||||
$dbForPlatform = new Database($adapter, $cache);
|
||||
$dbForPlatform->setNamespace('_console');
|
||||
|
||||
$dbForPlatform->setDocumentType('users', User::class);
|
||||
return $dbForPlatform;
|
||||
}, ['cache', 'register']);
|
||||
|
||||
@@ -86,6 +89,7 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
|
||||
|
||||
$adapter = new DatabasePool($pools->get($dsn->getHost()));
|
||||
$database = new Database($adapter, $cache);
|
||||
$database->setDocumentType('users', User::class);
|
||||
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
@@ -181,7 +185,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
->setSharedTables(true)
|
||||
->setNamespace('logsV1')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER);
|
||||
|
||||
// set tenant
|
||||
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
@@ -349,7 +353,7 @@ Server::setResource('deviceForSites', function (Document $project, Telemetry $te
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
Server::setResource('deviceForImports', function (Document $project, Telemetry $telemetry) {
|
||||
Server::setResource('deviceForMigrations', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
@@ -409,6 +413,13 @@ Server::setResource('logError', function (Registry $register, Document $project)
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
|
||||
if ($error->getPrevious() !== null) {
|
||||
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
|
||||
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
|
||||
}
|
||||
$log->addExtra('previousFile', $error->getPrevious()->getFile());
|
||||
$log->addExtra('previousLine', $error->getPrevious()->getLine());
|
||||
}
|
||||
|
||||
foreach (($extras ?? []) as $key => $value) {
|
||||
$log->addExtra($key, $value);
|
||||
@@ -429,11 +440,31 @@ Server::setResource('logError', function (Registry $register, Document $project)
|
||||
|
||||
Console::warning("Failed: {$error->getMessage()}");
|
||||
Console::warning($error->getTraceAsString());
|
||||
|
||||
if ($error->getPrevious() !== null) {
|
||||
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
|
||||
Console::warning("Previous Failed: {$error->getPrevious()->getMessage()}");
|
||||
}
|
||||
Console::warning("Previous File: {$error->getPrevious()->getFile()} Line: {$error->getPrevious()->getLine()}");
|
||||
}
|
||||
};
|
||||
}, ['register', 'project']);
|
||||
|
||||
Server::setResource('executor', fn () => new Executor());
|
||||
|
||||
Server::setResource('getAudit', function (Database $dbForPlatform, callable $getProjectDB) {
|
||||
return function (Document $project) use ($dbForPlatform, $getProjectDB) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
$adapter = new AdapterDatabase($dbForPlatform);
|
||||
return new UtopiaAudit($adapter);
|
||||
}
|
||||
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$adapter = new AdapterDatabase($dbForProject);
|
||||
return new UtopiaAudit($adapter);
|
||||
};
|
||||
}, ['dbForPlatform', 'getProjectDB']);
|
||||
|
||||
$pools = $register->get('pools');
|
||||
$platform = new Appwrite();
|
||||
$args = $platform->getEnv('argv');
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php doctor $@
|
||||
exec php /usr/src/code/app/cli.php doctor "$@"
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php install $@
|
||||
exec php /usr/src/code/app/cli.php install "$@"
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec php /usr/src/code/app/cli.php interval "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php maintenance $@
|
||||
exec php /usr/src/code/app/cli.php maintenance "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php migrate $@
|
||||
exec php /usr/src/code/app/cli.php migrate "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php queue-count --type=failed $@
|
||||
exec php /usr/src/code/app/cli.php queue-count --type=failed "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php queue-count --type=processing $@
|
||||
exec php /usr/src/code/app/cli.php queue-count --type=processing "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php queue-count --type=success $@
|
||||
exec php /usr/src/code/app/cli.php queue-count --type=success "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php queue-retry $@
|
||||
exec php /usr/src/code/app/cli.php queue-retry "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/realtime.php $@
|
||||
exec php /usr/src/code/app/realtime.php "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php schedule-executions $@
|
||||
exec php /usr/src/code/app/cli.php schedule-executions "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php schedule-functions $@
|
||||
exec php /usr/src/code/app/cli.php schedule-functions "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php schedule-messages $@
|
||||
exec php /usr/src/code/app/cli.php schedule-messages "$@"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php screenshot $@
|
||||
exec php /usr/src/code/app/cli.php screenshot "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php sdks $@
|
||||
exec php /usr/src/code/app/cli.php sdks "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php specs $@
|
||||
exec php /usr/src/code/app/cli.php specs "$@"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php ssl $@
|
||||
exec php /usr/src/code/app/cli.php ssl "$@"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user