mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
541
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb3e772774 | ||
|
|
1c18e90b8a | ||
|
|
894b6f478d | ||
|
|
64dd4bdce1 | ||
|
|
299664d18b | ||
|
|
7c727ea818 | ||
|
|
88075a75b9 | ||
|
|
c02b484128 | ||
|
|
705bd8a385 | ||
|
|
52936d7eb5 | ||
|
|
4313162b5e | ||
|
|
0df3bbec24 | ||
|
|
225e2c21c3 | ||
|
|
3cc53acbf2 | ||
|
|
51fe894a3f | ||
|
|
6f7d8d7f62 | ||
|
|
f7633c9c3b | ||
|
|
5fa4711247 | ||
|
|
62096dc008 | ||
|
|
de8d0d4406 | ||
|
|
5bec7a2c75 | ||
|
|
a9b648b2a9 | ||
|
|
f6cfc80a1d | ||
|
|
6403048acd | ||
|
|
f203800dd9 | ||
|
|
58658f335a | ||
|
|
1ed3dee135 | ||
|
|
0a22292d1b | ||
|
|
8ab1600417 | ||
|
|
99710e7b74 | ||
|
|
0f11c249b9 | ||
|
|
f9b868b339 | ||
|
|
73918b137e | ||
|
|
5a74f351dc | ||
|
|
766b335410 | ||
|
|
4cd951ae41 | ||
|
|
0d64f06aa4 | ||
|
|
b32cb30737 | ||
|
|
ea2192a83c | ||
|
|
a7f1cc128a | ||
|
|
f6658c4f5a | ||
|
|
38b3333349 | ||
|
|
16b360e77e | ||
|
|
8b6529934d | ||
|
|
3d991ede3e | ||
|
|
62817f5ca7 | ||
|
|
45c8584903 | ||
|
|
16d65f32b3 | ||
|
|
481c0b9d7a | ||
|
|
d5bb5d4c0e | ||
|
|
0bbfcd3888 | ||
|
|
1b93c78c5f | ||
|
|
7a64987da7 | ||
|
|
c9e2be0e08 | ||
|
|
20800d760b | ||
|
|
801724e90e | ||
|
|
c3d6dcad86 | ||
|
|
8a5a1609fe | ||
|
|
408dcbc263 | ||
|
|
16a8fdce4e | ||
|
|
c247f4cc88 | ||
|
|
9c1f93d47b | ||
|
|
de06af6c4b | ||
|
|
8fea73d3bf | ||
|
|
290ebbfc33 | ||
|
|
a246ba1a9e | ||
|
|
4fa8f01ce2 | ||
|
|
244942dd4e | ||
|
|
ecfe962d43 | ||
|
|
212222ef74 | ||
|
|
b0f3a99b4b | ||
|
|
4c1d539e04 | ||
|
|
119834fc96 | ||
|
|
d33ecfa7f5 | ||
|
|
e0ebeb837a | ||
|
|
c7a0e26439 | ||
|
|
3b4fea655a | ||
|
|
8f4594babb | ||
|
|
e90df389dd | ||
|
|
348d748039 | ||
|
|
7c2b6d9330 | ||
|
|
09410bfe97 | ||
|
|
dc6e4159e9 | ||
|
|
7a507f00bd | ||
|
|
919bb9f4bd | ||
|
|
76c29f72f0 | ||
|
|
473f97fe92 | ||
|
|
18c25aa03a | ||
|
|
a040bf754e | ||
|
|
d1ab8c4df8 | ||
|
|
24283dff7f | ||
|
|
a63acb7f23 | ||
|
|
5524fdb7b6 | ||
|
|
786e9576ce | ||
|
|
d38383bf78 | ||
|
|
5287e70f9a | ||
|
|
a6a67301a4 | ||
|
|
1df1bf34eb | ||
|
|
325c9d61cc | ||
|
|
c8cbcfefbb | ||
|
|
6502c91877 | ||
|
|
8bc37026cd | ||
|
|
0c3fed9ef1 | ||
|
|
ba24b668c9 | ||
|
|
2a6572ec4a | ||
|
|
03dd5a1579 | ||
|
|
eea9e22923 | ||
|
|
be1cada848 | ||
|
|
61dba4eefb | ||
|
|
25650e7501 | ||
|
|
40266481e0 | ||
|
|
60e0554fdf | ||
|
|
c34f8afa81 | ||
|
|
ab5632bfcd | ||
|
|
7efa5b59f5 | ||
|
|
a49c3a33f0 | ||
|
|
3e9a24232c | ||
|
|
160b6c3386 | ||
|
|
d1729404c1 | ||
|
|
c06948b266 | ||
|
|
16835f96bf | ||
|
|
e0bd500aa2 | ||
|
|
de509c6928 | ||
|
|
6dab42e59a | ||
|
|
4590ae11ee | ||
|
|
6e1848f52f | ||
|
|
fa513d6242 | ||
|
|
424f24af04 | ||
|
|
e06b77cf39 | ||
|
|
a7c0ef3d09 | ||
|
|
e87a66c426 | ||
|
|
0a4b86214a | ||
|
|
78eb1eec25 | ||
|
|
ac5bc22817 | ||
|
|
6080a363ee | ||
|
|
3887ba69d7 | ||
|
|
d19f205207 | ||
|
|
36d110b809 | ||
|
|
34527efc79 | ||
|
|
d037ced578 | ||
|
|
5faea67631 | ||
|
|
ef6ed675b3 | ||
|
|
b96bd3bff6 | ||
|
|
04bc24f42b | ||
|
|
ec77ab1007 | ||
|
|
a6ceda3543 | ||
|
|
a142c5f094 | ||
|
|
490d8d435e | ||
|
|
671f801cd5 | ||
|
|
4f4edc48af | ||
|
|
48e57be312 | ||
|
|
886687ed34 | ||
|
|
a45db60313 | ||
|
|
a408cd3b57 | ||
|
|
c3b186b3f9 | ||
|
|
ff174238e8 | ||
|
|
bbe39299ac | ||
|
|
825c32eb17 | ||
|
|
12f8cf5c00 | ||
|
|
d290460202 | ||
|
|
36aa38c58e | ||
|
|
d4195aa3a6 | ||
|
|
5f3e986ed3 | ||
|
|
14cd43ddfd | ||
|
|
769a8ee6c9 | ||
|
|
6ce8c6a73d | ||
|
|
d89676bd3b | ||
|
|
5ec88389d6 | ||
|
|
3cfc062d8d | ||
|
|
7e2fde3a0a | ||
|
|
634a2a37c4 | ||
|
|
49a0095cd5 | ||
|
|
1abd096347 | ||
|
|
ac2fa382a7 | ||
|
|
32dbafa353 | ||
|
|
e1a772fd3e | ||
|
|
3cb27d7b2c | ||
|
|
d9946c399b | ||
|
|
4069ae7165 | ||
|
|
2af4ef705d | ||
|
|
1551de729b | ||
|
|
2703ca1106 | ||
|
|
c610c9aff1 | ||
|
|
5e8ef5523d | ||
|
|
579d3d7f14 | ||
|
|
d14edf4207 | ||
|
|
54f55f65f4 | ||
|
|
38a02a9e16 | ||
|
|
51152ae879 | ||
|
|
b8d84ccaaf | ||
|
|
80449c5c10 | ||
|
|
e6d8221c89 | ||
|
|
333682c4dc | ||
|
|
bb20b3f52c | ||
|
|
c0a489311c | ||
|
|
05bcb92f73 | ||
|
|
2b3cab887f | ||
|
|
d3e400a21e | ||
|
|
d31810d08b | ||
|
|
f236b8e09a | ||
|
|
6625d4e4a8 | ||
|
|
744cf4fbb7 | ||
|
|
032fec3cae | ||
|
|
e9a5299547 | ||
|
|
8e4ceebaef | ||
|
|
df06cd9139 | ||
|
|
36cfc4bee0 | ||
|
|
5f15c34ad5 | ||
|
|
0b6467291b | ||
|
|
2841875714 | ||
|
|
d5bf134c92 | ||
|
|
e627cab92e | ||
|
|
7389c9a5fe | ||
|
|
26f0efc34d | ||
|
|
a8bb54a33e | ||
|
|
d642111bfa | ||
|
|
e000234dbd | ||
|
|
2b08fb59c3 | ||
|
|
9f7d474455 | ||
|
|
6c2e407ffb | ||
|
|
d4b480b014 | ||
|
|
77ccec337e | ||
|
|
eb81474962 | ||
|
|
4eb971d3b6 | ||
|
|
c4a764d175 | ||
|
|
43c06c6b70 | ||
|
|
672c8b2730 | ||
|
|
b1cfd1c0ff | ||
|
|
226c3c0372 | ||
|
|
5c7e4393a7 | ||
|
|
17d0053f37 | ||
|
|
52700f12c2 | ||
|
|
2274767720 | ||
|
|
8eb69eb22e | ||
|
|
31e03bfaae | ||
|
|
cfc36a9f47 | ||
|
|
52e5947822 | ||
|
|
fdf73f8a7a | ||
|
|
1cf26b1f46 | ||
|
|
0ace58f00b | ||
|
|
fc1e54c046 | ||
|
|
3228ccb4f9 | ||
|
|
868c416202 | ||
|
|
7116405793 | ||
|
|
a9469e8734 | ||
|
|
71ffe2bf52 | ||
|
|
5f97aafe46 | ||
|
|
0753163066 | ||
|
|
186f84830a | ||
|
|
1788896a57 | ||
|
|
fdd39a29c3 | ||
|
|
bff846480a | ||
|
|
711dc7bd08 | ||
|
|
df76c7c268 | ||
|
|
8978442039 | ||
|
|
b52c9648f1 | ||
|
|
1a9bf23181 | ||
|
|
7c3ac9e014 | ||
|
|
a42a669d6e | ||
|
|
12d119069e | ||
|
|
d532c874d5 | ||
|
|
a2c1ce4e8f | ||
|
|
bd83351092 | ||
|
|
3ff6ccbda1 | ||
|
|
75d681e6e2 | ||
|
|
3121cd1c07 | ||
|
|
ab908aa9cb | ||
|
|
77aad0bfce | ||
|
|
831472fc64 | ||
|
|
6d8a969334 | ||
|
|
46bef5a61d | ||
|
|
0101fbda8c | ||
|
|
fb1c0c85b0 | ||
|
|
001d13d7e9 | ||
|
|
585ee4b88e | ||
|
|
3a68c77406 | ||
|
|
3fe031d223 | ||
|
|
abe145dae2 | ||
|
|
f2eb1c3b3e | ||
|
|
9b454b07b9 | ||
|
|
e8bd577d4e | ||
|
|
5c59083f4d | ||
|
|
9db6f4cbee | ||
|
|
00acc2f73b | ||
|
|
f9d0f8fc38 | ||
|
|
a309f31a33 | ||
|
|
a95cd51be0 | ||
|
|
f6e3759f6b | ||
|
|
097b342869 | ||
|
|
6f5b144e70 | ||
|
|
8213b37cf4 | ||
|
|
0eb0054f38 | ||
|
|
4d24c08220 | ||
|
|
c31e928def | ||
|
|
9b3f1a2c4a | ||
|
|
985a31ca04 | ||
|
|
8ae99cb373 | ||
|
|
f0485ca870 | ||
|
|
21a406830a | ||
|
|
5ffca0f6fa | ||
|
|
9f7bf5a9f7 | ||
|
|
5512340cdd | ||
|
|
f94f7763c8 | ||
|
|
d2aaa99076 | ||
|
|
ab19f1323e | ||
|
|
904e4c8650 | ||
|
|
df9e80a962 | ||
|
|
42ae429153 | ||
|
|
cefc7a1d4b | ||
|
|
2d102a122c | ||
|
|
41490602f7 | ||
|
|
8e13aafbc8 | ||
|
|
39490e9819 | ||
|
|
1509aa544d | ||
|
|
f24863349e | ||
|
|
e4ad2ed566 | ||
|
|
72ddef855e | ||
|
|
151cb78993 | ||
|
|
321227db15 | ||
|
|
b621ec2e8b | ||
|
|
f04e47b3d4 | ||
|
|
92ce8d711d | ||
|
|
19ea682a52 | ||
|
|
906ef59d95 | ||
|
|
410a35deb5 | ||
|
|
cb0416be7b | ||
|
|
65c0db3285 | ||
|
|
504711845a | ||
|
|
32657e22cc | ||
|
|
cb66c9073c | ||
|
|
964b616e05 | ||
|
|
9a0c72294c | ||
|
|
d6d7cf1d9f | ||
|
|
b519f5e23a | ||
|
|
af509f3479 | ||
|
|
496eb5bd12 | ||
|
|
979fcacd6c | ||
|
|
82687c8ca4 | ||
|
|
8c46f73f69 | ||
|
|
e406dead9a | ||
|
|
121b9f32b8 | ||
|
|
20fa8b3017 | ||
|
|
1aa99a0f3e | ||
|
|
e1238d0fe9 | ||
|
|
82df6bf936 | ||
|
|
707d9554df | ||
|
|
1848de14cc | ||
|
|
c4b742ac0d | ||
|
|
6f55068838 | ||
|
|
c5b93ce231 | ||
|
|
a008c7c67b | ||
|
|
626b2390e4 | ||
|
|
745968ebd4 | ||
|
|
1ce6a85026 | ||
|
|
b30e5d4cda | ||
|
|
70bf8e2c41 | ||
|
|
cd40df8388 | ||
|
|
59453873b7 | ||
|
|
3075cbe626 | ||
|
|
d335aee549 | ||
|
|
b2359786e6 | ||
|
|
72d720f83e | ||
|
|
7dc64c12a6 | ||
|
|
59760bf529 | ||
|
|
42b85fad54 | ||
|
|
b263ed7ce6 | ||
|
|
e0a30fd25a | ||
|
|
7c1c025f9e | ||
|
|
e79e2857f9 | ||
|
|
db269d8b8f | ||
|
|
fdc69e77f4 | ||
|
|
bad70b8257 | ||
|
|
c439fb08fd | ||
|
|
cb537534e3 | ||
|
|
d633c3e278 | ||
|
|
555f3f9406 | ||
|
|
a89cf443aa | ||
|
|
269efbe2f0 | ||
|
|
107fc1d069 | ||
|
|
1752c6be44 | ||
|
|
1f83dab9e1 | ||
|
|
bc5ac28b84 | ||
|
|
413f3c20ab | ||
|
|
08b539ba05 | ||
|
|
4e780c0f75 | ||
|
|
6854451565 | ||
|
|
2c2e88f645 | ||
|
|
7aacaad94d | ||
|
|
550a479532 | ||
|
|
982ccf8873 | ||
|
|
850e4c420b | ||
|
|
d356d52c05 | ||
|
|
a93fa2cb0c | ||
|
|
964be49c3a | ||
|
|
209c610cbe | ||
|
|
ff702d774f | ||
|
|
79fd72ead4 | ||
|
|
f3a5572b59 | ||
|
|
c868c669ca | ||
|
|
2d03ce598b | ||
|
|
d8148c5491 | ||
|
|
6b1196fb26 | ||
|
|
e11e922587 | ||
|
|
3d3248cbbb | ||
|
|
b35f906384 | ||
|
|
732a8938ce | ||
|
|
ed0652f86a | ||
|
|
2fc483b9e0 | ||
|
|
b0e218cb3b | ||
|
|
404ad5b75d | ||
|
|
cf7e1121fa | ||
|
|
9419e36de0 | ||
|
|
1cdac28631 | ||
|
|
43e7c1e9a6 | ||
|
|
fd52a172a7 | ||
|
|
4b0c782891 | ||
|
|
c43177093e | ||
|
|
5f27d75c11 | ||
|
|
f1da0b2e61 | ||
|
|
2491f5c70c | ||
|
|
cd70d74233 | ||
|
|
1ca4904132 | ||
|
|
2d84816bc9 | ||
|
|
ff1a679c49 | ||
|
|
9b20802b4c | ||
|
|
42671400bb | ||
|
|
04967763e7 | ||
|
|
b47fd08f71 | ||
|
|
49b659a9ae | ||
|
|
aa5ce6200d | ||
|
|
9265ba743e | ||
|
|
ccc34b049a | ||
|
|
ab4ac2e568 | ||
|
|
919b5ac06d | ||
|
|
9a140f90d4 | ||
|
|
0a396b4dfa | ||
|
|
3800ccee1d | ||
|
|
d29740f0d7 | ||
|
|
41beb8af9c | ||
|
|
f12a578efe | ||
|
|
0ac5e2aa76 | ||
|
|
7593537f3d | ||
|
|
96274e9c77 | ||
|
|
0bb34e0d01 | ||
|
|
85b43d1f5c | ||
|
|
0ed7827fe4 | ||
|
|
4f87d72bcd | ||
|
|
ebb8d4a23b | ||
|
|
1a97848d9c | ||
|
|
5620f360c8 | ||
|
|
854f243fb1 | ||
|
|
a98ff71188 | ||
|
|
965cf9aac6 | ||
|
|
1ff717ecef | ||
|
|
203021ecb7 | ||
|
|
d67e9eec34 | ||
|
|
a0c57d20d8 | ||
|
|
b217221cef | ||
|
|
6fb318c788 | ||
|
|
c167201d7c | ||
|
|
eea5d610ba | ||
|
|
3b7ce7691e | ||
|
|
8de8ae03f3 | ||
|
|
519a444617 | ||
|
|
70be40468a | ||
|
|
5c4a92a6d9 | ||
|
|
6768406422 | ||
|
|
f39d654725 | ||
|
|
1bbce77c92 | ||
|
|
fe0ea40dc0 | ||
|
|
29541617de | ||
|
|
865d84f4e7 | ||
|
|
d9da0dd671 | ||
|
|
f54c468f51 | ||
|
|
1c7e6d7d81 | ||
|
|
03c48a5ef3 | ||
|
|
b3aaca317c | ||
|
|
c0efc232ad | ||
|
|
6a717f2272 | ||
|
|
475e2a64ce | ||
|
|
865bfdbd34 | ||
|
|
b4ab1c2c69 | ||
|
|
ed1a5b600d | ||
|
|
069ada8c4a | ||
|
|
72c647fba8 | ||
|
|
2412946939 | ||
|
|
b84fe6891c | ||
|
|
4c4b95a39f | ||
|
|
b31a1e2584 | ||
|
|
33a3674989 | ||
|
|
e457ed194f | ||
|
|
ef6bc042fd | ||
|
|
2ba6916e53 | ||
|
|
2d1299bb46 | ||
|
|
ec71fb22de | ||
|
|
f1bff1b62c | ||
|
|
d39060e0a8 | ||
|
|
610edc9584 | ||
|
|
e58033aff0 | ||
|
|
2c80823d96 | ||
|
|
ccd0e863db | ||
|
|
82be39af3d | ||
|
|
91cc3a3ff5 | ||
|
|
b23f7b0725 | ||
|
|
25b661b908 | ||
|
|
d5025e7db3 | ||
|
|
0e33fb2b64 | ||
|
|
abde5393df | ||
|
|
26c714da48 | ||
|
|
d000c160b6 | ||
|
|
ed0995fa49 | ||
|
|
5764ea9ade | ||
|
|
07bfe3823c | ||
|
|
97703b0332 | ||
|
|
3ebbc5976f | ||
|
|
15ffce36de | ||
|
|
68f5947b40 | ||
|
|
d503a436d0 | ||
|
|
75ec86eed4 | ||
|
|
73280fd173 | ||
|
|
6149f46621 | ||
|
|
bdb7350994 | ||
|
|
c08d34ead9 | ||
|
|
ddb0ce3175 | ||
|
|
f68f21a264 | ||
|
|
bc62405810 | ||
|
|
b5061da8c3 | ||
|
|
9c591bd42d | ||
|
|
bbcab38590 | ||
|
|
a7b7059916 | ||
|
|
d4b0ca7804 | ||
|
|
552c729c5a | ||
|
|
0994b0f2de | ||
|
|
ecfc5d68c2 | ||
|
|
06a5e76c50 | ||
|
|
87e0d167e2 | ||
|
|
1a5ae40e15 | ||
|
|
6a4581671f | ||
|
|
8bf1d4924b | ||
|
|
661329b977 | ||
|
|
4fd0e9e02d |
@@ -1,16 +0,0 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
lint:
|
||||
name: CodeQL
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Run CodeQL
|
||||
run: |
|
||||
docker run --rm -v $PWD:/app composer sh -c \
|
||||
"composer install --profile --ignore-platform-reqs && composer check"
|
||||
+15
-14
@@ -16,15 +16,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build Appwrite
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
VERSION=dev
|
||||
|
||||
- name: Cache Docker Image
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
@@ -51,10 +51,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
@@ -81,10 +81,10 @@ jobs:
|
||||
needs: setup
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
@@ -113,6 +113,7 @@ jobs:
|
||||
Console,
|
||||
Databases,
|
||||
Functions,
|
||||
FunctionsSchedule,
|
||||
GraphQL,
|
||||
Health,
|
||||
Locale,
|
||||
@@ -128,10 +129,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
@@ -141,7 +142,7 @@ jobs:
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
sleep 25
|
||||
sleep 30
|
||||
|
||||
- name: Run ${{matrix.service}} Tests
|
||||
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
|
||||
@@ -149,15 +150,15 @@ jobs:
|
||||
- name: Run ${{matrix.service}} Shared Tables Tests
|
||||
run: _APP_DATABASE_SHARED_TABLES=database_db_main docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
|
||||
|
||||
benchamrking:
|
||||
benchmarking:
|
||||
name: Benchmark
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
|
||||
@@ -319,10 +319,13 @@ These are the current metrics we collect usage stats for:
|
||||
| users | Total number of users per project|
|
||||
| executions | Total number of executions per project |
|
||||
| databases | Total number of databases per project |
|
||||
| databases.storage | Total amount of storage used by all databases per project (in bytes) |
|
||||
| collections | Total number of collections per project |
|
||||
| {databaseInternalId}.collections | Total number of collections per database|
|
||||
| {databaseInternalId}.storage | Sum of database storage (in bytes) |
|
||||
| documents | Total number of documents per project |
|
||||
| {databaseInternalId}.{collectionInternalId}.documents | Total number of documents per collection |
|
||||
| {databaseInternalId}.{collectionInternalId}.storage | Sum of database storage used by the collection (in bytes) |
|
||||
| buckets | Total number of buckets per project |
|
||||
| files | Total number of files per project |
|
||||
| {bucketInternalId}.files.storage | Sum of files.storage per bucket (in bytes) |
|
||||
|
||||
@@ -28,6 +28,8 @@ RUN \
|
||||
apk add boost boost-dev; \
|
||||
fi
|
||||
|
||||
RUN apk add libwebp
|
||||
|
||||
WORKDIR /usr/src/code
|
||||
|
||||
COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
| 1.3.x | :white_check_mark: |
|
||||
| 1.4.x | :white_check_mark: |
|
||||
| 1.5.x | :white_check_mark: |
|
||||
| 1.6.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
+175
-93
@@ -7,130 +7,214 @@ use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\Runtimes\Runtimes;
|
||||
use Swoole\Runtime;
|
||||
use Utopia\CLI\Adapters\Swoole as SwooleCLI;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\CLI;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DI\Dependency;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Service;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\System\System;
|
||||
|
||||
global $registry, $container;
|
||||
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
// overwriting runtimes to be architectur agnostic for CLI
|
||||
Config::setParam('runtimes', (new Runtimes('v4'))->getAll(supported: false));
|
||||
|
||||
// require controllers after overwriting runtimes
|
||||
require_once __DIR__ . '/controllers/general.php';
|
||||
|
||||
/**
|
||||
* @var Registry $registry
|
||||
* @var Container $container
|
||||
*/
|
||||
$context = new Dependency();
|
||||
$register = new Dependency();
|
||||
$logError = new Dependency();
|
||||
$queueForDeletes = new Dependency();
|
||||
$queueForFunctions = new Dependency();
|
||||
$queueForCertificates = new Dependency();
|
||||
Authorization::disable();
|
||||
|
||||
$context
|
||||
->setName('context')
|
||||
->setCallback(fn () => $container);
|
||||
CLI::setResource('register', fn () => $register);
|
||||
|
||||
$register
|
||||
->setName('register')
|
||||
->setCallback(function () use (&$registry): Registry {
|
||||
return $registry;
|
||||
});
|
||||
CLI::setResource('cache', function ($pools) {
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
$adapters = [];
|
||||
|
||||
$queueForFunctions
|
||||
->setName('queueForFunctions')
|
||||
->inject('queue')
|
||||
->setCallback(function (Connection $queue) {
|
||||
return new Func($queue);
|
||||
});
|
||||
foreach ($list as $value) {
|
||||
$adapters[] = $pools
|
||||
->get($value)
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
}
|
||||
|
||||
return new Cache(new Sharding($adapters));
|
||||
}, ['pools']);
|
||||
|
||||
$queueForDeletes
|
||||
->setName('queueForDeletes')
|
||||
->inject('queue')
|
||||
->setCallback(function (Connection $queue) {
|
||||
return new Delete($queue);
|
||||
});
|
||||
CLI::setResource('pools', function (Registry $register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
|
||||
$queueForCertificates
|
||||
->setName('queueForCertificates')
|
||||
->inject('queue')
|
||||
->setCallback(function (Connection $queue) {
|
||||
return new Certificate($queue);
|
||||
});
|
||||
CLI::setResource('dbForConsole', function ($pools, $cache) {
|
||||
$sleep = 3;
|
||||
$maxAttempts = 5;
|
||||
$attempts = 0;
|
||||
$ready = false;
|
||||
|
||||
$logError
|
||||
->setName('logError')
|
||||
->inject('register')
|
||||
->setCallback(function (Registry $register) {
|
||||
return function (Throwable $error, string $namespace, string $action) use ($register) {
|
||||
$logger = $register->get('logger');
|
||||
do {
|
||||
$attempts++;
|
||||
try {
|
||||
// Prepare database connection
|
||||
$dbAdapter = $pools
|
||||
->get('console')
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
if ($logger) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$dbForConsole = new Database($dbAdapter, $cache);
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace($namespace);
|
||||
$log->setServer(\gethostname());
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
$dbForConsole
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console');
|
||||
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
// Ensure tables exist
|
||||
$collections = Config::getParam('collections', [])['console'];
|
||||
$last = \array_key_last($collections);
|
||||
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Usage stats log pushed with status code: ' . $responseCode);
|
||||
if (!($dbForConsole->exists($dbForConsole->getDatabase(), $last))) { /** TODO cache ready variable using registry */
|
||||
throw new Exception('Tables not ready yet.');
|
||||
}
|
||||
|
||||
Console::warning("Failed: {$error->getMessage()}");
|
||||
Console::warning($error->getTraceAsString());
|
||||
};
|
||||
});
|
||||
$ready = true;
|
||||
} catch (\Throwable $err) {
|
||||
Console::warning($err->getMessage());
|
||||
$pools->get('console')->reclaim();
|
||||
sleep($sleep);
|
||||
}
|
||||
} while ($attempts < $maxAttempts && !$ready);
|
||||
|
||||
$container->set($context);
|
||||
$container->set($logError);
|
||||
$container->set($register);
|
||||
$container->set($queueForDeletes);
|
||||
$container->set($queueForFunctions);
|
||||
$container->set($queueForCertificates);
|
||||
if (!$ready) {
|
||||
throw new Exception("Console is not ready yet. Please try again later.");
|
||||
}
|
||||
|
||||
return $dbForConsole;
|
||||
}, ['pools', 'cache']);
|
||||
|
||||
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForConsole;
|
||||
}
|
||||
|
||||
try {
|
||||
$dsn = new DSN($project->getAttribute('database'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
// TODO: Temporary until all projects are using shared tables
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
if (isset($databases[$dsn->getHost()])) {
|
||||
$database = $databases[$dsn->getHost()];
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getInternalId());
|
||||
}
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
||||
$dbAdapter = $pools
|
||||
->get($dsn->getHost())
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$database = new Database($dbAdapter, $cache);
|
||||
|
||||
$databases[$dsn->getHost()] = $database;
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getInternalId());
|
||||
}
|
||||
|
||||
$database
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForConsole', 'cache']);
|
||||
|
||||
CLI::setResource('queue', function (Group $pools) {
|
||||
return $pools->get('queue')->pop()->getResource();
|
||||
}, ['pools']);
|
||||
CLI::setResource('queueForFunctions', function (Connection $queue) {
|
||||
return new Func($queue);
|
||||
}, ['queue']);
|
||||
CLI::setResource('queueForDeletes', function (Connection $queue) {
|
||||
return new Delete($queue);
|
||||
}, ['queue']);
|
||||
CLI::setResource('queueForCertificates', function (Connection $queue) {
|
||||
return new Certificate($queue);
|
||||
}, ['queue']);
|
||||
CLI::setResource('logError', function (Registry $register) {
|
||||
return function (Throwable $error, string $namespace, string $action) use ($register) {
|
||||
$logger = $register->get('logger');
|
||||
|
||||
if ($logger) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace($namespace);
|
||||
$log->setServer(\gethostname());
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
try {
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Error log pushed with status code: ' . $responseCode);
|
||||
} catch (Throwable $th) {
|
||||
Console::error('Error pushing log: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Console::warning("Failed: {$error->getMessage()}");
|
||||
Console::warning($error->getTraceAsString());
|
||||
};
|
||||
}, ['register']);
|
||||
|
||||
$platform = new Appwrite();
|
||||
$platform->init(Service::TYPE_TASK, ['adapter' => new SwooleCLI(1)]);
|
||||
$platform->init(Service::TYPE_TASK);
|
||||
|
||||
$cli = $platform->getCli();
|
||||
|
||||
$cli
|
||||
->init()
|
||||
->inject('authorization')
|
||||
->action(function (Authorization $authorization) {
|
||||
$authorization->disable();
|
||||
});
|
||||
|
||||
$cli
|
||||
->error()
|
||||
->inject('error')
|
||||
@@ -138,6 +222,4 @@ $cli
|
||||
Console::error($error->getMessage());
|
||||
});
|
||||
|
||||
$cli
|
||||
->setContainer($container)
|
||||
->run();
|
||||
$cli->run();
|
||||
|
||||
@@ -46,15 +46,15 @@ $commonCollections = [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('mimeType'),
|
||||
'$id' => ID::custom('headers'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 255, // https://tools.ietf.org/html/rfc4288#section-4.2
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'default' => [],
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => 'accessedAt',
|
||||
@@ -2406,6 +2406,17 @@ $projectCollections = array_merge([
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('originalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'signed' => true,
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -4109,13 +4120,24 @@ $projectCollections = array_merge([
|
||||
'$id' => ID::custom('source'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 8192,
|
||||
'size' => 8192, // reduce size
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('destination'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false, // make true after patch script
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('credentials'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -4508,6 +4530,28 @@ $consoleCollections = array_merge([
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('pingCount'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('pingedAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
]
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -4531,6 +4575,20 @@ $consoleCollections = array_merge([
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_pingCount'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['pingCount'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_pingedAt'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['pingedAt'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -24,6 +24,11 @@ return [
|
||||
'description' => 'Access to this API is forbidden.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::GENERAL_RESOURCE_BLOCKED => [
|
||||
'name' => Exception::GENERAL_RESOURCE_BLOCKED,
|
||||
'description' => 'Access to this resource is blocked.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::GENERAL_UNKNOWN_ORIGIN => [
|
||||
'name' => Exception::GENERAL_UNKNOWN_ORIGIN,
|
||||
'description' => 'The request originated from an unknown origin. If you trust this domain, please list it as a trusted platform in the Appwrite console.',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const TEMPLATE_RUNTIMES = [
|
||||
'NODE' => [
|
||||
'name' => 'node',
|
||||
'versions' => ['21.0', '20.0', '19.0', '18.0', '16.0', '14.5']
|
||||
'versions' => ['22', '21.0', '20.0', '19.0', '18.0', '16.0', '14.5']
|
||||
],
|
||||
'PYTHON' => [
|
||||
'name' => 'python',
|
||||
@@ -11,7 +11,7 @@ const TEMPLATE_RUNTIMES = [
|
||||
],
|
||||
'DART' => [
|
||||
'name' => 'dart',
|
||||
'versions' => ['3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16', '2.16']
|
||||
'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16', '2.16']
|
||||
],
|
||||
'GO' => [
|
||||
'name' => 'go',
|
||||
@@ -21,9 +21,13 @@ const TEMPLATE_RUNTIMES = [
|
||||
'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.0']
|
||||
'versions' => ['1.1', '1.0']
|
||||
],
|
||||
'RUBY' => [
|
||||
'name' => 'ruby',
|
||||
@@ -51,7 +55,7 @@ return [
|
||||
'id' => 'starter',
|
||||
'name' => 'Starter function',
|
||||
'tagline' =>
|
||||
'A simple function to get started. Edit this function to explore endless possibilities with Appwrite Functions.',
|
||||
'A simple function to get started. Edit this function to explore endless possibilities with Appwrite Functions.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
@@ -73,6 +77,7 @@ return [
|
||||
'src/index.php',
|
||||
'php/starter'
|
||||
),
|
||||
...getRuntimes(TEMPLATE_RUNTIMES['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter'),
|
||||
...getRuntimes(TEMPLATE_RUNTIMES['BUN'], 'bun install', 'src/main.ts', 'bun/starter'),
|
||||
...getRuntimes(TEMPLATE_RUNTIMES['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter'),
|
||||
],
|
||||
@@ -121,7 +126,7 @@ return [
|
||||
'description' => 'Authentication token to access your Upstash Vector database. <a class="u-bold" target="_blank" href="https://upstash.com/docs/vector/overall/getstarted">Learn more</a>.',
|
||||
'value' => '',
|
||||
'placeholder' =>
|
||||
'oe4wNTbwHVLcDNa6oceZfhBEABsCNYh43ii6Xdq4bKBH7mq7qJkUmc4cs3ABbYyuVKWZTxVQjiNjYgydn2dkhABNes4NAuDpj7qxUAmZYqGJT78',
|
||||
'oe4wNTbwHVLcDNa6oceZfhBEABsCNYh43ii6Xdq4bKBH7mq7qJkUmc4cs3ABbYyuVKWZTxVQjiNjYgydn2dkhABNes4NAuDpj7qxUAmZYqGJT78',
|
||||
'required' => true,
|
||||
'type' => 'password'
|
||||
]
|
||||
@@ -227,7 +232,7 @@ return [
|
||||
'id' => 'query-mongo-atlas',
|
||||
'name' => 'Query MongoDB Atlas',
|
||||
'tagline' =>
|
||||
'Realtime NoSQL document database with geospecial, graph, search, and vector suport.',
|
||||
'Realtime NoSQL document database with geospecial, graph, search, and vector suport.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
@@ -252,7 +257,7 @@ return [
|
||||
'description' => 'The endpoint to connect to your Mongo database. <a class="u-bold" target="_blank" href="https://www.mongodb.com/docs/atlas/getting-started/">Learn more</a>.',
|
||||
'value' => '',
|
||||
'placeholder' =>
|
||||
'mongodb+srv://appwrite:Yx42hafg7Q4fgkxe@cluster0.7mslfog.mongodb.net/?retryWrites=true&w=majority&appName=Appwrite',
|
||||
'mongodb+srv://appwrite:Yx42hafg7Q4fgkxe@cluster0.7mslfog.mongodb.net/?retryWrites=true&w=majority&appName=Appwrite',
|
||||
'required' => true,
|
||||
'type' => 'password'
|
||||
]
|
||||
@@ -264,7 +269,7 @@ return [
|
||||
'id' => 'query-neon-postgres',
|
||||
'name' => 'Query Neon Postgres',
|
||||
'tagline' =>
|
||||
'Reliable SQL database with replication, point-in-time recovery, and pgvector support.',
|
||||
'Reliable SQL database with replication, point-in-time recovery, and pgvector support.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
@@ -491,7 +496,7 @@ return [
|
||||
'id' => 'censor-with-redact',
|
||||
'name' => 'Censor with Redact',
|
||||
'tagline' =>
|
||||
'Censor sensitive information from a provided text string using Redact API by Pangea.',
|
||||
'Censor sensitive information from a provided text string using Redact API by Pangea.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
@@ -560,7 +565,7 @@ return [
|
||||
'id' => 'github-issue-bot',
|
||||
'name' => 'GitHub issue bot',
|
||||
'tagline' =>
|
||||
'Automate the process of responding to newly opened issues in a GitHub repository.',
|
||||
'Automate the process of responding to newly opened issues in a GitHub repository.',
|
||||
'permissions' => ['any'],
|
||||
'events' => [],
|
||||
'cron' => '',
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<?php
|
||||
|
||||
const APP_PLATFORM_SERVER = 'server';
|
||||
const APP_PLATFORM_CLIENT = 'client';
|
||||
const APP_PLATFORM_CONSOLE = 'console';
|
||||
|
||||
return [
|
||||
APP_PLATFORM_CLIENT => [
|
||||
'key' => APP_PLATFORM_CLIENT,
|
||||
@@ -15,7 +11,7 @@ return [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '16.0.0',
|
||||
'version' => '16.0.2',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-web',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -203,7 +199,7 @@ return [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Console',
|
||||
'version' => '1.1.0',
|
||||
'version' => '1.2.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-console',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
@@ -285,7 +281,7 @@ return [
|
||||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '12.0.0',
|
||||
'version' => '12.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -321,7 +317,7 @@ return [
|
||||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '12.1.0',
|
||||
'version' => '12.1.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -357,7 +353,7 @@ return [
|
||||
[
|
||||
'key' => 'dotnet',
|
||||
'name' => '.NET',
|
||||
'version' => '0.10.0',
|
||||
'version' => '0.10.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
|
||||
'package' => 'https://www.nuget.org/packages/Appwrite',
|
||||
'enabled' => true,
|
||||
|
||||
@@ -31,7 +31,7 @@ return [
|
||||
],
|
||||
'blr' => [
|
||||
'$id' => 'blr',
|
||||
'name' => 'Banglore',
|
||||
'name' => 'Bengaluru',
|
||||
'disabled' => true,
|
||||
'flag' => 'in',
|
||||
'default' => true,
|
||||
|
||||
@@ -17,7 +17,6 @@ $member = [
|
||||
'files.read',
|
||||
'files.write',
|
||||
'projects.read',
|
||||
'projects.write',
|
||||
'locale.read',
|
||||
'avatars.read',
|
||||
'execution.read',
|
||||
@@ -49,6 +48,7 @@ $admins = [
|
||||
'collections.write',
|
||||
'platforms.read',
|
||||
'platforms.write',
|
||||
'projects.write',
|
||||
'keys.read',
|
||||
'keys.write',
|
||||
'webhooks.read',
|
||||
@@ -75,7 +75,7 @@ $admins = [
|
||||
'topics.write',
|
||||
'topics.read',
|
||||
'subscribers.write',
|
||||
'subscribers.read'
|
||||
'subscribers.read',
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
@@ -6,7 +6,7 @@ return [
|
||||
Specification::S_05VCPU_512MB => [
|
||||
'slug' => Specification::S_05VCPU_512MB,
|
||||
'memory' => 512,
|
||||
'cpus' => 0.5
|
||||
'cpus' => 1 // TODO: revert this, it's a temporary change to test function performance.
|
||||
],
|
||||
Specification::S_1VCPU_512MB => [
|
||||
'slug' => Specification::S_1VCPU_512MB,
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"weight": 8,
|
||||
"weight": 9,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -94,7 +94,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "create",
|
||||
"weight": 7,
|
||||
"weight": 8,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -181,7 +181,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateEmail",
|
||||
"weight": 33,
|
||||
"weight": 34,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -239,7 +239,7 @@
|
||||
},
|
||||
"\/account\/identities": {
|
||||
"get": {
|
||||
"summary": "List Identities",
|
||||
"summary": "List identities",
|
||||
"operationId": "accountListIdentities",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -259,7 +259,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listIdentities",
|
||||
"weight": 56,
|
||||
"weight": 57,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -320,7 +320,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteIdentity",
|
||||
"weight": 57,
|
||||
"weight": 58,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -385,7 +385,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createJWT",
|
||||
"weight": 28,
|
||||
"weight": 29,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -436,7 +436,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listLogs",
|
||||
"weight": 30,
|
||||
"weight": 31,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -504,7 +504,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMFA",
|
||||
"weight": 43,
|
||||
"weight": 44,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -556,7 +556,7 @@
|
||||
},
|
||||
"\/account\/mfa\/authenticators\/{type}": {
|
||||
"post": {
|
||||
"summary": "Create Authenticator",
|
||||
"summary": "Create authenticator",
|
||||
"operationId": "accountCreateMfaAuthenticator",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -576,7 +576,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMfaAuthenticator",
|
||||
"weight": 45,
|
||||
"weight": 46,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -624,7 +624,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Verify Authenticator",
|
||||
"summary": "Verify authenticator",
|
||||
"operationId": "accountUpdateMfaAuthenticator",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -644,7 +644,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaAuthenticator",
|
||||
"weight": 46,
|
||||
"weight": 47,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -711,7 +711,7 @@
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete Authenticator",
|
||||
"summary": "Delete authenticator",
|
||||
"operationId": "accountDeleteMfaAuthenticator",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -724,7 +724,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteMfaAuthenticator",
|
||||
"weight": 50,
|
||||
"weight": 51,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -774,7 +774,7 @@
|
||||
},
|
||||
"\/account\/mfa\/challenge": {
|
||||
"post": {
|
||||
"summary": "Create MFA Challenge",
|
||||
"summary": "Create MFA challenge",
|
||||
"operationId": "accountCreateMfaChallenge",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -794,7 +794,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMfaChallenge",
|
||||
"weight": 51,
|
||||
"weight": 52,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -850,7 +850,7 @@
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"summary": "Create MFA Challenge (confirmation)",
|
||||
"summary": "Create MFA challenge (confirmation)",
|
||||
"operationId": "accountUpdateMfaChallenge",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -870,7 +870,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaChallenge",
|
||||
"weight": 52,
|
||||
"weight": 53,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -928,7 +928,7 @@
|
||||
},
|
||||
"\/account\/mfa\/factors": {
|
||||
"get": {
|
||||
"summary": "List Factors",
|
||||
"summary": "List factors",
|
||||
"operationId": "accountListMfaFactors",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -948,7 +948,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMfaFactors",
|
||||
"weight": 44,
|
||||
"weight": 45,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -981,7 +981,7 @@
|
||||
},
|
||||
"\/account\/mfa\/recovery-codes": {
|
||||
"get": {
|
||||
"summary": "Get MFA Recovery Codes",
|
||||
"summary": "Get MFA recovery codes",
|
||||
"operationId": "accountGetMfaRecoveryCodes",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -1001,7 +1001,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getMfaRecoveryCodes",
|
||||
"weight": 49,
|
||||
"weight": 50,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1032,7 +1032,7 @@
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create MFA Recovery Codes",
|
||||
"summary": "Create MFA recovery codes",
|
||||
"operationId": "accountCreateMfaRecoveryCodes",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -1052,7 +1052,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMfaRecoveryCodes",
|
||||
"weight": 47,
|
||||
"weight": 48,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1083,7 +1083,7 @@
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "Regenerate MFA Recovery Codes",
|
||||
"summary": "Regenerate MFA recovery codes",
|
||||
"operationId": "accountUpdateMfaRecoveryCodes",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -1103,7 +1103,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaRecoveryCodes",
|
||||
"weight": 48,
|
||||
"weight": 49,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1156,7 +1156,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateName",
|
||||
"weight": 31,
|
||||
"weight": 32,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1228,7 +1228,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePassword",
|
||||
"weight": 32,
|
||||
"weight": 33,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1305,7 +1305,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePhone",
|
||||
"weight": 34,
|
||||
"weight": 35,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1383,7 +1383,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getPrefs",
|
||||
"weight": 29,
|
||||
"weight": 30,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1434,7 +1434,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePrefs",
|
||||
"weight": 35,
|
||||
"weight": 36,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1506,7 +1506,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createRecovery",
|
||||
"weight": 37,
|
||||
"weight": 38,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1585,7 +1585,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateRecovery",
|
||||
"weight": 38,
|
||||
"weight": 39,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1669,7 +1669,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listSessions",
|
||||
"weight": 10,
|
||||
"weight": 11,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1713,7 +1713,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSessions",
|
||||
"weight": 11,
|
||||
"weight": 12,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1766,7 +1766,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createAnonymousSession",
|
||||
"weight": 16,
|
||||
"weight": 17,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1817,7 +1817,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createEmailPasswordSession",
|
||||
"weight": 15,
|
||||
"weight": 16,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1893,7 +1893,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMagicURLSession",
|
||||
"weight": 25,
|
||||
"weight": 26,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": true,
|
||||
@@ -1962,7 +1962,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createOAuth2Session",
|
||||
"weight": 18,
|
||||
"weight": 19,
|
||||
"cookies": false,
|
||||
"type": "webAuth",
|
||||
"deprecated": false,
|
||||
@@ -2105,7 +2105,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePhoneSession",
|
||||
"weight": 26,
|
||||
"weight": 27,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": true,
|
||||
@@ -2181,7 +2181,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSession",
|
||||
"weight": 17,
|
||||
"weight": 18,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2257,7 +2257,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getSession",
|
||||
"weight": 12,
|
||||
"weight": 13,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2320,7 +2320,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSession",
|
||||
"weight": 14,
|
||||
"weight": 15,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2376,7 +2376,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSession",
|
||||
"weight": 13,
|
||||
"weight": 14,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2441,7 +2441,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateStatus",
|
||||
"weight": 36,
|
||||
"weight": 37,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2494,7 +2494,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createPushTarget",
|
||||
"weight": 53,
|
||||
"weight": 54,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2575,7 +2575,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePushTarget",
|
||||
"weight": 54,
|
||||
"weight": 55,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2655,7 +2655,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deletePushTarget",
|
||||
"weight": 55,
|
||||
"weight": 56,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2718,7 +2718,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createEmailToken",
|
||||
"weight": 24,
|
||||
"weight": 25,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2799,7 +2799,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMagicURLToken",
|
||||
"weight": 23,
|
||||
"weight": 24,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2881,7 +2881,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createOAuth2Token",
|
||||
"weight": 22,
|
||||
"weight": 23,
|
||||
"cookies": false,
|
||||
"type": "webAuth",
|
||||
"deprecated": false,
|
||||
@@ -3024,7 +3024,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createPhoneToken",
|
||||
"weight": 27,
|
||||
"weight": 28,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -3103,7 +3103,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createVerification",
|
||||
"weight": 39,
|
||||
"weight": 40,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -3173,7 +3173,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateVerification",
|
||||
"weight": 40,
|
||||
"weight": 41,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -3251,7 +3251,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createPhoneVerification",
|
||||
"weight": 41,
|
||||
"weight": 42,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -3305,7 +3305,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePhoneVerification",
|
||||
"weight": 42,
|
||||
"weight": 43,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -3376,7 +3376,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getBrowser",
|
||||
"weight": 59,
|
||||
"weight": 60,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -3504,7 +3504,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getCreditCard",
|
||||
"weight": 58,
|
||||
"weight": 59,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -3636,7 +3636,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFavicon",
|
||||
"weight": 62,
|
||||
"weight": 63,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -3696,7 +3696,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFlag",
|
||||
"weight": 60,
|
||||
"weight": 61,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -4186,7 +4186,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getImage",
|
||||
"weight": 61,
|
||||
"weight": 62,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -4270,7 +4270,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getInitials",
|
||||
"weight": 64,
|
||||
"weight": 65,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -4364,7 +4364,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getQR",
|
||||
"weight": 63,
|
||||
"weight": 64,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -4465,7 +4465,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"weight": 108,
|
||||
"weight": 109,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -4552,7 +4552,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"weight": 107,
|
||||
"weight": 108,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -4661,7 +4661,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"weight": 109,
|
||||
"weight": 110,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -4758,7 +4758,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"weight": 111,
|
||||
"weight": 112,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -4859,7 +4859,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"weight": 112,
|
||||
"weight": 113,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -4945,7 +4945,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"weight": 304,
|
||||
"weight": 305,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5033,7 +5033,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"weight": 303,
|
||||
"weight": 304,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5084,7 +5084,7 @@
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "HTTP body of execution. Default value is empty string.",
|
||||
"x-example": null
|
||||
"x-example": "<BODY>"
|
||||
},
|
||||
"async": {
|
||||
"type": "boolean",
|
||||
@@ -5150,7 +5150,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"weight": 305,
|
||||
"weight": 306,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5226,7 +5226,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"weight": 329,
|
||||
"weight": 330,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"deprecated": false,
|
||||
@@ -5280,7 +5280,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"weight": 328,
|
||||
"weight": 329,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"deprecated": false,
|
||||
@@ -5334,7 +5334,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"weight": 116,
|
||||
"weight": 117,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5368,7 +5368,7 @@
|
||||
},
|
||||
"\/locale\/codes": {
|
||||
"get": {
|
||||
"summary": "List Locale Codes",
|
||||
"summary": "List locale codes",
|
||||
"operationId": "localeListCodes",
|
||||
"tags": [
|
||||
"locale"
|
||||
@@ -5388,7 +5388,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listCodes",
|
||||
"weight": 117,
|
||||
"weight": 118,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5442,7 +5442,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listContinents",
|
||||
"weight": 121,
|
||||
"weight": 122,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5496,7 +5496,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listCountries",
|
||||
"weight": 118,
|
||||
"weight": 119,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5550,7 +5550,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listCountriesEU",
|
||||
"weight": 119,
|
||||
"weight": 120,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5604,7 +5604,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listCountriesPhones",
|
||||
"weight": 120,
|
||||
"weight": 121,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5658,7 +5658,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listCurrencies",
|
||||
"weight": 122,
|
||||
"weight": 123,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5712,7 +5712,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listLanguages",
|
||||
"weight": 123,
|
||||
"weight": 124,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5766,7 +5766,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"weight": 380,
|
||||
"weight": 381,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5851,7 +5851,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"weight": 384,
|
||||
"weight": 385,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5928,7 +5928,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listFiles",
|
||||
"weight": 206,
|
||||
"weight": 207,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6016,7 +6016,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFile",
|
||||
"weight": 205,
|
||||
"weight": 206,
|
||||
"cookies": false,
|
||||
"type": "upload",
|
||||
"deprecated": false,
|
||||
@@ -6116,7 +6116,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFile",
|
||||
"weight": 207,
|
||||
"weight": 208,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6190,7 +6190,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateFile",
|
||||
"weight": 212,
|
||||
"weight": 213,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6268,7 +6268,7 @@
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete File",
|
||||
"summary": "Delete file",
|
||||
"operationId": "storageDeleteFile",
|
||||
"tags": [
|
||||
"storage"
|
||||
@@ -6281,7 +6281,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteFile",
|
||||
"weight": 213,
|
||||
"weight": 214,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6350,7 +6350,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFileDownload",
|
||||
"weight": 209,
|
||||
"weight": 210,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -6419,7 +6419,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFilePreview",
|
||||
"weight": 208,
|
||||
"weight": 209,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -6610,7 +6610,8 @@
|
||||
"jpeg",
|
||||
"gif",
|
||||
"png",
|
||||
"webp"
|
||||
"webp",
|
||||
"avif"
|
||||
],
|
||||
"x-enum-name": "ImageFormat",
|
||||
"x-enum-keys": [],
|
||||
@@ -6636,7 +6637,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFileView",
|
||||
"weight": 210,
|
||||
"weight": 211,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -6712,7 +6713,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "list",
|
||||
"weight": 217,
|
||||
"weight": 218,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6790,7 +6791,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "create",
|
||||
"weight": 216,
|
||||
"weight": 217,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6877,7 +6878,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"weight": 218,
|
||||
"weight": 219,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6941,7 +6942,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateName",
|
||||
"weight": 220,
|
||||
"weight": 221,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7017,7 +7018,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"weight": 222,
|
||||
"weight": 223,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7083,7 +7084,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMemberships",
|
||||
"weight": 224,
|
||||
"weight": 225,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7171,7 +7172,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMembership",
|
||||
"weight": 223,
|
||||
"weight": 224,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7284,7 +7285,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getMembership",
|
||||
"weight": 225,
|
||||
"weight": 226,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7358,7 +7359,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMembership",
|
||||
"weight": 226,
|
||||
"weight": 227,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7447,7 +7448,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteMembership",
|
||||
"weight": 228,
|
||||
"weight": 229,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7523,7 +7524,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMembershipStatus",
|
||||
"weight": 227,
|
||||
"weight": 228,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7623,7 +7624,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getPrefs",
|
||||
"weight": 219,
|
||||
"weight": 220,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7686,7 +7687,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePrefs",
|
||||
"weight": 221,
|
||||
"weight": 222,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -9405,7 +9406,7 @@
|
||||
"responseBody": {
|
||||
"type": "string",
|
||||
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
|
||||
"x-example": "Developers are awesome."
|
||||
"x-example": ""
|
||||
},
|
||||
"responseHeaders": {
|
||||
"type": "array",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -166,7 +166,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
|
||||
"description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User",
|
||||
@@ -239,7 +239,7 @@
|
||||
},
|
||||
"\/account\/identities": {
|
||||
"get": {
|
||||
"summary": "List Identities",
|
||||
"summary": "List identities",
|
||||
"operationId": "accountListIdentities",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -556,7 +556,7 @@
|
||||
},
|
||||
"\/account\/mfa\/authenticators\/{type}": {
|
||||
"post": {
|
||||
"summary": "Create Authenticator",
|
||||
"summary": "Create authenticator",
|
||||
"operationId": "accountCreateMfaAuthenticator",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -624,7 +624,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Verify Authenticator",
|
||||
"summary": "Verify authenticator",
|
||||
"operationId": "accountUpdateMfaAuthenticator",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -711,7 +711,7 @@
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete Authenticator",
|
||||
"summary": "Delete authenticator",
|
||||
"operationId": "accountDeleteMfaAuthenticator",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -774,7 +774,7 @@
|
||||
},
|
||||
"\/account\/mfa\/challenge": {
|
||||
"post": {
|
||||
"summary": "Create MFA Challenge",
|
||||
"summary": "Create MFA challenge",
|
||||
"operationId": "accountCreateMfaChallenge",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -850,7 +850,7 @@
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"summary": "Create MFA Challenge (confirmation)",
|
||||
"summary": "Create MFA challenge (confirmation)",
|
||||
"operationId": "accountUpdateMfaChallenge",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -928,7 +928,7 @@
|
||||
},
|
||||
"\/account\/mfa\/factors": {
|
||||
"get": {
|
||||
"summary": "List Factors",
|
||||
"summary": "List factors",
|
||||
"operationId": "accountListMfaFactors",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -981,7 +981,7 @@
|
||||
},
|
||||
"\/account\/mfa\/recovery-codes": {
|
||||
"get": {
|
||||
"summary": "Get MFA Recovery Codes",
|
||||
"summary": "Get MFA recovery codes",
|
||||
"operationId": "accountGetMfaRecoveryCodes",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -1032,7 +1032,7 @@
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create MFA Recovery Codes",
|
||||
"summary": "Create MFA recovery codes",
|
||||
"operationId": "accountCreateMfaRecoveryCodes",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -1083,7 +1083,7 @@
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "Regenerate MFA Recovery Codes",
|
||||
"summary": "Regenerate MFA recovery codes",
|
||||
"operationId": "accountUpdateMfaRecoveryCodes",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -1570,7 +1570,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
|
||||
"description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Token",
|
||||
@@ -1802,7 +1802,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Session",
|
||||
@@ -1954,7 +1954,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
|
||||
"responses": {
|
||||
"301": {
|
||||
"description": "File"
|
||||
@@ -2703,7 +2703,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -2784,7 +2784,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -2873,7 +2873,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"301": {
|
||||
"description": "File"
|
||||
@@ -3009,7 +3009,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -3088,7 +3088,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
|
||||
"description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -3368,7 +3368,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
|
||||
"description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -3496,7 +3496,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
|
||||
"description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -3628,7 +3628,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
|
||||
"description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -3688,7 +3688,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
|
||||
"description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -4178,7 +4178,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
|
||||
"description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -4262,7 +4262,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
|
||||
"description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -4356,7 +4356,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
|
||||
"description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -5319,7 +5319,7 @@
|
||||
"tags": [
|
||||
"locale"
|
||||
],
|
||||
"description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
|
||||
"description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Locale",
|
||||
@@ -5368,7 +5368,7 @@
|
||||
},
|
||||
"\/locale\/codes": {
|
||||
"get": {
|
||||
"summary": "List Locale Codes",
|
||||
"summary": "List locale codes",
|
||||
"operationId": "localeListCodes",
|
||||
"tags": [
|
||||
"locale"
|
||||
@@ -6001,7 +6001,7 @@
|
||||
"tags": [
|
||||
"storage"
|
||||
],
|
||||
"description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
|
||||
"description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "File",
|
||||
@@ -6268,7 +6268,7 @@
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete File",
|
||||
"summary": "Delete file",
|
||||
"operationId": "storageDeleteFile",
|
||||
"tags": [
|
||||
"storage"
|
||||
@@ -6610,7 +6610,8 @@
|
||||
"jpeg",
|
||||
"gif",
|
||||
"png",
|
||||
"webp"
|
||||
"webp",
|
||||
"avif"
|
||||
],
|
||||
"x-enum-name": "ImageFormat",
|
||||
"x-enum-keys": [],
|
||||
@@ -7156,7 +7157,7 @@
|
||||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
|
||||
"description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Membership",
|
||||
@@ -7343,7 +7344,7 @@
|
||||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
|
||||
"description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Membership",
|
||||
@@ -7508,7 +7509,7 @@
|
||||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
|
||||
"description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Membership",
|
||||
@@ -9405,7 +9406,7 @@
|
||||
"responseBody": {
|
||||
"type": "string",
|
||||
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
|
||||
"x-example": "Developers are awesome."
|
||||
"x-example": ""
|
||||
},
|
||||
"responseHeaders": {
|
||||
"type": "array",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -167,7 +167,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
|
||||
"description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User",
|
||||
@@ -241,7 +241,7 @@
|
||||
},
|
||||
"\/account\/identities": {
|
||||
"get": {
|
||||
"summary": "List Identities",
|
||||
"summary": "List identities",
|
||||
"operationId": "accountListIdentities",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -562,7 +562,7 @@
|
||||
},
|
||||
"\/account\/mfa\/authenticators\/{type}": {
|
||||
"post": {
|
||||
"summary": "Create Authenticator",
|
||||
"summary": "Create authenticator",
|
||||
"operationId": "accountCreateMfaAuthenticator",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -631,7 +631,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Verify Authenticator",
|
||||
"summary": "Verify authenticator",
|
||||
"operationId": "accountUpdateMfaAuthenticator",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -719,7 +719,7 @@
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete Authenticator",
|
||||
"summary": "Delete authenticator",
|
||||
"operationId": "accountDeleteMfaAuthenticator",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -783,7 +783,7 @@
|
||||
},
|
||||
"\/account\/mfa\/challenge": {
|
||||
"post": {
|
||||
"summary": "Create MFA Challenge",
|
||||
"summary": "Create MFA challenge",
|
||||
"operationId": "accountCreateMfaChallenge",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -859,7 +859,7 @@
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"summary": "Create MFA Challenge (confirmation)",
|
||||
"summary": "Create MFA challenge (confirmation)",
|
||||
"operationId": "accountUpdateMfaChallenge",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -938,7 +938,7 @@
|
||||
},
|
||||
"\/account\/mfa\/factors": {
|
||||
"get": {
|
||||
"summary": "List Factors",
|
||||
"summary": "List factors",
|
||||
"operationId": "accountListMfaFactors",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -992,7 +992,7 @@
|
||||
},
|
||||
"\/account\/mfa\/recovery-codes": {
|
||||
"get": {
|
||||
"summary": "Get MFA Recovery Codes",
|
||||
"summary": "Get MFA recovery codes",
|
||||
"operationId": "accountGetMfaRecoveryCodes",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -1044,7 +1044,7 @@
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create MFA Recovery Codes",
|
||||
"summary": "Create MFA recovery codes",
|
||||
"operationId": "accountCreateMfaRecoveryCodes",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -1096,7 +1096,7 @@
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "Regenerate MFA Recovery Codes",
|
||||
"summary": "Regenerate MFA recovery codes",
|
||||
"operationId": "accountUpdateMfaRecoveryCodes",
|
||||
"tags": [
|
||||
"account"
|
||||
@@ -1590,7 +1590,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
|
||||
"description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Token",
|
||||
@@ -1825,7 +1825,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Session",
|
||||
@@ -2370,7 +2370,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -2451,7 +2451,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -2540,7 +2540,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"301": {
|
||||
"description": "File"
|
||||
@@ -2676,7 +2676,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -2755,7 +2755,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
|
||||
"description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -3039,7 +3039,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
|
||||
"description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -3169,7 +3169,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
|
||||
"description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -3303,7 +3303,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
|
||||
"description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -3365,7 +3365,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
|
||||
"description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -3857,7 +3857,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
|
||||
"description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -3943,7 +3943,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
|
||||
"description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -4039,7 +4039,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
|
||||
"description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image"
|
||||
@@ -4211,7 +4211,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create a new Database.\n",
|
||||
"description": "Create a new Database.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Database",
|
||||
@@ -5026,7 +5026,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create a boolean attribute.\n",
|
||||
"description": "Create a boolean attribute.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeBoolean",
|
||||
@@ -5472,7 +5472,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create an email attribute.\n",
|
||||
"description": "Create an email attribute.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeEmail",
|
||||
@@ -5581,7 +5581,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeEmail",
|
||||
@@ -5695,7 +5695,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n",
|
||||
"description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeEnum",
|
||||
@@ -5813,7 +5813,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeEnum",
|
||||
@@ -5936,7 +5936,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n",
|
||||
"description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeFloat",
|
||||
@@ -6055,7 +6055,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeFloat",
|
||||
@@ -6181,7 +6181,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n",
|
||||
"description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeInteger",
|
||||
@@ -6300,7 +6300,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeInteger",
|
||||
@@ -6426,7 +6426,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create IP address attribute.\n",
|
||||
"description": "Create IP address attribute.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeIP",
|
||||
@@ -6535,7 +6535,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeIP",
|
||||
@@ -6649,7 +6649,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
|
||||
"description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeRelationship",
|
||||
@@ -6783,7 +6783,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create a string attribute.\n",
|
||||
"description": "Create a string attribute.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeString",
|
||||
@@ -6903,7 +6903,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeString",
|
||||
@@ -7022,7 +7022,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create a URL attribute.\n",
|
||||
"description": "Create a URL attribute.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeURL",
|
||||
@@ -7131,7 +7131,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeURL",
|
||||
@@ -7433,7 +7433,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
|
||||
"description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeRelationship",
|
||||
@@ -8119,7 +8119,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.",
|
||||
"description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "Index",
|
||||
@@ -8767,7 +8767,7 @@
|
||||
"tags": [
|
||||
"functions"
|
||||
],
|
||||
"description": "List allowed function specifications for this instance.\n",
|
||||
"description": "List allowed function specifications for this instance.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Specifications List",
|
||||
@@ -9249,7 +9249,7 @@
|
||||
"tags": [
|
||||
"functions"
|
||||
],
|
||||
"description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.",
|
||||
"description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "Deployment",
|
||||
@@ -10068,7 +10068,7 @@
|
||||
"tags": [
|
||||
"functions"
|
||||
],
|
||||
"description": "Delete a function execution by its unique ID.\n",
|
||||
"description": "Delete a function execution by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No content"
|
||||
@@ -11279,7 +11279,7 @@
|
||||
"tags": [
|
||||
"health"
|
||||
],
|
||||
"description": "Returns the amount of failed jobs in a given queue.\n",
|
||||
"description": "Returns the amount of failed jobs in a given queue.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Health Queue",
|
||||
@@ -12046,7 +12046,7 @@
|
||||
"tags": [
|
||||
"locale"
|
||||
],
|
||||
"description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
|
||||
"description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Locale",
|
||||
@@ -12097,7 +12097,7 @@
|
||||
},
|
||||
"\/locale\/codes": {
|
||||
"get": {
|
||||
"summary": "List Locale Codes",
|
||||
"summary": "List locale codes",
|
||||
"operationId": "localeListCodes",
|
||||
"tags": [
|
||||
"locale"
|
||||
@@ -12720,7 +12720,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Update an email message by its unique ID.\n",
|
||||
"description": "Update an email message by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Message",
|
||||
@@ -13027,7 +13027,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Update a push notification by its unique ID.\n",
|
||||
"description": "Update a push notification by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Message",
|
||||
@@ -13299,7 +13299,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Update an email message by its unique ID.\n",
|
||||
"description": "Update an email message by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Message",
|
||||
@@ -13414,7 +13414,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Get a message by its unique ID.\n",
|
||||
"description": "Get a message by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Message",
|
||||
@@ -15915,7 +15915,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Get a provider by its unique ID.\n",
|
||||
"description": "Get a provider by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Provider",
|
||||
@@ -16355,7 +16355,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Get a topic by its unique ID.\n",
|
||||
"description": "Get a topic by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Topic",
|
||||
@@ -16418,7 +16418,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Update a topic by its unique ID.\n",
|
||||
"description": "Update a topic by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Topic",
|
||||
@@ -16822,7 +16822,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Get a subscriber by its unique ID.\n",
|
||||
"description": "Get a subscriber by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Subscriber",
|
||||
@@ -17516,7 +17516,7 @@
|
||||
"tags": [
|
||||
"storage"
|
||||
],
|
||||
"description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
|
||||
"description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "File",
|
||||
@@ -17789,7 +17789,7 @@
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete File",
|
||||
"summary": "Delete file",
|
||||
"operationId": "storageDeleteFile",
|
||||
"tags": [
|
||||
"storage"
|
||||
@@ -18137,7 +18137,8 @@
|
||||
"jpeg",
|
||||
"gif",
|
||||
"png",
|
||||
"webp"
|
||||
"webp",
|
||||
"avif"
|
||||
],
|
||||
"x-enum-name": "ImageFormat",
|
||||
"x-enum-keys": [],
|
||||
@@ -18697,7 +18698,7 @@
|
||||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
|
||||
"description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Membership",
|
||||
@@ -18888,7 +18889,7 @@
|
||||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
|
||||
"description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Membership",
|
||||
@@ -19057,7 +19058,7 @@
|
||||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
|
||||
"description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Membership",
|
||||
@@ -19645,7 +19646,7 @@
|
||||
},
|
||||
"\/users\/identities": {
|
||||
"get": {
|
||||
"summary": "List Identities",
|
||||
"summary": "List identities",
|
||||
"operationId": "usersListIdentities",
|
||||
"tags": [
|
||||
"users"
|
||||
@@ -20580,7 +20581,7 @@
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
|
||||
"description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User",
|
||||
@@ -20885,7 +20886,7 @@
|
||||
},
|
||||
"\/users\/{userId}\/mfa\/authenticators\/{type}": {
|
||||
"delete": {
|
||||
"summary": "Delete Authenticator",
|
||||
"summary": "Delete authenticator",
|
||||
"operationId": "usersDeleteMfaAuthenticator",
|
||||
"tags": [
|
||||
"users"
|
||||
@@ -20964,7 +20965,7 @@
|
||||
},
|
||||
"\/users\/{userId}\/mfa\/factors": {
|
||||
"get": {
|
||||
"summary": "List Factors",
|
||||
"summary": "List factors",
|
||||
"operationId": "usersListMfaFactors",
|
||||
"tags": [
|
||||
"users"
|
||||
@@ -21028,7 +21029,7 @@
|
||||
},
|
||||
"\/users\/{userId}\/mfa\/recovery-codes": {
|
||||
"get": {
|
||||
"summary": "Get MFA Recovery Codes",
|
||||
"summary": "Get MFA recovery codes",
|
||||
"operationId": "usersGetMfaRecoveryCodes",
|
||||
"tags": [
|
||||
"users"
|
||||
@@ -21090,7 +21091,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Regenerate MFA Recovery Codes",
|
||||
"summary": "Regenerate MFA recovery codes",
|
||||
"operationId": "usersUpdateMfaRecoveryCodes",
|
||||
"tags": [
|
||||
"users"
|
||||
@@ -21152,7 +21153,7 @@
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "Create MFA Recovery Codes",
|
||||
"summary": "Create MFA recovery codes",
|
||||
"operationId": "usersCreateMfaRecoveryCodes",
|
||||
"tags": [
|
||||
"users"
|
||||
@@ -21677,7 +21678,7 @@
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
|
||||
"description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Session",
|
||||
@@ -21941,7 +21942,7 @@
|
||||
},
|
||||
"\/users\/{userId}\/targets": {
|
||||
"get": {
|
||||
"summary": "List User Targets",
|
||||
"summary": "List user targets",
|
||||
"operationId": "usersListTargets",
|
||||
"tags": [
|
||||
"users"
|
||||
@@ -22017,7 +22018,7 @@
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create User Target",
|
||||
"summary": "Create user target",
|
||||
"operationId": "usersCreateTarget",
|
||||
"tags": [
|
||||
"users"
|
||||
@@ -22130,7 +22131,7 @@
|
||||
},
|
||||
"\/users\/{userId}\/targets\/{targetId}": {
|
||||
"get": {
|
||||
"summary": "Get User Target",
|
||||
"summary": "Get user target",
|
||||
"operationId": "usersGetTarget",
|
||||
"tags": [
|
||||
"users"
|
||||
@@ -22203,7 +22204,7 @@
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "Update User target",
|
||||
"summary": "Update user target",
|
||||
"operationId": "usersUpdateTarget",
|
||||
"tags": [
|
||||
"users"
|
||||
@@ -22375,7 +22376,7 @@
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n",
|
||||
"description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -23677,6 +23678,16 @@
|
||||
"x-example": false,
|
||||
"nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "Attribute size.",
|
||||
@@ -23696,6 +23707,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"size"
|
||||
]
|
||||
},
|
||||
@@ -23734,6 +23747,16 @@
|
||||
"x-example": false,
|
||||
"nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"min": {
|
||||
"type": "integer",
|
||||
"description": "Minimum value to enforce for new documents.",
|
||||
@@ -23761,7 +23784,9 @@
|
||||
"type",
|
||||
"status",
|
||||
"error",
|
||||
"required"
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt"
|
||||
]
|
||||
},
|
||||
"attributeFloat": {
|
||||
@@ -23799,6 +23824,16 @@
|
||||
"x-example": false,
|
||||
"nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"min": {
|
||||
"type": "number",
|
||||
"description": "Minimum value to enforce for new documents.",
|
||||
@@ -23826,7 +23861,9 @@
|
||||
"type",
|
||||
"status",
|
||||
"error",
|
||||
"required"
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt"
|
||||
]
|
||||
},
|
||||
"attributeBoolean": {
|
||||
@@ -23864,6 +23901,16 @@
|
||||
"x-example": false,
|
||||
"nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"default": {
|
||||
"type": "boolean",
|
||||
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
|
||||
@@ -23876,7 +23923,9 @@
|
||||
"type",
|
||||
"status",
|
||||
"error",
|
||||
"required"
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt"
|
||||
]
|
||||
},
|
||||
"attributeEmail": {
|
||||
@@ -23914,6 +23963,16 @@
|
||||
"x-example": false,
|
||||
"nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"description": "String format.",
|
||||
@@ -23932,6 +23991,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"format"
|
||||
]
|
||||
},
|
||||
@@ -23970,6 +24031,16 @@
|
||||
"x-example": false,
|
||||
"nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"elements": {
|
||||
"type": "array",
|
||||
"description": "Array of elements in enumerated type.",
|
||||
@@ -23996,6 +24067,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"elements",
|
||||
"format"
|
||||
]
|
||||
@@ -24035,6 +24108,16 @@
|
||||
"x-example": false,
|
||||
"nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"description": "String format.",
|
||||
@@ -24053,6 +24136,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"format"
|
||||
]
|
||||
},
|
||||
@@ -24091,6 +24176,16 @@
|
||||
"x-example": false,
|
||||
"nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"description": "String format.",
|
||||
@@ -24109,6 +24204,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"format"
|
||||
]
|
||||
},
|
||||
@@ -24147,6 +24244,16 @@
|
||||
"x-example": false,
|
||||
"nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"description": "ISO 8601 format.",
|
||||
@@ -24165,6 +24272,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"format"
|
||||
]
|
||||
},
|
||||
@@ -24203,6 +24312,16 @@
|
||||
"x-example": false,
|
||||
"nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"relatedCollection": {
|
||||
"type": "string",
|
||||
"description": "The ID of the related collection.",
|
||||
@@ -24240,6 +24359,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"relatedCollection",
|
||||
"relationType",
|
||||
"twoWay",
|
||||
@@ -24288,6 +24409,16 @@
|
||||
},
|
||||
"x-example": [],
|
||||
"nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Index creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Index update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -24295,7 +24426,9 @@
|
||||
"type",
|
||||
"status",
|
||||
"error",
|
||||
"attributes"
|
||||
"attributes",
|
||||
"$createdAt",
|
||||
"$updatedAt"
|
||||
]
|
||||
},
|
||||
"document": {
|
||||
@@ -25966,7 +26099,7 @@
|
||||
"responseBody": {
|
||||
"type": "string",
|
||||
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
|
||||
"x-example": "Developers are awesome."
|
||||
"x-example": ""
|
||||
},
|
||||
"responseHeaders": {
|
||||
"type": "array",
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"weight": 8,
|
||||
"weight": 9,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -140,7 +140,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "create",
|
||||
"weight": 7,
|
||||
"weight": 8,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -233,7 +233,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateEmail",
|
||||
"weight": 33,
|
||||
"weight": 34,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -293,7 +293,7 @@
|
||||
},
|
||||
"\/account\/identities": {
|
||||
"get": {
|
||||
"summary": "List Identities",
|
||||
"summary": "List identities",
|
||||
"operationId": "accountListIdentities",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -315,7 +315,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listIdentities",
|
||||
"weight": 56,
|
||||
"weight": 57,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -379,7 +379,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteIdentity",
|
||||
"weight": 57,
|
||||
"weight": 58,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -444,7 +444,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createJWT",
|
||||
"weight": 28,
|
||||
"weight": 29,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -497,7 +497,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listLogs",
|
||||
"weight": 30,
|
||||
"weight": 31,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -566,7 +566,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMFA",
|
||||
"weight": 43,
|
||||
"weight": 44,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -619,7 +619,7 @@
|
||||
},
|
||||
"\/account\/mfa\/authenticators\/{type}": {
|
||||
"post": {
|
||||
"summary": "Create Authenticator",
|
||||
"summary": "Create authenticator",
|
||||
"operationId": "accountCreateMfaAuthenticator",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -641,7 +641,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMfaAuthenticator",
|
||||
"weight": 45,
|
||||
"weight": 46,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -687,7 +687,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Verify Authenticator",
|
||||
"summary": "Verify authenticator",
|
||||
"operationId": "accountUpdateMfaAuthenticator",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -709,7 +709,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaAuthenticator",
|
||||
"weight": 46,
|
||||
"weight": 47,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -773,7 +773,7 @@
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete Authenticator",
|
||||
"summary": "Delete authenticator",
|
||||
"operationId": "accountDeleteMfaAuthenticator",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -790,7 +790,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteMfaAuthenticator",
|
||||
"weight": 50,
|
||||
"weight": 51,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -838,7 +838,7 @@
|
||||
},
|
||||
"\/account\/mfa\/challenge": {
|
||||
"post": {
|
||||
"summary": "Create MFA Challenge",
|
||||
"summary": "Create MFA challenge",
|
||||
"operationId": "accountCreateMfaChallenge",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -860,7 +860,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMfaChallenge",
|
||||
"weight": 51,
|
||||
"weight": 52,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -917,7 +917,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Create MFA Challenge (confirmation)",
|
||||
"summary": "Create MFA challenge (confirmation)",
|
||||
"operationId": "accountUpdateMfaChallenge",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -934,7 +934,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaChallenge",
|
||||
"weight": 52,
|
||||
"weight": 53,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -994,7 +994,7 @@
|
||||
},
|
||||
"\/account\/mfa\/factors": {
|
||||
"get": {
|
||||
"summary": "List Factors",
|
||||
"summary": "List factors",
|
||||
"operationId": "accountListMfaFactors",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1016,7 +1016,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMfaFactors",
|
||||
"weight": 44,
|
||||
"weight": 45,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1049,7 +1049,7 @@
|
||||
},
|
||||
"\/account\/mfa\/recovery-codes": {
|
||||
"get": {
|
||||
"summary": "Get MFA Recovery Codes",
|
||||
"summary": "Get MFA recovery codes",
|
||||
"operationId": "accountGetMfaRecoveryCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1071,7 +1071,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getMfaRecoveryCodes",
|
||||
"weight": 49,
|
||||
"weight": 50,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1102,7 +1102,7 @@
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create MFA Recovery Codes",
|
||||
"summary": "Create MFA recovery codes",
|
||||
"operationId": "accountCreateMfaRecoveryCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1124,7 +1124,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMfaRecoveryCodes",
|
||||
"weight": 47,
|
||||
"weight": 48,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1155,7 +1155,7 @@
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "Regenerate MFA Recovery Codes",
|
||||
"summary": "Regenerate MFA recovery codes",
|
||||
"operationId": "accountUpdateMfaRecoveryCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1177,7 +1177,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaRecoveryCodes",
|
||||
"weight": 48,
|
||||
"weight": 49,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1232,7 +1232,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateName",
|
||||
"weight": 31,
|
||||
"weight": 32,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1307,7 +1307,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePassword",
|
||||
"weight": 32,
|
||||
"weight": 33,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1388,7 +1388,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePhone",
|
||||
"weight": 34,
|
||||
"weight": 35,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1470,7 +1470,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getPrefs",
|
||||
"weight": 29,
|
||||
"weight": 30,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1523,7 +1523,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePrefs",
|
||||
"weight": 35,
|
||||
"weight": 36,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1598,7 +1598,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createRecovery",
|
||||
"weight": 37,
|
||||
"weight": 38,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1681,7 +1681,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateRecovery",
|
||||
"weight": 38,
|
||||
"weight": 39,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1770,7 +1770,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listSessions",
|
||||
"weight": 10,
|
||||
"weight": 11,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1818,7 +1818,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSessions",
|
||||
"weight": 11,
|
||||
"weight": 12,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1873,7 +1873,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createAnonymousSession",
|
||||
"weight": 16,
|
||||
"weight": 17,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -1926,7 +1926,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createEmailPasswordSession",
|
||||
"weight": 15,
|
||||
"weight": 16,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2006,7 +2006,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMagicURLSession",
|
||||
"weight": 25,
|
||||
"weight": 26,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": true,
|
||||
@@ -2083,7 +2083,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createOAuth2Session",
|
||||
"weight": 18,
|
||||
"weight": 19,
|
||||
"cookies": false,
|
||||
"type": "webAuth",
|
||||
"deprecated": false,
|
||||
@@ -2221,7 +2221,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePhoneSession",
|
||||
"weight": 26,
|
||||
"weight": 27,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": true,
|
||||
@@ -2301,7 +2301,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSession",
|
||||
"weight": 17,
|
||||
"weight": 18,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2381,7 +2381,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getSession",
|
||||
"weight": 12,
|
||||
"weight": 13,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2444,7 +2444,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSession",
|
||||
"weight": 14,
|
||||
"weight": 15,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2502,7 +2502,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSession",
|
||||
"weight": 13,
|
||||
"weight": 14,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2567,7 +2567,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateStatus",
|
||||
"weight": 36,
|
||||
"weight": 37,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2622,7 +2622,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createPushTarget",
|
||||
"weight": 53,
|
||||
"weight": 54,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2708,7 +2708,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePushTarget",
|
||||
"weight": 54,
|
||||
"weight": 55,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2784,7 +2784,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deletePushTarget",
|
||||
"weight": 55,
|
||||
"weight": 56,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2847,7 +2847,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createEmailToken",
|
||||
"weight": 24,
|
||||
"weight": 25,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -2933,7 +2933,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMagicURLToken",
|
||||
"weight": 23,
|
||||
"weight": 24,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -3025,7 +3025,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createOAuth2Token",
|
||||
"weight": 22,
|
||||
"weight": 23,
|
||||
"cookies": false,
|
||||
"type": "webAuth",
|
||||
"deprecated": false,
|
||||
@@ -3163,7 +3163,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createPhoneToken",
|
||||
"weight": 27,
|
||||
"weight": 28,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -3246,7 +3246,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createVerification",
|
||||
"weight": 39,
|
||||
"weight": 40,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -3319,7 +3319,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateVerification",
|
||||
"weight": 40,
|
||||
"weight": 41,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -3401,7 +3401,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createPhoneVerification",
|
||||
"weight": 41,
|
||||
"weight": 42,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -3457,7 +3457,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePhoneVerification",
|
||||
"weight": 42,
|
||||
"weight": 43,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -3539,7 +3539,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getBrowser",
|
||||
"weight": 59,
|
||||
"weight": 60,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -3668,7 +3668,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getCreditCard",
|
||||
"weight": 58,
|
||||
"weight": 59,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -3801,7 +3801,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFavicon",
|
||||
"weight": 62,
|
||||
"weight": 63,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -3868,7 +3868,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFlag",
|
||||
"weight": 60,
|
||||
"weight": 61,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -4359,7 +4359,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getImage",
|
||||
"weight": 61,
|
||||
"weight": 62,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -4446,7 +4446,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getInitials",
|
||||
"weight": 64,
|
||||
"weight": 65,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -4541,7 +4541,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getQR",
|
||||
"weight": 63,
|
||||
"weight": 64,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -4636,7 +4636,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"weight": 108,
|
||||
"weight": 109,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -4720,7 +4720,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"weight": 107,
|
||||
"weight": 108,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -4828,7 +4828,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"weight": 109,
|
||||
"weight": 110,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -4920,7 +4920,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"weight": 111,
|
||||
"weight": 112,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5019,7 +5019,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"weight": 112,
|
||||
"weight": 113,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5101,7 +5101,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"weight": 304,
|
||||
"weight": 305,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5167,7 +5167,7 @@
|
||||
"summary": "Create execution",
|
||||
"operationId": "functionsCreateExecution",
|
||||
"consumes": [
|
||||
"multipart\/form-data"
|
||||
"application\/json"
|
||||
],
|
||||
"produces": [
|
||||
"multipart\/form-data"
|
||||
@@ -5186,7 +5186,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"weight": 303,
|
||||
"weight": 304,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5226,65 +5226,59 @@
|
||||
"in": "path"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"description": "HTTP body of execution. Default value is empty string.",
|
||||
"required": false,
|
||||
"type": "payload",
|
||||
"default": "",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"name": "async",
|
||||
"description": "Execute code in the background. Default value is false.",
|
||||
"required": false,
|
||||
"type": "boolean",
|
||||
"x-example": false,
|
||||
"default": false,
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"description": "HTTP path of execution. Path can include query params. Default value is \/",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"x-example": "<PATH>",
|
||||
"default": "\/",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"name": "method",
|
||||
"description": "HTTP method of execution. Default value is GET.",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"x-example": "GET",
|
||||
"enum": [
|
||||
"GET",
|
||||
"POST",
|
||||
"PUT",
|
||||
"PATCH",
|
||||
"DELETE",
|
||||
"OPTIONS"
|
||||
],
|
||||
"x-enum-name": "ExecutionMethod",
|
||||
"x-enum-keys": [],
|
||||
"default": "POST",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"name": "headers",
|
||||
"description": "HTTP headers of execution. Defaults to empty.",
|
||||
"required": false,
|
||||
"type": "object",
|
||||
"default": [],
|
||||
"x-example": "{}",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"name": "scheduledAt",
|
||||
"description": "Scheduled execution time in [ISO 8601](https:\/\/www.iso.org\/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"in": "formData"
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "HTTP body of execution. Default value is empty string.",
|
||||
"default": "",
|
||||
"x-example": "<BODY>"
|
||||
},
|
||||
"async": {
|
||||
"type": "boolean",
|
||||
"description": "Execute code in the background. Default value is false.",
|
||||
"default": false,
|
||||
"x-example": false
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "HTTP path of execution. Path can include query params. Default value is \/",
|
||||
"default": "\/",
|
||||
"x-example": "<PATH>"
|
||||
},
|
||||
"method": {
|
||||
"type": "string",
|
||||
"description": "HTTP method of execution. Default value is GET.",
|
||||
"default": "POST",
|
||||
"x-example": "GET",
|
||||
"enum": [
|
||||
"GET",
|
||||
"POST",
|
||||
"PUT",
|
||||
"PATCH",
|
||||
"DELETE",
|
||||
"OPTIONS"
|
||||
],
|
||||
"x-enum-name": "ExecutionMethod",
|
||||
"x-enum-keys": []
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
"description": "HTTP headers of execution. Defaults to empty.",
|
||||
"default": [],
|
||||
"x-example": "{}"
|
||||
},
|
||||
"scheduledAt": {
|
||||
"type": "string",
|
||||
"description": "Scheduled execution time in [ISO 8601](https:\/\/www.iso.org\/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.",
|
||||
"default": null,
|
||||
"x-example": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5313,7 +5307,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"weight": 305,
|
||||
"weight": 306,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5387,7 +5381,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"weight": 329,
|
||||
"weight": 330,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"deprecated": false,
|
||||
@@ -5463,7 +5457,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"weight": 328,
|
||||
"weight": 329,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"deprecated": false,
|
||||
@@ -5539,7 +5533,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"weight": 116,
|
||||
"weight": 117,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5573,7 +5567,7 @@
|
||||
},
|
||||
"\/locale\/codes": {
|
||||
"get": {
|
||||
"summary": "List Locale Codes",
|
||||
"summary": "List locale codes",
|
||||
"operationId": "localeListCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -5595,7 +5589,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listCodes",
|
||||
"weight": 117,
|
||||
"weight": 118,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5651,7 +5645,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listContinents",
|
||||
"weight": 121,
|
||||
"weight": 122,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5707,7 +5701,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listCountries",
|
||||
"weight": 118,
|
||||
"weight": 119,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5763,7 +5757,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listCountriesEU",
|
||||
"weight": 119,
|
||||
"weight": 120,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5819,7 +5813,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listCountriesPhones",
|
||||
"weight": 120,
|
||||
"weight": 121,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5875,7 +5869,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listCurrencies",
|
||||
"weight": 122,
|
||||
"weight": 123,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5931,7 +5925,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listLanguages",
|
||||
"weight": 123,
|
||||
"weight": 124,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5987,7 +5981,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"weight": 380,
|
||||
"weight": 381,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6076,7 +6070,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"weight": 384,
|
||||
"weight": 385,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6151,7 +6145,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listFiles",
|
||||
"weight": 206,
|
||||
"weight": 207,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6236,7 +6230,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFile",
|
||||
"weight": 205,
|
||||
"weight": 206,
|
||||
"cookies": false,
|
||||
"type": "upload",
|
||||
"deprecated": false,
|
||||
@@ -6330,7 +6324,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFile",
|
||||
"weight": 207,
|
||||
"weight": 208,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6402,7 +6396,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateFile",
|
||||
"weight": 212,
|
||||
"weight": 213,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6476,7 +6470,7 @@
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete File",
|
||||
"summary": "Delete file",
|
||||
"operationId": "storageDeleteFile",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -6493,7 +6487,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteFile",
|
||||
"weight": 213,
|
||||
"weight": 214,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6567,7 +6561,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFileDownload",
|
||||
"weight": 209,
|
||||
"weight": 210,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -6641,7 +6635,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFilePreview",
|
||||
"weight": 208,
|
||||
"weight": 209,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -6807,7 +6801,8 @@
|
||||
"jpeg",
|
||||
"gif",
|
||||
"png",
|
||||
"webp"
|
||||
"webp",
|
||||
"avif"
|
||||
],
|
||||
"x-enum-name": "ImageFormat",
|
||||
"x-enum-keys": [],
|
||||
@@ -6841,7 +6836,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFileView",
|
||||
"weight": 210,
|
||||
"weight": 211,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -6915,7 +6910,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "list",
|
||||
"weight": 217,
|
||||
"weight": 218,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6992,7 +6987,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "create",
|
||||
"weight": 216,
|
||||
"weight": 217,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7086,7 +7081,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"weight": 218,
|
||||
"weight": 219,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7150,7 +7145,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateName",
|
||||
"weight": 220,
|
||||
"weight": 221,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7227,7 +7222,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"weight": 222,
|
||||
"weight": 223,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7293,7 +7288,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMemberships",
|
||||
"weight": 224,
|
||||
"weight": 225,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7378,7 +7373,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMembership",
|
||||
"weight": 223,
|
||||
"weight": 224,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7495,7 +7490,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getMembership",
|
||||
"weight": 225,
|
||||
"weight": 226,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7567,7 +7562,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMembership",
|
||||
"weight": 226,
|
||||
"weight": 227,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7655,7 +7650,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteMembership",
|
||||
"weight": 228,
|
||||
"weight": 229,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7729,7 +7724,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMembershipStatus",
|
||||
"weight": 227,
|
||||
"weight": 228,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7827,7 +7822,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getPrefs",
|
||||
"weight": 219,
|
||||
"weight": 220,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7890,7 +7885,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePrefs",
|
||||
"weight": 221,
|
||||
"weight": 222,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -9591,7 +9586,7 @@
|
||||
"responseBody": {
|
||||
"type": "string",
|
||||
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
|
||||
"x-example": "Developers are awesome."
|
||||
"x-example": ""
|
||||
},
|
||||
"responseHeaders": {
|
||||
"type": "array",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -222,7 +222,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
|
||||
"description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User",
|
||||
@@ -293,7 +293,7 @@
|
||||
},
|
||||
"\/account\/identities": {
|
||||
"get": {
|
||||
"summary": "List Identities",
|
||||
"summary": "List identities",
|
||||
"operationId": "accountListIdentities",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -619,7 +619,7 @@
|
||||
},
|
||||
"\/account\/mfa\/authenticators\/{type}": {
|
||||
"post": {
|
||||
"summary": "Create Authenticator",
|
||||
"summary": "Create authenticator",
|
||||
"operationId": "accountCreateMfaAuthenticator",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -687,7 +687,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Verify Authenticator",
|
||||
"summary": "Verify authenticator",
|
||||
"operationId": "accountUpdateMfaAuthenticator",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -773,7 +773,7 @@
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete Authenticator",
|
||||
"summary": "Delete authenticator",
|
||||
"operationId": "accountDeleteMfaAuthenticator",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -838,7 +838,7 @@
|
||||
},
|
||||
"\/account\/mfa\/challenge": {
|
||||
"post": {
|
||||
"summary": "Create MFA Challenge",
|
||||
"summary": "Create MFA challenge",
|
||||
"operationId": "accountCreateMfaChallenge",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -917,7 +917,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Create MFA Challenge (confirmation)",
|
||||
"summary": "Create MFA challenge (confirmation)",
|
||||
"operationId": "accountUpdateMfaChallenge",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -994,7 +994,7 @@
|
||||
},
|
||||
"\/account\/mfa\/factors": {
|
||||
"get": {
|
||||
"summary": "List Factors",
|
||||
"summary": "List factors",
|
||||
"operationId": "accountListMfaFactors",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1049,7 +1049,7 @@
|
||||
},
|
||||
"\/account\/mfa\/recovery-codes": {
|
||||
"get": {
|
||||
"summary": "Get MFA Recovery Codes",
|
||||
"summary": "Get MFA recovery codes",
|
||||
"operationId": "accountGetMfaRecoveryCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1102,7 +1102,7 @@
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create MFA Recovery Codes",
|
||||
"summary": "Create MFA recovery codes",
|
||||
"operationId": "accountCreateMfaRecoveryCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1155,7 +1155,7 @@
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "Regenerate MFA Recovery Codes",
|
||||
"summary": "Regenerate MFA recovery codes",
|
||||
"operationId": "accountUpdateMfaRecoveryCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1670,7 +1670,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
|
||||
"description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Token",
|
||||
@@ -1915,7 +1915,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Session",
|
||||
@@ -2075,7 +2075,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
|
||||
"responses": {
|
||||
"301": {
|
||||
"description": "No content"
|
||||
@@ -2836,7 +2836,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -2922,7 +2922,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -3017,7 +3017,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"301": {
|
||||
"description": "No content"
|
||||
@@ -3152,7 +3152,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -3235,7 +3235,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
|
||||
"description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -3528,7 +3528,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
|
||||
"description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -3657,7 +3657,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
|
||||
"description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -3790,7 +3790,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
|
||||
"description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -3857,7 +3857,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
|
||||
"description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -4348,7 +4348,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
|
||||
"description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -4435,7 +4435,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
|
||||
"description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -4530,7 +4530,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
|
||||
"description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -5528,7 +5528,7 @@
|
||||
"tags": [
|
||||
"locale"
|
||||
],
|
||||
"description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
|
||||
"description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Locale",
|
||||
@@ -5573,7 +5573,7 @@
|
||||
},
|
||||
"\/locale\/codes": {
|
||||
"get": {
|
||||
"summary": "List Locale Codes",
|
||||
"summary": "List locale codes",
|
||||
"operationId": "localeListCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -6225,7 +6225,7 @@
|
||||
"tags": [
|
||||
"storage"
|
||||
],
|
||||
"description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
|
||||
"description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "File",
|
||||
@@ -6476,7 +6476,7 @@
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete File",
|
||||
"summary": "Delete file",
|
||||
"operationId": "storageDeleteFile",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -6807,7 +6807,8 @@
|
||||
"jpeg",
|
||||
"gif",
|
||||
"png",
|
||||
"webp"
|
||||
"webp",
|
||||
"avif"
|
||||
],
|
||||
"x-enum-name": "ImageFormat",
|
||||
"x-enum-keys": [],
|
||||
@@ -7367,7 +7368,7 @@
|
||||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
|
||||
"description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Membership",
|
||||
@@ -7556,7 +7557,7 @@
|
||||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
|
||||
"description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Membership",
|
||||
@@ -7718,7 +7719,7 @@
|
||||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
|
||||
"description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Membership",
|
||||
@@ -9589,9 +9590,9 @@
|
||||
"format": "int32"
|
||||
},
|
||||
"responseBody": {
|
||||
"type": "string",
|
||||
"type": "payload",
|
||||
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
|
||||
"x-example": "Developers are awesome."
|
||||
"x-example": ""
|
||||
},
|
||||
"responseHeaders": {
|
||||
"type": "array",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -238,7 +238,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
|
||||
"description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User",
|
||||
@@ -310,7 +310,7 @@
|
||||
},
|
||||
"\/account\/identities": {
|
||||
"get": {
|
||||
"summary": "List Identities",
|
||||
"summary": "List identities",
|
||||
"operationId": "accountListIdentities",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -640,7 +640,7 @@
|
||||
},
|
||||
"\/account\/mfa\/authenticators\/{type}": {
|
||||
"post": {
|
||||
"summary": "Create Authenticator",
|
||||
"summary": "Create authenticator",
|
||||
"operationId": "accountCreateMfaAuthenticator",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -709,7 +709,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Verify Authenticator",
|
||||
"summary": "Verify authenticator",
|
||||
"operationId": "accountUpdateMfaAuthenticator",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -796,7 +796,7 @@
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete Authenticator",
|
||||
"summary": "Delete authenticator",
|
||||
"operationId": "accountDeleteMfaAuthenticator",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -862,7 +862,7 @@
|
||||
},
|
||||
"\/account\/mfa\/challenge": {
|
||||
"post": {
|
||||
"summary": "Create MFA Challenge",
|
||||
"summary": "Create MFA challenge",
|
||||
"operationId": "accountCreateMfaChallenge",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -941,7 +941,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Create MFA Challenge (confirmation)",
|
||||
"summary": "Create MFA challenge (confirmation)",
|
||||
"operationId": "accountUpdateMfaChallenge",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1019,7 +1019,7 @@
|
||||
},
|
||||
"\/account\/mfa\/factors": {
|
||||
"get": {
|
||||
"summary": "List Factors",
|
||||
"summary": "List factors",
|
||||
"operationId": "accountListMfaFactors",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1075,7 +1075,7 @@
|
||||
},
|
||||
"\/account\/mfa\/recovery-codes": {
|
||||
"get": {
|
||||
"summary": "Get MFA Recovery Codes",
|
||||
"summary": "Get MFA recovery codes",
|
||||
"operationId": "accountGetMfaRecoveryCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1129,7 +1129,7 @@
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create MFA Recovery Codes",
|
||||
"summary": "Create MFA recovery codes",
|
||||
"operationId": "accountCreateMfaRecoveryCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1183,7 +1183,7 @@
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "Regenerate MFA Recovery Codes",
|
||||
"summary": "Regenerate MFA recovery codes",
|
||||
"operationId": "accountUpdateMfaRecoveryCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -1705,7 +1705,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
|
||||
"description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Token",
|
||||
@@ -1953,7 +1953,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Session",
|
||||
@@ -2518,7 +2518,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -2604,7 +2604,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -2699,7 +2699,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"301": {
|
||||
"description": "No content"
|
||||
@@ -2834,7 +2834,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -2917,7 +2917,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
|
||||
"description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -3214,7 +3214,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
|
||||
"description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -3345,7 +3345,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
|
||||
"description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -3480,7 +3480,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
|
||||
"description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -3549,7 +3549,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
|
||||
"description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -4042,7 +4042,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
|
||||
"description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -4131,7 +4131,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
|
||||
"description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -4228,7 +4228,7 @@
|
||||
"tags": [
|
||||
"avatars"
|
||||
],
|
||||
"description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
|
||||
"description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Image",
|
||||
@@ -4400,7 +4400,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create a new Database.\n",
|
||||
"description": "Create a new Database.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Database",
|
||||
@@ -5217,7 +5217,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create a boolean attribute.\n",
|
||||
"description": "Create a boolean attribute.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeBoolean",
|
||||
@@ -5653,7 +5653,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create an email attribute.\n",
|
||||
"description": "Create an email attribute.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeEmail",
|
||||
@@ -5760,7 +5760,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeEmail",
|
||||
@@ -5871,7 +5871,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n",
|
||||
"description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeEnum",
|
||||
@@ -5988,7 +5988,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeEnum",
|
||||
@@ -6109,7 +6109,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n",
|
||||
"description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeFloat",
|
||||
@@ -6228,7 +6228,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeFloat",
|
||||
@@ -6353,7 +6353,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n",
|
||||
"description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeInteger",
|
||||
@@ -6472,7 +6472,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeInteger",
|
||||
@@ -6597,7 +6597,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create IP address attribute.\n",
|
||||
"description": "Create IP address attribute.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeIP",
|
||||
@@ -6704,7 +6704,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeIP",
|
||||
@@ -6815,7 +6815,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
|
||||
"description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeRelationship",
|
||||
@@ -6951,7 +6951,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create a string attribute.\n",
|
||||
"description": "Create a string attribute.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeString",
|
||||
@@ -7071,7 +7071,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeString",
|
||||
@@ -7188,7 +7188,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Create a URL attribute.\n",
|
||||
"description": "Create a URL attribute.\r\n",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "AttributeURL",
|
||||
@@ -7295,7 +7295,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n",
|
||||
"description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeURL",
|
||||
@@ -7586,7 +7586,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
|
||||
"description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AttributeRelationship",
|
||||
@@ -8250,7 +8250,7 @@
|
||||
"tags": [
|
||||
"databases"
|
||||
],
|
||||
"description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.",
|
||||
"description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "Index",
|
||||
@@ -8917,7 +8917,7 @@
|
||||
"tags": [
|
||||
"functions"
|
||||
],
|
||||
"description": "List allowed function specifications for this instance.\n",
|
||||
"description": "List allowed function specifications for this instance.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Specifications List",
|
||||
@@ -9415,7 +9415,7 @@
|
||||
"tags": [
|
||||
"functions"
|
||||
],
|
||||
"description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.",
|
||||
"description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "Deployment",
|
||||
@@ -10231,7 +10231,7 @@
|
||||
"tags": [
|
||||
"functions"
|
||||
],
|
||||
"description": "Delete a function execution by its unique ID.\n",
|
||||
"description": "Delete a function execution by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No content"
|
||||
@@ -11494,7 +11494,7 @@
|
||||
"tags": [
|
||||
"health"
|
||||
],
|
||||
"description": "Returns the amount of failed jobs in a given queue.\n",
|
||||
"description": "Returns the amount of failed jobs in a given queue.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Health Queue",
|
||||
@@ -12265,7 +12265,7 @@
|
||||
"tags": [
|
||||
"locale"
|
||||
],
|
||||
"description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
|
||||
"description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Locale",
|
||||
@@ -12312,7 +12312,7 @@
|
||||
},
|
||||
"\/locale\/codes": {
|
||||
"get": {
|
||||
"summary": "List Locale Codes",
|
||||
"summary": "List locale codes",
|
||||
"operationId": "localeListCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -12968,7 +12968,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Update an email message by its unique ID.\n",
|
||||
"description": "Update an email message by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Message",
|
||||
@@ -13302,7 +13302,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Update a push notification by its unique ID.\n",
|
||||
"description": "Update a push notification by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Message",
|
||||
@@ -13596,7 +13596,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Update an email message by its unique ID.\n",
|
||||
"description": "Update an email message by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Message",
|
||||
@@ -13715,7 +13715,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Get a message by its unique ID.\n",
|
||||
"description": "Get a message by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Message",
|
||||
@@ -16355,7 +16355,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Get a provider by its unique ID.\n",
|
||||
"description": "Get a provider by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Provider",
|
||||
@@ -16803,7 +16803,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Get a topic by its unique ID.\n",
|
||||
"description": "Get a topic by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Topic",
|
||||
@@ -16866,7 +16866,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Update a topic by its unique ID.\n",
|
||||
"description": "Update a topic by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Topic",
|
||||
@@ -17270,7 +17270,7 @@
|
||||
"tags": [
|
||||
"messaging"
|
||||
],
|
||||
"description": "Get a subscriber by its unique ID.\n",
|
||||
"description": "Get a subscriber by its unique ID.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Subscriber",
|
||||
@@ -17981,7 +17981,7 @@
|
||||
"tags": [
|
||||
"storage"
|
||||
],
|
||||
"description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
|
||||
"description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "File",
|
||||
@@ -18238,7 +18238,7 @@
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete File",
|
||||
"summary": "Delete file",
|
||||
"operationId": "storageDeleteFile",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -18575,7 +18575,8 @@
|
||||
"jpeg",
|
||||
"gif",
|
||||
"png",
|
||||
"webp"
|
||||
"webp",
|
||||
"avif"
|
||||
],
|
||||
"x-enum-name": "ImageFormat",
|
||||
"x-enum-keys": [],
|
||||
@@ -19149,7 +19150,7 @@
|
||||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
|
||||
"description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Membership",
|
||||
@@ -19342,7 +19343,7 @@
|
||||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
|
||||
"description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Membership",
|
||||
@@ -19508,7 +19509,7 @@
|
||||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
|
||||
"description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Membership",
|
||||
@@ -20105,7 +20106,7 @@
|
||||
},
|
||||
"\/users\/identities": {
|
||||
"get": {
|
||||
"summary": "List Identities",
|
||||
"summary": "List identities",
|
||||
"operationId": "usersListIdentities",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -21087,7 +21088,7 @@
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
|
||||
"description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User",
|
||||
@@ -21383,7 +21384,7 @@
|
||||
},
|
||||
"\/users\/{userId}\/mfa\/authenticators\/{type}": {
|
||||
"delete": {
|
||||
"summary": "Delete Authenticator",
|
||||
"summary": "Delete authenticator",
|
||||
"operationId": "usersDeleteMfaAuthenticator",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -21460,7 +21461,7 @@
|
||||
},
|
||||
"\/users\/{userId}\/mfa\/factors": {
|
||||
"get": {
|
||||
"summary": "List Factors",
|
||||
"summary": "List factors",
|
||||
"operationId": "usersListMfaFactors",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -21524,7 +21525,7 @@
|
||||
},
|
||||
"\/users\/{userId}\/mfa\/recovery-codes": {
|
||||
"get": {
|
||||
"summary": "Get MFA Recovery Codes",
|
||||
"summary": "Get MFA recovery codes",
|
||||
"operationId": "usersGetMfaRecoveryCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -21586,7 +21587,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Regenerate MFA Recovery Codes",
|
||||
"summary": "Regenerate MFA recovery codes",
|
||||
"operationId": "usersUpdateMfaRecoveryCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -21648,7 +21649,7 @@
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "Create MFA Recovery Codes",
|
||||
"summary": "Create MFA recovery codes",
|
||||
"operationId": "usersCreateMfaRecoveryCodes",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -22175,7 +22176,7 @@
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
|
||||
"description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Session",
|
||||
@@ -22434,7 +22435,7 @@
|
||||
},
|
||||
"\/users\/{userId}\/targets": {
|
||||
"get": {
|
||||
"summary": "List User Targets",
|
||||
"summary": "List user targets",
|
||||
"operationId": "usersListTargets",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -22509,7 +22510,7 @@
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create User Target",
|
||||
"summary": "Create user target",
|
||||
"operationId": "usersCreateTarget",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -22625,7 +22626,7 @@
|
||||
},
|
||||
"\/users\/{userId}\/targets\/{targetId}": {
|
||||
"get": {
|
||||
"summary": "Get User Target",
|
||||
"summary": "Get user target",
|
||||
"operationId": "usersGetTarget",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -22696,7 +22697,7 @@
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "Update User target",
|
||||
"summary": "Update user target",
|
||||
"operationId": "usersUpdateTarget",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -22875,7 +22876,7 @@
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n",
|
||||
"description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -24166,6 +24167,16 @@
|
||||
"x-example": false,
|
||||
"x-nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "Attribute size.",
|
||||
@@ -24185,6 +24196,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"size"
|
||||
]
|
||||
},
|
||||
@@ -24223,6 +24236,16 @@
|
||||
"x-example": false,
|
||||
"x-nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"min": {
|
||||
"type": "integer",
|
||||
"description": "Minimum value to enforce for new documents.",
|
||||
@@ -24250,7 +24273,9 @@
|
||||
"type",
|
||||
"status",
|
||||
"error",
|
||||
"required"
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt"
|
||||
]
|
||||
},
|
||||
"attributeFloat": {
|
||||
@@ -24288,6 +24313,16 @@
|
||||
"x-example": false,
|
||||
"x-nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"min": {
|
||||
"type": "number",
|
||||
"description": "Minimum value to enforce for new documents.",
|
||||
@@ -24315,7 +24350,9 @@
|
||||
"type",
|
||||
"status",
|
||||
"error",
|
||||
"required"
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt"
|
||||
]
|
||||
},
|
||||
"attributeBoolean": {
|
||||
@@ -24353,6 +24390,16 @@
|
||||
"x-example": false,
|
||||
"x-nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"default": {
|
||||
"type": "boolean",
|
||||
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
|
||||
@@ -24365,7 +24412,9 @@
|
||||
"type",
|
||||
"status",
|
||||
"error",
|
||||
"required"
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt"
|
||||
]
|
||||
},
|
||||
"attributeEmail": {
|
||||
@@ -24403,6 +24452,16 @@
|
||||
"x-example": false,
|
||||
"x-nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"description": "String format.",
|
||||
@@ -24421,6 +24480,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"format"
|
||||
]
|
||||
},
|
||||
@@ -24459,6 +24520,16 @@
|
||||
"x-example": false,
|
||||
"x-nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"elements": {
|
||||
"type": "array",
|
||||
"description": "Array of elements in enumerated type.",
|
||||
@@ -24485,6 +24556,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"elements",
|
||||
"format"
|
||||
]
|
||||
@@ -24524,6 +24597,16 @@
|
||||
"x-example": false,
|
||||
"x-nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"description": "String format.",
|
||||
@@ -24542,6 +24625,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"format"
|
||||
]
|
||||
},
|
||||
@@ -24580,6 +24665,16 @@
|
||||
"x-example": false,
|
||||
"x-nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"description": "String format.",
|
||||
@@ -24598,6 +24693,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"format"
|
||||
]
|
||||
},
|
||||
@@ -24636,6 +24733,16 @@
|
||||
"x-example": false,
|
||||
"x-nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"description": "ISO 8601 format.",
|
||||
@@ -24654,6 +24761,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"format"
|
||||
]
|
||||
},
|
||||
@@ -24692,6 +24801,16 @@
|
||||
"x-example": false,
|
||||
"x-nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Attribute update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"relatedCollection": {
|
||||
"type": "string",
|
||||
"description": "The ID of the related collection.",
|
||||
@@ -24729,6 +24848,8 @@
|
||||
"status",
|
||||
"error",
|
||||
"required",
|
||||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"relatedCollection",
|
||||
"relationType",
|
||||
"twoWay",
|
||||
@@ -24777,6 +24898,16 @@
|
||||
},
|
||||
"x-example": [],
|
||||
"x-nullable": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
"description": "Index creation date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
},
|
||||
"$updatedAt": {
|
||||
"type": "string",
|
||||
"description": "Index update date in ISO 8601 format.",
|
||||
"x-example": "2020-10-15T06:38:00.000+00:00"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -24784,7 +24915,9 @@
|
||||
"type",
|
||||
"status",
|
||||
"error",
|
||||
"attributes"
|
||||
"attributes",
|
||||
"$createdAt",
|
||||
"$updatedAt"
|
||||
]
|
||||
},
|
||||
"document": {
|
||||
@@ -26458,9 +26591,9 @@
|
||||
"format": "int32"
|
||||
},
|
||||
"responseBody": {
|
||||
"type": "string",
|
||||
"type": "payload",
|
||||
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
|
||||
"x-example": "Developers are awesome."
|
||||
"x-example": ""
|
||||
},
|
||||
"responseHeaders": {
|
||||
"type": "array",
|
||||
|
||||
@@ -6,6 +6,8 @@ return [
|
||||
'image/gif',
|
||||
'image/png',
|
||||
'image/webp',
|
||||
// 'image/heic',
|
||||
'image/avif',
|
||||
|
||||
// Video Files
|
||||
'video/mp4',
|
||||
@@ -31,6 +33,7 @@ return [
|
||||
'audio/ogg', // Ogg Vorbis RFC 5334
|
||||
'audio/vorbis', // Vorbis RFC 5215
|
||||
'audio/vnd.wav', // wav RFC 2361
|
||||
'audio/x-wav', // php reads .wav as this - https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
|
||||
'audio/aac', //AAC audio
|
||||
'audio/x-hx-aac-adts', // AAC audio
|
||||
|
||||
|
||||
@@ -6,4 +6,7 @@ return [ // Accepted outputs files
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
'webp' => 'image/webp',
|
||||
// 'heic' => 'image/heic',
|
||||
// 'heics' => 'image/heic',
|
||||
'avif' => 'image/avif'
|
||||
];
|
||||
|
||||
@@ -254,7 +254,7 @@ return [
|
||||
'name' => '_APP_WORKER_PER_CORE',
|
||||
'description' => 'Internal Worker per core for the API, Realtime and Executor containers. Can be configured to optimize performance.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => 2,
|
||||
'default' => 6,
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
|
||||
+282
-310
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ use Appwrite\URL\URL as URLParse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -14,17 +14,15 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Fetch\Client;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\Boolean;
|
||||
use Utopia\Http\Validator\HexColor;
|
||||
use Utopia\Http\Validator\Range;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\URL;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\HexColor;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
$avatarCallback = function (string $type, string $code, int $width, int $height, int $quality, Response $response) {
|
||||
|
||||
@@ -57,15 +55,15 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
$data = $image->output($output, $quality);
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/png')
|
||||
->file($data);
|
||||
unset($image);
|
||||
};
|
||||
|
||||
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger, Authorization $auth) {
|
||||
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger) {
|
||||
try {
|
||||
$user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
@@ -116,7 +114,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
|
||||
->setAttribute('providerRefreshToken', $refreshToken)
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
|
||||
|
||||
$auth->skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
} catch (Throwable $err) {
|
||||
@@ -124,7 +122,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
|
||||
do {
|
||||
$previousAccessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
|
||||
$user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
$gitHubSession = new Document();
|
||||
@@ -156,42 +154,11 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
|
||||
'id' => $githubId
|
||||
];
|
||||
} catch (Exception $error) {
|
||||
if ($logger) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace('console');
|
||||
$log->setServer(\gethostname());
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $error->getTrace());
|
||||
|
||||
$log->setAction('avatarsGetGitHub');
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('GitHub error log pushed with status code: ' . $responseCode);
|
||||
}
|
||||
|
||||
Console::warning("Failed: {$error->getMessage()}");
|
||||
Console::warning($error->getTraceAsString());
|
||||
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
Http::get('/v1/avatars/credit-cards/:code')
|
||||
App::get('/v1/avatars/credit-cards/:code')
|
||||
->desc('Get credit card icon')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
@@ -211,7 +178,7 @@ Http::get('/v1/avatars/credit-cards/:code')
|
||||
->inject('response')
|
||||
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response));
|
||||
|
||||
Http::get('/v1/avatars/browsers/:code')
|
||||
App::get('/v1/avatars/browsers/:code')
|
||||
->desc('Get browser icon')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
@@ -231,7 +198,7 @@ Http::get('/v1/avatars/browsers/:code')
|
||||
->inject('response')
|
||||
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response));
|
||||
|
||||
Http::get('/v1/avatars/flags/:code')
|
||||
App::get('/v1/avatars/flags/:code')
|
||||
->desc('Get country flag')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
@@ -251,7 +218,7 @@ Http::get('/v1/avatars/flags/:code')
|
||||
->inject('response')
|
||||
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response));
|
||||
|
||||
Http::get('/v1/avatars/image')
|
||||
App::get('/v1/avatars/image')
|
||||
->desc('Get image from URL')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
@@ -308,13 +275,13 @@ Http::get('/v1/avatars/image')
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/png')
|
||||
->file($data);
|
||||
unset($image);
|
||||
});
|
||||
|
||||
Http::get('/v1/avatars/favicon')
|
||||
App::get('/v1/avatars/favicon')
|
||||
->desc('Get favicon')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
@@ -442,7 +409,7 @@ Http::get('/v1/avatars/favicon')
|
||||
throw new Exception(Exception::AVATAR_ICON_NOT_FOUND, 'Favicon not found');
|
||||
}
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/x-icon')
|
||||
->file($data);
|
||||
}
|
||||
@@ -453,13 +420,13 @@ Http::get('/v1/avatars/favicon')
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/png')
|
||||
->file($data);
|
||||
unset($image);
|
||||
});
|
||||
|
||||
Http::get('/v1/avatars/qr')
|
||||
App::get('/v1/avatars/qr')
|
||||
->desc('Get QR code')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
@@ -494,12 +461,12 @@ Http::get('/v1/avatars/qr')
|
||||
$image->crop((int) $size, (int) $size);
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->send($image->output('png', 9));
|
||||
});
|
||||
|
||||
Http::get('/v1/avatars/initials')
|
||||
App::get('/v1/avatars/initials')
|
||||
->desc('Get user initials')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
@@ -577,13 +544,13 @@ Http::get('/v1/avatars/initials')
|
||||
$image->compositeImage($punch, Imagick::COMPOSITE_COPYOPACITY, 0, 0);
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->file($image->getImageBlob());
|
||||
});
|
||||
|
||||
Http::get('/v1/cards/cloud')
|
||||
->desc('Get Front Of Cloud Card')
|
||||
App::get('/v1/cards/cloud')
|
||||
->desc('Get front Of Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
@@ -604,9 +571,8 @@ Http::get('/v1/cards/cloud')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) {
|
||||
$user = $authorization->skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -617,7 +583,7 @@ Http::get('/v1/cards/cloud')
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $authorization);
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
@@ -785,13 +751,13 @@ Http::get('/v1/cards/cloud')
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->file($baseImage->getImageBlob());
|
||||
});
|
||||
|
||||
Http::get('/v1/cards/cloud-back')
|
||||
->desc('Get Back Of Cloud Card')
|
||||
App::get('/v1/cards/cloud-back')
|
||||
->desc('Get back Of Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
@@ -812,9 +778,8 @@ Http::get('/v1/cards/cloud-back')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) {
|
||||
$user = $authorization->skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -824,7 +789,7 @@ Http::get('/v1/cards/cloud-back')
|
||||
$userId = $user->getId();
|
||||
$email = $user->getAttribute('email', '');
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $authorization);
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
@@ -864,13 +829,13 @@ Http::get('/v1/cards/cloud-back')
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->file($baseImage->getImageBlob());
|
||||
});
|
||||
|
||||
Http::get('/v1/cards/cloud-og')
|
||||
->desc('Get OG Image From Cloud Card')
|
||||
App::get('/v1/cards/cloud-og')
|
||||
->desc('Get OG image From Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
@@ -891,9 +856,8 @@ Http::get('/v1/cards/cloud-og')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) {
|
||||
$user = $authorization->skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -908,7 +872,7 @@ Http::get('/v1/cards/cloud-og')
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $authorization);
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
@@ -1255,7 +1219,7 @@ Http::get('/v1/cards/cloud-og')
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->file($baseImage->getImageBlob());
|
||||
});
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['console'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
@@ -17,7 +17,7 @@ Http::init()
|
||||
});
|
||||
|
||||
|
||||
Http::get('/v1/console/variables')
|
||||
App::get('/v1/console/variables')
|
||||
->desc('Get variables')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
@@ -56,8 +56,8 @@ Http::get('/v1/console/variables')
|
||||
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
|
||||
});
|
||||
|
||||
Http::post('/v1/console/assistant')
|
||||
->desc('Ask Query')
|
||||
App::post('/v1/console/assistant')
|
||||
->desc('Ask query')
|
||||
->groups(['api', 'assistant'])
|
||||
->label('scope', 'assistant.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
|
||||
+291
-250
File diff suppressed because it is too large
Load Diff
+149
-118
@@ -2,7 +2,6 @@
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Authentication;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
@@ -12,7 +11,6 @@ use Appwrite\Event\Validator\FunctionEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Functions\Validator\Headers;
|
||||
use Appwrite\Functions\Validator\Payload;
|
||||
use Appwrite\Functions\Validator\RuntimeSpecification;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Platform\Tasks\ScheduleExecutions;
|
||||
@@ -21,11 +19,11 @@ use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Deployments;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Executions;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Functions;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model\Rule;
|
||||
use Executor\Executor;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
@@ -38,25 +36,25 @@ use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\Roles;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\AnyOf;
|
||||
use Utopia\Http\Validator\ArrayList;
|
||||
use Utopia\Http\Validator\Assoc;
|
||||
use Utopia\Http\Validator\Boolean;
|
||||
use Utopia\Http\Validator\Nullable;
|
||||
use Utopia\Http\Validator\Range;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Validator\File;
|
||||
use Utopia\Storage\Validator\FileExt;
|
||||
use Utopia\Storage\Validator\FileSize;
|
||||
use Utopia\Storage\Validator\Upload;
|
||||
use Utopia\Swoole\Request;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
@@ -135,11 +133,12 @@ $redeployVcs = function (Request $request, Document $function, Document $project
|
||||
->setTemplate($template);
|
||||
};
|
||||
|
||||
Http::post('/v1/functions')
|
||||
App::post('/v1/functions')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Create function')
|
||||
->label('scope', 'functions.write')
|
||||
->label('event', 'functions.[functionId].create')
|
||||
->label('resourceType', 'functions')
|
||||
->label('audits.event', 'function.create')
|
||||
->label('audits.resource', 'function/{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
@@ -173,8 +172,8 @@ Http::post('/v1/functions')
|
||||
->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
|
||||
$plan,
|
||||
Config::getParam('runtime-specifications', []),
|
||||
System::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT),
|
||||
System::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT)
|
||||
App::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT),
|
||||
App::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT)
|
||||
), 'Runtime specification for the function and builds.', true, ['plan'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -185,8 +184,7 @@ Http::post('/v1/functions')
|
||||
->inject('queueForBuilds')
|
||||
->inject('dbForConsole')
|
||||
->inject('gitHub')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github, Authorization $authorization) use ($redeployVcs) {
|
||||
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
|
||||
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
|
||||
|
||||
$allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
|
||||
@@ -250,7 +248,7 @@ Http::post('/v1/functions')
|
||||
'specification' => $specification
|
||||
]));
|
||||
|
||||
$schedule = $authorization->skip(
|
||||
$schedule = Authorization::skip(
|
||||
fn () => $dbForConsole->createDocument('schedules', new Document([
|
||||
'region' => System::getEnv('_APP_REGION', 'default'), // Todo replace with projects region
|
||||
'resourceType' => 'function',
|
||||
@@ -332,7 +330,7 @@ Http::post('/v1/functions')
|
||||
$routeSubdomain = ID::unique();
|
||||
$domain = "{$routeSubdomain}.{$functionsDomain}";
|
||||
|
||||
$rule = $authorization->skip(
|
||||
$rule = Authorization::skip(
|
||||
fn () => $dbForConsole->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
@@ -399,10 +397,11 @@ Http::post('/v1/functions')
|
||||
->dynamic($function, Response::MODEL_FUNCTION);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions')
|
||||
App::get('/v1/functions')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('List functions')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'list')
|
||||
@@ -435,6 +434,12 @@ Http::get('/v1/functions')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$functionId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -453,10 +458,11 @@ Http::get('/v1/functions')
|
||||
]), Response::MODEL_FUNCTION_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/runtimes')
|
||||
App::get('/v1/functions/runtimes')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('List runtimes')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'listRuntimes')
|
||||
@@ -486,10 +492,11 @@ Http::get('/v1/functions/runtimes')
|
||||
]), Response::MODEL_RUNTIME_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/specifications')
|
||||
App::get('/v1/functions/specifications')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('List available function runtime specifications')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'listSpecifications')
|
||||
@@ -522,10 +529,11 @@ Http::get('/v1/functions/specifications')
|
||||
]), Response::MODEL_SPECIFICATION_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/:functionId')
|
||||
App::get('/v1/functions/:functionId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Get function')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'get')
|
||||
@@ -546,10 +554,11 @@ Http::get('/v1/functions/:functionId')
|
||||
$response->dynamic($function, Response::MODEL_FUNCTION);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/:functionId/usage')
|
||||
App::get('/v1/functions/:functionId/usage')
|
||||
->desc('Get function usage')
|
||||
->groups(['api', 'functions', 'usage'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'getFunctionUsage')
|
||||
@@ -560,8 +569,7 @@ Http::get('/v1/functions/:functionId/usage')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, string $range, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
->action(function (string $functionId, string $range, Response $response, Database $dbForProject) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -584,7 +592,7 @@ Http::get('/v1/functions/:functionId/usage')
|
||||
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS)
|
||||
];
|
||||
|
||||
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
@@ -651,10 +659,11 @@ Http::get('/v1/functions/:functionId/usage')
|
||||
]), Response::MODEL_USAGE_FUNCTION);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/usage')
|
||||
App::get('/v1/functions/usage')
|
||||
->desc('Get functions usage')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'getUsage')
|
||||
@@ -664,8 +673,7 @@ Http::get('/v1/functions/usage')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
->action(function (string $range, Response $response, Database $dbForProject) {
|
||||
|
||||
$periods = Config::getParam('usage', []);
|
||||
$stats = $usage = [];
|
||||
@@ -683,7 +691,7 @@ Http::get('/v1/functions/usage')
|
||||
METRIC_EXECUTIONS_MB_SECONDS,
|
||||
];
|
||||
|
||||
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
@@ -751,10 +759,11 @@ Http::get('/v1/functions/usage')
|
||||
]), Response::MODEL_USAGE_FUNCTIONS);
|
||||
});
|
||||
|
||||
Http::put('/v1/functions/:functionId')
|
||||
App::put('/v1/functions/:functionId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Update function')
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', 'functions')
|
||||
->label('event', 'functions.[functionId].update')
|
||||
->label('audits.event', 'function.update')
|
||||
->label('audits.resource', 'function/{response.$id}')
|
||||
@@ -785,8 +794,8 @@ Http::put('/v1/functions/:functionId')
|
||||
->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
|
||||
$plan,
|
||||
Config::getParam('runtime-specifications', []),
|
||||
System::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT),
|
||||
System::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT)
|
||||
App::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT),
|
||||
App::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT)
|
||||
), 'Runtime specification for the function and builds.', true, ['plan'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -796,8 +805,7 @@ Http::put('/v1/functions/:functionId')
|
||||
->inject('queueForBuilds')
|
||||
->inject('dbForConsole')
|
||||
->inject('gitHub')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github, Authorization $authorization) use ($redeployVcs) {
|
||||
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
|
||||
// TODO: If only branch changes, re-deploy
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -900,7 +908,7 @@ Http::put('/v1/functions/:functionId')
|
||||
|
||||
// Enforce Cold Start if spec limits change.
|
||||
if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deployment'))) {
|
||||
$executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
|
||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||
try {
|
||||
$executor->deleteRuntime($project->getId(), $function->getAttribute('deployment'));
|
||||
} catch (\Throwable $th) {
|
||||
@@ -947,17 +955,18 @@ Http::put('/v1/functions/:functionId')
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
$authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$queueForEvents->setParam('functionId', $function->getId());
|
||||
|
||||
$response->dynamic($function, Response::MODEL_FUNCTION);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||
App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Download deployment')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'getDeploymentDownload')
|
||||
@@ -994,7 +1003,7 @@ Http::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||
|
||||
$response
|
||||
->setContentType('application/gzip')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
|
||||
|
||||
@@ -1039,10 +1048,11 @@ Http::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||
}
|
||||
});
|
||||
|
||||
Http::patch('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
App::patch('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Update deployment')
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', 'functions')
|
||||
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
|
||||
->label('audits.event', 'deployment.update')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
@@ -1059,8 +1069,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForConsole')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForConsole, Authorization $authorization) {
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForConsole) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
@@ -1093,7 +1102,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
$authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('functionId', $function->getId())
|
||||
@@ -1102,10 +1111,11 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
$response->dynamic($function, Response::MODEL_FUNCTION);
|
||||
});
|
||||
|
||||
Http::delete('/v1/functions/:functionId')
|
||||
App::delete('/v1/functions/:functionId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Delete function')
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', 'functions')
|
||||
->label('event', 'functions.[functionId].delete')
|
||||
->label('audits.event', 'function.delete')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
@@ -1121,8 +1131,7 @@ Http::delete('/v1/functions/:functionId')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForConsole')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForConsole, Authorization $authorization) {
|
||||
->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForConsole) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -1139,7 +1148,7 @@ Http::delete('/v1/functions/:functionId')
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('active', false);
|
||||
$authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
@@ -1150,10 +1159,11 @@ Http::delete('/v1/functions/:functionId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::post('/v1/functions/:functionId/deployments')
|
||||
App::post('/v1/functions/:functionId/deployments')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Create deployment')
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', 'functions')
|
||||
->label('event', 'functions.[functionId].deployments.[deploymentId].create')
|
||||
->label('audits.event', 'deployment.create')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
@@ -1369,10 +1379,11 @@ Http::post('/v1/functions/:functionId/deployments')
|
||||
->dynamic($deployment, Response::MODEL_DEPLOYMENT);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/:functionId/deployments')
|
||||
App::get('/v1/functions/:functionId/deployments')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('List deployments')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'listDeployments')
|
||||
@@ -1416,6 +1427,12 @@ Http::get('/v1/functions/:functionId/deployments')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$deploymentId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
@@ -1446,10 +1463,11 @@ Http::get('/v1/functions/:functionId/deployments')
|
||||
]), Response::MODEL_DEPLOYMENT_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
App::get('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Get deployment')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'getDeployment')
|
||||
@@ -1489,10 +1507,11 @@ Http::get('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
|
||||
});
|
||||
|
||||
Http::delete('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
App::delete('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Delete deployment')
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', 'functions')
|
||||
->label('event', 'functions.[functionId].deployments.[deploymentId].delete')
|
||||
->label('audits.event', 'deployment.delete')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
@@ -1553,11 +1572,12 @@ Http::delete('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::post('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
App::post('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
->alias('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Rebuild deployment')
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', 'functions')
|
||||
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
|
||||
->label('audits.event', 'deployment.update')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
@@ -1576,8 +1596,7 @@ Http::post('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->inject('deviceForFunctions')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions, Authorization $authorization) {
|
||||
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
@@ -1623,10 +1642,11 @@ Http::post('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
App::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Cancel deployment')
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', 'functions')
|
||||
->label('audits.event', 'deployment.update')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
@@ -1641,8 +1661,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Authorization $authorization) {
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
@@ -1655,7 +1674,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = $authorization->skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
$buildId = ID::unique();
|
||||
@@ -1697,7 +1716,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
$dbForProject->purgeCachedDocument('deployments', $deployment->getId());
|
||||
|
||||
try {
|
||||
$executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
|
||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||
$executor->deleteRuntime($project->getId(), $deploymentId . "-build");
|
||||
} catch (\Throwable $th) {
|
||||
// Don't throw if the deployment doesn't exist
|
||||
@@ -1713,11 +1732,13 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
$response->dynamic($build, Response::MODEL_BUILD);
|
||||
});
|
||||
|
||||
Http::post('/v1/functions/:functionId/executions')
|
||||
App::post('/v1/functions/:functionId/executions')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Create execution')
|
||||
->label('scope', 'execution.write')
|
||||
->label('resourceType', 'functions')
|
||||
->label('event', 'functions.[functionId].executions.[executionId].create')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'createExecution')
|
||||
@@ -1727,8 +1748,8 @@ Http::post('/v1/functions/:functionId/executions')
|
||||
->label('sdk.response.model', Response::MODEL_EXECUTION)
|
||||
->label('sdk.request.type', Response::CONTENT_TYPE_JSON)
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('body', '', new Payload(10485760, 0), 'HTTP body of execution. Default value is empty string.', true)
|
||||
->param('async', false, new Boolean(), 'Execute code in the background. Default value is false.', true)
|
||||
->param('body', '', new Text(10485760, 0), 'HTTP body of execution. Default value is empty string.', true)
|
||||
->param('async', false, new Boolean(true), 'Execute code in the background. Default value is false.', true)
|
||||
->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true)
|
||||
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true)
|
||||
->param('headers', [], new AnyOf([new Assoc(), new Text(65535)], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true)
|
||||
@@ -1743,9 +1764,8 @@ Http::post('/v1/functions/:functionId/executions')
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->inject('authentication')
|
||||
->action(function (string $functionId, string $body, bool $async, string $path, string $method, array $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForConsole, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, Authorization $authorization, Authentication $authentication) {
|
||||
->action(function (string $functionId, string $body, mixed $async, string $path, string $method, mixed $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForConsole, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) {
|
||||
$async = \strval($async) === 'true' || \strval($async) === '1';
|
||||
|
||||
if (!$async && !is_null($scheduledAt)) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Scheduled executions must run asynchronously. Set scheduledAt to a future date, or set async to true.');
|
||||
@@ -1774,10 +1794,10 @@ Http::post('/v1/functions/:functionId/executions')
|
||||
throw new Exception($validator->getDescription(), 400);
|
||||
}
|
||||
|
||||
$function = $authorization->skip(fn () => $dbForProject->getDocument('functions', $functionId));
|
||||
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
@@ -1793,7 +1813,7 @@ Http::post('/v1/functions/:functionId/executions')
|
||||
throw new Exception(Exception::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
|
||||
}
|
||||
|
||||
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
|
||||
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
|
||||
@@ -1804,7 +1824,7 @@ Http::post('/v1/functions/:functionId/executions')
|
||||
}
|
||||
|
||||
/** Check if build has completed */
|
||||
$build = $authorization->skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
@@ -1813,8 +1833,10 @@ Http::post('/v1/functions/:functionId/executions')
|
||||
throw new Exception(Exception::BUILD_NOT_READY);
|
||||
}
|
||||
|
||||
if (!$authorization->isValid(new Input('execute', $function->getAttribute('execute')))) { // Check if user has write access to execute function
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
$validator = new Authorization('execute');
|
||||
|
||||
if (!$validator->isValid($function->getAttribute('execute'))) { // Check if user has write access to execute function
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
|
||||
}
|
||||
|
||||
$jwt = ''; // initialize
|
||||
@@ -1824,7 +1846,7 @@ Http::post('/v1/functions/:functionId/executions')
|
||||
|
||||
foreach ($sessions as $session) {
|
||||
/** @var Utopia\Database\Document $session */
|
||||
if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) { // If current session delete the cookies too
|
||||
if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
|
||||
$current = $session;
|
||||
}
|
||||
}
|
||||
@@ -1908,9 +1930,8 @@ Http::post('/v1/functions/:functionId/executions')
|
||||
->setContext('function', $function);
|
||||
|
||||
if ($async) {
|
||||
|
||||
if (is_null($scheduledAt)) {
|
||||
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
$queueForFunctions
|
||||
->setType('http')
|
||||
->setExecution($execution)
|
||||
@@ -1951,7 +1972,7 @@ Http::post('/v1/functions/:functionId/executions')
|
||||
->setAttribute('scheduleInternalId', $schedule->getInternalId())
|
||||
->setAttribute('scheduledAt', $scheduledAt);
|
||||
|
||||
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
|
||||
return $response
|
||||
@@ -2037,7 +2058,8 @@ Http::post('/v1/functions/:functionId/executions')
|
||||
runtimeEntrypoint: $command,
|
||||
cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
|
||||
memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
|
||||
logging: $function->getAttribute('logging', true)
|
||||
logging: $function->getAttribute('logging', true),
|
||||
requestTimeout: 30
|
||||
);
|
||||
|
||||
$headersFiltered = [];
|
||||
@@ -2078,10 +2100,10 @@ Http::post('/v1/functions/:functionId/executions')
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
|
||||
;
|
||||
|
||||
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
|
||||
$roles = $authorization->getRoles();
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
@@ -2114,10 +2136,11 @@ Http::post('/v1/functions/:functionId/executions')
|
||||
->dynamic($execution, Response::MODEL_EXECUTION);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/:functionId/executions')
|
||||
App::get('/v1/functions/:functionId/executions')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('List executions')
|
||||
->label('scope', 'execution.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'listExecutions')
|
||||
@@ -2131,12 +2154,11 @@ Http::get('/v1/functions/:functionId/executions')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject, string $mode, Authorization $authorization) {
|
||||
$function = $authorization->skip(fn () => $dbForProject->getDocument('functions', $functionId));
|
||||
->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) {
|
||||
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
@@ -2164,6 +2186,12 @@ Http::get('/v1/functions/:functionId/executions')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$executionId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('executions', $executionId);
|
||||
|
||||
@@ -2179,7 +2207,7 @@ Http::get('/v1/functions/:functionId/executions')
|
||||
$results = $dbForProject->find('executions', $queries);
|
||||
$total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT);
|
||||
|
||||
$roles = $authorization->getRoles();
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
if (!$isPrivilegedUser && !$isAppUser) {
|
||||
@@ -2196,10 +2224,11 @@ Http::get('/v1/functions/:functionId/executions')
|
||||
]), Response::MODEL_EXECUTION_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/:functionId/executions/:executionId')
|
||||
App::get('/v1/functions/:functionId/executions/:executionId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Get execution')
|
||||
->label('scope', 'execution.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'getExecution')
|
||||
@@ -2212,12 +2241,11 @@ Http::get('/v1/functions/:functionId/executions/:executionId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, string $mode, Authorization $authorization) {
|
||||
$function = $authorization->skip(fn () => $dbForProject->getDocument('functions', $functionId));
|
||||
->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, string $mode) {
|
||||
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
@@ -2233,7 +2261,7 @@ Http::get('/v1/functions/:functionId/executions/:executionId')
|
||||
throw new Exception(Exception::EXECUTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$roles = $authorization->getRoles();
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
if (!$isPrivilegedUser && !$isAppUser) {
|
||||
@@ -2244,10 +2272,11 @@ Http::get('/v1/functions/:functionId/executions/:executionId')
|
||||
$response->dynamic($execution, Response::MODEL_EXECUTION);
|
||||
});
|
||||
|
||||
Http::delete('/v1/functions/:functionId/executions/:executionId')
|
||||
App::delete('/v1/functions/:functionId/executions/:executionId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Delete execution')
|
||||
->label('scope', 'execution.write')
|
||||
->label('resourceType', 'functions')
|
||||
->label('event', 'functions.[functionId].executions.[executionId].delete')
|
||||
->label('audits.event', 'executions.delete')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
@@ -2263,8 +2292,7 @@ Http::delete('/v1/functions/:functionId/executions/:executionId')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForConsole, Event $queueForEvents, Authorization $authorization) {
|
||||
->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForConsole, Event $queueForEvents) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
@@ -2301,7 +2329,7 @@ Http::delete('/v1/functions/:functionId/executions/:executionId')
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('active', false);
|
||||
|
||||
$authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2315,10 +2343,11 @@ Http::delete('/v1/functions/:functionId/executions/:executionId')
|
||||
|
||||
// Variables
|
||||
|
||||
Http::post('/v1/functions/:functionId/variables')
|
||||
App::post('/v1/functions/:functionId/variables')
|
||||
->desc('Create variable')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', 'functions')
|
||||
->label('audits.event', 'variable.create')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
@@ -2334,8 +2363,7 @@ Http::post('/v1/functions/:functionId/variables')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization) {
|
||||
->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
@@ -2373,17 +2401,18 @@ Http::post('/v1/functions/:functionId/variables')
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
$authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/:functionId/variables')
|
||||
App::get('/v1/functions/:functionId/variables')
|
||||
->desc('List variables')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'listVariables')
|
||||
@@ -2407,10 +2436,11 @@ Http::get('/v1/functions/:functionId/variables')
|
||||
]), Response::MODEL_VARIABLE_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/:functionId/variables/:variableId')
|
||||
App::get('/v1/functions/:functionId/variables/:variableId')
|
||||
->desc('Get variable')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'getVariable')
|
||||
@@ -2446,10 +2476,11 @@ Http::get('/v1/functions/:functionId/variables/:variableId')
|
||||
$response->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
});
|
||||
|
||||
Http::put('/v1/functions/:functionId/variables/:variableId')
|
||||
App::put('/v1/functions/:functionId/variables/:variableId')
|
||||
->desc('Update variable')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', 'functions')
|
||||
->label('audits.event', 'variable.update')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
@@ -2466,8 +2497,7 @@ Http::put('/v1/functions/:functionId/variables/:variableId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization) {
|
||||
->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -2503,15 +2533,16 @@ Http::put('/v1/functions/:functionId/variables/:variableId')
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
$authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$response->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
});
|
||||
|
||||
Http::delete('/v1/functions/:functionId/variables/:variableId')
|
||||
App::delete('/v1/functions/:functionId/variables/:variableId')
|
||||
->desc('Delete variable')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', 'functions')
|
||||
->label('audits.event', 'variable.delete')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
@@ -2525,8 +2556,7 @@ Http::delete('/v1/functions/:functionId/variables/:variableId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('authorization')
|
||||
->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization) {
|
||||
->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
@@ -2552,15 +2582,16 @@ Http::delete('/v1/functions/:functionId/variables/:variableId')
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
$authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/templates')
|
||||
App::get('/v1/functions/templates')
|
||||
->groups(['api'])
|
||||
->desc('List function templates')
|
||||
->label('scope', 'public')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'listTemplates')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
@@ -2595,9 +2626,10 @@ Http::get('/v1/functions/templates')
|
||||
]), Response::MODEL_TEMPLATE_FUNCTION_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/functions/templates/:templateId')
|
||||
App::get('/v1/functions/templates/:templateId')
|
||||
->desc('Get function template')
|
||||
->label('scope', 'public')
|
||||
->label('resourceType', 'functions')
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'getTemplate')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
@@ -2610,10 +2642,9 @@ Http::get('/v1/functions/templates/:templateId')
|
||||
->action(function (string $templateId, Response $response) {
|
||||
$templates = Config::getParam('function-templates', []);
|
||||
|
||||
$array = \array_filter($templates, function ($template) use ($templateId) {
|
||||
$template = array_shift(\array_filter($templates, function ($template) use ($templateId) {
|
||||
return $template['id'] === $templateId;
|
||||
});
|
||||
$template = array_shift($array);
|
||||
}));
|
||||
|
||||
if (empty($template)) {
|
||||
throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND);
|
||||
|
||||
@@ -14,28 +14,27 @@ use GraphQL\Validator\Rules\DisableIntrospection;
|
||||
use GraphQL\Validator\Rules\QueryComplexity;
|
||||
use GraphQL\Validator\Rules\QueryDepth;
|
||||
use Swoole\Coroutine\WaitGroup;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\JSON;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\JSON;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['graphql'])
|
||||
->inject('project')
|
||||
->inject('authorization')
|
||||
->action(function (Document $project, Authorization $authorization) {
|
||||
->action(function (Document $project) {
|
||||
if (
|
||||
array_key_exists('graphql', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['graphql']
|
||||
&& !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
});
|
||||
|
||||
Http::get('/v1/graphql')
|
||||
App::get('/v1/graphql')
|
||||
->desc('GraphQL endpoint')
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
@@ -75,7 +74,7 @@ Http::get('/v1/graphql')
|
||||
->json($output);
|
||||
});
|
||||
|
||||
Http::post('/v1/graphql/mutation')
|
||||
App::post('/v1/graphql/mutation')
|
||||
->desc('GraphQL endpoint')
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
@@ -120,7 +119,7 @@ Http::post('/v1/graphql/mutation')
|
||||
->json($output);
|
||||
});
|
||||
|
||||
Http::post('/v1/graphql')
|
||||
App::post('/v1/graphql')
|
||||
->desc('GraphQL endpoint')
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
@@ -157,6 +156,7 @@ Http::post('/v1/graphql')
|
||||
if (\str_starts_with($type, 'multipart/form-data')) {
|
||||
$query = parseMultipart($query, $request);
|
||||
}
|
||||
|
||||
$output = execute($schema, $promiseAdapter, $query);
|
||||
|
||||
$response
|
||||
@@ -205,7 +205,7 @@ function execute(
|
||||
$validations[] = new QueryComplexity($maxComplexity);
|
||||
$validations[] = new QueryDepth($maxDepth);
|
||||
}
|
||||
if (Http::getMode() === Http::MODE_TYPE_PRODUCTION) {
|
||||
if (App::getMode() === App::MODE_TYPE_PRODUCTION) {
|
||||
$flags = DebugFlag::NONE;
|
||||
}
|
||||
|
||||
@@ -306,10 +306,9 @@ function processResult($result, $debugFlags): array
|
||||
);
|
||||
}
|
||||
|
||||
Http::shutdown()
|
||||
App::shutdown()
|
||||
->groups(['schema'])
|
||||
->inject('project')
|
||||
->inject('schemaVariable')
|
||||
->action(function (Document $project, Schema $schemaVariable) {
|
||||
$schemaVariable->setDirty($project->getId());
|
||||
->action(function (Document $project) {
|
||||
Schema::setDirty($project->getId());
|
||||
});
|
||||
|
||||
+151
-189
@@ -3,19 +3,12 @@
|
||||
use Appwrite\ClamAV\Network;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Queue\Connections;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Adapter\MySQL;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Domains\Validator\PublicDomain;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\Domain;
|
||||
use Utopia\Http\Validator\Integer;
|
||||
use Utopia\Http\Validator\Multiple;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Registry\Registry;
|
||||
@@ -23,8 +16,13 @@ use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Domain;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Multiple;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
Http::get('/v1/health')
|
||||
App::get('/v1/health')
|
||||
->desc('Get HTTP')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -47,7 +45,7 @@ Http::get('/v1/health')
|
||||
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/version')
|
||||
App::get('/v1/health/version')
|
||||
->desc('Get version')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'public')
|
||||
@@ -59,7 +57,7 @@ Http::get('/v1/health/version')
|
||||
$response->dynamic(new Document([ 'version' => APP_VERSION_STABLE ]), Response::MODEL_HEALTH_VERSION);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/db')
|
||||
App::get('/v1/health/db')
|
||||
->desc('Get DB')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -72,34 +70,21 @@ Http::get('/v1/health/db')
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->inject('response')
|
||||
->inject('pools')
|
||||
->inject('connections')
|
||||
->action(function (Response $response, array $pools, Connections $connections) {
|
||||
->action(function (Response $response, Group $pools) {
|
||||
|
||||
$output = [];
|
||||
|
||||
$configs = [
|
||||
'console' => Config::getParam('pools-console'),
|
||||
'database' => Config::getParam('pools-database'),
|
||||
'Console.DB' => Config::getParam('pools-console'),
|
||||
'Projects.DB' => Config::getParam('pools-database'),
|
||||
];
|
||||
|
||||
foreach ($configs as $key => $config) {
|
||||
foreach ($config as $database) {
|
||||
$checkStart = \microtime(true);
|
||||
|
||||
try {
|
||||
$adapter = $pools->get($database)->pop()->getResource();
|
||||
|
||||
$pool = $pools['pools-'.$key.'-'.$database]['pool'];
|
||||
$dsn = $pools['pools-'.$key.'-'.$database]['dsn'];
|
||||
|
||||
$connection = $pool->get();
|
||||
$connections->add($connection, $pool);
|
||||
$adapter = match ($dsn->getScheme()) {
|
||||
'mariadb' => new MariaDB($connection),
|
||||
'mysql' => new MySQL($connection),
|
||||
default => null
|
||||
};
|
||||
$adapter->setDatabase($dsn->getPath());
|
||||
|
||||
$checkStart = \microtime(true);
|
||||
|
||||
if ($adapter->ping()) {
|
||||
$output[] = new Document([
|
||||
@@ -126,7 +111,7 @@ Http::get('/v1/health/db')
|
||||
]), Response::MODEL_HEALTH_STATUS_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/cache')
|
||||
App::get('/v1/health/cache')
|
||||
->desc('Get cache')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -139,8 +124,7 @@ Http::get('/v1/health/cache')
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->inject('response')
|
||||
->inject('pools')
|
||||
->inject('connections')
|
||||
->action(function (Response $response, array $pools, Connections $connections) {
|
||||
->action(function (Response $response, Group $pools) {
|
||||
|
||||
$output = [];
|
||||
|
||||
@@ -150,142 +134,8 @@ Http::get('/v1/health/cache')
|
||||
|
||||
foreach ($configs as $key => $config) {
|
||||
foreach ($config as $database) {
|
||||
$checkStart = \microtime(true);
|
||||
try {
|
||||
$pool = $pools['pools-cache-' . $database]['pool'];
|
||||
$dsn = $pools['pools-cache-' . $database]['dsn'];
|
||||
$connection = $pool->get();
|
||||
$connections->add($connection, $pool);
|
||||
|
||||
$adapter = new Connection\Redis($dsn->getHost(), $dsn->getPort());
|
||||
|
||||
|
||||
if ($adapter->ping()) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'pass',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
} else {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
} finally {
|
||||
$connections->reclaim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'statuses' => $output,
|
||||
'total' => count($output),
|
||||
]), Response::MODEL_HEALTH_STATUS_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/queue')
|
||||
->desc('Get queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueue')
|
||||
->label('sdk.description', '/docs/references/health/get-queue.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->inject('response')
|
||||
->inject('pools')
|
||||
->inject('connections')
|
||||
->action(function (Response $response, array $pools, Connections $connections) {
|
||||
|
||||
$output = [];
|
||||
|
||||
$configs = [
|
||||
'Queue' => Config::getParam('pools-queue'),
|
||||
];
|
||||
|
||||
foreach ($configs as $key => $config) {
|
||||
$checkStart = \microtime(true);
|
||||
|
||||
foreach ($config as $database) {
|
||||
try {
|
||||
$pool = $pools['pools-queue-' . $database]['pool'];
|
||||
$dsn = $pools['pools-queue-' . $database]['dsn'];
|
||||
$connection = $pool->get();
|
||||
$connections->add($connection, $pool);
|
||||
|
||||
$adapter = new Connection\Redis($dsn->getHost(), $dsn->getPort());
|
||||
if ($adapter->ping()) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'pass',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
} else {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
} finally {
|
||||
$connections->reclaim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'statuses' => $output,
|
||||
'total' => count($output),
|
||||
]), Response::MODEL_HEALTH_STATUS_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/pubsub')
|
||||
->desc('Get pubsub')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getPubSub')
|
||||
->label('sdk.description', '/docs/references/health/get-pubsub.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->inject('response')
|
||||
->inject('pools')
|
||||
->inject('connections')
|
||||
->action(function (Response $response, array $pools, Connections $connections) {
|
||||
|
||||
$output = [];
|
||||
|
||||
$configs = [
|
||||
'PubSub' => Config::getParam('pools-pubsub'),
|
||||
];
|
||||
|
||||
foreach ($configs as $key => $config) {
|
||||
foreach ($config as $database) {
|
||||
try {
|
||||
$pool = $pools['pools-pubsub-' . $database]['pool'];
|
||||
|
||||
$connection = $pool->get();
|
||||
$connections->add($connection, $pool);
|
||||
|
||||
$adapter = new Connection\Redis($connection);
|
||||
$adapter = $pools->get($database)->pop()->getResource();
|
||||
|
||||
$checkStart = \microtime(true);
|
||||
|
||||
@@ -308,8 +158,6 @@ Http::get('/v1/health/pubsub')
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
} finally {
|
||||
$connections->reclaim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,7 +168,121 @@ Http::get('/v1/health/pubsub')
|
||||
]), Response::MODEL_HEALTH_STATUS_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/time')
|
||||
App::get('/v1/health/queue')
|
||||
->desc('Get queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueue')
|
||||
->label('sdk.description', '/docs/references/health/get-queue.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->inject('response')
|
||||
->inject('pools')
|
||||
->action(function (Response $response, Group $pools) {
|
||||
|
||||
$output = [];
|
||||
|
||||
$configs = [
|
||||
'Queue' => Config::getParam('pools-queue'),
|
||||
];
|
||||
|
||||
foreach ($configs as $key => $config) {
|
||||
foreach ($config as $database) {
|
||||
try {
|
||||
$adapter = $pools->get($database)->pop()->getResource();
|
||||
|
||||
$checkStart = \microtime(true);
|
||||
|
||||
if ($adapter->ping()) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'pass',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
} else {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'statuses' => $output,
|
||||
'total' => count($output),
|
||||
]), Response::MODEL_HEALTH_STATUS_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/health/pubsub')
|
||||
->desc('Get pubsub')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getPubSub')
|
||||
->label('sdk.description', '/docs/references/health/get-pubsub.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->inject('response')
|
||||
->inject('pools')
|
||||
->action(function (Response $response, Group $pools) {
|
||||
|
||||
$output = [];
|
||||
|
||||
$configs = [
|
||||
'PubSub' => Config::getParam('pools-pubsub'),
|
||||
];
|
||||
|
||||
foreach ($configs as $key => $config) {
|
||||
foreach ($config as $database) {
|
||||
try {
|
||||
$adapter = $pools->get($database)->pop()->getResource();
|
||||
|
||||
$checkStart = \microtime(true);
|
||||
|
||||
if ($adapter->ping()) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'pass',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
} else {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'statuses' => $output,
|
||||
'total' => count($output),
|
||||
]), Response::MODEL_HEALTH_STATUS_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/health/time')
|
||||
->desc('Get time')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -377,7 +339,7 @@ Http::get('/v1/health/time')
|
||||
$response->dynamic(new Document($output), Response::MODEL_HEALTH_TIME);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/queue/webhooks')
|
||||
App::get('/v1/health/queue/webhooks')
|
||||
->desc('Get webhooks queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -404,7 +366,7 @@ Http::get('/v1/health/queue/webhooks')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
Http::get('/v1/health/queue/logs')
|
||||
App::get('/v1/health/queue/logs')
|
||||
->desc('Get logs queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -431,7 +393,7 @@ Http::get('/v1/health/queue/logs')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
Http::get('/v1/health/certificate')
|
||||
App::get('/v1/health/certificate')
|
||||
->desc('Get the SSL certificate for a domain')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -481,7 +443,7 @@ Http::get('/v1/health/certificate')
|
||||
]), Response::MODEL_HEALTH_CERTIFICATE);
|
||||
}, ['response']);
|
||||
|
||||
Http::get('/v1/health/queue/certificates')
|
||||
App::get('/v1/health/queue/certificates')
|
||||
->desc('Get certificates queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -508,7 +470,7 @@ Http::get('/v1/health/queue/certificates')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
Http::get('/v1/health/queue/builds')
|
||||
App::get('/v1/health/queue/builds')
|
||||
->desc('Get builds queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -535,7 +497,7 @@ Http::get('/v1/health/queue/builds')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
Http::get('/v1/health/queue/databases')
|
||||
App::get('/v1/health/queue/databases')
|
||||
->desc('Get databases queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -563,7 +525,7 @@ Http::get('/v1/health/queue/databases')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
Http::get('/v1/health/queue/deletes')
|
||||
App::get('/v1/health/queue/deletes')
|
||||
->desc('Get deletes queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -590,7 +552,7 @@ Http::get('/v1/health/queue/deletes')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
Http::get('/v1/health/queue/mails')
|
||||
App::get('/v1/health/queue/mails')
|
||||
->desc('Get mails queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -617,7 +579,7 @@ Http::get('/v1/health/queue/mails')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
Http::get('/v1/health/queue/messaging')
|
||||
App::get('/v1/health/queue/messaging')
|
||||
->desc('Get messaging queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -644,7 +606,7 @@ Http::get('/v1/health/queue/messaging')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
Http::get('/v1/health/queue/migrations')
|
||||
App::get('/v1/health/queue/migrations')
|
||||
->desc('Get migrations queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -671,7 +633,7 @@ Http::get('/v1/health/queue/migrations')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
Http::get('/v1/health/queue/functions')
|
||||
App::get('/v1/health/queue/functions')
|
||||
->desc('Get functions queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -698,7 +660,7 @@ Http::get('/v1/health/queue/functions')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
Http::get('/v1/health/queue/usage')
|
||||
App::get('/v1/health/queue/usage')
|
||||
->desc('Get usage queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -725,7 +687,7 @@ Http::get('/v1/health/queue/usage')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/queue/usage-dump')
|
||||
App::get('/v1/health/queue/usage-dump')
|
||||
->desc('Get usage dump queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -752,7 +714,7 @@ Http::get('/v1/health/queue/usage-dump')
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/storage/local')
|
||||
App::get('/v1/health/storage/local')
|
||||
->desc('Get local storage')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -795,7 +757,7 @@ Http::get('/v1/health/storage/local')
|
||||
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/storage')
|
||||
App::get('/v1/health/storage')
|
||||
->desc('Get storage')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -836,7 +798,7 @@ Http::get('/v1/health/storage')
|
||||
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/anti-virus')
|
||||
App::get('/v1/health/anti-virus')
|
||||
->desc('Get antivirus')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -875,7 +837,7 @@ Http::get('/v1/health/anti-virus')
|
||||
$response->dynamic(new Document($output), Response::MODEL_HEALTH_ANTIVIRUS);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/queue/failed/:name')
|
||||
App::get('/v1/health/queue/failed/:name')
|
||||
->desc('Get number of failed queue jobs')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
@@ -916,7 +878,7 @@ Http::get('/v1/health/queue/failed/:name')
|
||||
$response->dynamic(new Document([ 'size' => $failed ]), Response::MODEL_HEALTH_QUEUE);
|
||||
});
|
||||
|
||||
Http::get('/v1/health/stats') // Currently only used internally
|
||||
App::get('/v1/health/stats') // Currently only used internally
|
||||
->desc('Get system stats')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'root')
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Locale\Locale;
|
||||
|
||||
Http::get('/v1/locale')
|
||||
App::get('/v1/locale')
|
||||
->desc('Get user locale')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -63,13 +63,13 @@ Http::get('/v1/locale')
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'public, max-age=' . $time)
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
;
|
||||
$response->dynamic(new Document($output), Response::MODEL_LOCALE);
|
||||
});
|
||||
|
||||
Http::get('/v1/locale/codes')
|
||||
->desc('List Locale Codes')
|
||||
App::get('/v1/locale/codes')
|
||||
->desc('List locale codes')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
@@ -90,7 +90,7 @@ Http::get('/v1/locale/codes')
|
||||
]), Response::MODEL_LOCALE_CODE_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/locale/countries')
|
||||
App::get('/v1/locale/countries')
|
||||
->desc('List countries')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -123,7 +123,7 @@ Http::get('/v1/locale/countries')
|
||||
$response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/locale/countries/eu')
|
||||
App::get('/v1/locale/countries/eu')
|
||||
->desc('List EU countries')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -158,7 +158,7 @@ Http::get('/v1/locale/countries/eu')
|
||||
$response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/locale/countries/phones')
|
||||
App::get('/v1/locale/countries/phones')
|
||||
->desc('List countries phone codes')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -192,7 +192,7 @@ Http::get('/v1/locale/countries/phones')
|
||||
$response->dynamic(new Document(['phones' => $output, 'total' => \count($output)]), Response::MODEL_PHONE_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/locale/continents')
|
||||
App::get('/v1/locale/continents')
|
||||
->desc('List continents')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -224,7 +224,7 @@ Http::get('/v1/locale/continents')
|
||||
$response->dynamic(new Document(['continents' => $output, 'total' => \count($output)]), Response::MODEL_CONTINENT_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/locale/currencies')
|
||||
App::get('/v1/locale/currencies')
|
||||
->desc('List currencies')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
@@ -247,7 +247,7 @@ Http::get('/v1/locale/currencies')
|
||||
});
|
||||
|
||||
|
||||
Http::get('/v1/locale/languages')
|
||||
App::get('/v1/locale/languages')
|
||||
->desc('List languages')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
|
||||
@@ -20,6 +20,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Targets;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Topics;
|
||||
use Appwrite\Utopia\Response;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -29,33 +30,33 @@ use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\Roles;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\ArrayList;
|
||||
use Utopia\Http\Validator\Boolean;
|
||||
use Utopia\Http\Validator\Integer;
|
||||
use Utopia\Http\Validator\JSON;
|
||||
use Utopia\Http\Validator\Range;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\JSON;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
use function Swoole\Coroutine\batch;
|
||||
|
||||
Http::post('/v1/messaging/providers/mailgun')
|
||||
App::post('/v1/messaging/providers/mailgun')
|
||||
->desc('Create Mailgun 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createMailgunProvider')
|
||||
@@ -136,13 +137,14 @@ Http::post('/v1/messaging/providers/mailgun')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/providers/sendgrid')
|
||||
App::post('/v1/messaging/providers/sendgrid')
|
||||
->desc('Create Sendgrid 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createSendgridProvider')
|
||||
@@ -211,13 +213,14 @@ Http::post('/v1/messaging/providers/sendgrid')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/providers/smtp')
|
||||
App::post('/v1/messaging/providers/smtp')
|
||||
->desc('Create SMTP 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createSmtpProvider')
|
||||
@@ -299,12 +302,13 @@ Http::post('/v1/messaging/providers/smtp')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/providers/msg91')
|
||||
App::post('/v1/messaging/providers/msg91')
|
||||
->desc('Create Msg91 provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.create')
|
||||
->label('audits.resource', 'provider/{response.$id}')
|
||||
->label('scope', 'providers.write')
|
||||
->label('resourceType', 'providers')
|
||||
->label('event', 'providers.[providerId].create')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
@@ -375,13 +379,14 @@ Http::post('/v1/messaging/providers/msg91')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/providers/telesign')
|
||||
App::post('/v1/messaging/providers/telesign')
|
||||
->desc('Create Telesign 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createTelesignProvider')
|
||||
@@ -452,13 +457,14 @@ Http::post('/v1/messaging/providers/telesign')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/providers/textmagic')
|
||||
App::post('/v1/messaging/providers/textmagic')
|
||||
->desc('Create Textmagic 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createTextmagicProvider')
|
||||
@@ -529,13 +535,14 @@ Http::post('/v1/messaging/providers/textmagic')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/providers/twilio')
|
||||
App::post('/v1/messaging/providers/twilio')
|
||||
->desc('Create Twilio 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createTwilioProvider')
|
||||
@@ -606,13 +613,14 @@ Http::post('/v1/messaging/providers/twilio')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/providers/vonage')
|
||||
App::post('/v1/messaging/providers/vonage')
|
||||
->desc('Create Vonage 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createVonageProvider')
|
||||
@@ -683,13 +691,14 @@ Http::post('/v1/messaging/providers/vonage')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/providers/fcm')
|
||||
App::post('/v1/messaging/providers/fcm')
|
||||
->desc('Create FCM 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createFcmProvider')
|
||||
@@ -746,13 +755,14 @@ Http::post('/v1/messaging/providers/fcm')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/providers/apns')
|
||||
App::post('/v1/messaging/providers/apns')
|
||||
->desc('Create APNS 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createApnsProvider')
|
||||
@@ -832,10 +842,11 @@ Http::post('/v1/messaging/providers/apns')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/providers')
|
||||
App::get('/v1/messaging/providers')
|
||||
->desc('List providers')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'providers.read')
|
||||
->label('resourceType', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'listProviders')
|
||||
@@ -847,8 +858,7 @@ Http::get('/v1/messaging/providers')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->inject('authorization')
|
||||
->action(function (array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) {
|
||||
->action(function (array $queries, string $search, Database $dbForProject, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -868,8 +878,13 @@ Http::get('/v1/messaging/providers')
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$providerId = $cursor->getValue();
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId));
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Provider '{$providerId}' for the 'cursor' value not found.");
|
||||
@@ -884,10 +899,11 @@ Http::get('/v1/messaging/providers')
|
||||
]), Response::MODEL_PROVIDER_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/providers/:providerId/logs')
|
||||
App::get('/v1/messaging/providers/:providerId/logs')
|
||||
->desc('List provider logs')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'providers.read')
|
||||
->label('resourceType', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'listProviderLogs')
|
||||
@@ -972,10 +988,11 @@ Http::get('/v1/messaging/providers/:providerId/logs')
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/providers/:providerId')
|
||||
App::get('/v1/messaging/providers/:providerId')
|
||||
->desc('Get provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'providers.read')
|
||||
->label('resourceType', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'getProvider')
|
||||
@@ -996,13 +1013,14 @@ Http::get('/v1/messaging/providers/:providerId')
|
||||
$response->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/providers/mailgun/:providerId')
|
||||
App::patch('/v1/messaging/providers/mailgun/:providerId')
|
||||
->desc('Update Mailgun 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateMailgunProvider')
|
||||
@@ -1102,13 +1120,14 @@ Http::patch('/v1/messaging/providers/mailgun/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/providers/sendgrid/:providerId')
|
||||
App::patch('/v1/messaging/providers/sendgrid/:providerId')
|
||||
->desc('Update Sendgrid 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateSendgridProvider')
|
||||
@@ -1193,13 +1212,14 @@ Http::patch('/v1/messaging/providers/sendgrid/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/providers/smtp/:providerId')
|
||||
App::patch('/v1/messaging/providers/smtp/:providerId')
|
||||
->desc('Update SMTP 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateSmtpProvider')
|
||||
@@ -1315,13 +1335,14 @@ Http::patch('/v1/messaging/providers/smtp/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/providers/msg91/:providerId')
|
||||
App::patch('/v1/messaging/providers/msg91/:providerId')
|
||||
->desc('Update Msg91 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateMsg91Provider')
|
||||
@@ -1395,13 +1416,14 @@ Http::patch('/v1/messaging/providers/msg91/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/providers/telesign/:providerId')
|
||||
App::patch('/v1/messaging/providers/telesign/:providerId')
|
||||
->desc('Update Telesign 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateTelesignProvider')
|
||||
@@ -1477,13 +1499,14 @@ Http::patch('/v1/messaging/providers/telesign/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/providers/textmagic/:providerId')
|
||||
App::patch('/v1/messaging/providers/textmagic/:providerId')
|
||||
->desc('Update Textmagic 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateTextmagicProvider')
|
||||
@@ -1559,13 +1582,14 @@ Http::patch('/v1/messaging/providers/textmagic/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/providers/twilio/:providerId')
|
||||
App::patch('/v1/messaging/providers/twilio/:providerId')
|
||||
->desc('Update Twilio 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateTwilioProvider')
|
||||
@@ -1641,13 +1665,14 @@ Http::patch('/v1/messaging/providers/twilio/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/providers/vonage/:providerId')
|
||||
App::patch('/v1/messaging/providers/vonage/:providerId')
|
||||
->desc('Update Vonage 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateVonageProvider')
|
||||
@@ -1723,13 +1748,14 @@ Http::patch('/v1/messaging/providers/vonage/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/providers/fcm/:providerId')
|
||||
App::patch('/v1/messaging/providers/fcm/:providerId')
|
||||
->desc('Update FCM 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateFcmProvider')
|
||||
@@ -1792,13 +1818,14 @@ Http::patch('/v1/messaging/providers/fcm/:providerId')
|
||||
});
|
||||
|
||||
|
||||
Http::patch('/v1/messaging/providers/apns/:providerId')
|
||||
App::patch('/v1/messaging/providers/apns/:providerId')
|
||||
->desc('Update APNS 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', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateApnsProvider')
|
||||
@@ -1887,13 +1914,14 @@ Http::patch('/v1/messaging/providers/apns/:providerId')
|
||||
->dynamic($provider, Response::MODEL_PROVIDER);
|
||||
});
|
||||
|
||||
Http::delete('/v1/messaging/providers/:providerId')
|
||||
App::delete('/v1/messaging/providers/:providerId')
|
||||
->desc('Delete provider')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'provider.delete')
|
||||
->label('audits.resource', 'provider/{request.$providerId}')
|
||||
->label('event', 'providers.[providerId].delete')
|
||||
->label('scope', 'providers.write')
|
||||
->label('resourceType', 'providers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'deleteProvider')
|
||||
@@ -1922,13 +1950,14 @@ Http::delete('/v1/messaging/providers/:providerId')
|
||||
->noContent();
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/topics')
|
||||
App::post('/v1/messaging/topics')
|
||||
->desc('Create topic')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'topic.create')
|
||||
->label('audits.resource', 'topic/{response.$id}')
|
||||
->label('event', 'topics.[topicId].create')
|
||||
->label('scope', 'topics.write')
|
||||
->label('resourceType', 'topics')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createTopic')
|
||||
@@ -1965,10 +1994,11 @@ Http::post('/v1/messaging/topics')
|
||||
->dynamic($topic, Response::MODEL_TOPIC);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/topics')
|
||||
App::get('/v1/messaging/topics')
|
||||
->desc('List topics')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'topics.read')
|
||||
->label('resourceType', 'topics')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'listTopics')
|
||||
@@ -1980,8 +2010,7 @@ Http::get('/v1/messaging/topics')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->inject('authorization')
|
||||
->action(function (array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) {
|
||||
->action(function (array $queries, string $search, Database $dbForProject, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -2001,8 +2030,13 @@ Http::get('/v1/messaging/topics')
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$topicId = $cursor->getValue();
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Topic '{$topicId}' for the 'cursor' value not found.");
|
||||
@@ -2017,10 +2051,11 @@ Http::get('/v1/messaging/topics')
|
||||
]), Response::MODEL_TOPIC_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/topics/:topicId/logs')
|
||||
App::get('/v1/messaging/topics/:topicId/logs')
|
||||
->desc('List topic logs')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'topics.read')
|
||||
->label('resourceType', 'topics')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'listTopicLogs')
|
||||
@@ -2034,8 +2069,7 @@ Http::get('/v1/messaging/topics/:topicId/logs')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->action(function (string $topicId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
|
||||
->action(function (string $topicId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
$topic = $dbForProject->getDocument('topics', $topicId);
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
@@ -2107,10 +2141,11 @@ Http::get('/v1/messaging/topics/:topicId/logs')
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/topics/:topicId')
|
||||
App::get('/v1/messaging/topics/:topicId')
|
||||
->desc('Get topic')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'topics.read')
|
||||
->label('resourceType', 'topics')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'getTopic')
|
||||
@@ -2132,13 +2167,14 @@ Http::get('/v1/messaging/topics/:topicId')
|
||||
->dynamic($topic, Response::MODEL_TOPIC);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/topics/:topicId')
|
||||
App::patch('/v1/messaging/topics/:topicId')
|
||||
->desc('Update topic')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'topic.update')
|
||||
->label('audits.resource', 'topic/{response.$id}')
|
||||
->label('event', 'topics.[topicId].update')
|
||||
->label('scope', 'topics.write')
|
||||
->label('resourceType', 'topics')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateTopic')
|
||||
@@ -2176,13 +2212,14 @@ Http::patch('/v1/messaging/topics/:topicId')
|
||||
->dynamic($topic, Response::MODEL_TOPIC);
|
||||
});
|
||||
|
||||
Http::delete('/v1/messaging/topics/:topicId')
|
||||
App::delete('/v1/messaging/topics/:topicId')
|
||||
->desc('Delete topic')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'topic.delete')
|
||||
->label('audits.resource', 'topic/{request.$topicId}')
|
||||
->label('event', 'topics.[topicId].delete')
|
||||
->label('scope', 'topics.write')
|
||||
->label('resourceType', 'topics')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'deleteTopic')
|
||||
@@ -2216,13 +2253,14 @@ Http::delete('/v1/messaging/topics/:topicId')
|
||||
->noContent();
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
App::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
->desc('Create subscriber')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'subscriber.create')
|
||||
->label('audits.resource', 'subscriber/{response.$id}')
|
||||
->label('event', 'topics.[topicId].subscribers.[subscriberId].create')
|
||||
->label('scope', 'subscribers.write')
|
||||
->label('resourceType', 'subscribers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createSubscriber')
|
||||
@@ -2236,27 +2274,28 @@ Http::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->inject('authorization')
|
||||
->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response, Authorization $authorization) {
|
||||
->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
$subscriberId = $subscriberId == 'unique()' ? ID::unique() : $subscriberId;
|
||||
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$authorization->isValid(new Input('subscribe', $topic->getAttribute('subscribe')))) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
$validator = new Authorization('subscribe');
|
||||
|
||||
if (!$validator->isValid($topic->getAttribute('subscribe'))) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
|
||||
}
|
||||
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
|
||||
$subscriber = new Document([
|
||||
'$id' => $subscriberId,
|
||||
@@ -2289,7 +2328,7 @@ Http::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
|
||||
};
|
||||
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute(
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
@@ -2311,10 +2350,11 @@ Http::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
->dynamic($subscriber, Response::MODEL_SUBSCRIBER);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
->desc('List subscribers')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'subscribers.read')
|
||||
->label('resourceType', 'subscribers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'listSubscribers')
|
||||
@@ -2327,8 +2367,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->inject('authorization')
|
||||
->action(function (string $topicId, array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) {
|
||||
->action(function (string $topicId, array $queries, string $search, Database $dbForProject, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -2339,7 +2378,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
@@ -2356,8 +2395,13 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$subscriberId = $cursor->getValue();
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Subscriber '{$subscriberId}' for the 'cursor' value not found.");
|
||||
@@ -2368,10 +2412,10 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
|
||||
$subscribers = $dbForProject->find('subscribers', $queries);
|
||||
|
||||
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject, $authorization) {
|
||||
return function () use ($subscriber, $dbForProject, $authorization) {
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) {
|
||||
return function () use ($subscriber, $dbForProject) {
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
|
||||
return $subscriber
|
||||
->setAttribute('target', $target)
|
||||
@@ -2386,10 +2430,11 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
]), Response::MODEL_SUBSCRIBER_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/subscribers/:subscriberId/logs')
|
||||
App::get('/v1/messaging/subscribers/:subscriberId/logs')
|
||||
->desc('List subscriber logs')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'subscribers.read')
|
||||
->label('resourceType', 'subscribers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'listSubscriberLogs')
|
||||
@@ -2403,8 +2448,7 @@ Http::get('/v1/messaging/subscribers/:subscriberId/logs')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->action(function (string $subscriberId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
|
||||
->action(function (string $subscriberId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
$subscriber = $dbForProject->getDocument('subscribers', $subscriberId);
|
||||
|
||||
if ($subscriber->isEmpty()) {
|
||||
@@ -2476,10 +2520,11 @@ Http::get('/v1/messaging/subscribers/:subscriberId/logs')
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->desc('Get subscriber')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'subscribers.read')
|
||||
->label('resourceType', 'subscribers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'getSubscriber')
|
||||
@@ -2491,9 +2536,8 @@ Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->param('subscriberId', '', new UID(), 'Subscriber ID.')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->inject('authorization')
|
||||
->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response, Authorization $authorization) {
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response) {
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
@@ -2505,8 +2549,8 @@ Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
throw new Exception(Exception::SUBSCRIBER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
|
||||
$subscriber
|
||||
->setAttribute('target', $target)
|
||||
@@ -2516,13 +2560,14 @@ Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->dynamic($subscriber, Response::MODEL_SUBSCRIBER);
|
||||
});
|
||||
|
||||
Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->desc('Delete subscriber')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'subscriber.delete')
|
||||
->label('audits.resource', 'subscriber/{request.$subscriberId}')
|
||||
->label('event', 'topics.[topicId].subscribers.[subscriberId].delete')
|
||||
->label('scope', 'subscribers.write')
|
||||
->label('resourceType', 'subscribers')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'deleteSubscriber')
|
||||
@@ -2535,9 +2580,8 @@ Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->inject('authorization')
|
||||
->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response, Authorization $authorization) {
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
@@ -2560,7 +2604,7 @@ Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
|
||||
};
|
||||
|
||||
$authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute(
|
||||
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
@@ -2576,13 +2620,14 @@ Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->noContent();
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/messages/email')
|
||||
App::post('/v1/messaging/messages/email')
|
||||
->desc('Create email')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.create')
|
||||
->label('audits.resource', 'message/{response.$id}')
|
||||
->label('event', 'messages.[messageId].create')
|
||||
->label('scope', 'messages.write')
|
||||
->label('resourceType', 'messages')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createEmail')
|
||||
@@ -2728,13 +2773,14 @@ Http::post('/v1/messaging/messages/email')
|
||||
->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/messages/sms')
|
||||
App::post('/v1/messaging/messages/sms')
|
||||
->desc('Create SMS')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.create')
|
||||
->label('audits.resource', 'message/{response.$id}')
|
||||
->label('event', 'messages.[messageId].create')
|
||||
->label('scope', 'messages.write')
|
||||
->label('resourceType', 'messages')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createSms')
|
||||
@@ -2844,13 +2890,14 @@ Http::post('/v1/messaging/messages/sms')
|
||||
->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
Http::post('/v1/messaging/messages/push')
|
||||
App::post('/v1/messaging/messages/push')
|
||||
->desc('Create push notification')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.create')
|
||||
->label('audits.resource', 'message/{response.$id}')
|
||||
->label('event', 'messages.[messageId].create')
|
||||
->label('scope', 'messages.write')
|
||||
->label('resourceType', 'messages')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'createPush')
|
||||
@@ -3020,10 +3067,11 @@ Http::post('/v1/messaging/messages/push')
|
||||
->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/messages')
|
||||
App::get('/v1/messaging/messages')
|
||||
->desc('List messages')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'messages.read')
|
||||
->label('resourceType', 'messages')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'listMessages')
|
||||
@@ -3035,8 +3083,7 @@ Http::get('/v1/messaging/messages')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->inject('authorization')
|
||||
->action(function (array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) {
|
||||
->action(function (array $queries, string $search, Database $dbForProject, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -3056,8 +3103,13 @@ Http::get('/v1/messaging/messages')
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$messageId = $cursor->getValue();
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('messages', $messageId));
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('messages', $messageId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Message '{$messageId}' for the 'cursor' value not found.");
|
||||
@@ -3072,10 +3124,11 @@ Http::get('/v1/messaging/messages')
|
||||
]), Response::MODEL_MESSAGE_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/messages/:messageId/logs')
|
||||
App::get('/v1/messaging/messages/:messageId/logs')
|
||||
->desc('List message logs')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'messages.read')
|
||||
->label('resourceType', 'messages')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'listMessageLogs')
|
||||
@@ -3089,8 +3142,7 @@ Http::get('/v1/messaging/messages/:messageId/logs')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->action(function (string $messageId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
|
||||
->action(function (string $messageId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -3162,10 +3214,11 @@ Http::get('/v1/messaging/messages/:messageId/logs')
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/messages/:messageId/targets')
|
||||
App::get('/v1/messaging/messages/:messageId/targets')
|
||||
->desc('List message targets')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'messages.read')
|
||||
->label('resourceType', 'messages')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'listTargets')
|
||||
@@ -3211,6 +3264,11 @@ Http::get('/v1/messaging/messages/:messageId/targets')
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$targetId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('targets', $targetId);
|
||||
|
||||
@@ -3227,10 +3285,11 @@ Http::get('/v1/messaging/messages/:messageId/targets')
|
||||
]), Response::MODEL_TARGET_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/messaging/messages/:messageId')
|
||||
App::get('/v1/messaging/messages/:messageId')
|
||||
->desc('Get message')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('scope', 'messages.read')
|
||||
->label('resourceType', 'messages')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'getMessage')
|
||||
@@ -3251,13 +3310,14 @@ Http::get('/v1/messaging/messages/:messageId')
|
||||
$response->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/messages/email/:messageId')
|
||||
App::patch('/v1/messaging/messages/email/:messageId')
|
||||
->desc('Update email')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.update')
|
||||
->label('audits.resource', 'message/{response.$id}')
|
||||
->label('event', 'messages.[messageId].update')
|
||||
->label('scope', 'messages.write')
|
||||
->label('resourceType', 'messages')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateEmail')
|
||||
@@ -3451,13 +3511,14 @@ Http::patch('/v1/messaging/messages/email/:messageId')
|
||||
->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/messages/sms/:messageId')
|
||||
App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
->desc('Update SMS')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.update')
|
||||
->label('audits.resource', 'message/{response.$id}')
|
||||
->label('event', 'messages.[messageId].update')
|
||||
->label('scope', 'messages.write')
|
||||
->label('resourceType', 'messages')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updateSms')
|
||||
@@ -3606,13 +3667,14 @@ Http::patch('/v1/messaging/messages/sms/:messageId')
|
||||
->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
Http::patch('/v1/messaging/messages/push/:messageId')
|
||||
App::patch('/v1/messaging/messages/push/:messageId')
|
||||
->desc('Update push notification')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.update')
|
||||
->label('audits.resource', 'message/{response.$id}')
|
||||
->label('event', 'messages.[messageId].update')
|
||||
->label('scope', 'messages.write')
|
||||
->label('resourceType', 'messages')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'updatePush')
|
||||
@@ -3844,13 +3906,14 @@ Http::patch('/v1/messaging/messages/push/:messageId')
|
||||
->dynamic($message, Response::MODEL_MESSAGE);
|
||||
});
|
||||
|
||||
Http::delete('/v1/messaging/messages/:messageId')
|
||||
App::delete('/v1/messaging/messages/:messageId')
|
||||
->desc('Delete message')
|
||||
->groups(['api', 'messaging'])
|
||||
->label('audits.event', 'message.delete')
|
||||
->label('audits.resource', 'message/{request.messageId}')
|
||||
->label('event', 'messages.[messageId].delete')
|
||||
->label('scope', 'messages.write')
|
||||
->label('resourceType', 'messages')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'messaging')
|
||||
->label('sdk.method', 'delete')
|
||||
|
||||
@@ -9,31 +9,32 @@ use Appwrite\Role;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Migrations;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\ArrayList;
|
||||
use Utopia\Http\Validator\Host;
|
||||
use Utopia\Http\Validator\Integer;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\URL;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\Migration\Sources\Appwrite;
|
||||
use Utopia\Migration\Sources\Firebase;
|
||||
use Utopia\Migration\Sources\NHost;
|
||||
use Utopia\Migration\Sources\Supabase;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
include_once __DIR__ . '/../shared/api.php';
|
||||
|
||||
Http::post('/v1/migrations/appwrite')
|
||||
App::post('/v1/migrations/appwrite')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Migrate Appwrite Data')
|
||||
->desc('Migrate Appwrite data')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('event', 'migrations.[migrationId].create')
|
||||
->label('audits.event', 'migration.create')
|
||||
@@ -60,6 +61,7 @@ Http::post('/v1/migrations/appwrite')
|
||||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => Appwrite::getName(),
|
||||
'destination' => Appwrite::getName(),
|
||||
'credentials' => [
|
||||
'endpoint' => $endpoint,
|
||||
'projectId' => $projectId,
|
||||
@@ -85,9 +87,9 @@ Http::post('/v1/migrations/appwrite')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
Http::post('/v1/migrations/firebase/oauth')
|
||||
App::post('/v1/migrations/firebase/oauth')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Migrate Firebase Data (OAuth)')
|
||||
->desc('Migrate Firebase data (OAuth)')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('event', 'migrations.[migrationId].create')
|
||||
->label('audits.event', 'migration.create')
|
||||
@@ -164,6 +166,7 @@ Http::post('/v1/migrations/firebase/oauth')
|
||||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => Firebase::getName(),
|
||||
'destination' => Appwrite::getName(),
|
||||
'credentials' => [
|
||||
'serviceAccount' => json_encode($serviceAccount),
|
||||
],
|
||||
@@ -187,9 +190,9 @@ Http::post('/v1/migrations/firebase/oauth')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
Http::post('/v1/migrations/firebase')
|
||||
App::post('/v1/migrations/firebase')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Migrate Firebase Data (Service Account)')
|
||||
->desc('Migrate Firebase data (Service Account)')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('event', 'migrations.[migrationId].create')
|
||||
->label('audits.event', 'migration.create')
|
||||
@@ -224,6 +227,7 @@ Http::post('/v1/migrations/firebase')
|
||||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => Firebase::getName(),
|
||||
'destination' => Appwrite::getName(),
|
||||
'credentials' => [
|
||||
'serviceAccount' => $serviceAccount,
|
||||
],
|
||||
@@ -247,9 +251,9 @@ Http::post('/v1/migrations/firebase')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
Http::post('/v1/migrations/supabase')
|
||||
App::post('/v1/migrations/supabase')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Migrate Supabase Data')
|
||||
->desc('Migrate Supabase data')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('event', 'migrations.[migrationId].create')
|
||||
->label('audits.event', 'migration.create')
|
||||
@@ -279,6 +283,7 @@ Http::post('/v1/migrations/supabase')
|
||||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => Supabase::getName(),
|
||||
'destination' => Appwrite::getName(),
|
||||
'credentials' => [
|
||||
'endpoint' => $endpoint,
|
||||
'apiKey' => $apiKey,
|
||||
@@ -307,9 +312,9 @@ Http::post('/v1/migrations/supabase')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
Http::post('/v1/migrations/nhost')
|
||||
App::post('/v1/migrations/nhost')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Migrate NHost Data')
|
||||
->desc('Migrate NHost data')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('event', 'migrations.[migrationId].create')
|
||||
->label('audits.event', 'migration.create')
|
||||
@@ -340,6 +345,7 @@ Http::post('/v1/migrations/nhost')
|
||||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => NHost::getName(),
|
||||
'destination' => Appwrite::getName(),
|
||||
'credentials' => [
|
||||
'subdomain' => $subdomain,
|
||||
'region' => $region,
|
||||
@@ -369,9 +375,9 @@ Http::post('/v1/migrations/nhost')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
Http::get('/v1/migrations')
|
||||
App::get('/v1/migrations')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('List Migrations')
|
||||
->desc('List migrations')
|
||||
->label('scope', 'migrations.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'migrations')
|
||||
@@ -404,6 +410,12 @@ Http::get('/v1/migrations')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$migrationId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('migrations', $migrationId);
|
||||
|
||||
@@ -422,9 +434,9 @@ Http::get('/v1/migrations')
|
||||
]), Response::MODEL_MIGRATION_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/migrations/:migrationId')
|
||||
App::get('/v1/migrations/:migrationId')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Get Migration')
|
||||
->desc('Get migration')
|
||||
->label('scope', 'migrations.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'migrations')
|
||||
@@ -446,9 +458,9 @@ Http::get('/v1/migrations/:migrationId')
|
||||
$response->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
Http::get('/v1/migrations/appwrite/report')
|
||||
App::get('/v1/migrations/appwrite/report')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Generate a report on Appwrite Data')
|
||||
->desc('Generate a report on Appwrite data')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'migrations')
|
||||
@@ -488,9 +500,9 @@ Http::get('/v1/migrations/appwrite/report')
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
Http::get('/v1/migrations/firebase/report')
|
||||
App::get('/v1/migrations/firebase/report')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Generate a report on Firebase Data')
|
||||
->desc('Generate a report on Firebase data')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'migrations')
|
||||
@@ -535,9 +547,9 @@ Http::get('/v1/migrations/firebase/report')
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
Http::get('/v1/migrations/firebase/report/oauth')
|
||||
App::get('/v1/migrations/firebase/report/oauth')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Generate a report on Firebase Data using OAuth')
|
||||
->desc('Generate a report on Firebase data using OAuth')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'migrations')
|
||||
@@ -626,8 +638,8 @@ Http::get('/v1/migrations/firebase/report/oauth')
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
Http::get('/v1/migrations/firebase/connect')
|
||||
->desc('Authorize with firebase')
|
||||
App::get('/v1/migrations/firebase/connect')
|
||||
->desc('Authorize with Firebase')
|
||||
->groups(['api', 'migrations'])
|
||||
->label('scope', 'migrations.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
@@ -668,7 +680,7 @@ Http::get('/v1/migrations/firebase/connect')
|
||||
->redirect($url);
|
||||
});
|
||||
|
||||
Http::get('/v1/migrations/firebase/redirect')
|
||||
App::get('/v1/migrations/firebase/redirect')
|
||||
->desc('Capture and receive data on Firebase authorization')
|
||||
->groups(['api', 'migrations'])
|
||||
->label('scope', 'public')
|
||||
@@ -780,8 +792,8 @@ Http::get('/v1/migrations/firebase/redirect')
|
||||
->redirect($redirect);
|
||||
});
|
||||
|
||||
Http::get('/v1/migrations/firebase/projects')
|
||||
->desc('List Firebase Projects')
|
||||
App::get('/v1/migrations/firebase/projects')
|
||||
->desc('List Firebase projects')
|
||||
->groups(['api', 'migrations'])
|
||||
->label('scope', 'migrations.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
@@ -869,8 +881,8 @@ Http::get('/v1/migrations/firebase/projects')
|
||||
]), Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/migrations/firebase/deauthorize')
|
||||
->desc('Revoke Appwrite\'s authorization to access Firebase Projects')
|
||||
App::get('/v1/migrations/firebase/deauthorize')
|
||||
->desc('Revoke Appwrite\'s authorization to access Firebase projects')
|
||||
->groups(['api', 'migrations'])
|
||||
->label('scope', 'migrations.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
@@ -897,7 +909,7 @@ Http::get('/v1/migrations/firebase/deauthorize')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::get('/v1/migrations/supabase/report')
|
||||
App::get('/v1/migrations/supabase/report')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Generate a report on Supabase Data')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -940,7 +952,7 @@ Http::get('/v1/migrations/supabase/report')
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
Http::get('/v1/migrations/nhost/report')
|
||||
App::get('/v1/migrations/nhost/report')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Generate a report on NHost Data')
|
||||
->label('scope', 'migrations.write')
|
||||
@@ -983,9 +995,9 @@ Http::get('/v1/migrations/nhost/report')
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/migrations/:migrationId')
|
||||
App::patch('/v1/migrations/:migrationId')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Retry Migration')
|
||||
->desc('Retry migration')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('event', 'migrations.[migrationId].retry')
|
||||
->label('audits.event', 'migration.retry')
|
||||
@@ -1028,9 +1040,9 @@ Http::patch('/v1/migrations/:migrationId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::delete('/v1/migrations/:migrationId')
|
||||
App::delete('/v1/migrations/:migrationId')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Delete Migration')
|
||||
->desc('Delete migration')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('event', 'migrations.[migrationId].delete')
|
||||
->label('audits.event', 'migrationId.delete')
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
@@ -12,11 +13,10 @@ use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Datetime as DateTimeValidator;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
Http::get('/v1/project/usage')
|
||||
App::get('/v1/project/usage')
|
||||
->desc('Get project usage stats')
|
||||
->groups(['api', 'usage'])
|
||||
->label('scope', 'projects.read')
|
||||
@@ -31,8 +31,7 @@ Http::get('/v1/project/usage')
|
||||
->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject) {
|
||||
$stats = $total = $usage = [];
|
||||
$format = 'Y-m-d 00:00:00';
|
||||
$firstDay = (new DateTime($startDate))->format($format);
|
||||
@@ -48,6 +47,7 @@ Http::get('/v1/project/usage')
|
||||
METRIC_USERS,
|
||||
METRIC_BUCKETS,
|
||||
METRIC_FILES_STORAGE,
|
||||
METRIC_DATABASES_STORAGE,
|
||||
METRIC_DEPLOYMENTS_STORAGE,
|
||||
METRIC_BUILDS_STORAGE
|
||||
],
|
||||
@@ -57,6 +57,7 @@ Http::get('/v1/project/usage')
|
||||
METRIC_NETWORK_OUTBOUND,
|
||||
METRIC_USERS,
|
||||
METRIC_EXECUTIONS,
|
||||
METRIC_DATABASES_STORAGE,
|
||||
METRIC_EXECUTIONS_MB_SECONDS,
|
||||
METRIC_BUILDS_MB_SECONDS
|
||||
]
|
||||
@@ -77,7 +78,7 @@ Http::get('/v1/project/usage')
|
||||
'1d' => 'Y-m-d\T00:00:00.000P',
|
||||
};
|
||||
|
||||
$authorization->skip(function () use ($dbForProject, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
|
||||
Authorization::skip(function () use ($dbForProject, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
|
||||
foreach ($metrics['total'] as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
@@ -183,6 +184,23 @@ Http::get('/v1/project/usage')
|
||||
];
|
||||
}, $dbForProject->find('buckets'));
|
||||
|
||||
$databasesStorageBreakdown = array_map(function ($database) use ($dbForProject) {
|
||||
$id = $database->getId();
|
||||
$name = $database->getAttribute('name');
|
||||
$metric = str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_STORAGE);
|
||||
|
||||
$value = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
return [
|
||||
'resourceId' => $id,
|
||||
'name' => $name,
|
||||
'value' => $value['value'] ?? 0,
|
||||
];
|
||||
}, $dbForProject->find('databases'));
|
||||
|
||||
$functionsStorageBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
$id = $function->getId();
|
||||
$name = $function->getAttribute('name');
|
||||
@@ -270,6 +288,7 @@ Http::get('/v1/project/usage')
|
||||
'buildsMbSecondsTotal' => $total[METRIC_BUILDS_MB_SECONDS],
|
||||
'documentsTotal' => $total[METRIC_DOCUMENTS],
|
||||
'databasesTotal' => $total[METRIC_DATABASES],
|
||||
'databasesStorageTotal' => $total[METRIC_DATABASES_STORAGE],
|
||||
'usersTotal' => $total[METRIC_USERS],
|
||||
'bucketsTotal' => $total[METRIC_BUCKETS],
|
||||
'filesStorageTotal' => $total[METRIC_FILES_STORAGE],
|
||||
@@ -277,7 +296,10 @@ Http::get('/v1/project/usage')
|
||||
'buildsStorageTotal' => $total[METRIC_BUILDS_STORAGE],
|
||||
'deploymentsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE],
|
||||
'executionsBreakdown' => $executionsBreakdown,
|
||||
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
|
||||
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
|
||||
'bucketsBreakdown' => $bucketsBreakdown,
|
||||
'databasesStorageBreakdown' => $databasesStorageBreakdown,
|
||||
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
|
||||
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
|
||||
'functionsStorageBreakdown' => $functionsStorageBreakdown,
|
||||
@@ -286,8 +308,8 @@ Http::get('/v1/project/usage')
|
||||
|
||||
|
||||
// Variables
|
||||
Http::post('/v1/project/variables')
|
||||
->desc('Create Variable')
|
||||
App::post('/v1/project/variables')
|
||||
->desc('Create variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('audits.event', 'variable.create')
|
||||
@@ -341,8 +363,8 @@ Http::post('/v1/project/variables')
|
||||
->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
});
|
||||
|
||||
Http::get('/v1/project/variables')
|
||||
->desc('List Variables')
|
||||
App::get('/v1/project/variables')
|
||||
->desc('List variables')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
@@ -366,8 +388,8 @@ Http::get('/v1/project/variables')
|
||||
]), Response::MODEL_VARIABLE_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/project/variables/:variableId')
|
||||
->desc('Get Variable')
|
||||
App::get('/v1/project/variables/:variableId')
|
||||
->desc('Get variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
@@ -390,8 +412,8 @@ Http::get('/v1/project/variables/:variableId')
|
||||
$response->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
});
|
||||
|
||||
Http::put('/v1/project/variables/:variableId')
|
||||
->desc('Update Variable')
|
||||
App::put('/v1/project/variables/:variableId')
|
||||
->desc('Update variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
@@ -436,8 +458,8 @@ Http::put('/v1/project/variables/:variableId')
|
||||
$response->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
});
|
||||
|
||||
Http::delete('/v1/project/variables/:variableId')
|
||||
->desc('Delete Variable')
|
||||
App::delete('/v1/project/variables/:variableId')
|
||||
->desc('Delete variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
|
||||
+153
-151
@@ -13,16 +13,14 @@ use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Database\Validator\ProjectId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||
use Appwrite\Utopia\Queue\Connections;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Utopia\Abuse\Adapters\Database\TimeLimit;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Adapter\MySQL;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
@@ -32,25 +30,25 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Validator\PublicDomain;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\ArrayList;
|
||||
use Utopia\Http\Validator\Boolean;
|
||||
use Utopia\Http\Validator\Hostname;
|
||||
use Utopia\Http\Validator\Integer;
|
||||
use Utopia\Http\Validator\Multiple;
|
||||
use Utopia\Http\Validator\Range;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\URL;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Multiple;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['projects'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
@@ -59,7 +57,7 @@ Http::init()
|
||||
}
|
||||
});
|
||||
|
||||
Http::post('/v1/projects')
|
||||
App::post('/v1/projects')
|
||||
->desc('Create project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('audits.event', 'projects.create')
|
||||
@@ -89,9 +87,8 @@ Http::post('/v1/projects')
|
||||
->inject('cache')
|
||||
->inject('pools')
|
||||
->inject('hooks')
|
||||
->inject('authorization')
|
||||
->inject('connections')
|
||||
->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForConsole, Cache $cache, array $pools, Hooks $hooks, Authorization $authorization, Connections $connections) {
|
||||
->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForConsole, Cache $cache, Group $pools, Hooks $hooks) {
|
||||
|
||||
$team = $dbForConsole->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
@@ -193,21 +190,8 @@ Http::post('/v1/projects')
|
||||
$dsn = new DSN('mysql://' . $dsn);
|
||||
}
|
||||
|
||||
$pool = $pools['pools-database-' . $dsn->getHost()]['pool'];
|
||||
$connectionDsn = $pools['pools-database-' . $dsn->getHost()]['dsn'];
|
||||
$connection = $pool->get();
|
||||
$connections->add($connection, $pool);
|
||||
|
||||
$adapter = match ($connectionDsn->getScheme()) {
|
||||
'mariadb' => new MariaDB($connection),
|
||||
'mysql' => new MySQL($connection),
|
||||
default => null
|
||||
};
|
||||
|
||||
$adapter->setDatabase($connectionDsn->getPath());
|
||||
|
||||
$adapter = $pools->get($dsn->getHost())->pop()->getResource();
|
||||
$dbForProject = new Database($adapter, $cache);
|
||||
$dbForProject->setAuthorization($authorization);
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$dbForProject
|
||||
@@ -225,8 +209,10 @@ Http::post('/v1/projects')
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$audit->setup();
|
||||
|
||||
$abuse = new TimeLimit('', 0, 1, $dbForProject);
|
||||
$abuse->setup();
|
||||
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', [])['projects'] ?? [];
|
||||
|
||||
@@ -249,17 +235,17 @@ Http::post('/v1/projects')
|
||||
// Collection already exists
|
||||
}
|
||||
}
|
||||
$connections->reclaim();
|
||||
|
||||
// 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)
|
||||
->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::get('/v1/projects')
|
||||
App::get('/v1/projects')
|
||||
->desc('List projects')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
@@ -274,6 +260,7 @@ Http::get('/v1/projects')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForConsole) {
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -293,6 +280,12 @@ Http::get('/v1/projects')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$projectId = $cursor->getValue();
|
||||
$cursorDocument = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
@@ -311,7 +304,7 @@ Http::get('/v1/projects')
|
||||
]), Response::MODEL_PROJECT_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId')
|
||||
App::get('/v1/projects/:projectId')
|
||||
->desc('Get project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
@@ -325,6 +318,7 @@ Http::get('/v1/projects/:projectId')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -334,7 +328,7 @@ Http::get('/v1/projects/:projectId')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId')
|
||||
App::patch('/v1/projects/:projectId')
|
||||
->desc('Update project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -358,34 +352,31 @@ Http::patch('/v1/projects/:projectId')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$project = $dbForConsole->updateDocument(
|
||||
'projects',
|
||||
$project->getId(),
|
||||
$project
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('description', $description)
|
||||
->setAttribute('logo', $logo)
|
||||
->setAttribute('url', $url)
|
||||
->setAttribute('legalName', $legalName)
|
||||
->setAttribute('legalCountry', $legalCountry)
|
||||
->setAttribute('legalState', $legalState)
|
||||
->setAttribute('legalCity', $legalCity)
|
||||
->setAttribute('legalAddress', $legalAddress)
|
||||
->setAttribute('legalTaxId', $legalTaxId)
|
||||
->setAttribute('search', implode(' ', [$projectId, $name]))
|
||||
);
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('description', $description)
|
||||
->setAttribute('logo', $logo)
|
||||
->setAttribute('url', $url)
|
||||
->setAttribute('legalName', $legalName)
|
||||
->setAttribute('legalCountry', $legalCountry)
|
||||
->setAttribute('legalState', $legalState)
|
||||
->setAttribute('legalCity', $legalCity)
|
||||
->setAttribute('legalAddress', $legalAddress)
|
||||
->setAttribute('legalTaxId', $legalTaxId)
|
||||
->setAttribute('search', implode(' ', [$projectId, $name])));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/team')
|
||||
->desc('Update Project Team')
|
||||
App::patch('/v1/projects/:projectId/team')
|
||||
->desc('Update project team')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
@@ -399,6 +390,7 @@ Http::patch('/v1/projects/:projectId/team')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $teamId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
$team = $dbForConsole->getDocument('teams', $teamId);
|
||||
|
||||
@@ -451,7 +443,7 @@ Http::patch('/v1/projects/:projectId/team')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/service')
|
||||
App::patch('/v1/projects/:projectId/service')
|
||||
->desc('Update service status')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -467,6 +459,7 @@ Http::patch('/v1/projects/:projectId/service')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $service, bool $status, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -481,7 +474,7 @@ Http::patch('/v1/projects/:projectId/service')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/service/all')
|
||||
App::patch('/v1/projects/:projectId/service/all')
|
||||
->desc('Update all service status')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -496,6 +489,7 @@ Http::patch('/v1/projects/:projectId/service/all')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $status, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -514,7 +508,7 @@ Http::patch('/v1/projects/:projectId/service/all')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/api')
|
||||
App::patch('/v1/projects/:projectId/api')
|
||||
->desc('Update API status')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -530,6 +524,7 @@ Http::patch('/v1/projects/:projectId/api')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $api, bool $status, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -544,7 +539,7 @@ Http::patch('/v1/projects/:projectId/api')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/api/all')
|
||||
App::patch('/v1/projects/:projectId/api/all')
|
||||
->desc('Update all API status')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -559,6 +554,7 @@ Http::patch('/v1/projects/:projectId/api/all')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $status, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -577,7 +573,7 @@ Http::patch('/v1/projects/:projectId/api/all')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/oauth2')
|
||||
App::patch('/v1/projects/:projectId/oauth2')
|
||||
->desc('Update project OAuth2')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -595,6 +591,7 @@ Http::patch('/v1/projects/:projectId/oauth2')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $provider, ?string $appId, ?string $secret, ?bool $enabled, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -620,7 +617,7 @@ Http::patch('/v1/projects/:projectId/oauth2')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/auth/session-alerts')
|
||||
App::patch('/v1/projects/:projectId/auth/session-alerts')
|
||||
->desc('Update project sessions emails')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -651,7 +648,7 @@ Http::patch('/v1/projects/:projectId/auth/session-alerts')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/auth/limit')
|
||||
App::patch('/v1/projects/:projectId/auth/limit')
|
||||
->desc('Update project users limit')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -666,6 +663,7 @@ Http::patch('/v1/projects/:projectId/auth/limit')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -675,17 +673,13 @@ Http::patch('/v1/projects/:projectId/auth/limit')
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['limit'] = $limit;
|
||||
|
||||
$dbForConsole->updateDocument(
|
||||
'projects',
|
||||
$project->getId(),
|
||||
$project
|
||||
->setAttribute('auths', $auths)
|
||||
);
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/auth/duration')
|
||||
App::patch('/v1/projects/:projectId/auth/duration')
|
||||
->desc('Update project authentication duration')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -700,6 +694,7 @@ Http::patch('/v1/projects/:projectId/auth/duration')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, int $duration, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -709,17 +704,13 @@ Http::patch('/v1/projects/:projectId/auth/duration')
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['duration'] = $duration;
|
||||
|
||||
$dbForConsole->updateDocument(
|
||||
'projects',
|
||||
$project->getId(),
|
||||
$project
|
||||
->setAttribute('auths', $auths)
|
||||
);
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/auth/:method')
|
||||
App::patch('/v1/projects/:projectId/auth/:method')
|
||||
->desc('Update project auth method status. Use this endpoint to enable or disable a given auth method for this project.')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -735,9 +726,10 @@ Http::patch('/v1/projects/:projectId/auth/:method')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $method, bool $status, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
$authConfig = Config::getParam('auth')[$method] ?? [];
|
||||
$authKey = $authConfig['key'] ?? '';
|
||||
$auth = Config::getParam('auth')[$method] ?? [];
|
||||
$authKey = $auth['key'] ?? '';
|
||||
$status = ($status === '1' || $status === 'true' || $status === 1 || $status === true);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -752,7 +744,7 @@ Http::patch('/v1/projects/:projectId/auth/:method')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/auth/password-history')
|
||||
App::patch('/v1/projects/:projectId/auth/password-history')
|
||||
->desc('Update authentication password history. Use this endpoint to set the number of password history to save and 0 to disable password history.')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -767,6 +759,7 @@ Http::patch('/v1/projects/:projectId/auth/password-history')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -776,17 +769,13 @@ Http::patch('/v1/projects/:projectId/auth/password-history')
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['passwordHistory'] = $limit;
|
||||
|
||||
$dbForConsole->updateDocument(
|
||||
'projects',
|
||||
$project->getId(),
|
||||
$project
|
||||
->setAttribute('auths', $auths)
|
||||
);
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/auth/password-dictionary')
|
||||
App::patch('/v1/projects/:projectId/auth/password-dictionary')
|
||||
->desc('Update authentication password dictionary status. Use this endpoint to enable or disable the dicitonary check for user password')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -801,6 +790,7 @@ Http::patch('/v1/projects/:projectId/auth/password-dictionary')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -810,17 +800,13 @@ Http::patch('/v1/projects/:projectId/auth/password-dictionary')
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['passwordDictionary'] = $enabled;
|
||||
|
||||
$dbForConsole->updateDocument(
|
||||
'projects',
|
||||
$project->getId(),
|
||||
$project
|
||||
->setAttribute('auths', $auths)
|
||||
);
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/auth/personal-data')
|
||||
App::patch('/v1/projects/:projectId/auth/personal-data')
|
||||
->desc('Enable or disable checking user passwords for similarity with their personal data.')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -835,6 +821,7 @@ Http::patch('/v1/projects/:projectId/auth/personal-data')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -844,17 +831,13 @@ Http::patch('/v1/projects/:projectId/auth/personal-data')
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['personalDataCheck'] = $enabled;
|
||||
|
||||
$dbForConsole->updateDocument(
|
||||
'projects',
|
||||
$project->getId(),
|
||||
$project
|
||||
->setAttribute('auths', $auths)
|
||||
);
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/auth/max-sessions')
|
||||
App::patch('/v1/projects/:projectId/auth/max-sessions')
|
||||
->desc('Update project user sessions limit')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -869,6 +852,7 @@ Http::patch('/v1/projects/:projectId/auth/max-sessions')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -878,17 +862,13 @@ Http::patch('/v1/projects/:projectId/auth/max-sessions')
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['maxSessions'] = $limit;
|
||||
|
||||
$dbForConsole->updateDocument(
|
||||
'projects',
|
||||
$project->getId(),
|
||||
$project
|
||||
->setAttribute('auths', $auths)
|
||||
);
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/auth/mock-numbers')
|
||||
App::patch('/v1/projects/:projectId/auth/mock-numbers')
|
||||
->desc('Update the mock numbers for the project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -927,7 +907,7 @@ Http::patch('/v1/projects/:projectId/auth/mock-numbers')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::delete('/v1/projects/:projectId')
|
||||
App::delete('/v1/projects/:projectId')
|
||||
->desc('Delete project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('audits.event', 'projects.delete')
|
||||
@@ -950,6 +930,7 @@ Http::delete('/v1/projects/:projectId')
|
||||
}
|
||||
|
||||
$queueForDeletes
|
||||
->setProject($project)
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($project);
|
||||
|
||||
@@ -962,7 +943,7 @@ Http::delete('/v1/projects/:projectId')
|
||||
|
||||
// Webhooks
|
||||
|
||||
Http::post('/v1/projects/:projectId/webhooks')
|
||||
App::post('/v1/projects/:projectId/webhooks')
|
||||
->desc('Create webhook')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -983,13 +964,14 @@ Http::post('/v1/projects/:projectId/webhooks')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$security = (bool)filter_var($security, FILTER_VALIDATE_BOOLEAN);
|
||||
$security = (bool) filter_var($security, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
$webhook = new Document([
|
||||
'$id' => ID::unique(),
|
||||
@@ -1019,7 +1001,7 @@ Http::post('/v1/projects/:projectId/webhooks')
|
||||
->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId/webhooks')
|
||||
App::get('/v1/projects/:projectId/webhooks')
|
||||
->desc('List webhooks')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
@@ -1033,6 +1015,7 @@ Http::get('/v1/projects/:projectId/webhooks')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1050,7 +1033,7 @@ Http::get('/v1/projects/:projectId/webhooks')
|
||||
]), Response::MODEL_WEBHOOK_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
App::get('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->desc('Get webhook')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
@@ -1065,6 +1048,7 @@ Http::get('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1083,7 +1067,7 @@ Http::get('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
Http::put('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
App::put('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->desc('Update webhook')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1105,6 +1089,7 @@ Http::put('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $webhookId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1141,7 +1126,7 @@ Http::put('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
|
||||
App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
|
||||
->desc('Update webhook signature key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1156,6 +1141,7 @@ Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1179,7 +1165,7 @@ Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
Http::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
App::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->desc('Delete webhook')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1193,6 +1179,7 @@ Http::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1217,10 +1204,10 @@ Http::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
|
||||
// Keys
|
||||
|
||||
Http::post('/v1/projects/:projectId/keys')
|
||||
App::post('/v1/projects/:projectId/keys')
|
||||
->desc('Create key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('scope', 'keys.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'createKey')
|
||||
@@ -1234,6 +1221,7 @@ Http::post('/v1/projects/:projectId/keys')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1266,10 +1254,10 @@ Http::post('/v1/projects/:projectId/keys')
|
||||
->dynamic($key, Response::MODEL_KEY);
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId/keys')
|
||||
App::get('/v1/projects/:projectId/keys')
|
||||
->desc('List keys')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('scope', 'keys.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'listKeys')
|
||||
@@ -1280,6 +1268,7 @@ Http::get('/v1/projects/:projectId/keys')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1297,10 +1286,10 @@ Http::get('/v1/projects/:projectId/keys')
|
||||
]), Response::MODEL_KEY_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId/keys/:keyId')
|
||||
App::get('/v1/projects/:projectId/keys/:keyId')
|
||||
->desc('Get key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('scope', 'keys.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'getKey')
|
||||
@@ -1312,6 +1301,7 @@ Http::get('/v1/projects/:projectId/keys/:keyId')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1330,10 +1320,10 @@ Http::get('/v1/projects/:projectId/keys/:keyId')
|
||||
$response->dynamic($key, Response::MODEL_KEY);
|
||||
});
|
||||
|
||||
Http::put('/v1/projects/:projectId/keys/:keyId')
|
||||
App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
->desc('Update key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('scope', 'keys.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateKey')
|
||||
@@ -1348,6 +1338,7 @@ Http::put('/v1/projects/:projectId/keys/:keyId')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1375,10 +1366,10 @@ Http::put('/v1/projects/:projectId/keys/:keyId')
|
||||
$response->dynamic($key, Response::MODEL_KEY);
|
||||
});
|
||||
|
||||
Http::delete('/v1/projects/:projectId/keys/:keyId')
|
||||
App::delete('/v1/projects/:projectId/keys/:keyId')
|
||||
->desc('Delete key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('scope', 'keys.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'deleteKey')
|
||||
@@ -1389,6 +1380,7 @@ Http::delete('/v1/projects/:projectId/keys/:keyId')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1413,7 +1405,7 @@ Http::delete('/v1/projects/:projectId/keys/:keyId')
|
||||
|
||||
// JWT Keys
|
||||
|
||||
Http::post('/v1/projects/:projectId/jwts')
|
||||
App::post('/v1/projects/:projectId/jwts')
|
||||
->groups(['api', 'projects'])
|
||||
->desc('Create JWT')
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1448,11 +1440,11 @@ Http::post('/v1/projects/:projectId/jwts')
|
||||
|
||||
// Platforms
|
||||
|
||||
Http::post('/v1/projects/:projectId/platforms')
|
||||
App::post('/v1/projects/:projectId/platforms')
|
||||
->desc('Create platform')
|
||||
->groups(['api', 'projects'])
|
||||
->label('audits.event', 'platforms.create')
|
||||
->label('scope', 'projects.write')
|
||||
->label('scope', 'platforms.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'createPlatform')
|
||||
@@ -1499,10 +1491,10 @@ Http::post('/v1/projects/:projectId/platforms')
|
||||
->dynamic($platform, Response::MODEL_PLATFORM);
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId/platforms')
|
||||
App::get('/v1/projects/:projectId/platforms')
|
||||
->desc('List platforms')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('scope', 'platforms.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'listPlatforms')
|
||||
@@ -1513,6 +1505,7 @@ Http::get('/v1/projects/:projectId/platforms')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1530,10 +1523,10 @@ Http::get('/v1/projects/:projectId/platforms')
|
||||
]), Response::MODEL_PLATFORM_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId/platforms/:platformId')
|
||||
App::get('/v1/projects/:projectId/platforms/:platformId')
|
||||
->desc('Get platform')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('scope', 'platforms.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'getPlatform')
|
||||
@@ -1545,6 +1538,7 @@ Http::get('/v1/projects/:projectId/platforms/:platformId')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1563,10 +1557,10 @@ Http::get('/v1/projects/:projectId/platforms/:platformId')
|
||||
$response->dynamic($platform, Response::MODEL_PLATFORM);
|
||||
});
|
||||
|
||||
Http::put('/v1/projects/:projectId/platforms/:platformId')
|
||||
App::put('/v1/projects/:projectId/platforms/:platformId')
|
||||
->desc('Update platform')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('scope', 'platforms.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updatePlatform')
|
||||
@@ -1610,11 +1604,11 @@ Http::put('/v1/projects/:projectId/platforms/:platformId')
|
||||
$response->dynamic($platform, Response::MODEL_PLATFORM);
|
||||
});
|
||||
|
||||
Http::delete('/v1/projects/:projectId/platforms/:platformId')
|
||||
App::delete('/v1/projects/:projectId/platforms/:platformId')
|
||||
->desc('Delete platform')
|
||||
->groups(['api', 'projects'])
|
||||
->label('audits.event', 'platforms.delete')
|
||||
->label('scope', 'projects.write')
|
||||
->label('scope', 'platforms.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'deletePlatform')
|
||||
@@ -1625,6 +1619,7 @@ Http::delete('/v1/projects/:projectId/platforms/:platformId')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1649,7 +1644,7 @@ Http::delete('/v1/projects/:projectId/platforms/:platformId')
|
||||
|
||||
|
||||
// CUSTOM SMTP and Templates
|
||||
Http::patch('/v1/projects/:projectId/smtp')
|
||||
App::patch('/v1/projects/:projectId/smtp')
|
||||
->desc('Update SMTP')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1672,6 +1667,7 @@ Http::patch('/v1/projects/:projectId/smtp')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $enabled, string $senderName, string $senderEmail, string $replyTo, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1738,7 +1734,7 @@ Http::patch('/v1/projects/:projectId/smtp')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::post('/v1/projects/:projectId/smtp/tests')
|
||||
App::post('/v1/projects/:projectId/smtp/tests')
|
||||
->desc('Create SMTP test')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1756,7 +1752,7 @@ Http::post('/v1/projects/:projectId/smtp/tests')
|
||||
->param('port', 587, new Integer(), 'SMTP server port', true)
|
||||
->param('username', '', new Text(0, 0), 'SMTP server username', true)
|
||||
->param('password', '', new Text(0, 0), 'SMTP server password', true)
|
||||
->param('secure', '', new WhiteList(['tls'], true), 'Does SMTP server use secure connection', true)
|
||||
->param('secure', '', new WhiteList(['tls', 'ssl'], true), 'Does SMTP server use secure connection', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('queueForMails')
|
||||
@@ -1797,7 +1793,7 @@ Http::post('/v1/projects/:projectId/smtp/tests')
|
||||
return $response->noContent();
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
App::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->desc('Get custom SMS template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1813,6 +1809,7 @@ Http::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) {
|
||||
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
@@ -1822,7 +1819,7 @@ Http::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
}
|
||||
|
||||
$templates = $project->getAttribute('templates', []);
|
||||
$template = $templates['sms.' . $type . '-' . $locale] ?? null;
|
||||
$template = $templates['sms.' . $type . '-' . $locale] ?? null;
|
||||
|
||||
if (is_null($template)) {
|
||||
$template = [
|
||||
@@ -1837,7 +1834,7 @@ Http::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
});
|
||||
|
||||
|
||||
Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
App::get('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
->desc('Get custom email template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1853,6 +1850,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1860,7 +1858,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
}
|
||||
|
||||
$templates = $project->getAttribute('templates', []);
|
||||
$template = $templates['email.' . $type . '-' . $locale] ?? null;
|
||||
$template = $templates['email.' . $type . '-' . $locale] ?? null;
|
||||
|
||||
$localeObj = new Locale($locale);
|
||||
if (is_null($template)) {
|
||||
@@ -1868,7 +1866,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
$message
|
||||
->setParam('{{hello}}', $localeObj->getText("emails.{$type}.hello"))
|
||||
->setParam('{{footer}}', $localeObj->getText("emails.{$type}.footer"))
|
||||
->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'), escape: false)
|
||||
->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'), escapeHtml: false)
|
||||
->setParam('{{thanks}}', $localeObj->getText("emails.{$type}.thanks"))
|
||||
->setParam('{{signature}}', $localeObj->getText("emails.{$type}.signature"))
|
||||
->setParam('{{direction}}', $localeObj->getText('settings.direction'));
|
||||
@@ -1888,7 +1886,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
$response->dynamic(new Document($template), Response::MODEL_EMAIL_TEMPLATE);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
App::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->desc('Update custom SMS template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1905,6 +1903,7 @@ Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $type, string $locale, string $message, Response $response, Database $dbForConsole) {
|
||||
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
@@ -1927,7 +1926,7 @@ Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
]), Response::MODEL_SMS_TEMPLATE);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
App::patch('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
->desc('Update custom email templates')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1948,6 +1947,7 @@ Http::patch('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $type, string $locale, string $subject, string $message, string $senderName, string $senderEmail, string $replyTo, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -1976,7 +1976,7 @@ Http::patch('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
]), Response::MODEL_EMAIL_TEMPLATE);
|
||||
});
|
||||
|
||||
Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
App::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->desc('Reset custom SMS template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -1992,6 +1992,7 @@ Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) {
|
||||
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
@@ -2001,7 +2002,7 @@ Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
}
|
||||
|
||||
$templates = $project->getAttribute('templates', []);
|
||||
$template = $templates['sms.' . $type . '-' . $locale] ?? null;
|
||||
$template = $templates['sms.' . $type . '-' . $locale] ?? null;
|
||||
|
||||
if (is_null($template)) {
|
||||
throw new Exception(Exception::PROJECT_TEMPLATE_DEFAULT_DELETION);
|
||||
@@ -2018,7 +2019,7 @@ Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
]), Response::MODEL_SMS_TEMPLATE);
|
||||
});
|
||||
|
||||
Http::delete('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
App::delete('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
->desc('Reset custom email template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
@@ -2034,6 +2035,7 @@ Http::delete('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -2041,7 +2043,7 @@ Http::delete('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
}
|
||||
|
||||
$templates = $project->getAttribute('templates', []);
|
||||
$template = $templates['email.' . $type . '-' . $locale] ?? null;
|
||||
$template = $templates['email.' . $type . '-' . $locale] ?? null;
|
||||
|
||||
if (is_null($template)) {
|
||||
throw new Exception(Exception::PROJECT_TEMPLATE_DEFAULT_DELETION);
|
||||
|
||||
@@ -7,23 +7,24 @@ use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\CNAME;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Rules;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
Http::post('/v1/proxy/rules')
|
||||
App::post('/v1/proxy/rules')
|
||||
->groups(['api', 'proxy'])
|
||||
->desc('Create Rule')
|
||||
->desc('Create rule')
|
||||
->label('scope', 'rules.write')
|
||||
->label('event', 'rules.[ruleId].create')
|
||||
->label('audits.event', 'rule.create')
|
||||
@@ -147,9 +148,9 @@ Http::post('/v1/proxy/rules')
|
||||
->dynamic($rule, Response::MODEL_PROXY_RULE);
|
||||
});
|
||||
|
||||
Http::get('/v1/proxy/rules')
|
||||
App::get('/v1/proxy/rules')
|
||||
->groups(['api', 'proxy'])
|
||||
->desc('List Rules')
|
||||
->desc('List rules')
|
||||
->label('scope', 'rules.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'proxy')
|
||||
@@ -185,6 +186,12 @@ Http::get('/v1/proxy/rules')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$ruleId = $cursor->getValue();
|
||||
$cursorDocument = $dbForConsole->getDocument('rules', $ruleId);
|
||||
|
||||
@@ -210,9 +217,9 @@ Http::get('/v1/proxy/rules')
|
||||
]), Response::MODEL_PROXY_RULE_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/proxy/rules/:ruleId')
|
||||
App::get('/v1/proxy/rules/:ruleId')
|
||||
->groups(['api', 'proxy'])
|
||||
->desc('Get Rule')
|
||||
->desc('Get rule')
|
||||
->label('scope', 'rules.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'proxy')
|
||||
@@ -239,9 +246,9 @@ Http::get('/v1/proxy/rules/:ruleId')
|
||||
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
|
||||
});
|
||||
|
||||
Http::delete('/v1/proxy/rules/:ruleId')
|
||||
App::delete('/v1/proxy/rules/:ruleId')
|
||||
->groups(['api', 'proxy'])
|
||||
->desc('Delete Rule')
|
||||
->desc('Delete rule')
|
||||
->label('scope', 'rules.write')
|
||||
->label('event', 'rules.[ruleId].delete')
|
||||
->label('audits.event', 'rules.delete')
|
||||
@@ -276,8 +283,8 @@ Http::delete('/v1/proxy/rules/:ruleId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::patch('/v1/proxy/rules/:ruleId/verification')
|
||||
->desc('Update Rule Verification Status')
|
||||
App::patch('/v1/proxy/rules/:ruleId/verification')
|
||||
->desc('Update rule verification status')
|
||||
->groups(['api', 'proxy'])
|
||||
->label('scope', 'rules.write')
|
||||
->label('event', 'rules.[ruleId].update')
|
||||
|
||||
+146
-118
@@ -11,8 +11,8 @@ use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Buckets;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Files;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
@@ -23,16 +23,9 @@ use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\ArrayList;
|
||||
use Utopia\Http\Validator\Boolean;
|
||||
use Utopia\Http\Validator\HexColor;
|
||||
use Utopia\Http\Validator\Range;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Storage\Compression\Algorithms\GZIP;
|
||||
use Utopia\Storage\Compression\Algorithms\Zstd;
|
||||
@@ -43,12 +36,20 @@ use Utopia\Storage\Validator\File;
|
||||
use Utopia\Storage\Validator\FileExt;
|
||||
use Utopia\Storage\Validator\FileSize;
|
||||
use Utopia\Storage\Validator\Upload;
|
||||
use Utopia\Swoole\Request;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\HexColor;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
Http::post('/v1/storage/buckets')
|
||||
App::post('/v1/storage/buckets')
|
||||
->desc('Create bucket')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'buckets.write')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('event', 'buckets.[bucketId].create')
|
||||
->label('audits.event', 'bucket.create')
|
||||
->label('audits.resource', 'bucket/{response.$id}')
|
||||
@@ -143,10 +144,11 @@ Http::post('/v1/storage/buckets')
|
||||
->dynamic($bucket, Response::MODEL_BUCKET);
|
||||
});
|
||||
|
||||
Http::get('/v1/storage/buckets')
|
||||
App::get('/v1/storage/buckets')
|
||||
->desc('List buckets')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'buckets.read')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'listBuckets')
|
||||
@@ -179,6 +181,12 @@ Http::get('/v1/storage/buckets')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$bucketId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
||||
@@ -197,10 +205,11 @@ Http::get('/v1/storage/buckets')
|
||||
]), Response::MODEL_BUCKET_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/storage/buckets/:bucketId')
|
||||
App::get('/v1/storage/buckets/:bucketId')
|
||||
->desc('Get bucket')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'buckets.read')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getBucket')
|
||||
@@ -222,10 +231,11 @@ Http::get('/v1/storage/buckets/:bucketId')
|
||||
$response->dynamic($bucket, Response::MODEL_BUCKET);
|
||||
});
|
||||
|
||||
Http::put('/v1/storage/buckets/:bucketId')
|
||||
App::put('/v1/storage/buckets/:bucketId')
|
||||
->desc('Update bucket')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'buckets.write')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('event', 'buckets.[bucketId].update')
|
||||
->label('audits.event', 'bucket.update')
|
||||
->label('audits.resource', 'bucket/{response.$id}')
|
||||
@@ -289,10 +299,11 @@ Http::put('/v1/storage/buckets/:bucketId')
|
||||
$response->dynamic($bucket, Response::MODEL_BUCKET);
|
||||
});
|
||||
|
||||
Http::delete('/v1/storage/buckets/:bucketId')
|
||||
App::delete('/v1/storage/buckets/:bucketId')
|
||||
->desc('Delete bucket')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'buckets.write')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('audits.event', 'bucket.delete')
|
||||
->label('event', 'buckets.[bucketId].delete')
|
||||
->label('audits.resource', 'bucket/{request.bucketId}')
|
||||
@@ -330,11 +341,12 @@ Http::delete('/v1/storage/buckets/:bucketId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->alias('/v1/storage/files', ['bucketId' => 'default'])
|
||||
->desc('Create file')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.write')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('audits.event', 'file.create')
|
||||
->label('event', 'buckets.[bucketId].files.[fileId].create')
|
||||
->label('audits.resource', 'file/{response.$id}')
|
||||
@@ -362,19 +374,19 @@ Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
->inject('mode')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForLocal')
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal, Authorization $authorization) {
|
||||
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal) {
|
||||
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
|
||||
$validator = new Authorization(Database::PERMISSION_CREATE);
|
||||
if (!$validator->isValid($bucket->getCreate())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@@ -398,7 +410,7 @@ Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
}
|
||||
|
||||
// Users can only manage their own roles, API keys and Admin users can manage any
|
||||
$roles = $authorization->getRoles();
|
||||
$roles = Authorization::getRoles();
|
||||
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
@@ -411,7 +423,7 @@ Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!$authorization->isRole($role)) {
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
|
||||
}
|
||||
}
|
||||
@@ -631,10 +643,11 @@ Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
* However as with chunk upload even if we are updating, we are essentially creating a file
|
||||
* adding it's new chunk so we validate create permission instead of update
|
||||
*/
|
||||
if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
|
||||
$validator = new Authorization(Database::PERMISSION_CREATE);
|
||||
if (!$validator->isValid($bucket->getCreate())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
}
|
||||
} else {
|
||||
if ($file->isEmpty()) {
|
||||
@@ -669,10 +682,11 @@ Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
* However as with chunk upload even if we are updating, we are essentially creating a file
|
||||
* adding it's new chunk so we validate create permission instead of update
|
||||
*/
|
||||
if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
|
||||
$validator = new Authorization(Database::PERMISSION_CREATE);
|
||||
if (!$validator->isValid($bucket->getCreate())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -689,11 +703,12 @@ Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
->dynamic($file, Response::MODEL_FILE);
|
||||
});
|
||||
|
||||
Http::get('/v1/storage/buckets/:bucketId/files')
|
||||
App::get('/v1/storage/buckets/:bucketId/files')
|
||||
->alias('/v1/storage/files', ['bucketId' => 'default'])
|
||||
->desc('List files')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'listFiles')
|
||||
@@ -707,19 +722,19 @@ Http::get('/v1/storage/buckets/:bucketId/files')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, array $queries, string $search, Response $response, Database $dbForProject, string $mode, Authorization $authorization) {
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
->action(function (string $bucketId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
@@ -743,12 +758,18 @@ Http::get('/v1/storage/buckets/:bucketId/files')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$fileId = $cursor->getValue();
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
$cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
@@ -764,8 +785,8 @@ Http::get('/v1/storage/buckets/:bucketId/files')
|
||||
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries);
|
||||
$total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
|
||||
} else {
|
||||
$files = $authorization->skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries));
|
||||
$total = $authorization->skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
|
||||
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries));
|
||||
$total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
@@ -774,11 +795,12 @@ Http::get('/v1/storage/buckets/:bucketId/files')
|
||||
]), Response::MODEL_FILE_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
App::get('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
|
||||
->desc('Get file')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getFile')
|
||||
@@ -791,19 +813,19 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, string $mode, Authorization $authorization) {
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, string $mode) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
@@ -811,7 +833,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
@@ -821,11 +843,12 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
$response->dynamic($file, Response::MODEL_FILE);
|
||||
});
|
||||
|
||||
Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
->alias('/v1/storage/files/:fileId/preview', ['bucketId' => 'default'])
|
||||
->desc('Get file preview')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'bucket/{request.bucketId}')
|
||||
->label('cache.resource', 'file/{request.fileId}')
|
||||
@@ -856,24 +879,24 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
->inject('mode')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForLocal')
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal, Authorization $authorization) {
|
||||
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal) {
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
@@ -881,17 +904,13 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' === $output)) { // Fallback webp to jpeg when no browser support
|
||||
$output = 'jpg';
|
||||
}
|
||||
|
||||
$inputs = Config::getParam('storage-inputs');
|
||||
$outputs = Config::getParam('storage-outputs');
|
||||
$fileLogos = Config::getParam('storage-logos');
|
||||
@@ -989,7 +1008,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
$contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg'];
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType($contentType)
|
||||
->file($data)
|
||||
;
|
||||
@@ -997,11 +1016,12 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
unset($image);
|
||||
});
|
||||
|
||||
Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
->alias('/v1/storage/files/:fileId/download', ['bucketId' => 'default'])
|
||||
->desc('Get file for download')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getFileDownload')
|
||||
@@ -1016,20 +1036,20 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('deviceForFiles')
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceForFiles, Authorization $authorization) {
|
||||
->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceForFiles) {
|
||||
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
@@ -1037,7 +1057,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
@@ -1052,7 +1072,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
|
||||
$response
|
||||
->setContentType($file->getAttribute('mimeType'))
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $file->getAttribute('name', '') . '"')
|
||||
;
|
||||
@@ -1137,11 +1157,15 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
}
|
||||
});
|
||||
|
||||
Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
->alias('/v1/storage/files/:fileId/view', ['bucketId' => 'default'])
|
||||
->desc('Get file for view')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'bucket/{request.bucketId}')
|
||||
->label('cache.resource', 'file/{request.fileId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getFileView')
|
||||
@@ -1156,19 +1180,19 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('deviceForFiles')
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceForFiles, Authorization $authorization) {
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceForFiles) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
@@ -1176,7 +1200,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
@@ -1202,7 +1226,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
->addHeader('Content-Security-Policy', 'script-src none;')
|
||||
->addHeader('X-Content-Type-Options', 'nosniff')
|
||||
->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
;
|
||||
|
||||
@@ -1261,20 +1285,21 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
|
||||
if (!empty($source)) {
|
||||
if (!empty($rangeHeader)) {
|
||||
$response->send(substr($source, $start, ($end - $start + 1)));
|
||||
$response->file(substr($source, $start, ($end - $start + 1)));
|
||||
}
|
||||
$response->send($source);
|
||||
$response->file($source);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($rangeHeader)) {
|
||||
$response->send($deviceForFiles->read($path, $start, ($end - $start + 1)));
|
||||
$response->file($deviceForFiles->read($path, $start, ($end - $start + 1)));
|
||||
return;
|
||||
}
|
||||
|
||||
$size = $deviceForFiles->getFileSize($path);
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
|
||||
$response->chunk(
|
||||
$deviceForFiles->read(
|
||||
$path,
|
||||
@@ -1285,14 +1310,15 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$response->send($deviceForFiles->read($path));
|
||||
$response->file($deviceForFiles->read($path));
|
||||
}
|
||||
});
|
||||
|
||||
Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
||||
App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
||||
->desc('Get file for push notification')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'public')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', '*/*')
|
||||
->label('sdk.methodType', 'location')
|
||||
@@ -1305,9 +1331,8 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
||||
->inject('project')
|
||||
->inject('mode')
|
||||
->inject('deviceForFiles')
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Document $project, string $mode, Device $deviceForFiles, Authorization $authorization) {
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Document $project, string $mode, Device $deviceForFiles) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$decoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
|
||||
|
||||
@@ -1325,14 +1350,14 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
@@ -1357,7 +1382,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
||||
->addHeader('Content-Security-Policy', 'script-src none;')
|
||||
->addHeader('X-Content-Type-Options', 'nosniff')
|
||||
->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->addHeader('X-Peak', \memory_get_peak_usage());
|
||||
|
||||
$size = $file->getAttribute('sizeOriginal', 0);
|
||||
@@ -1443,11 +1468,12 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
||||
}
|
||||
});
|
||||
|
||||
Http::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
|
||||
->desc('Update file')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.write')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('event', 'buckets.[bucketId].files.[fileId].update')
|
||||
->label('audits.event', 'file.update')
|
||||
->label('audits.resource', 'file/{response.$id}')
|
||||
@@ -1470,26 +1496,26 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->inject('user')
|
||||
->inject('mode')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $queueForEvents, Authorization $authorization) {
|
||||
->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $queueForEvents) {
|
||||
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_UPDATE, $bucket->getUpdate()));
|
||||
$validator = new Authorization(Database::PERMISSION_UPDATE);
|
||||
$valid = $validator->isValid($bucket->getUpdate());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Read permission should not be required for update
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
@@ -1503,7 +1529,7 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
]);
|
||||
|
||||
// Users can only manage their own roles, API keys and Admin users can manage any
|
||||
$roles = $authorization->getRoles();
|
||||
$roles = Authorization::getRoles();
|
||||
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles) && !\is_null($permissions)) {
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
@@ -1516,7 +1542,7 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!$authorization->isRole($role)) {
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
|
||||
}
|
||||
}
|
||||
@@ -1536,7 +1562,7 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
} else {
|
||||
$file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
@@ -1548,10 +1574,11 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
$response->dynamic($file, Response::MODEL_FILE);
|
||||
});
|
||||
|
||||
Http::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->desc('Delete File')
|
||||
App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->desc('Delete file')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.write')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('event', 'buckets.[bucketId].files.[fileId].delete')
|
||||
->label('audits.event', 'file.delete')
|
||||
->label('audits.resource', 'file/{request.fileId}')
|
||||
@@ -1572,32 +1599,32 @@ Http::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->inject('mode')
|
||||
->inject('deviceForFiles')
|
||||
->inject('queueForDeletes')
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes, Authorization $authorization) {
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_DELETE, $bucket->getDelete()));
|
||||
$validator = new Authorization(Database::PERMISSION_DELETE);
|
||||
$valid = $validator->isValid($bucket->getDelete());
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Read permission should not be required for delete
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Make sure we don't delete the file before the document permission check occurs
|
||||
if ($fileSecurity && !$valid && !$authorization->isValid(new Input(Database::PERMISSION_DELETE, $file->getDelete()))) {
|
||||
if ($fileSecurity && !$valid && !$validator->isValid($file->getDelete())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@@ -1621,7 +1648,7 @@ Http::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
if ($fileSecurity && !$valid) {
|
||||
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$deleted = $authorization->skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if (!$deleted) {
|
||||
@@ -1641,10 +1668,11 @@ Http::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::get('/v1/storage/usage')
|
||||
App::get('/v1/storage/usage')
|
||||
->desc('Get storage usage stats')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getUsage')
|
||||
@@ -1654,8 +1682,7 @@ Http::get('/v1/storage/usage')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
->action(function (string $range, Response $response, Database $dbForProject) {
|
||||
|
||||
$periods = Config::getParam('usage', []);
|
||||
$stats = $usage = [];
|
||||
@@ -1667,7 +1694,7 @@ Http::get('/v1/storage/usage')
|
||||
];
|
||||
|
||||
$total = [];
|
||||
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
@@ -1721,10 +1748,11 @@ Http::get('/v1/storage/usage')
|
||||
]), Response::MODEL_USAGE_STORAGE);
|
||||
});
|
||||
|
||||
Http::get('/v1/storage/:bucketId/usage')
|
||||
App::get('/v1/storage/:bucketId/usage')
|
||||
->desc('Get bucket usage stats')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('resourceType', 'buckets')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'getBucketUsage')
|
||||
@@ -1735,8 +1763,7 @@ Http::get('/v1/storage/:bucketId/usage')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $range, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
->action(function (string $bucketId, string $range, Response $response, Database $dbForProject) {
|
||||
|
||||
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
||||
@@ -1752,7 +1779,8 @@ Http::get('/v1/storage/:bucketId/usage')
|
||||
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE),
|
||||
];
|
||||
|
||||
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Authentication;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
@@ -19,6 +18,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Teams;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
@@ -34,18 +34,19 @@ use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
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\Database\Validator\UID;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\ArrayList;
|
||||
use Utopia\Http\Validator\Assoc;
|
||||
use Utopia\Http\Validator\Host;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
Http::post('/v1/teams')
|
||||
App::post('/v1/teams')
|
||||
->desc('Create team')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].create')
|
||||
@@ -66,16 +67,15 @@ Http::post('/v1/teams')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
|
||||
->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 = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
$teamId = $teamId == 'unique()' ? ID::unique() : $teamId;
|
||||
|
||||
try {
|
||||
$team = $authorization->skip(fn () => $dbForProject->createDocument('teams', new Document([
|
||||
$team = Authorization::skip(fn () => $dbForProject->createDocument('teams', new Document([
|
||||
'$id' => $teamId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team($teamId)),
|
||||
@@ -134,7 +134,7 @@ Http::post('/v1/teams')
|
||||
->dynamic($team, Response::MODEL_TEAM);
|
||||
});
|
||||
|
||||
Http::get('/v1/teams')
|
||||
App::get('/v1/teams')
|
||||
->desc('List teams')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
@@ -171,6 +171,12 @@ Http::get('/v1/teams')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$teamId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
@@ -192,7 +198,7 @@ Http::get('/v1/teams')
|
||||
]), Response::MODEL_TEAM_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/teams/:teamId')
|
||||
App::get('/v1/teams/:teamId')
|
||||
->desc('Get team')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
@@ -219,7 +225,7 @@ Http::get('/v1/teams/:teamId')
|
||||
$response->dynamic($team, Response::MODEL_TEAM);
|
||||
});
|
||||
|
||||
Http::get('/v1/teams/:teamId/prefs')
|
||||
App::get('/v1/teams/:teamId/prefs')
|
||||
->desc('Get team preferences')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
@@ -247,7 +253,7 @@ Http::get('/v1/teams/:teamId/prefs')
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
Http::put('/v1/teams/:teamId')
|
||||
App::put('/v1/teams/:teamId')
|
||||
->desc('Update name')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].update')
|
||||
@@ -290,7 +296,7 @@ Http::put('/v1/teams/:teamId')
|
||||
$response->dynamic($team, Response::MODEL_TEAM);
|
||||
});
|
||||
|
||||
Http::put('/v1/teams/:teamId/prefs')
|
||||
App::put('/v1/teams/:teamId/prefs')
|
||||
->desc('Update preferences')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].update.prefs')
|
||||
@@ -326,7 +332,7 @@ Http::put('/v1/teams/:teamId/prefs')
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
Http::delete('/v1/teams/:teamId')
|
||||
App::delete('/v1/teams/:teamId')
|
||||
->desc('Delete team')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].delete')
|
||||
@@ -375,7 +381,7 @@ Http::delete('/v1/teams/:teamId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::post('/v1/teams/:teamId/memberships')
|
||||
App::post('/v1/teams/:teamId/memberships')
|
||||
->desc('Create team membership')
|
||||
->groups(['api', 'teams', 'auth'])
|
||||
->label('event', 'teams.[teamId].memberships.[membershipId].create')
|
||||
@@ -396,7 +402,17 @@ Http::post('/v1/teams/:teamId/memberships')
|
||||
->param('email', '', new Email(), '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', [], 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.')
|
||||
->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]);
|
||||
});
|
||||
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 ($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // TODO add our own built-in confirm page
|
||||
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
@@ -407,10 +423,9 @@ Http::post('/v1/teams/:teamId/memberships')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->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, Authorization $authorization) {
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents) {
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
$url = htmlentities($url);
|
||||
if (empty($url)) {
|
||||
@@ -422,8 +437,8 @@ Http::post('/v1/teams/:teamId/memberships')
|
||||
if (empty($userId) && empty($email) && empty($phone)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'At least one of userId, email, or phone is required');
|
||||
}
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAppUser = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser && empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED);
|
||||
@@ -483,7 +498,7 @@ Http::post('/v1/teams/:teamId/memberships')
|
||||
|
||||
try {
|
||||
$userId = ID::unique();
|
||||
$invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', new Document([
|
||||
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
@@ -519,7 +534,7 @@ Http::post('/v1/teams/:teamId/memberships')
|
||||
}
|
||||
}
|
||||
|
||||
$isOwner = $authorization->isRole('team:' . $team->getId() . '/owner');
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team');
|
||||
@@ -551,12 +566,12 @@ Http::post('/v1/teams/:teamId/memberships')
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership
|
||||
try {
|
||||
$membership = $authorization->skip(fn () => $dbForProject->createDocument('memberships', $membership));
|
||||
$membership = Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $invitee->getId());
|
||||
} else {
|
||||
@@ -578,7 +593,7 @@ Http::post('/v1/teams/:teamId/memberships')
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message
|
||||
->setParam('{{body}}', $body, escape: false)
|
||||
->setParam('{{body}}', $body, escapeHtml: false)
|
||||
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
|
||||
@@ -696,7 +711,7 @@ Http::post('/v1/teams/:teamId/memberships')
|
||||
);
|
||||
});
|
||||
|
||||
Http::get('/v1/teams/:teamId/memberships')
|
||||
App::get('/v1/teams/:teamId/memberships')
|
||||
->desc('List team memberships')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
@@ -743,6 +758,13 @@ Http::get('/v1/teams/:teamId/memberships')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
|
||||
$membershipId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('memberships', $membershipId);
|
||||
|
||||
@@ -799,7 +821,7 @@ Http::get('/v1/teams/:teamId/memberships')
|
||||
]), Response::MODEL_MEMBERSHIP_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->desc('Get team membership')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
@@ -855,7 +877,7 @@ Http::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
|
||||
});
|
||||
|
||||
Http::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->desc('Update membership')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].memberships.[membershipId].update')
|
||||
@@ -871,14 +893,23 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
|
||||
->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]);
|
||||
});
|
||||
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}
|
||||
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}, 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
|
||||
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
if ($team->isEmpty()) {
|
||||
@@ -895,9 +926,9 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAppUser = Auth::isAppUser($authorization->getRoles());
|
||||
$isOwner = $authorization->isRole('team:' . $team->getId() . '/owner');
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to modify roles');
|
||||
@@ -928,7 +959,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
);
|
||||
});
|
||||
|
||||
Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->desc('Update team membership status')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].memberships.[membershipId].update.status')
|
||||
@@ -954,9 +985,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->inject('authentication')
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Authorization $authorization, Authentication $authentication) {
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
@@ -965,7 +994,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$team = $authorization->skip(fn () => $dbForProject->getDocument('teams', $teamId));
|
||||
$team = Authorization::skip(fn () => $dbForProject->getDocument('teams', $teamId));
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
@@ -1000,11 +1029,11 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->setAttribute('confirm', true)
|
||||
;
|
||||
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
|
||||
// Log user in
|
||||
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
@@ -1034,13 +1063,13 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
$authorization->addRole(Role::user($userId)->toString());
|
||||
Authorization::setRole(Role::user($userId)->toString());
|
||||
|
||||
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
@@ -1050,13 +1079,13 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
$response
|
||||
->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $secret)]))
|
||||
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
|
||||
;
|
||||
}
|
||||
|
||||
$response
|
||||
->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
;
|
||||
|
||||
$response->dynamic(
|
||||
@@ -1068,7 +1097,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
);
|
||||
});
|
||||
|
||||
Http::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->desc('Delete team membership')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].memberships.[membershipId].delete')
|
||||
@@ -1086,8 +1115,7 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
|
||||
->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
|
||||
@@ -1122,7 +1150,7 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
if ($membership->getAttribute('confirm')) { // Count only confirmed members
|
||||
$authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
|
||||
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
@@ -1135,7 +1163,7 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::get('/v1/teams/:teamId/logs')
|
||||
App::get('/v1/teams/:teamId/logs')
|
||||
->desc('List team logs')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
@@ -1152,8 +1180,7 @@ Http::get('/v1/teams/:teamId/logs')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
|
||||
->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Users;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
@@ -35,19 +36,19 @@ 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\Database\Validator\UID;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\ArrayList;
|
||||
use Utopia\Http\Validator\Assoc;
|
||||
use Utopia\Http\Validator\Boolean;
|
||||
use Utopia\Http\Validator\Integer;
|
||||
use Utopia\Http\Validator\Range;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
/** TODO: Remove function when we move to using utopia/platform */
|
||||
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks): Document
|
||||
@@ -180,7 +181,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
||||
return $user;
|
||||
}
|
||||
|
||||
Http::post('/v1/users')
|
||||
App::post('/v1/users')
|
||||
->desc('Create user')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
@@ -211,7 +212,7 @@ Http::post('/v1/users')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::post('/v1/users/bcrypt')
|
||||
App::post('/v1/users/bcrypt')
|
||||
->desc('Create user with bcrypt password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
@@ -242,7 +243,7 @@ Http::post('/v1/users/bcrypt')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::post('/v1/users/md5')
|
||||
App::post('/v1/users/md5')
|
||||
->desc('Create user with MD5 password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
@@ -273,7 +274,7 @@ Http::post('/v1/users/md5')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::post('/v1/users/argon2')
|
||||
App::post('/v1/users/argon2')
|
||||
->desc('Create user with Argon2 password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
@@ -304,7 +305,7 @@ Http::post('/v1/users/argon2')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::post('/v1/users/sha')
|
||||
App::post('/v1/users/sha')
|
||||
->desc('Create user with SHA password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
@@ -342,7 +343,7 @@ Http::post('/v1/users/sha')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::post('/v1/users/phpass')
|
||||
App::post('/v1/users/phpass')
|
||||
->desc('Create user with PHPass password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
@@ -373,7 +374,7 @@ Http::post('/v1/users/phpass')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::post('/v1/users/scrypt')
|
||||
App::post('/v1/users/scrypt')
|
||||
->desc('Create user with Scrypt password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
@@ -417,7 +418,7 @@ Http::post('/v1/users/scrypt')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::post('/v1/users/scrypt-modified')
|
||||
App::post('/v1/users/scrypt-modified')
|
||||
->desc('Create user with Scrypt modified password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
@@ -451,8 +452,8 @@ Http::post('/v1/users/scrypt-modified')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::post('/v1/users/:userId/targets')
|
||||
->desc('Create User Target')
|
||||
App::post('/v1/users/:userId/targets')
|
||||
->desc('Create user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.create')
|
||||
->label('audits.resource', 'target/response.$id')
|
||||
@@ -540,7 +541,7 @@ Http::post('/v1/users/:userId/targets')
|
||||
->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
Http::get('/v1/users')
|
||||
App::get('/v1/users')
|
||||
->desc('List users')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -576,6 +577,12 @@ Http::get('/v1/users')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$userId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -594,7 +601,7 @@ Http::get('/v1/users')
|
||||
]), Response::MODEL_USER_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/users/:userId')
|
||||
App::get('/v1/users/:userId')
|
||||
->desc('Get user')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -619,7 +626,7 @@ Http::get('/v1/users/:userId')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::get('/v1/users/:userId/prefs')
|
||||
App::get('/v1/users/:userId/prefs')
|
||||
->desc('Get user preferences')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -646,8 +653,8 @@ Http::get('/v1/users/:userId/prefs')
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
Http::get('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Get User Target')
|
||||
App::get('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Get user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'targets.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
@@ -678,7 +685,7 @@ Http::get('/v1/users/:userId/targets/:targetId')
|
||||
$response->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
Http::get('/v1/users/:userId/sessions')
|
||||
App::get('/v1/users/:userId/sessions')
|
||||
->desc('List user sessions')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -719,7 +726,7 @@ Http::get('/v1/users/:userId/sessions')
|
||||
]), Response::MODEL_SESSION_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/users/:userId/memberships')
|
||||
App::get('/v1/users/:userId/memberships')
|
||||
->desc('List user memberships')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -758,7 +765,7 @@ Http::get('/v1/users/:userId/memberships')
|
||||
]), Response::MODEL_MEMBERSHIP_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/users/:userId/logs')
|
||||
App::get('/v1/users/:userId/logs')
|
||||
->desc('List user logs')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -775,8 +782,7 @@ Http::get('/v1/users/:userId/logs')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
|
||||
->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -848,8 +854,8 @@ Http::get('/v1/users/:userId/logs')
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/users/:userId/targets')
|
||||
->desc('List User Targets')
|
||||
App::get('/v1/users/:userId/targets')
|
||||
->desc('List user targets')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'targets.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
@@ -887,6 +893,11 @@ Http::get('/v1/users/:userId/targets')
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$targetId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('targets', $targetId);
|
||||
|
||||
@@ -903,8 +914,8 @@ Http::get('/v1/users/:userId/targets')
|
||||
]), Response::MODEL_TARGET_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/users/identities')
|
||||
->desc('List Identities')
|
||||
App::get('/v1/users/identities')
|
||||
->desc('List identities')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
@@ -939,6 +950,12 @@ Http::get('/v1/users/identities')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$identityId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('identities', $identityId);
|
||||
|
||||
@@ -957,7 +974,7 @@ Http::get('/v1/users/identities')
|
||||
]), Response::MODEL_IDENTITY_LIST);
|
||||
});
|
||||
|
||||
Http::patch('/v1/users/:userId/status')
|
||||
App::patch('/v1/users/:userId/status')
|
||||
->desc('Update user status')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.status')
|
||||
@@ -993,7 +1010,7 @@ Http::patch('/v1/users/:userId/status')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::put('/v1/users/:userId/labels')
|
||||
App::put('/v1/users/:userId/labels')
|
||||
->desc('Update user labels')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.labels')
|
||||
@@ -1030,7 +1047,7 @@ Http::put('/v1/users/:userId/labels')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/users/:userId/verification/phone')
|
||||
App::patch('/v1/users/:userId/verification/phone')
|
||||
->desc('Update phone verification')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.verification')
|
||||
@@ -1065,7 +1082,7 @@ Http::patch('/v1/users/:userId/verification/phone')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/users/:userId/name')
|
||||
App::patch('/v1/users/:userId/name')
|
||||
->desc('Update name')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.name')
|
||||
@@ -1102,7 +1119,7 @@ Http::patch('/v1/users/:userId/name')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/users/:userId/password')
|
||||
App::patch('/v1/users/:userId/password')
|
||||
->desc('Update password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.password')
|
||||
@@ -1179,7 +1196,7 @@ Http::patch('/v1/users/:userId/password')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/users/:userId/email')
|
||||
App::patch('/v1/users/:userId/email')
|
||||
->desc('Update email')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.email')
|
||||
@@ -1274,7 +1291,7 @@ Http::patch('/v1/users/:userId/email')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/users/:userId/phone')
|
||||
App::patch('/v1/users/:userId/phone')
|
||||
->desc('Update phone')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.phone')
|
||||
@@ -1357,7 +1374,7 @@ Http::patch('/v1/users/:userId/phone')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/users/:userId/verification')
|
||||
App::patch('/v1/users/:userId/verification')
|
||||
->desc('Update email verification')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.verification')
|
||||
@@ -1392,7 +1409,7 @@ Http::patch('/v1/users/:userId/verification')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::patch('/v1/users/:userId/prefs')
|
||||
App::patch('/v1/users/:userId/prefs')
|
||||
->desc('Update user preferences')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.prefs')
|
||||
@@ -1425,8 +1442,8 @@ Http::patch('/v1/users/:userId/prefs')
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
Http::patch('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Update User target')
|
||||
App::patch('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Update user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.update')
|
||||
->label('audits.resource', 'target/{response.$id}')
|
||||
@@ -1486,7 +1503,9 @@ Http::patch('/v1/users/:userId/targets/:targetId')
|
||||
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
|
||||
}
|
||||
|
||||
$target->setAttribute('identifier', $identifier);
|
||||
$target
|
||||
->setAttribute('identifier', $identifier)
|
||||
->setAttribute('expired', false);
|
||||
}
|
||||
|
||||
if ($providerId) {
|
||||
@@ -1500,8 +1519,9 @@ Http::patch('/v1/users/:userId/targets/:targetId')
|
||||
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
|
||||
}
|
||||
|
||||
$target->setAttribute('providerId', $provider->getId());
|
||||
$target->setAttribute('providerInternalId', $provider->getInternalId());
|
||||
$target
|
||||
->setAttribute('providerId', $provider->getId())
|
||||
->setAttribute('providerInternalId', $provider->getInternalId());
|
||||
}
|
||||
|
||||
if ($name) {
|
||||
@@ -1519,7 +1539,7 @@ Http::patch('/v1/users/:userId/targets/:targetId')
|
||||
->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
Http::patch('/v1/users/:userId/mfa')
|
||||
App::patch('/v1/users/:userId/mfa')
|
||||
->desc('Update MFA')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.mfa')
|
||||
@@ -1557,8 +1577,8 @@ Http::patch('/v1/users/:userId/mfa')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
Http::get('/v1/users/:userId/mfa/factors')
|
||||
->desc('List Factors')
|
||||
App::get('/v1/users/:userId/mfa/factors')
|
||||
->desc('List factors')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
->label('usage.metric', 'users.{scope}.requests.read')
|
||||
@@ -1590,8 +1610,8 @@ Http::get('/v1/users/:userId/mfa/factors')
|
||||
$response->dynamic($factors, Response::MODEL_MFA_FACTORS);
|
||||
});
|
||||
|
||||
Http::get('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Get MFA Recovery Codes')
|
||||
App::get('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Get MFA recovery codes')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
->label('usage.metric', 'users.{scope}.requests.read')
|
||||
@@ -1625,8 +1645,8 @@ Http::get('/v1/users/:userId/mfa/recovery-codes')
|
||||
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
|
||||
});
|
||||
|
||||
Http::patch('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Create MFA Recovery Codes')
|
||||
App::patch('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Create MFA recovery codes')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create.mfa.recovery-codes')
|
||||
->label('scope', 'users.write')
|
||||
@@ -1671,8 +1691,8 @@ Http::patch('/v1/users/:userId/mfa/recovery-codes')
|
||||
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
|
||||
});
|
||||
|
||||
Http::put('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Regenerate MFA Recovery Codes')
|
||||
App::put('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Regenerate MFA recovery codes')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.mfa.recovery-codes')
|
||||
->label('scope', 'users.write')
|
||||
@@ -1716,8 +1736,8 @@ Http::put('/v1/users/:userId/mfa/recovery-codes')
|
||||
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
|
||||
});
|
||||
|
||||
Http::delete('/v1/users/:userId/mfa/authenticators/:type')
|
||||
->desc('Delete Authenticator')
|
||||
App::delete('/v1/users/:userId/mfa/authenticators/:type')
|
||||
->desc('Delete authenticator')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].delete.mfa')
|
||||
->label('scope', 'users.write')
|
||||
@@ -1758,7 +1778,7 @@ Http::delete('/v1/users/:userId/mfa/authenticators/:type')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::post('/v1/users/:userId/sessions')
|
||||
App::post('/v1/users/:userId/sessions')
|
||||
->desc('Create session')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
@@ -1828,7 +1848,7 @@ Http::post('/v1/users/:userId/sessions')
|
||||
->dynamic($session, Response::MODEL_SESSION);
|
||||
});
|
||||
|
||||
Http::post('/v1/users/:userId/tokens')
|
||||
App::post('/v1/users/:userId/tokens')
|
||||
->desc('Create token')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].tokens.[tokenId].create')
|
||||
@@ -1885,7 +1905,7 @@ Http::post('/v1/users/:userId/tokens')
|
||||
->dynamic($token, Response::MODEL_TOKEN);
|
||||
});
|
||||
|
||||
Http::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
App::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
->desc('Delete user session')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].sessions.[sessionId].delete')
|
||||
@@ -1928,7 +1948,7 @@ Http::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::delete('/v1/users/:userId/sessions')
|
||||
App::delete('/v1/users/:userId/sessions')
|
||||
->desc('Delete user sessions')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].sessions.delete')
|
||||
@@ -1970,7 +1990,7 @@ Http::delete('/v1/users/:userId/sessions')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::delete('/v1/users/:userId')
|
||||
App::delete('/v1/users/:userId')
|
||||
->desc('Delete user')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].delete')
|
||||
@@ -2012,7 +2032,7 @@ Http::delete('/v1/users/:userId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::delete('/v1/users/:userId/targets/:targetId')
|
||||
App::delete('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Delete user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.delete')
|
||||
@@ -2063,7 +2083,7 @@ Http::delete('/v1/users/:userId/targets/:targetId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::delete('/v1/users/identities/:identityId')
|
||||
App::delete('/v1/users/identities/:identityId')
|
||||
->desc('Delete identity')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].identities.[identityId].delete')
|
||||
@@ -2098,7 +2118,7 @@ Http::delete('/v1/users/identities/:identityId')
|
||||
return $response->noContent();
|
||||
});
|
||||
|
||||
Http::post('/v1/users/:userId/jwts')
|
||||
App::post('/v1/users/:userId/jwts')
|
||||
->desc('Create user JWT')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.write')
|
||||
@@ -2148,7 +2168,7 @@ Http::post('/v1/users/:userId/jwts')
|
||||
])]), Response::MODEL_JWT);
|
||||
});
|
||||
|
||||
Http::get('/v1/users/usage')
|
||||
App::get('/v1/users/usage')
|
||||
->desc('Get users usage stats')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
@@ -2161,8 +2181,8 @@ Http::get('/v1/users/usage')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
->inject('register')
|
||||
->action(function (string $range, Response $response, Database $dbForProject) {
|
||||
|
||||
$periods = Config::getParam('usage', []);
|
||||
$stats = $usage = [];
|
||||
@@ -2172,7 +2192,7 @@ Http::get('/v1/users/usage')
|
||||
METRIC_SESSIONS,
|
||||
];
|
||||
|
||||
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $count => $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
|
||||
+51
-45
@@ -8,6 +8,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Installations;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\Comment;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
@@ -19,6 +20,7 @@ use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Detector\Adapter\Bun;
|
||||
use Utopia\Detector\Adapter\CPP;
|
||||
use Utopia\Detector\Adapter\Dart;
|
||||
@@ -31,17 +33,17 @@ use Utopia\Detector\Adapter\Python;
|
||||
use Utopia\Detector\Adapter\Ruby;
|
||||
use Utopia\Detector\Adapter\Swift;
|
||||
use Utopia\Detector\Detector;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\Boolean;
|
||||
use Utopia\Http\Validator\Host;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
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 $dbForConsole, Build $queueForBuilds, callable $getProjectDB, Request $request, Authorization $auth) {
|
||||
$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 $dbForConsole, Build $queueForBuilds, callable $getProjectDB, Request $request) {
|
||||
$errors = [];
|
||||
foreach ($repositories as $resource) {
|
||||
try {
|
||||
$resourceType = $resource->getAttribute('resourceType');
|
||||
@@ -51,11 +53,11 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
}
|
||||
|
||||
$projectId = $resource->getAttribute('projectId');
|
||||
$project = $auth->skip(fn () => $dbForConsole->getDocument('projects', $projectId));
|
||||
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$functionId = $resource->getAttribute('resourceId');
|
||||
$function = $auth->skip(fn () => $dbForProject->getDocument('functions', $functionId));
|
||||
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
|
||||
$functionInternalId = $function->getInternalId();
|
||||
|
||||
$deploymentId = ID::unique();
|
||||
@@ -101,8 +103,8 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
|
||||
$latestCommentId = '';
|
||||
|
||||
if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false)) {
|
||||
$latestComment = $auth->skip(fn () => $dbForConsole->findOne('vcsComments', [
|
||||
if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false) === false) {
|
||||
$latestComment = Authorization::skip(fn () => $dbForConsole->findOne('vcsComments', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::equal('providerPullRequestId', [$providerPullRequestId]),
|
||||
Query::orderDesc('$createdAt'),
|
||||
@@ -123,7 +125,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
if (!empty($latestCommentId)) {
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
|
||||
$latestComment = $auth->skip(fn () => $dbForConsole->createDocument('vcsComments', new Document([
|
||||
$latestComment = Authorization::skip(fn () => $dbForConsole->createDocument('vcsComments', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
@@ -144,7 +146,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
}
|
||||
}
|
||||
} elseif (!empty($providerBranch)) {
|
||||
$latestComments = $auth->skip(fn () => $dbForConsole->find('vcsComments', [
|
||||
$latestComments = Authorization::skip(fn () => $dbForConsole->find('vcsComments', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::equal('providerBranch', [$providerBranch]),
|
||||
Query::orderDesc('$createdAt'),
|
||||
@@ -261,8 +263,8 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
}
|
||||
};
|
||||
|
||||
Http::get('/v1/vcs/github/authorize')
|
||||
->desc('Install GitHub App')
|
||||
App::get('/v1/vcs/github/authorize')
|
||||
->desc('Install GitHub app')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
->label('sdk.namespace', 'vcs')
|
||||
@@ -303,8 +305,8 @@ Http::get('/v1/vcs/github/authorize')
|
||||
->redirect($url);
|
||||
});
|
||||
|
||||
Http::get('/v1/vcs/github/callback')
|
||||
->desc('Capture installation and authorization from GitHub App')
|
||||
App::get('/v1/vcs/github/callback')
|
||||
->desc('Capture installation and authorization from GitHub app')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'public')
|
||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||
@@ -463,7 +465,7 @@ Http::get('/v1/vcs/github/callback')
|
||||
->redirect($redirectSuccess);
|
||||
});
|
||||
|
||||
Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/contents')
|
||||
App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/contents')
|
||||
->desc('Get files and directories of a VCS repository')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
@@ -524,7 +526,7 @@ Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
|
||||
]), Response::MODEL_VCS_CONTENT_LIST);
|
||||
});
|
||||
|
||||
Http::post('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection')
|
||||
App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection')
|
||||
->desc('Detect runtime settings from source code')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.write')
|
||||
@@ -596,8 +598,8 @@ Http::post('/v1/vcs/github/installations/:installationId/providerRepositories/:p
|
||||
$response->dynamic(new Document($detection), Response::MODEL_DETECTION);
|
||||
});
|
||||
|
||||
Http::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
->desc('List Repositories')
|
||||
App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
->desc('List repositories')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
->label('sdk.namespace', 'vcs')
|
||||
@@ -691,7 +693,7 @@ Http::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
]), Response::MODEL_PROVIDER_REPOSITORY_LIST);
|
||||
});
|
||||
|
||||
Http::post('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
App::post('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
->desc('Create repository')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.write')
|
||||
@@ -792,7 +794,7 @@ Http::post('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
$response->dynamic(new Document($repository), Response::MODEL_PROVIDER_REPOSITORY);
|
||||
});
|
||||
|
||||
Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId')
|
||||
App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId')
|
||||
->desc('Get repository')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
@@ -841,8 +843,8 @@ Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
|
||||
$response->dynamic(new Document($repository), Response::MODEL_PROVIDER_REPOSITORY);
|
||||
});
|
||||
|
||||
Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches')
|
||||
->desc('List Repository Branches')
|
||||
App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches')
|
||||
->desc('List repository branches')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
->label('sdk.namespace', 'vcs')
|
||||
@@ -890,8 +892,8 @@ Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
|
||||
]), Response::MODEL_BRANCH_LIST);
|
||||
});
|
||||
|
||||
Http::post('/v1/vcs/github/events')
|
||||
->desc('Create Event')
|
||||
App::post('/v1/vcs/github/events')
|
||||
->desc('Create event')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'public')
|
||||
->inject('gitHub')
|
||||
@@ -900,9 +902,8 @@ Http::post('/v1/vcs/github/events')
|
||||
->inject('dbForConsole')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->inject('authorization')
|
||||
->action(
|
||||
function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds, Authorization $authorization) use ($createGitDeployments) {
|
||||
function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
$payload = $request->getRawPayload();
|
||||
$signatureRemote = $request->getHeader('x-hub-signature-256', '');
|
||||
$signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', '');
|
||||
@@ -936,14 +937,14 @@ Http::post('/v1/vcs/github/events')
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
//find functionId from functions table
|
||||
$repositories = $authorization->skip(fn () => $dbForConsole->find('repositories', [
|
||||
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::limit(100),
|
||||
]));
|
||||
|
||||
// create new deployment only on push and not when branch is created
|
||||
if (!$providerBranchCreated) {
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $authorization);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
|
||||
}
|
||||
} elseif ($event == $github::EVENT_INSTALLATION) {
|
||||
if ($parsedPayload["action"] == "deleted") {
|
||||
@@ -956,13 +957,13 @@ Http::post('/v1/vcs/github/events')
|
||||
]);
|
||||
|
||||
foreach ($installations as $installation) {
|
||||
$repositories = $authorization->skip(fn () => $dbForConsole->find('repositories', [
|
||||
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
|
||||
Query::equal('installationInternalId', [$installation->getInternalId()]),
|
||||
Query::limit(1000)
|
||||
]));
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
$authorization->skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId()));
|
||||
Authorization::skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId()));
|
||||
}
|
||||
|
||||
$dbForConsole->deleteDocument('installations', $installation->getId());
|
||||
@@ -994,12 +995,12 @@ Http::post('/v1/vcs/github/events')
|
||||
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
|
||||
$providerCommitMessage = $commitDetails["commitMessage"] ?? '';
|
||||
|
||||
$repositories = $authorization->skip(fn () => $dbForConsole->find('repositories', [
|
||||
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::orderDesc('$createdAt')
|
||||
]));
|
||||
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $authorization);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
|
||||
} elseif ($parsedPayload["action"] == "closed") {
|
||||
// Allowed external contributions cleanup
|
||||
|
||||
@@ -1008,7 +1009,7 @@ Http::post('/v1/vcs/github/events')
|
||||
$external = $parsedPayload["external"] ?? true;
|
||||
|
||||
if ($external) {
|
||||
$repositories = $authorization->skip(fn () => $dbForConsole->find('repositories', [
|
||||
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::orderDesc('$createdAt')
|
||||
]));
|
||||
@@ -1019,7 +1020,7 @@ Http::post('/v1/vcs/github/events')
|
||||
if (\in_array($providerPullRequestId, $providerPullRequestIds)) {
|
||||
$providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]);
|
||||
$repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds);
|
||||
$repository = $authorization->skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
|
||||
$repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1030,7 +1031,7 @@ Http::post('/v1/vcs/github/events')
|
||||
}
|
||||
);
|
||||
|
||||
Http::get('/v1/vcs/installations')
|
||||
App::get('/v1/vcs/installations')
|
||||
->desc('List installations')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
@@ -1069,6 +1070,12 @@ Http::get('/v1/vcs/installations')
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$installationId = $cursor->getValue();
|
||||
$cursorDocument = $dbForConsole->getDocument('installations', $installationId);
|
||||
|
||||
@@ -1090,7 +1097,7 @@ Http::get('/v1/vcs/installations')
|
||||
]), Response::MODEL_INSTALLATION_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/vcs/installations/:installationId')
|
||||
App::get('/v1/vcs/installations/:installationId')
|
||||
->desc('Get installation')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
@@ -1119,8 +1126,8 @@ Http::get('/v1/vcs/installations/:installationId')
|
||||
$response->dynamic($installation, Response::MODEL_INSTALLATION);
|
||||
});
|
||||
|
||||
Http::delete('/v1/vcs/installations/:installationId')
|
||||
->desc('Delete Installation')
|
||||
App::delete('/v1/vcs/installations/:installationId')
|
||||
->desc('Delete installation')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.write')
|
||||
->label('sdk.namespace', 'vcs')
|
||||
@@ -1152,7 +1159,7 @@ Http::delete('/v1/vcs/installations/:installationId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::patch('/v1/vcs/github/installations/:installationId/repositories/:repositoryId')
|
||||
App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositoryId')
|
||||
->desc('Authorize external deployment')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.write')
|
||||
@@ -1172,15 +1179,14 @@ Http::patch('/v1/vcs/github/installations/:installationId/repositories/:reposito
|
||||
->inject('dbForConsole')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->inject('authorization')
|
||||
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds, Authorization $authorization) use ($createGitDeployments) {
|
||||
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$repository = $authorization->skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [
|
||||
$repository = Authorization::skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()])
|
||||
]));
|
||||
|
||||
@@ -1197,7 +1203,7 @@ Http::patch('/v1/vcs/github/installations/:installationId/repositories/:reposito
|
||||
|
||||
// TODO: Delete from array when PR is closed
|
||||
|
||||
$repository = $authorization->skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
|
||||
$repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
|
||||
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
@@ -1221,7 +1227,7 @@ Http::patch('/v1/vcs/github/installations/:installationId/repositories/:reposito
|
||||
$providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? '';
|
||||
$providerCommitHash = $pullRequestResponse['head']['sha'] ?? '';
|
||||
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $authorization);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
+169
-152
@@ -1,13 +1,15 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Usage;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Utopia\Queue\Connections;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
|
||||
use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
|
||||
@@ -19,44 +21,45 @@ use Appwrite\Utopia\Response\Filters\V18 as ResponseV18;
|
||||
use Appwrite\Utopia\View;
|
||||
use Executor\Executor;
|
||||
use MaxMind\Db\Reader;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Route;
|
||||
use Utopia\Http\Validator\Hostname;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Logger\Adapter\Sentry;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
Config::setParam('domainVerification', false);
|
||||
Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
function router(Database $dbForConsole, callable $getProjectDB, Request $request, Response $response, Route $route, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, Authorization $auth)
|
||||
function router(App $utopia, Database $dbForConsole, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked)
|
||||
{
|
||||
$route?->label('error', __DIR__ . '/../views/general/error.phtml');
|
||||
$utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
|
||||
|
||||
$host = $request->getHostname() ?? '';
|
||||
|
||||
$rule = $auth->skip(
|
||||
$route = Authorization::skip(
|
||||
fn () => $dbForConsole->find('rules', [
|
||||
Query::equal('domain', [$host]),
|
||||
Query::limit(1)
|
||||
])
|
||||
)[0] ?? null;
|
||||
|
||||
if ($rule === null) {
|
||||
if ($route === null) {
|
||||
if ($host === System::getEnv('_APP_DOMAIN_FUNCTIONS', '')) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain cannot be used for security reasons. Please use any subdomain instead.');
|
||||
}
|
||||
@@ -72,12 +75,12 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
|
||||
}
|
||||
|
||||
// Act as API - no Proxy logic
|
||||
$route?->label('error', '');
|
||||
$utopia->getRoute()?->label('error', '');
|
||||
return false;
|
||||
}
|
||||
|
||||
$projectId = $rule->getAttribute('projectId');
|
||||
$project = $auth->skip(
|
||||
$projectId = $route->getAttribute('projectId');
|
||||
$project = Authorization::skip(
|
||||
fn () => $dbForConsole->getDocument('projects', $projectId)
|
||||
);
|
||||
if (array_key_exists('proxy', $project->getAttribute('services', []))) {
|
||||
@@ -88,16 +91,16 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
|
||||
}
|
||||
|
||||
// Skip Appwrite Router for ACME challenge. Nessessary for certificate generation
|
||||
$path = ($request->getURI() ?? '/');
|
||||
$path = ($swooleRequest->server['request_uri'] ?? '/');
|
||||
if (\str_starts_with($path, '/.well-known/acme-challenge')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = $rule->getAttribute('resourceType');
|
||||
$type = $route->getAttribute('resourceType');
|
||||
|
||||
if ($type === 'function') {
|
||||
$route->label('sdk.namespace', 'functions');
|
||||
$route->label('sdk.method', 'createExecution');
|
||||
$utopia->getRoute()?->label('sdk.namespace', 'functions');
|
||||
$utopia->getRoute()?->label('sdk.method', 'createExecution');
|
||||
|
||||
if (System::getEnv('_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
|
||||
if ($request->getProtocol() !== 'https') {
|
||||
@@ -109,30 +112,35 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
|
||||
}
|
||||
}
|
||||
|
||||
$functionId = $rule->getAttribute('resourceId');
|
||||
$projectId = $rule->getAttribute('projectId');
|
||||
$functionId = $route->getAttribute('resourceId');
|
||||
$projectId = $route->getAttribute('projectId');
|
||||
|
||||
$path = ($request->getURI() ?? '/');
|
||||
$query = ($request->getQueryString() ?? '');
|
||||
$path = ($swooleRequest->server['request_uri'] ?? '/');
|
||||
$query = ($swooleRequest->server['query_string'] ?? '');
|
||||
if (!empty($query)) {
|
||||
$path .= '?' . $query;
|
||||
}
|
||||
|
||||
$body = $request->getRawPayload() ?? '';
|
||||
$method = $request->getMethod();
|
||||
|
||||
$body = $swooleRequest->getContent() ?? '';
|
||||
$method = $swooleRequest->server['request_method'];
|
||||
|
||||
$requestHeaders = $request->getHeaders();
|
||||
|
||||
$project = $auth->skip(fn () => $dbForConsole->getDocument('projects', $projectId));
|
||||
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
|
||||
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$function = $auth->skip(fn () => $dbForProject->getDocument('functions', $functionId));
|
||||
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
|
||||
|
||||
if ($function->isEmpty() || !$function->getAttribute('enabled')) {
|
||||
throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($isResourceBlocked($project, 'functions', $functionId)) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_RESOURCE_BLOCKED);
|
||||
}
|
||||
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
|
||||
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT)];
|
||||
@@ -143,7 +151,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
|
||||
throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
|
||||
}
|
||||
|
||||
$deployment = $auth->skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
|
||||
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
|
||||
throw new AppwriteException(AppwriteException::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
|
||||
@@ -154,7 +162,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
|
||||
}
|
||||
|
||||
/** Check if build has completed */
|
||||
$build = $auth->skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
if ($build->isEmpty()) {
|
||||
throw new AppwriteException(AppwriteException::BUILD_NOT_FOUND);
|
||||
}
|
||||
@@ -215,7 +223,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
|
||||
'deploymentInternalId' => $deployment->getInternalId(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'trigger' => 'http', // http / schedule / event
|
||||
'status' => 'processing', // waiting / processing / completed / failed
|
||||
'status' => 'processing', // waiting / processing / completed / failed
|
||||
'responseStatusCode' => 0,
|
||||
'responseHeaders' => [],
|
||||
'requestPath' => $path,
|
||||
@@ -311,6 +319,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
|
||||
cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
|
||||
memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
|
||||
logging: $function->getAttribute('logging', true),
|
||||
requestTimeout: 30
|
||||
);
|
||||
|
||||
$headersFiltered = [];
|
||||
@@ -328,6 +337,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
|
||||
$execution->setAttribute('logs', $executionResponse['logs']);
|
||||
$execution->setAttribute('errors', $executionResponse['errors']);
|
||||
$execution->setAttribute('duration', $executionResponse['duration']);
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
$durationEnd = \microtime(true);
|
||||
|
||||
@@ -362,8 +372,11 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
|
||||
->trigger()
|
||||
;
|
||||
|
||||
/** @var Document $execution */
|
||||
$execution = $auth->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
$queueForFunctions
|
||||
->setType(Func::TYPE_ASYNC_WRITE)
|
||||
->setExecution($execution)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$execution->setAttribute('logs', '');
|
||||
@@ -395,15 +408,18 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
|
||||
|
||||
return true;
|
||||
} elseif ($type === 'api') {
|
||||
$route?->label('error', '');
|
||||
$utopia->getRoute()?->label('error', '');
|
||||
return false;
|
||||
} else {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type);
|
||||
}
|
||||
|
||||
$utopia->getRoute()?->label('error', '');
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['api'])
|
||||
->inject('project')
|
||||
->inject('mode')
|
||||
@@ -414,7 +430,7 @@ Http::init()
|
||||
});
|
||||
*/
|
||||
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['database', 'functions', 'storage', 'messaging'])
|
||||
->inject('project')
|
||||
->inject('request')
|
||||
@@ -427,11 +443,12 @@ Http::init()
|
||||
}
|
||||
});
|
||||
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['api', 'web'])
|
||||
->inject('utopia')
|
||||
->inject('swooleRequest')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('route')
|
||||
->inject('console')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
@@ -443,27 +460,9 @@ Http::init()
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForCertificates')
|
||||
->inject('authorization')
|
||||
->action(function (
|
||||
Request $request,
|
||||
Response $response,
|
||||
Route $route,
|
||||
Document $console,
|
||||
Document $project,
|
||||
Database $dbForConsole,
|
||||
$getProjectDB,
|
||||
Locale $locale,
|
||||
array $localeCodes,
|
||||
array $clients,
|
||||
/**
|
||||
* @disregard P1009 Undefined type
|
||||
*/
|
||||
Reader $geodb,
|
||||
Usage $queueForUsage,
|
||||
Event $queueForEvents,
|
||||
Certificate $queueForCertificates,
|
||||
Authorization $authorization
|
||||
) {
|
||||
->inject('queueForFunctions')
|
||||
->inject('isResourceBlocked')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, callable $isResourceBlocked) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
@@ -471,7 +470,7 @@ Http::init()
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain) {
|
||||
if (router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization)) {
|
||||
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -479,8 +478,8 @@ Http::init()
|
||||
/*
|
||||
* Request format
|
||||
*/
|
||||
//$route = $utopia->getRoute();
|
||||
//Request::setRoute($route);
|
||||
$route = $utopia->getRoute();
|
||||
Request::setRoute($route);
|
||||
|
||||
if ($route === null) {
|
||||
return $response
|
||||
@@ -512,7 +511,7 @@ Http::init()
|
||||
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
|
||||
Console::warning('Skipping SSL certificates generation on ACME challenge.');
|
||||
} else {
|
||||
$authorization->disable();
|
||||
Authorization::disable();
|
||||
|
||||
$envDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$mainDomain = null;
|
||||
@@ -551,12 +550,12 @@ Http::init()
|
||||
}
|
||||
$domains[$domain->get()] = true;
|
||||
|
||||
$authorization->reset(); // ensure authorization is re-enabled
|
||||
Authorization::reset(); // ensure authorization is re-enabled
|
||||
}
|
||||
Config::setParam('domains', $domains);
|
||||
}
|
||||
|
||||
$localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
|
||||
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
|
||||
if (\in_array($localeParam, $localeCodes)) {
|
||||
$locale->setDefault($localeParam);
|
||||
}
|
||||
@@ -584,7 +583,7 @@ Http::init()
|
||||
Config::setParam(
|
||||
'domainVerification',
|
||||
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
|
||||
$endDomain->getRegisterable() !== ''
|
||||
$endDomain->getRegisterable() !== ''
|
||||
);
|
||||
|
||||
$isLocalHost = $request->getHostname() === 'localhost' || $request->getHostname() === 'localhost:' . $request->getPort();
|
||||
@@ -599,8 +598,8 @@ Http::init()
|
||||
? null
|
||||
: (
|
||||
$isConsoleProject && $isConsoleRootSession
|
||||
? '.' . $selfDomain->getRegisterable()
|
||||
: '.' . $request->getHostname()
|
||||
? '.' . $selfDomain->getRegisterable()
|
||||
: '.' . $request->getHostname()
|
||||
)
|
||||
);
|
||||
|
||||
@@ -619,7 +618,7 @@ Http::init()
|
||||
$response->addFilter(new ResponseV18());
|
||||
}
|
||||
if (version_compare($responseFormat, APP_VERSION_STABLE, '>')) {
|
||||
$response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is " . APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks");
|
||||
$response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is ". APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,9 +629,7 @@ Http::init()
|
||||
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
|
||||
*/
|
||||
if (System::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
|
||||
if ($request->getProtocol() !== 'https' // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
|
||||
&& ($request->getHeader('host') ?? '') !== 'localhost'
|
||||
&& ($request->getHeader('host') ?? '') !== APP_HOSTNAME_INTERNAL) {
|
||||
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->getMethod() !== Request::METHOD_GET) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.');
|
||||
}
|
||||
@@ -672,17 +669,19 @@ Http::init()
|
||||
}
|
||||
});
|
||||
|
||||
Http::options()
|
||||
->inject('route')
|
||||
App::options()
|
||||
->inject('utopia')
|
||||
->inject('swooleRequest')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->action(function (Route $route, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, Authorization $authorization) {
|
||||
->inject('isResourceBlocked')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
@@ -690,7 +689,7 @@ Http::options()
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain) {
|
||||
if (router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization)) {
|
||||
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -707,25 +706,18 @@ Http::options()
|
||||
->noContent();
|
||||
});
|
||||
|
||||
Http::error()
|
||||
App::error()
|
||||
->inject('error')
|
||||
->inject('user')
|
||||
->inject('route')
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('logger')
|
||||
->inject('log')
|
||||
->inject('authorization')
|
||||
->inject('connections')
|
||||
->inject('queueForUsage')
|
||||
->action(function (Throwable $error, Document $user, ?Route $route, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Authorization $authorization, Connections $connections, Usage $queueForUsage) {
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Usage $queueForUsage) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
if (is_null($route)) {
|
||||
$route = new Route($request->getMethod(), $request->getURI());
|
||||
}
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
$class = \get_class($error);
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
@@ -746,9 +738,9 @@ Http::error()
|
||||
Console::error('[Error] File: ' . $file);
|
||||
Console::error('[Error] Line: ' . $line);
|
||||
}
|
||||
|
||||
switch ($class) {
|
||||
case 'Utopia\Servers\Exception':
|
||||
case 'Utopia\Http\Exception':
|
||||
case 'Utopia\Exception':
|
||||
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
|
||||
switch ($code) {
|
||||
case 400:
|
||||
@@ -793,36 +785,35 @@ Http::error()
|
||||
} else {
|
||||
$publish = $error->getCode() === 0 || $error->getCode() >= 500;
|
||||
}
|
||||
|
||||
if ($error->getCode() >= 400 && $error->getCode() < 500) {
|
||||
// Register error logger
|
||||
$providerName = System::getEnv('_APP_EXPERIMENT_LOGGING_PROVIDER', '');
|
||||
$providerConfig = System::getEnv('_APP_EXPERIMENT_LOGGING_CONFIG', '');
|
||||
|
||||
if (!(empty($providerName) || empty($providerConfig))) {
|
||||
try {
|
||||
$loggingProvider = new DSN($providerConfig);
|
||||
$providerName = $loggingProvider->getScheme();
|
||||
try {
|
||||
$loggingProvider = new DSN($providerConfig ?? '');
|
||||
$providerName = $loggingProvider->getScheme();
|
||||
|
||||
if (!empty($providerName) && $providerName === 'sentry') {
|
||||
$key = $loggingProvider->getPassword();
|
||||
$projectId = $loggingProvider->getUser() ?? '';
|
||||
$host = 'https://' . $loggingProvider->getHost();
|
||||
if (!empty($providerName) && $providerName === 'sentry') {
|
||||
$key = $loggingProvider->getPassword();
|
||||
$projectId = $loggingProvider->getUser() ?? '';
|
||||
$host = 'https://' . $loggingProvider->getHost();
|
||||
|
||||
$adapter = new Sentry($projectId, $key, $host);
|
||||
$logger = new Logger($adapter);
|
||||
$logger->setSample(0.04);
|
||||
$publish = true;
|
||||
} else {
|
||||
throw new \Exception('Invalid experimental logging provider');
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning('Failed to initialize logging provider: ' . $th->getMessage());
|
||||
$adapter = new Sentry($projectId, $key, $host);
|
||||
$logger = new Logger($adapter);
|
||||
$logger->setSample(0.04);
|
||||
$publish = true;
|
||||
} else {
|
||||
throw new \Exception('Invalid experimental logging provider');
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning('Failed to initialize logging provider: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if ($publish && $project->getId() !== 'console') {
|
||||
if (!Auth::isPrivilegedUser($authorization->getRoles())) {
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
if (!empty($file)) {
|
||||
@@ -841,7 +832,14 @@ Http::error()
|
||||
}
|
||||
|
||||
|
||||
if ($logger && ($publish || $error->getCode() === 0)) {
|
||||
if ($logger && $publish) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $utopia->getResource('user');
|
||||
} catch (\Throwable) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
}
|
||||
@@ -871,7 +869,7 @@ Http::error()
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('roles', $authorization->getRoles());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
$log->setAction($action);
|
||||
@@ -879,13 +877,17 @@ Http::error()
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Log pushed with status code: ' . $responseCode);
|
||||
try {
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Error log pushed with status code: ' . $responseCode);
|
||||
} catch (Throwable $th) {
|
||||
Console::error('Error pushing log: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Wrap all exceptions inside Appwrite\Extend\Exception */
|
||||
if (!($error instanceof AppwriteException)) {
|
||||
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, (int)$code, $error);
|
||||
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
|
||||
}
|
||||
|
||||
switch ($code) { // Don't show 500 errors!
|
||||
@@ -905,14 +907,14 @@ Http::error()
|
||||
break;
|
||||
default:
|
||||
$code = 500; // All other errors get the generic 500 server error status code
|
||||
$message = (Http::getMode() === Http::MODE_TYPE_DEVELOPMENT) ? $message : 'Server Error';
|
||||
$message = 'Server Error';
|
||||
}
|
||||
|
||||
//$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
|
||||
|
||||
$type = $error->getType();
|
||||
|
||||
$output = ((Http::isDevelopment())) ? [
|
||||
$output = ((App::isDevelopment())) ? [
|
||||
'message' => $message,
|
||||
'code' => $code,
|
||||
'file' => $file,
|
||||
@@ -940,7 +942,7 @@ Http::error()
|
||||
|
||||
$layout
|
||||
->setParam('title', $project->getAttribute('name') . ' - Error')
|
||||
->setParam('development', Http::isDevelopment())
|
||||
->setParam('development', App::isDevelopment())
|
||||
->setParam('projectName', $project->getAttribute('name'))
|
||||
->setParam('projectURL', $project->getAttribute('url'))
|
||||
->setParam('message', $output['message'] ?? '')
|
||||
@@ -951,28 +953,28 @@ Http::error()
|
||||
$response->html($layout->render());
|
||||
}
|
||||
|
||||
$connections->reclaim();
|
||||
|
||||
$response->dynamic(
|
||||
new Document($output),
|
||||
Http::isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR
|
||||
$utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR
|
||||
);
|
||||
});
|
||||
|
||||
Http::get('/robots.txt')
|
||||
App::get('/robots.txt')
|
||||
->desc('Robots.txt File')
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->inject('utopia')
|
||||
->inject('swooleRequest')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('geodb')
|
||||
->inject('route')
|
||||
->inject('authorization')
|
||||
->action(function (Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, ?Route $route, Authorization $authorization) {
|
||||
->inject('isResourceBlocked')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked) {
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
@@ -980,27 +982,26 @@ Http::get('/robots.txt')
|
||||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (is_null($route)) {
|
||||
$route = new Route($request->getMethod(), $request->getURI());
|
||||
}
|
||||
router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization);
|
||||
router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked);
|
||||
}
|
||||
});
|
||||
|
||||
Http::get('/humans.txt')
|
||||
App::get('/humans.txt')
|
||||
->desc('Humans.txt File')
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->inject('utopia')
|
||||
->inject('swooleRequest')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('geodb')
|
||||
->inject('route')
|
||||
->inject('authorization')
|
||||
->action(function (Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, Route $route, Authorization $authorization) {
|
||||
->inject('isResourceBlocked')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked) {
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
@@ -1008,11 +1009,11 @@ Http::get('/humans.txt')
|
||||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization);
|
||||
router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked);
|
||||
}
|
||||
});
|
||||
|
||||
Http::get('/.well-known/acme-challenge/*')
|
||||
App::get('/.well-known/acme-challenge/*')
|
||||
->desc('SSL Verification')
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
@@ -1062,32 +1063,48 @@ Http::get('/.well-known/acme-challenge/*')
|
||||
$response->text($content);
|
||||
});
|
||||
|
||||
Http::wildcard()
|
||||
include_once __DIR__ . '/shared/api.php';
|
||||
include_once __DIR__ . '/shared/api/auth.php';
|
||||
|
||||
App::get('/v1/ping')
|
||||
->groups(['api', 'general'])
|
||||
->desc('Test the connection between the Appwrite and the SDK.')
|
||||
->label('scope', 'global')
|
||||
->label('event', 'projects.[projectId].ping')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('queueForEvents')
|
||||
->action(function (Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) {
|
||||
if ($project->isEmpty()) {
|
||||
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$pingCount = $project->getAttribute('pingCount', 0) + 1;
|
||||
$pingedAt = DateTime::now();
|
||||
|
||||
$project
|
||||
->setAttribute('pingCount', $pingCount)
|
||||
->setAttribute('pingedAt', $pingedAt);
|
||||
|
||||
Authorization::skip(function () use ($dbForConsole, $project) {
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project);
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
->setParam('projectId', $project->getId())
|
||||
->setPayload($response->output($project, Response::MODEL_PROJECT));
|
||||
|
||||
$response->text('Pong!');
|
||||
});
|
||||
|
||||
App::wildcard()
|
||||
->groups(['api'])
|
||||
->label('scope', 'global')
|
||||
->action(function () {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
});
|
||||
|
||||
include_once 'mock.php';
|
||||
include_once 'shared/api.php';
|
||||
include_once 'shared/api/auth.php';
|
||||
include_once 'api/account.php';
|
||||
include_once 'api/avatars.php';
|
||||
include_once 'api/console.php';
|
||||
include_once 'api/databases.php';
|
||||
include_once 'api/functions.php';
|
||||
include_once 'api/graphql.php';
|
||||
include_once 'api/health.php';
|
||||
include_once 'api/locale.php';
|
||||
include_once 'api/messaging.php';
|
||||
include_once 'api/migrations.php';
|
||||
include_once 'api/project.php';
|
||||
include_once 'api/projects.php';
|
||||
include_once 'api/proxy.php';
|
||||
include_once 'api/storage.php';
|
||||
include_once 'api/teams.php';
|
||||
include_once 'api/users.php';
|
||||
include_once 'api/vcs.php';
|
||||
include_once 'web/console.php';
|
||||
include_once 'web/home.php';
|
||||
foreach (Config::getParam('services', []) as $service) {
|
||||
include_once $service['controller'];
|
||||
}
|
||||
|
||||
+19
-17
@@ -3,7 +3,9 @@
|
||||
global $utopia, $request, $response;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
@@ -11,15 +13,13 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Route;
|
||||
use Utopia\Http\Validator\Host;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
|
||||
Http::get('/v1/mock/tests/general/oauth2')
|
||||
App::get('/v1/mock/tests/general/oauth2')
|
||||
->desc('OAuth Login')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
@@ -35,7 +35,7 @@ Http::get('/v1/mock/tests/general/oauth2')
|
||||
$response->redirect($redirectURI . '?' . \http_build_query(['code' => 'abcdef', 'state' => $state]));
|
||||
});
|
||||
|
||||
Http::get('/v1/mock/tests/general/oauth2/token')
|
||||
App::get('/v1/mock/tests/general/oauth2/token')
|
||||
->desc('OAuth2 Token')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
@@ -81,7 +81,7 @@ Http::get('/v1/mock/tests/general/oauth2/token')
|
||||
}
|
||||
});
|
||||
|
||||
Http::get('/v1/mock/tests/general/oauth2/user')
|
||||
App::get('/v1/mock/tests/general/oauth2/user')
|
||||
->desc('OAuth2 User')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
@@ -101,7 +101,7 @@ Http::get('/v1/mock/tests/general/oauth2/user')
|
||||
]);
|
||||
});
|
||||
|
||||
Http::get('/v1/mock/tests/general/oauth2/success')
|
||||
App::get('/v1/mock/tests/general/oauth2/success')
|
||||
->desc('OAuth2 Success')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
@@ -114,7 +114,7 @@ Http::get('/v1/mock/tests/general/oauth2/success')
|
||||
]);
|
||||
});
|
||||
|
||||
Http::get('/v1/mock/tests/general/oauth2/failure')
|
||||
App::get('/v1/mock/tests/general/oauth2/failure')
|
||||
->desc('OAuth2 Failure')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
@@ -129,7 +129,7 @@ Http::get('/v1/mock/tests/general/oauth2/failure')
|
||||
]);
|
||||
});
|
||||
|
||||
Http::patch('/v1/mock/functions-v2')
|
||||
App::patch('/v1/mock/functions-v2')
|
||||
->desc('Update Function Version to V2 (outdated code syntax)')
|
||||
->groups(['mock', 'api', 'functions'])
|
||||
->label('scope', 'functions.write')
|
||||
@@ -155,10 +155,10 @@ Http::patch('/v1/mock/functions-v2')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
Http::post('/v1/mock/api-key-unprefixed')
|
||||
App::post('/v1/mock/api-key-unprefixed')
|
||||
->desc('Create API Key (without standard prefix)')
|
||||
->groups(['mock', 'api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->param('projectId', '', new UID(), 'Project ID.')
|
||||
->inject('response')
|
||||
@@ -204,7 +204,7 @@ Http::post('/v1/mock/api-key-unprefixed')
|
||||
->dynamic($key, Response::MODEL_KEY);
|
||||
});
|
||||
|
||||
Http::get('/v1/mock/github/callback')
|
||||
App::get('/v1/mock/github/callback')
|
||||
->desc('Create installation document using GitHub installation id')
|
||||
->groups(['mock', 'api', 'vcs'])
|
||||
->label('scope', 'public')
|
||||
@@ -264,13 +264,15 @@ Http::get('/v1/mock/github/callback')
|
||||
]);
|
||||
});
|
||||
|
||||
Http::shutdown()
|
||||
App::shutdown()
|
||||
->groups(['mock'])
|
||||
->inject('route')
|
||||
->inject('utopia')
|
||||
->inject('response')
|
||||
->action(function (Route $route, Response $response) {
|
||||
->inject('request')
|
||||
->action(function (App $utopia, Response $response, Request $request) {
|
||||
|
||||
$result = [];
|
||||
$route = $utopia->getRoute();
|
||||
$path = APP_STORAGE_CACHE . '/tests.json';
|
||||
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
|
||||
|
||||
|
||||
+114
-110
@@ -15,11 +15,11 @@ use Appwrite\Event\Usage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Utopia\Queue\Connections;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\Database\TimeLimit;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
@@ -28,11 +28,8 @@ use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Route;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
$parseLabel = function (string $label, array $responsePayload, array $requestParams, Document $user) {
|
||||
preg_match_all('/{(.*?)}/', $label, $matches);
|
||||
@@ -98,7 +95,7 @@ $databaseListener = function (string $event, Document $document, Document $proje
|
||||
$databaseInternalId = $parts[1] ?? 0;
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_COLLECTIONS, $value) // per project
|
||||
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value) // per database
|
||||
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value)
|
||||
;
|
||||
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
@@ -153,9 +150,9 @@ $databaseListener = function (string $event, Document $document, Document $proje
|
||||
}
|
||||
};
|
||||
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['api'])
|
||||
->inject('route')
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('dbForConsole')
|
||||
->inject('project')
|
||||
@@ -163,41 +160,22 @@ Http::init()
|
||||
->inject('session')
|
||||
->inject('servers')
|
||||
->inject('mode')
|
||||
->inject('authorization')
|
||||
->action(function (Route $route, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Authorization $authorization) {
|
||||
->inject('team')
|
||||
->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) {
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* ACL Check
|
||||
*/
|
||||
/** Default role */
|
||||
$roles = Config::getParam('roles', []);
|
||||
$role = ($user->isEmpty())
|
||||
? Role::guests()->toString()
|
||||
: Role::users()->toString();
|
||||
|
||||
// Add user roles
|
||||
$memberships = $user->find('teamId', $project->getAttribute('teamId'), 'memberships');
|
||||
|
||||
if ($memberships) {
|
||||
foreach ($memberships->getAttribute('roles', []) as $memberRole) {
|
||||
switch ($memberRole) {
|
||||
case 'owner':
|
||||
$role = Auth::USER_ROLE_OWNER;
|
||||
break;
|
||||
case 'admin':
|
||||
$role = Auth::USER_ROLE_ADMIN;
|
||||
break;
|
||||
case 'developer':
|
||||
$role = Auth::USER_ROLE_DEVELOPER;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$roles = Config::getParam('roles', []);
|
||||
$scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
|
||||
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role
|
||||
/** Allowed Scopes for the role */
|
||||
$scopes = $roles[$role]['scopes'];
|
||||
|
||||
$apiKey = $request->getHeader('x-appwrite-key', '');
|
||||
|
||||
@@ -243,8 +221,8 @@ Http::init()
|
||||
$role = Auth::USER_ROLE_APPS;
|
||||
$scopes = \array_merge($roles[$role]['scopes'], $tokenScopes);
|
||||
|
||||
$authorization->addRole(Auth::USER_ROLE_APPS);
|
||||
$authorization->setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
Authorization::setRole(Auth::USER_ROLE_APPS);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
}
|
||||
} elseif ($keyType === API_KEY_STANDARD) {
|
||||
// No underline means no prefix. Backwards compatibility.
|
||||
@@ -269,8 +247,8 @@ Http::init()
|
||||
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
$authorization->addRole(Auth::USER_ROLE_APPS);
|
||||
$authorization->setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
Authorization::setRole(Auth::USER_ROLE_APPS);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
|
||||
$accessedAt = $key->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
|
||||
@@ -296,31 +274,56 @@ Http::init()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Admin User Authentication
|
||||
elseif (($project->getId() === 'console' && !$team->isEmpty() && !$user->isEmpty()) || ($project->getId() !== 'console' && !$user->isEmpty() && $mode === APP_MODE_ADMIN)) {
|
||||
$teamId = $team->getId();
|
||||
$adminRoles = [];
|
||||
$memberships = $user->getAttribute('memberships', []);
|
||||
foreach ($memberships as $membership) {
|
||||
if ($membership->getAttribute('confirm', false) === true && $membership->getAttribute('teamId') === $teamId) {
|
||||
$adminRoles = $membership->getAttribute('roles', []);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$authorization->addRole($role);
|
||||
if (empty($adminRoles)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
foreach (Auth::getRoles($user, $authorization) as $authRole) {
|
||||
$authorization->addRole($authRole);
|
||||
$scopes = []; // reset scope if admin
|
||||
foreach ($adminRoles as $role) {
|
||||
$scopes = \array_merge($scopes, $roles[$role]['scopes']);
|
||||
}
|
||||
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
}
|
||||
|
||||
$scopes = \array_unique($scopes);
|
||||
|
||||
Authorization::setRole($role);
|
||||
foreach (Auth::getRoles($user) as $authRole) {
|
||||
Authorization::setRole($authRole);
|
||||
}
|
||||
|
||||
/** Do not allow access to disabled services */
|
||||
$service = $route->getLabel('sdk.namespace', '');
|
||||
if (!empty($service)) {
|
||||
if (
|
||||
array_key_exists($service, $project->getAttribute('services', []))
|
||||
&& !$project->getAttribute('services', [])[$service]
|
||||
&& !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
) {
|
||||
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
|
||||
}
|
||||
}
|
||||
if (!\in_array($scope, $scopes)) {
|
||||
if ($project->isEmpty()) { // Check if permission is denied because project is missing
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
/** Do now allow access if scope is not allowed */
|
||||
$scope = $route->getLabel('scope', 'none');
|
||||
if (!\in_array($scope, $scopes)) {
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')');
|
||||
}
|
||||
|
||||
/** Do not allow access to blocked accounts */
|
||||
if (false === $user->getAttribute('status')) { // Account is blocked
|
||||
throw new Exception(Exception::USER_BLOCKED);
|
||||
}
|
||||
@@ -343,9 +346,9 @@ Http::init()
|
||||
}
|
||||
});
|
||||
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['api'])
|
||||
->inject('route')
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
@@ -359,12 +362,14 @@ Http::init()
|
||||
->inject('queueForUsage')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('authorization')
|
||||
->action(function (Route $route, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode, Authorization $authorization) use ($databaseListener) {
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($databaseListener) {
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
if (
|
||||
array_key_exists('rest', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['rest']
|
||||
&& !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
@@ -394,7 +399,7 @@ Http::init()
|
||||
|
||||
$closestLimit = null;
|
||||
|
||||
$roles = $authorization->getRoles();
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
@@ -457,8 +462,12 @@ Http::init()
|
||||
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
if ($useCache) {
|
||||
$key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
|
||||
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$keyParams = $request->getURI() . '*' . implode('*', $request->getParams());
|
||||
if(!empty($request->getRangeStart())) $keyParams .= '*'.$request->getRangeStart();
|
||||
if(!empty($request->getRangeEnd())) $keyParams .= '*'.$request->getRangeEnd();
|
||||
$keyParams .= '*' . APP_CACHE_BUSTER;
|
||||
$key = md5($keyParams);
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$cache = new Cache(
|
||||
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
|
||||
);
|
||||
@@ -466,23 +475,25 @@ Http::init()
|
||||
$data = $cache->load($key, $timestamp);
|
||||
|
||||
if (!empty($data) && !$cacheLog->isEmpty()) {
|
||||
|
||||
$parts = explode('/', $cacheLog->getAttribute('resourceType'));
|
||||
$type = $parts[0] ?? null;
|
||||
|
||||
if ($type === 'bucket') {
|
||||
$bucketId = $parts[1] ?? null;
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
@@ -493,31 +504,33 @@ Http::init()
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $timestamp) . ' GMT')
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->setContentType($cacheLog->getAttribute('mimeType'))
|
||||
->send($data);
|
||||
->addHeader('Cache-Control', sprintf('private, max-age=%d', $timestamp))
|
||||
->addHeader('X-Appwrite-Cache', 'hit');
|
||||
foreach ($cacheLog->getAttribute('headers') as $key => $value) {
|
||||
$response->addHeader($key, $value);
|
||||
}
|
||||
|
||||
$response->send($data);
|
||||
} else {
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->addHeader('Expires', 0)
|
||||
->addHeader('Expires', '0')
|
||||
->addHeader('X-Appwrite-Cache', 'miss')
|
||||
;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['session'])
|
||||
->inject('user')
|
||||
->inject('request')
|
||||
@@ -537,12 +550,14 @@ Http::init()
|
||||
* Delete older sessions if the number of sessions have crossed
|
||||
* the session limit set for the project
|
||||
*/
|
||||
Http::shutdown()
|
||||
App::shutdown()
|
||||
->groups(['session'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (Response $response, Document $project, Database $dbForProject) {
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Database $dbForProject) {
|
||||
$sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT;
|
||||
$session = $response->getPayload();
|
||||
$userId = $session['userId'] ?? '';
|
||||
@@ -569,9 +584,9 @@ Http::shutdown()
|
||||
$dbForProject->purgeCachedDocument('users', $userId);
|
||||
});
|
||||
|
||||
Http::shutdown()
|
||||
App::shutdown()
|
||||
->groups(['api'])
|
||||
->inject('route')
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
@@ -587,29 +602,7 @@ Http::shutdown()
|
||||
->inject('queueForFunctions')
|
||||
->inject('mode')
|
||||
->inject('dbForConsole')
|
||||
->inject('authorization')
|
||||
->action(function (
|
||||
Route $route,
|
||||
Request $request,
|
||||
Response $response,
|
||||
Document $project,
|
||||
Document $user,
|
||||
Event $queueForEvents,
|
||||
Audit $queueForAudits,
|
||||
Usage $queueForUsage,
|
||||
Delete $queueForDeletes,
|
||||
EventDatabase $queueForDatabase,
|
||||
Build $queueForBuilds,
|
||||
Messaging $queueForMessaging,
|
||||
Database $dbForProject,
|
||||
Func $queueForFunctions,
|
||||
string $mode,
|
||||
Database $dbForConsole,
|
||||
Authorization $authorization,
|
||||
) use ($parseLabel) {
|
||||
if (!empty($user) && !$user->isEmpty() && empty($user->getInternalId())) {
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $user->getId()));
|
||||
}
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) {
|
||||
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
@@ -669,6 +662,7 @@ Http::shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
$requestParams = $route->getParamsValues();
|
||||
|
||||
/**
|
||||
@@ -717,7 +711,8 @@ Http::shutdown()
|
||||
if (!empty($queueForMessaging->getType())) {
|
||||
$queueForMessaging->trigger();
|
||||
}
|
||||
|
||||
var_dump($request->getRangeStart());
|
||||
var_dump($request->getRangeEnd());
|
||||
/**
|
||||
* Cache label
|
||||
*/
|
||||
@@ -725,7 +720,9 @@ Http::shutdown()
|
||||
if ($useCache) {
|
||||
$resource = $resourceType = null;
|
||||
$data = $response->getPayload();
|
||||
var_dump($response->getHeaders());
|
||||
if (!empty($data['payload'])) {
|
||||
|
||||
$pattern = $route->getLabel('cache.resource', null);
|
||||
if (!empty($pattern)) {
|
||||
$resource = $parseLabel($pattern, $responsePayload, $requestParams, $user);
|
||||
@@ -736,23 +733,36 @@ Http::shutdown()
|
||||
$resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user);
|
||||
}
|
||||
|
||||
$key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
|
||||
$keyParams = $request->getURI() . '*' . implode('*', $request->getParams());
|
||||
if(!empty($request->getRangeStart())) $keyParams .= '*'.$request->getRangeStart();
|
||||
if(!empty($request->getRangeEnd())) $keyParams .= '*'.$request->getRangeEnd();
|
||||
$keyParams .= '*'. APP_CACHE_BUSTER;
|
||||
|
||||
$key = md5($keyParams);
|
||||
$signature = md5($data['payload']);
|
||||
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
|
||||
$now = DateTime::now();
|
||||
if ($cacheLog->isEmpty()) {
|
||||
$authorization->skip(fn () => $dbForProject->createDocument('cache', new Document([
|
||||
$filterList = ['Cache-Control', 'Pragma', 'Expires', 'X-Appwrite-Cache'];
|
||||
$headers = [];
|
||||
foreach ($response->getHeaders() as $name => $value) {
|
||||
if(!in_array($name, $filterList)) {
|
||||
$headers[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
|
||||
'$id' => $key,
|
||||
'resource' => $resource,
|
||||
'resourceType' => $resourceType,
|
||||
'mimeType' => $response->getContentType(),
|
||||
'headers' => $headers,
|
||||
'accessedAt' => $now,
|
||||
'signature' => $signature,
|
||||
])));
|
||||
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
|
||||
$cacheLog->setAttribute('accessedAt', $now);
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
|
||||
}
|
||||
|
||||
if ($signature !== $cacheLog->getAttribute('signature')) {
|
||||
@@ -765,7 +775,7 @@ Http::shutdown()
|
||||
}
|
||||
|
||||
if ($project->getId() !== 'console') {
|
||||
if (!Auth::isPrivilegedUser($authorization->getRoles())) {
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
if (!empty($file)) {
|
||||
@@ -790,7 +800,7 @@ Http::shutdown()
|
||||
$accessedAt = $project->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
$authorization->skip(fn () => $dbForConsole->updateDocument('projects', $project->getId(), $project));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('projects', $project->getId(), $project));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -811,16 +821,10 @@ Http::shutdown()
|
||||
}
|
||||
});
|
||||
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['usage'])
|
||||
->action(function () {
|
||||
if (System::getEnv('_APP_USAGE_STATS', 'enabled') !== 'enabled') {
|
||||
throw new Exception(Exception::GENERAL_USAGE_DISABLED);
|
||||
}
|
||||
});
|
||||
|
||||
Http::shutdown()
|
||||
->inject('connections')
|
||||
->action(function (Connections $connections) {
|
||||
$connections->reclaim();
|
||||
});
|
||||
|
||||
@@ -4,14 +4,13 @@ use Appwrite\Auth\Auth;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Request;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Route;
|
||||
use Utopia\System\System;
|
||||
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['mfaProtected'])
|
||||
->inject('session')
|
||||
->action(function (Document $session) {
|
||||
@@ -30,14 +29,13 @@ Http::init()
|
||||
}
|
||||
});
|
||||
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['auth'])
|
||||
->inject('route')
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->action(function (Route $route, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
|
||||
->action(function (App $utopia, Request $request, Document $project, Reader $geodb) {
|
||||
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
|
||||
if (!empty($denylist && $project->getId() === 'console')) {
|
||||
$countries = explode(',', $denylist);
|
||||
@@ -48,17 +46,15 @@ Http::init()
|
||||
}
|
||||
}
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAppUser = Auth::isAppUser($authorization->getRoles());
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||
return;
|
||||
}
|
||||
|
||||
if ($route->getLabel('sdk.namespace', '') === 'graphql') { // Skip for graphQL recursive call
|
||||
return;
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
switch ($route->getLabel('auth.type', '')) {
|
||||
case 'emailPassword':
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\App;
|
||||
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['web'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -16,7 +16,7 @@ Http::init()
|
||||
;
|
||||
});
|
||||
|
||||
Http::get('/')
|
||||
App::get('/')
|
||||
->alias('auth/*')
|
||||
->alias('/invite')
|
||||
->alias('/login')
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Http\Http;
|
||||
|
||||
Http::get('/versions')
|
||||
App::get('/versions')
|
||||
->desc('Get Version')
|
||||
->groups(['home', 'web'])
|
||||
->label('scope', 'public')
|
||||
|
||||
+297
-204
@@ -1,242 +1,335 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
require_once __DIR__ . '/controllers/general.php';
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Appwrite\Utopia\Queue\Connections;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Swoole\Constant;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
use Swoole\Http\Server;
|
||||
use Swoole\Process;
|
||||
use Utopia\Abuse\Adapters\Database\TimeLimit;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Adapter\MySQL;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Http\Adapter\Swoole\Server;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Swoole\Files;
|
||||
use Utopia\System\System;
|
||||
|
||||
global $registry, $container;
|
||||
$http = new Server(
|
||||
host: "0.0.0.0",
|
||||
port: System::getEnv('PORT', 80),
|
||||
mode: SWOOLE_PROCESS,
|
||||
);
|
||||
|
||||
$payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
|
||||
$workerNumber = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
|
||||
$server = new Server('0.0.0.0', '80', [
|
||||
'open_http2_protocol' => true,
|
||||
'http_compression' => true,
|
||||
'http_compression_level' => 6,
|
||||
'package_max_length' => $payloadSize,
|
||||
'buffer_output_size' => $payloadSize,
|
||||
// Server
|
||||
// 'log_level' => 0,
|
||||
'dispatch_mode' => 2,
|
||||
'worker_num' => $workerNumber,
|
||||
'reactor_num' => swoole_cpu_num() * 2,
|
||||
'open_cpu_affinity' => true,
|
||||
// Coroutine
|
||||
'enable_coroutine' => true,
|
||||
'send_yield' => true,
|
||||
'tcp_fastopen' => true,
|
||||
]);
|
||||
$http
|
||||
->set([
|
||||
'worker_num' => $workerNumber,
|
||||
'open_http2_protocol' => true,
|
||||
'http_compression' => true,
|
||||
'http_compression_level' => 6,
|
||||
'package_max_length' => $payloadSize,
|
||||
'buffer_output_size' => $payloadSize,
|
||||
]);
|
||||
|
||||
$http = new Http($server, $container, 'UTC');
|
||||
$http->setRequestClass(Request::class);
|
||||
$http->setResponseClass(Response::class);
|
||||
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
|
||||
Console::success('Worker ' . ++$workerId . ' started successfully');
|
||||
});
|
||||
|
||||
Http::onStart()
|
||||
->inject('authorization')
|
||||
->inject('cache')
|
||||
->inject('pools')
|
||||
->inject('connections')
|
||||
->action(function (Authorization $authorization, Cache $cache, array $pools, Connections $connections) {
|
||||
try {
|
||||
// wait for database to be ready
|
||||
$attempts = 0;
|
||||
$max = 15;
|
||||
$sleep = 2;
|
||||
$http->on(Constant::EVENT_BEFORE_RELOAD, function ($server, $workerId) {
|
||||
Console::success('Starting reload...');
|
||||
});
|
||||
|
||||
do {
|
||||
try {
|
||||
$attempts++;
|
||||
$pool = $pools['pools-console-console']['pool'];
|
||||
$dsn = $pools['pools-console-console']['dsn'];
|
||||
$connection = $pool->get();
|
||||
$connections->add($connection, $pool);
|
||||
$http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
|
||||
Console::success('Reload completed...');
|
||||
});
|
||||
|
||||
$adapter = match ($dsn->getScheme()) {
|
||||
'mariadb' => new MariaDB($connection),
|
||||
'mysql' => new MySQL($connection),
|
||||
default => null
|
||||
};
|
||||
include __DIR__ . '/controllers/general.php';
|
||||
|
||||
$adapter->setDatabase($dsn->getPath());
|
||||
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
|
||||
$app = new App('UTC');
|
||||
|
||||
$dbForConsole = new Database($adapter, $cache);
|
||||
$dbForConsole->setAuthorization($authorization);
|
||||
go(function () use ($register, $app) {
|
||||
$pools = $register->get('pools');
|
||||
/** @var Group $pools */
|
||||
App::setResource('pools', fn () => $pools);
|
||||
|
||||
$dbForConsole
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
|
||||
// wait for database to be ready
|
||||
$attempts = 0;
|
||||
$max = 10;
|
||||
$sleep = 1;
|
||||
|
||||
$dbForConsole->ping();
|
||||
break; // leave the do-while if successful
|
||||
} catch (\Throwable $e) {
|
||||
Console::warning("Database not ready. Retrying connection ({$attempts})...");
|
||||
if ($attempts >= $max) {
|
||||
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
|
||||
}
|
||||
sleep($sleep);
|
||||
do {
|
||||
try {
|
||||
$attempts++;
|
||||
$dbForConsole = $app->getResource('dbForConsole');
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
break; // leave the do-while if successful
|
||||
} catch (\Throwable $e) {
|
||||
Console::warning("Database not ready. Retrying connection ({$attempts})...");
|
||||
if ($attempts >= $max) {
|
||||
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
|
||||
}
|
||||
} while ($attempts < $max);
|
||||
sleep($sleep);
|
||||
}
|
||||
} while ($attempts < $max);
|
||||
|
||||
Console::success('[Setup] - Server database init started...');
|
||||
Console::success('[Setup] - Server database init started...');
|
||||
|
||||
try {
|
||||
Console::success('[Setup] - Creating database: appwrite...');
|
||||
$dbForConsole->create();
|
||||
} catch (\Throwable $e) {
|
||||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
}
|
||||
|
||||
if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($dbForConsole);
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) {
|
||||
$adapter = new TimeLimit("", 0, 1, $dbForConsole);
|
||||
$adapter->setup();
|
||||
}
|
||||
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
$consoleCollections = $collections['console'];
|
||||
foreach ($consoleCollections as $key => $collection) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
}
|
||||
if (!$dbForConsole->getCollection($key)->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
|
||||
|
||||
$attributes = [];
|
||||
$indexes = [];
|
||||
|
||||
foreach ($collection['attributes'] as $attribute) {
|
||||
$attributes[] = new Document([
|
||||
'$id' => ID::custom($attribute['$id']),
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
'default' => $attribute['default'] ?? null,
|
||||
'format' => $attribute['format'] ?? ''
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($collection['indexes'] as $index) {
|
||||
$indexes[] = new Document([
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
'orders' => $index['orders'],
|
||||
]);
|
||||
}
|
||||
|
||||
$dbForConsole->createCollection($key, $attributes, $indexes);
|
||||
}
|
||||
|
||||
if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) {
|
||||
Console::success('[Setup] - Creating default bucket...');
|
||||
$dbForConsole->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('default'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'name' => 'Default',
|
||||
'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB
|
||||
'allowedFileExtensions' => [],
|
||||
'enabled' => true,
|
||||
'compression' => 'gzip',
|
||||
'encryption' => true,
|
||||
'antivirus' => true,
|
||||
'fileSecurity' => true,
|
||||
'$permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'search' => 'buckets Default',
|
||||
]));
|
||||
|
||||
$bucket = $dbForConsole->getDocument('buckets', 'default');
|
||||
|
||||
Console::success('[Setup] - Creating files collection for default bucket...');
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
throw new Exception('Files collection is not configured.');
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
$indexes = [];
|
||||
|
||||
foreach ($files['attributes'] as $attribute) {
|
||||
$attributes[] = new Document([
|
||||
'$id' => ID::custom($attribute['$id']),
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
'default' => $attribute['default'] ?? null,
|
||||
'format' => $attribute['format'] ?? ''
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($files['indexes'] as $index) {
|
||||
$indexes[] = new Document([
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
'orders' => $index['orders'],
|
||||
]);
|
||||
}
|
||||
|
||||
$dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
|
||||
}
|
||||
|
||||
$pools->reclaim();
|
||||
|
||||
Console::success('[Setup] - Server database init completed...');
|
||||
});
|
||||
|
||||
Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)');
|
||||
Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}");
|
||||
|
||||
// listen ctrl + c
|
||||
Process::signal(2, function () use ($http) {
|
||||
Console::log('Stop by Ctrl+C');
|
||||
$http->shutdown();
|
||||
});
|
||||
});
|
||||
|
||||
$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
|
||||
App::setResource('swooleRequest', fn () => $swooleRequest);
|
||||
App::setResource('swooleResponse', fn () => $swooleResponse);
|
||||
|
||||
$request = new Request($swooleRequest);
|
||||
$response = new Response($swooleResponse);
|
||||
|
||||
if (Files::isFileLoaded($request->getURI())) {
|
||||
$time = (60 * 60 * 24 * 365 * 2); // 45 days cache
|
||||
|
||||
$response
|
||||
->setContentType(Files::getFileMimeType($request->getURI()))
|
||||
->addHeader('Cache-Control', 'public, max-age=' . $time)
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
|
||||
->send(Files::getFileContents($request->getURI()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$app = new App('UTC');
|
||||
|
||||
$pools = $register->get('pools');
|
||||
App::setResource('pools', fn () => $pools);
|
||||
|
||||
try {
|
||||
Authorization::cleanRoles();
|
||||
Authorization::setRole(Role::any()->toString());
|
||||
|
||||
$app->run($request, $response);
|
||||
} catch (\Throwable $th) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$logger = $app->getResource("logger");
|
||||
if ($logger) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $app->getResource('user');
|
||||
} catch (\Throwable $_th) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
$route = $app->getRoute();
|
||||
|
||||
$log = $app->getResource("log");
|
||||
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
}
|
||||
|
||||
$log->setNamespace("http");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($th->getMessage());
|
||||
|
||||
$log->addTag('method', $route->getMethod());
|
||||
$log->addTag('url', $route->getPath());
|
||||
$log->addTag('verboseType', get_class($th));
|
||||
$log->addTag('code', $th->getCode());
|
||||
// $log->addTag('projectId', $project->getId()); // TODO: Figure out how to get ProjectID, if it becomes relevant
|
||||
$log->addTag('hostname', $request->getHostname());
|
||||
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
|
||||
|
||||
$log->addExtra('file', $th->getFile());
|
||||
$log->addExtra('line', $th->getLine());
|
||||
$log->addExtra('trace', $th->getTraceAsString());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
try {
|
||||
Console::success('[Setup] - Creating database: appwrite...');
|
||||
$dbForConsole->create();
|
||||
} catch (\Throwable $e) {
|
||||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
return true;
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Error log pushed with status code: ' . $responseCode);
|
||||
} catch (Throwable $th) {
|
||||
Console::error('Error pushing log: ' . $th->getMessage());
|
||||
}
|
||||
|
||||
if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($dbForConsole);
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) {
|
||||
$abuse = new TimeLimit("", 0, 1, $dbForConsole);
|
||||
$abuse->setup();
|
||||
}
|
||||
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
$consoleCollections = $collections['console'];
|
||||
foreach ($consoleCollections as $key => $collection) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
}
|
||||
if (!$dbForConsole->getCollection($key)->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
|
||||
|
||||
$attributes = [];
|
||||
$indexes = [];
|
||||
|
||||
foreach ($collection['attributes'] as $attribute) {
|
||||
$attributes[] = new Document([
|
||||
'$id' => ID::custom($attribute['$id']),
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
'default' => $attribute['default'] ?? null,
|
||||
'format' => $attribute['format'] ?? ''
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($collection['indexes'] as $index) {
|
||||
$indexes[] = new Document([
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
'orders' => $index['orders'],
|
||||
]);
|
||||
}
|
||||
|
||||
$dbForConsole->createCollection($key, $attributes, $indexes);
|
||||
}
|
||||
|
||||
if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) {
|
||||
Console::success('[Setup] - Creating default bucket...');
|
||||
$dbForConsole->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('default'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'name' => 'Default',
|
||||
'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB
|
||||
'allowedFileExtensions' => [],
|
||||
'enabled' => true,
|
||||
'compression' => 'gzip',
|
||||
'encryption' => true,
|
||||
'antivirus' => true,
|
||||
'fileSecurity' => true,
|
||||
'$permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'search' => 'buckets Default',
|
||||
]));
|
||||
|
||||
$bucket = $dbForConsole->getDocument('buckets', 'default');
|
||||
|
||||
Console::success('[Setup] - Creating files collection for default bucket...');
|
||||
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
throw new Exception('Files collection is not configured.');
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
$indexes = [];
|
||||
|
||||
foreach ($files['attributes'] as $attribute) {
|
||||
$attributes[] = new Document([
|
||||
'$id' => ID::custom($attribute['$id']),
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
'default' => $attribute['default'] ?? null,
|
||||
'format' => $attribute['format'] ?? ''
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($files['indexes'] as $index) {
|
||||
$indexes[] = new Document([
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
'orders' => $index['orders'],
|
||||
]);
|
||||
}
|
||||
|
||||
$dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
|
||||
}
|
||||
|
||||
$connections->reclaim();
|
||||
|
||||
Console::success('[Setup] - Server database init completed...');
|
||||
Console::success('Server started successfully');
|
||||
} catch (\Throwable $e) {
|
||||
Console::warning('Database not ready: ' . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
Http::init()
|
||||
->inject('authorization')
|
||||
->action(function (Authorization $authorization) {
|
||||
$authorization->cleanRoles();
|
||||
$authorization->addRole(Role::any()->toString());
|
||||
});
|
||||
Console::error('[Error] Type: ' . get_class($th));
|
||||
Console::error('[Error] Message: ' . $th->getMessage());
|
||||
Console::error('[Error] File: ' . $th->getFile());
|
||||
Console::error('[Error] Line: ' . $th->getLine());
|
||||
|
||||
$swooleResponse->setStatusCode(500);
|
||||
|
||||
$output = ((App::isDevelopment())) ? [
|
||||
'message' => 'Error: ' . $th->getMessage(),
|
||||
'code' => 500,
|
||||
'file' => $th->getFile(),
|
||||
'line' => $th->getLine(),
|
||||
'trace' => $th->getTrace(),
|
||||
'version' => $version,
|
||||
] : [
|
||||
'message' => 'Error: Server Error',
|
||||
'code' => 500,
|
||||
'version' => $version,
|
||||
];
|
||||
|
||||
$swooleResponse->end(\json_encode($output));
|
||||
} finally {
|
||||
$pools->reclaim();
|
||||
}
|
||||
});
|
||||
|
||||
$http->start();
|
||||
|
||||
+1648
-228
File diff suppressed because it is too large
Load Diff
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
use Utopia\Config\Config;
|
||||
|
||||
Config::load('events', __DIR__ . '/../config/events.php');
|
||||
Config::load('auth', __DIR__ . '/../config/auth.php');
|
||||
Config::load('apis', __DIR__ . '/../config/apis.php');
|
||||
Config::load('errors', __DIR__ . '/../config/errors.php');
|
||||
Config::load('oAuthProviders', __DIR__ . '/../config/oAuthProviders.php');
|
||||
Config::load('platforms', __DIR__ . '/../config/platforms.php');
|
||||
Config::load('collections', __DIR__ . '/../config/collections.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('runtime-specifications', __DIR__ . '/../config/runtimes/specifications.php');
|
||||
Config::load('function-templates', __DIR__ . '/../config/function-templates.php');
|
||||
@@ -1,194 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Functions\Specification;
|
||||
|
||||
const APP_NAME = 'Appwrite';
|
||||
const APP_DOMAIN = 'appwrite.io';
|
||||
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
|
||||
const APP_EMAIL_SECURITY = ''; // Default security email address
|
||||
const APP_USERAGENT = APP_NAME . '-Server v%s. Please report abuse at %s';
|
||||
const APP_MODE_DEFAULT = 'default';
|
||||
const APP_MODE_ADMIN = 'admin';
|
||||
const APP_PAGING_LIMIT = 12;
|
||||
const APP_LIMIT_COUNT = 5000;
|
||||
const APP_LIMIT_USERS = 10_000;
|
||||
const APP_LIMIT_USER_PASSWORD_HISTORY = 20;
|
||||
const APP_LIMIT_USER_SESSIONS_MAX = 100;
|
||||
const APP_LIMIT_USER_SESSIONS_DEFAULT = 10;
|
||||
const APP_LIMIT_ANTIVIRUS = 20_000_000; //20MB
|
||||
const APP_LIMIT_ENCRYPTION = 20_000_000; //20MB
|
||||
const APP_LIMIT_COMPRESSION = 20_000_000; //20MB
|
||||
const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
|
||||
const APP_LIMIT_ARRAY_LABELS_SIZE = 1000; // Default maximum of how many labels elements can there be in API parameter that expects array value
|
||||
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
|
||||
const APP_LIMIT_SUBQUERY = 1000;
|
||||
const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1_000_000;
|
||||
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
|
||||
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
|
||||
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
|
||||
const APP_KEY_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 4318;
|
||||
const APP_VERSION_STABLE = '1.6.0';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
const APP_DATABASE_ATTRIBUTE_DATETIME = 'datetime';
|
||||
const APP_DATABASE_ATTRIBUTE_URL = 'url';
|
||||
const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange';
|
||||
const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange';
|
||||
const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1_073_741_824; // 2^32 bits / 4 bits per char
|
||||
const APP_DATABASE_TIMEOUT_MILLISECONDS = 15_000;
|
||||
const APP_STORAGE_UPLOADS = '/storage/uploads';
|
||||
const APP_STORAGE_FUNCTIONS = '/storage/functions';
|
||||
const APP_STORAGE_BUILDS = '/storage/builds';
|
||||
const APP_STORAGE_CACHE = '/storage/cache';
|
||||
const APP_STORAGE_CERTIFICATES = '/storage/certificates';
|
||||
const APP_STORAGE_CONFIG = '/storage/config';
|
||||
const APP_STORAGE_READ_BUFFER = 20 * (1000 * 1000); //20MB other names `APP_STORAGE_MEMORY_LIMIT`, `APP_STORAGE_MEMORY_BUFFER`, `APP_STORAGE_READ_LIMIT`, `APP_STORAGE_BUFFER_LIMIT`
|
||||
const APP_SOCIAL_TWITTER = 'https://twitter.com/appwrite';
|
||||
const APP_SOCIAL_TWITTER_HANDLE = 'appwrite';
|
||||
const APP_SOCIAL_FACEBOOK = 'https://www.facebook.com/appwrite.io';
|
||||
const APP_SOCIAL_LINKEDIN = 'https://www.linkedin.com/company/appwrite';
|
||||
const APP_SOCIAL_INSTAGRAM = 'https://www.instagram.com/appwrite.io';
|
||||
const APP_SOCIAL_GITHUB = 'https://github.com/appwrite';
|
||||
const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord';
|
||||
const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
|
||||
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
|
||||
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
|
||||
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
|
||||
const APP_HOSTNAME_INTERNAL = 'appwrite';
|
||||
const APP_FUNCTION_SPECIFICATION_DEFAULT = Specification::S_05VCPU_512MB;
|
||||
const APP_FUNCTION_CPUS_DEFAULT = 0.5;
|
||||
const APP_FUNCTION_MEMORY_DEFAULT = 512;
|
||||
const DATABASE_SHARED_TABLES = 'database_db_fra1_self_hosted_16_0';
|
||||
// Database Reconnect
|
||||
const DATABASE_RECONNECT_SLEEP = 2;
|
||||
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
|
||||
|
||||
// Database Worker Types
|
||||
const DATABASE_TYPE_CREATE_ATTRIBUTE = 'createAttribute';
|
||||
const DATABASE_TYPE_CREATE_INDEX = 'createIndex';
|
||||
const DATABASE_TYPE_DELETE_ATTRIBUTE = 'deleteAttribute';
|
||||
const DATABASE_TYPE_DELETE_INDEX = 'deleteIndex';
|
||||
const DATABASE_TYPE_DELETE_COLLECTION = 'deleteCollection';
|
||||
const DATABASE_TYPE_DELETE_DATABASE = 'deleteDatabase';
|
||||
|
||||
// Build Worker Types
|
||||
const BUILD_TYPE_DEPLOYMENT = 'deployment';
|
||||
const BUILD_TYPE_RETRY = 'retry';
|
||||
|
||||
// Deletion Types
|
||||
const DELETE_TYPE_DATABASES = 'databases';
|
||||
const DELETE_TYPE_DOCUMENT = 'document';
|
||||
const DELETE_TYPE_COLLECTIONS = 'collections';
|
||||
const DELETE_TYPE_PROJECTS = 'projects';
|
||||
const DELETE_TYPE_FUNCTIONS = 'functions';
|
||||
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
|
||||
const DELETE_TYPE_USERS = 'users';
|
||||
const DELETE_TYPE_TEAM_PROJECTS = 'teams_projects';
|
||||
const DELETE_TYPE_EXECUTIONS = 'executions';
|
||||
const DELETE_TYPE_AUDIT = 'audit';
|
||||
const DELETE_TYPE_ABUSE = 'abuse';
|
||||
const DELETE_TYPE_USAGE = 'usage';
|
||||
const DELETE_TYPE_REALTIME = 'realtime';
|
||||
const DELETE_TYPE_BUCKETS = 'buckets';
|
||||
const DELETE_TYPE_INSTALLATIONS = 'installations';
|
||||
const DELETE_TYPE_RULES = 'rules';
|
||||
const DELETE_TYPE_SESSIONS = 'sessions';
|
||||
const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';
|
||||
const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource';
|
||||
const DELETE_TYPE_SCHEDULES = 'schedules';
|
||||
const DELETE_TYPE_TOPIC = 'topic';
|
||||
const DELETE_TYPE_TARGET = 'target';
|
||||
const DELETE_TYPE_EXPIRED_TARGETS = 'invalid_targets';
|
||||
const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
|
||||
|
||||
// Message types
|
||||
const MESSAGE_SEND_TYPE_INTERNAL = 'internal';
|
||||
const MESSAGE_SEND_TYPE_EXTERNAL = 'external';
|
||||
// Mail Types
|
||||
const MAIL_TYPE_VERIFICATION = 'verification';
|
||||
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
|
||||
const MAIL_TYPE_RECOVERY = 'recovery';
|
||||
const MAIL_TYPE_INVITATION = 'invitation';
|
||||
const MAIL_TYPE_CERTIFICATE = 'certificate';
|
||||
// Auth Types
|
||||
const APP_AUTH_TYPE_SESSION = 'Session';
|
||||
const APP_AUTH_TYPE_JWT = 'JWT';
|
||||
const APP_AUTH_TYPE_KEY = 'Key';
|
||||
const APP_AUTH_TYPE_ADMIN = 'Admin';
|
||||
// Response related
|
||||
const MAX_OUTPUT_CHUNK_SIZE = 2 * 1024 * 1024; // 2MB
|
||||
// Function headers
|
||||
const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host'];
|
||||
const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
|
||||
// Message types
|
||||
const MESSAGE_TYPE_EMAIL = 'email';
|
||||
const MESSAGE_TYPE_SMS = 'sms';
|
||||
const MESSAGE_TYPE_PUSH = 'push';
|
||||
// API key types
|
||||
const API_KEY_STANDARD = 'standard';
|
||||
const API_KEY_DYNAMIC = 'dynamic';
|
||||
// Usage metrics
|
||||
const METRIC_TEAMS = 'teams';
|
||||
const METRIC_USERS = 'users';
|
||||
const METRIC_AUTH_METHOD_PHONE = 'auth.method.phone';
|
||||
const METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE = METRIC_AUTH_METHOD_PHONE . '.{countryCode}';
|
||||
const METRIC_MESSAGES = 'messages';
|
||||
const METRIC_MESSAGES_SENT = METRIC_MESSAGES . '.sent';
|
||||
const METRIC_MESSAGES_FAILED = METRIC_MESSAGES . '.failed';
|
||||
const METRIC_MESSAGES_TYPE = METRIC_MESSAGES . '.{type}';
|
||||
const METRIC_MESSAGES_TYPE_SENT = METRIC_MESSAGES . '.{type}.sent';
|
||||
const METRIC_MESSAGES_TYPE_FAILED = METRIC_MESSAGES . '.{type}.failed';
|
||||
const METRIC_MESSAGES_TYPE_PROVIDER = METRIC_MESSAGES . '.{type}.{provider}';
|
||||
const METRIC_MESSAGES_TYPE_PROVIDER_SENT = METRIC_MESSAGES . '.{type}.{provider}.sent';
|
||||
const METRIC_MESSAGES_TYPE_PROVIDER_FAILED = METRIC_MESSAGES . '.{type}.{provider}.failed';
|
||||
const METRIC_SESSIONS = 'sessions';
|
||||
const METRIC_DATABASES = 'databases';
|
||||
const METRIC_COLLECTIONS = 'collections';
|
||||
const METRIC_DATABASE_ID_COLLECTIONS = '{databaseInternalId}.collections';
|
||||
const METRIC_DOCUMENTS = 'documents';
|
||||
const METRIC_DATABASE_ID_DOCUMENTS = '{databaseInternalId}.documents';
|
||||
const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS = '{databaseInternalId}.{collectionInternalId}.documents';
|
||||
const METRIC_BUCKETS = 'buckets';
|
||||
const METRIC_FILES = 'files';
|
||||
const METRIC_FILES_STORAGE = 'files.storage';
|
||||
const METRIC_BUCKET_ID_FILES = '{bucketInternalId}.files';
|
||||
const METRIC_BUCKET_ID_FILES_STORAGE = '{bucketInternalId}.files.storage';
|
||||
const METRIC_FUNCTIONS = 'functions';
|
||||
const METRIC_DEPLOYMENTS = 'deployments';
|
||||
const METRIC_DEPLOYMENTS_STORAGE = 'deployments.storage';
|
||||
const METRIC_BUILDS = 'builds';
|
||||
const METRIC_BUILDS_SUCCESS = 'builds.success';
|
||||
const METRIC_BUILDS_FAILED = 'builds.failed';
|
||||
|
||||
const METRIC_BUILDS_STORAGE = 'builds.storage';
|
||||
const METRIC_BUILDS_COMPUTE = 'builds.compute';
|
||||
const METRIC_BUILDS_COMPUTE_SUCCESS = 'builds.compute.success';
|
||||
const METRIC_BUILDS_COMPUTE_FAILED = 'builds.compute.failed';
|
||||
const METRIC_BUILDS_MB_SECONDS = 'builds.mbSeconds';
|
||||
|
||||
const METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS = '{functionInternalId}.builds.compute.success';
|
||||
const METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED = '{functionInternalId}.builds.compute.failed';
|
||||
const METRIC_FUNCTION_ID_BUILDS = '{functionInternalId}.builds';
|
||||
const METRIC_FUNCTION_ID_BUILDS_SUCCESS = '{functionInternalId}.builds.success';
|
||||
const METRIC_FUNCTION_ID_BUILDS_FAILED = '{functionInternalId}.builds.failed';
|
||||
|
||||
const METRIC_FUNCTION_ID_BUILDS_STORAGE = '{functionInternalId}.builds.storage';
|
||||
const METRIC_FUNCTION_ID_BUILDS_COMPUTE = '{functionInternalId}.builds.compute';
|
||||
const METRIC_FUNCTION_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
|
||||
const METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
|
||||
const METRIC_FUNCTION_ID_BUILDS_MB_SECONDS = '{functionInternalId}.builds.mbSeconds';
|
||||
|
||||
const METRIC_EXECUTIONS = 'executions';
|
||||
const METRIC_EXECUTIONS_COMPUTE = 'executions.compute';
|
||||
const METRIC_EXECUTIONS_MB_SECONDS = 'executions.mbSeconds';
|
||||
const METRIC_FUNCTION_ID_EXECUTIONS = '{functionInternalId}.executions';
|
||||
const METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE = '{functionInternalId}.executions.compute';
|
||||
const METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS = '{functionInternalId}.executions.mbSeconds';
|
||||
const METRIC_NETWORK_REQUESTS = 'network.requests';
|
||||
const METRIC_NETWORK_INBOUND = 'network.inbound';
|
||||
const METRIC_NETWORK_OUTBOUND = 'network.outbound';
|
||||
@@ -1,397 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\System\System;
|
||||
|
||||
Database::addFilter(
|
||||
'casting',
|
||||
function (mixed $value) {
|
||||
return json_encode(['value' => $value], JSON_PRESERVE_ZERO_FRACTION);
|
||||
},
|
||||
function (mixed $value) {
|
||||
if (is_null($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return json_decode($value, true)['value'];
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'enum',
|
||||
function (mixed $value, Document $attribute) {
|
||||
if ($attribute->isSet('elements')) {
|
||||
$attribute->removeAttribute('elements');
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
function (mixed $value, Document $attribute) {
|
||||
$formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true);
|
||||
if (isset($formatOptions['elements'])) {
|
||||
$attribute->setAttribute('elements', $formatOptions['elements']);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'range',
|
||||
function (mixed $value, Document $attribute) {
|
||||
if ($attribute->isSet('min')) {
|
||||
$attribute->removeAttribute('min');
|
||||
}
|
||||
if ($attribute->isSet('max')) {
|
||||
$attribute->removeAttribute('max');
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
function (mixed $value, Document $attribute) {
|
||||
$formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true);
|
||||
if (isset($formatOptions['min']) || isset($formatOptions['max'])) {
|
||||
$attribute
|
||||
->setAttribute('min', $formatOptions['min'])
|
||||
->setAttribute('max', $formatOptions['max'])
|
||||
;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryAttributes',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
$attributes = $database->find('attributes', [
|
||||
Query::equal('collectionInternalId', [$document->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit($database->getLimitForAttributes()),
|
||||
]);
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
|
||||
$options = $attribute->getAttribute('options');
|
||||
foreach ($options as $key => $value) {
|
||||
$attribute->setAttribute($key, $value);
|
||||
}
|
||||
$attribute->removeAttribute('options');
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryIndexes',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('indexes', [
|
||||
Query::equal('collectionInternalId', [$document->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit($database->getLimitForIndexes()),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryPlatforms',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('platforms', [
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryKeys',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('keys', [
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryWebhooks',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('webhooks', [
|
||||
Query::equal('projectInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQuerySessions',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database->getAuthorization()->skip(fn () => $database->find('sessions', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryTokens',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('tokens', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryChallenges',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('challenges', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryAuthenticators',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('authenticators', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryMemberships',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('memberships', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryVariables',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('variables', [
|
||||
Query::equal('resourceInternalId', [$document->getInternalId()]),
|
||||
Query::equal('resourceType', ['function']),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'encrypt',
|
||||
function (mixed $value) {
|
||||
$key = System::getEnv('_APP_OPENSSL_KEY_V1');
|
||||
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
|
||||
$tag = null;
|
||||
|
||||
return json_encode([
|
||||
'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
|
||||
'method' => OpenSSL::CIPHER_AES_128_GCM,
|
||||
'iv' => \bin2hex($iv),
|
||||
'tag' => \bin2hex($tag ?? ''),
|
||||
'version' => '1',
|
||||
]);
|
||||
},
|
||||
function (mixed $value) {
|
||||
if (is_null($value)) {
|
||||
return;
|
||||
}
|
||||
$value = json_decode($value, true);
|
||||
$key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']);
|
||||
|
||||
return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag']));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryProjectVariables',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('variables', [
|
||||
Query::equal('resourceType', ['project']),
|
||||
Query::limit(APP_LIMIT_SUBQUERY)
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'userSearch',
|
||||
function (mixed $value, Document $user) {
|
||||
$searchValues = [
|
||||
$user->getId(),
|
||||
$user->getAttribute('email', ''),
|
||||
$user->getAttribute('name', ''),
|
||||
$user->getAttribute('phone', '')
|
||||
];
|
||||
|
||||
foreach ($user->getAttribute('labels', []) as $label) {
|
||||
$searchValues[] = 'label:' . $label;
|
||||
}
|
||||
|
||||
$search = implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryTargets',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('targets', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY)
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryTopicTargets',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
$targetIds = $database->getAuthorization()->skip(fn () => \array_map(
|
||||
fn ($document) => $document->getAttribute('targetInternalId'),
|
||||
$database->find('subscribers', [
|
||||
Query::equal('topicInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY)
|
||||
])
|
||||
));
|
||||
if (\count($targetIds) > 0) {
|
||||
return $database->find('targets', [
|
||||
Query::equal('$internalId', $targetIds)
|
||||
]);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'providerSearch',
|
||||
function (mixed $value, Document $provider) {
|
||||
$searchValues = [
|
||||
$provider->getId(),
|
||||
$provider->getAttribute('name', ''),
|
||||
$provider->getAttribute('provider', ''),
|
||||
$provider->getAttribute('type', '')
|
||||
];
|
||||
|
||||
$search = \implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'topicSearch',
|
||||
function (mixed $value, Document $topic) {
|
||||
$searchValues = [
|
||||
$topic->getId(),
|
||||
$topic->getAttribute('name', ''),
|
||||
$topic->getAttribute('description', ''),
|
||||
];
|
||||
|
||||
$search = \implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'messageSearch',
|
||||
function (mixed $value, Document $message) {
|
||||
$searchValues = [
|
||||
$message->getId(),
|
||||
$message->getAttribute('description', ''),
|
||||
$message->getAttribute('status', ''),
|
||||
];
|
||||
|
||||
$data = \json_decode($message->getAttribute('data', []), true);
|
||||
$providerType = $message->getAttribute('providerType', '');
|
||||
|
||||
if ($providerType === MESSAGE_TYPE_EMAIL) {
|
||||
$searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]);
|
||||
} elseif ($providerType === MESSAGE_TYPE_SMS) {
|
||||
$searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]);
|
||||
} else {
|
||||
$searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]);
|
||||
}
|
||||
|
||||
$search = \implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Http\Validator\IP;
|
||||
use Utopia\Http\Validator\Range;
|
||||
use Utopia\Http\Validator\URL;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function () {
|
||||
return new Email();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_DATETIME, function () {
|
||||
return new DatetimeValidator();
|
||||
}, Database::VAR_DATETIME);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) {
|
||||
$elements = $attribute['formatOptions']['elements'];
|
||||
return new WhiteList($elements, true);
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function () {
|
||||
return new IP();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function () {
|
||||
return new URL();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) {
|
||||
$min = $attribute['formatOptions']['min'] ?? -INF;
|
||||
$max = $attribute['formatOptions']['max'] ?? INF;
|
||||
return new Range($min, $max, Range::TYPE_INTEGER);
|
||||
}, Database::VAR_INTEGER);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) {
|
||||
$min = $attribute['formatOptions']['min'] ?? -INF;
|
||||
$max = $attribute['formatOptions']['max'] ?? INF;
|
||||
return new Range($min, $max, Range::TYPE_FLOAT);
|
||||
}, Database::VAR_FLOAT);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Locale\Locale;
|
||||
|
||||
Locale::$exceptions = false;
|
||||
|
||||
$locales = Config::getParam('locale-codes', []);
|
||||
|
||||
foreach ($locales as $locale) {
|
||||
$code = $locale['code'];
|
||||
|
||||
$path = __DIR__ . '/../config/locale/translations/' . $code . '.json';
|
||||
|
||||
if (!\file_exists($path)) {
|
||||
$path = __DIR__ . '/../config/locale/translations/' . \substr($code, 0, 2) . '.json'; // if `ar-ae` doesn't exist, look for `ar`
|
||||
if (!\file_exists($path)) {
|
||||
$path = __DIR__ . '/../config/locale/translations/en.json'; // if none translation exists, use default from `en.json`
|
||||
}
|
||||
}
|
||||
|
||||
Locale::setLanguageFromJSON($code, $path);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+178
-116
@@ -5,48 +5,144 @@ use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Utopia\Queue\Connections;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
use Swoole\Http\Response as SwooleHttpResponse;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
use Swoole\Runtime;
|
||||
use Swoole\Table;
|
||||
use Swoole\Timer;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\Database\TimeLimit;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\DI\Dependency;
|
||||
use Utopia\Http\Adapter\Swoole\Request as UtopiaRequest;
|
||||
use Utopia\Http\Adapter\Swoole\Response as HttpResponse;
|
||||
use Utopia\Http\Adapter\Swoole\Response as UtopiaResponse;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Pools\Connection;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\System\System;
|
||||
use Utopia\WebSocket\Adapter;
|
||||
use Utopia\WebSocket\Server;
|
||||
|
||||
/**
|
||||
* @var Registry $registry
|
||||
* @var Container $container
|
||||
* @var \Utopia\Registry\Registry $register
|
||||
*/
|
||||
global $registry, $container;
|
||||
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
$realtime = new Realtime();
|
||||
// Allows overriding
|
||||
if (!function_exists('getConsoleDB')) {
|
||||
function getConsoleDB(): Database
|
||||
{
|
||||
global $register;
|
||||
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
$pools = $register->get('pools');
|
||||
|
||||
$dbAdapter = $pools
|
||||
->get('console')
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
|
||||
$database = new Database($dbAdapter, getCache());
|
||||
|
||||
$database
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', '_console');
|
||||
|
||||
return $database;
|
||||
}
|
||||
}
|
||||
|
||||
// Allows overriding
|
||||
if (!function_exists('getProjectDB')) {
|
||||
function getProjectDB(Document $project): Database
|
||||
{
|
||||
global $register;
|
||||
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
$pools = $register->get('pools');
|
||||
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return getConsoleDB();
|
||||
}
|
||||
|
||||
try {
|
||||
$dsn = new DSN($project->getAttribute('database'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
// TODO: Temporary until all projects are using shared tables
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
$adapter = $pools
|
||||
->get($dsn->getHost())
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$database = new Database($adapter, getCache());
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getInternalId());
|
||||
}
|
||||
|
||||
$database
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
return $database;
|
||||
}
|
||||
}
|
||||
|
||||
// Allows overriding
|
||||
if (!function_exists('getCache')) {
|
||||
function getCache(): Cache
|
||||
{
|
||||
global $register;
|
||||
|
||||
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
|
||||
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
$adapters = [];
|
||||
|
||||
foreach ($list as $value) {
|
||||
$adapters[] = $pools
|
||||
->get($value)
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
}
|
||||
|
||||
return new Cache(new Sharding($adapters));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('getRealtime')) {
|
||||
function getRealtime(): Realtime
|
||||
{
|
||||
return new Realtime();
|
||||
}
|
||||
}
|
||||
|
||||
$realtime = getRealtime();
|
||||
|
||||
/**
|
||||
* Table for statistics across all workers.
|
||||
@@ -70,8 +166,8 @@ $adapter
|
||||
|
||||
$server = new Server($adapter);
|
||||
|
||||
$logError = function (Throwable $error, string $action) use ($registry) {
|
||||
$logger = $registry->get('logger');
|
||||
$logError = function (Throwable $error, string $action) use ($register) {
|
||||
$logger = $register->get('logger');
|
||||
|
||||
if ($logger && !$error instanceof Exception) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
@@ -95,8 +191,12 @@ $logError = function (Throwable $error, string $action) use ($registry) {
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Realtime log pushed with status code: ' . $responseCode);
|
||||
try {
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Error log pushed with status code: ' . $responseCode);
|
||||
} catch (Throwable $th) {
|
||||
Console::error('Error pushing log: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
@@ -107,16 +207,16 @@ $logError = function (Throwable $error, string $action) use ($registry) {
|
||||
|
||||
$server->error($logError);
|
||||
|
||||
$server->onStart(function () use ($stats, $container, $containerId, &$statsDocument, $logError) {
|
||||
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) {
|
||||
sleep(5); // wait for the initial database schema to be ready
|
||||
Console::success('Server started successfully');
|
||||
$authorization = $container->get('authorization');
|
||||
|
||||
/**
|
||||
* Create document for this worker to share stats across Containers.
|
||||
*/
|
||||
go(function () use ($container, $containerId, &$statsDocument) {
|
||||
go(function () use ($register, $containerId, &$statsDocument) {
|
||||
$attempts = 0;
|
||||
$database = $container->get('dbForConsole');
|
||||
$database = getConsoleDB();
|
||||
|
||||
do {
|
||||
try {
|
||||
@@ -130,15 +230,14 @@ $server->onStart(function () use ($stats, $container, $containerId, &$statsDocum
|
||||
'value' => '{}'
|
||||
]);
|
||||
|
||||
$authorization = $container->get('authorization');
|
||||
$statsDocument = $authorization->skip(fn () => $database->createDocument('realtime', $document));
|
||||
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
|
||||
break;
|
||||
} catch (Throwable) {
|
||||
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
|
||||
sleep(DATABASE_RECONNECT_SLEEP);
|
||||
}
|
||||
} while (true);
|
||||
($container->get('connections'))->reclaim();
|
||||
$register->get('pools')->reclaim();
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -146,7 +245,7 @@ $server->onStart(function () use ($stats, $container, $containerId, &$statsDocum
|
||||
*/
|
||||
// TODO: Remove this if check once it doesn't cause issues for cloud
|
||||
if (System::getEnv('_APP_EDITION', 'self-hosted') === 'self-hosted') {
|
||||
Timer::tick(5000, function () use ($container, $stats, &$statsDocument, $logError, $authorization) {
|
||||
Timer::tick(5000, function () use ($register, $stats, &$statsDocument, $logError) {
|
||||
$payload = [];
|
||||
foreach ($stats as $projectId => $value) {
|
||||
$payload[$projectId] = $stats->get($projectId, 'connectionsTotal');
|
||||
@@ -156,43 +255,40 @@ $server->onStart(function () use ($stats, $container, $containerId, &$statsDocum
|
||||
}
|
||||
|
||||
try {
|
||||
$database = $container->get('dbForConsole');
|
||||
$database = getConsoleDB();
|
||||
|
||||
$statsDocument
|
||||
->setAttribute('timestamp', DateTime::now())
|
||||
->setAttribute('value', json_encode($payload));
|
||||
|
||||
$authorization->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
} catch (Throwable $th) {
|
||||
call_user_func($logError, $th, "updateWorkerDocument");
|
||||
} finally {
|
||||
($container->get('connections'))->reclaim();
|
||||
$container->refresh('dbForConsole');
|
||||
$register->get('pools')->reclaim();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$server->onWorkerStart(function (int $workerId) use ($server, $container, $stats, $realtime, $logError) {
|
||||
$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime, $logError) {
|
||||
Console::success('Worker ' . $workerId . ' started successfully');
|
||||
|
||||
$attempts = 0;
|
||||
$start = time();
|
||||
|
||||
$authorization = $container->get('authorization');
|
||||
|
||||
Timer::tick(5000, function () use ($server, $container, $realtime, $stats, $logError, $authorization) {
|
||||
Timer::tick(5000, function () use ($server, $register, $realtime, $stats, $logError) {
|
||||
/**
|
||||
* Sending current connections to project channels on the console project every 5 seconds.
|
||||
*/
|
||||
// TODO: Remove this if check once it doesn't cause issues for cloud
|
||||
if (System::getEnv('_APP_EDITION', 'self-hosted') === 'self-hosted') {
|
||||
if ($realtime->hasSubscriber('console', Role::users()->toString(), 'project')) {
|
||||
$database = $container->get('dbForConsole');
|
||||
$database = getConsoleDB();
|
||||
|
||||
$payload = [];
|
||||
|
||||
$list = $authorization->skip(fn () => $database->find('realtime', [
|
||||
$list = Authorization::skip(fn () => $database->find('realtime', [
|
||||
Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)),
|
||||
]));
|
||||
|
||||
@@ -232,8 +328,8 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats
|
||||
'data' => $event['data']
|
||||
]));
|
||||
}
|
||||
($container->get('connections'))->reclaim();
|
||||
$container->refresh('dbForConsole');
|
||||
|
||||
$register->get('pools')->reclaim();
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -263,26 +359,13 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats
|
||||
while ($attempts < 300) {
|
||||
try {
|
||||
if ($attempts > 0) {
|
||||
Console::error(
|
||||
'Pub/sub connection lost (lasted ' . (time() - $start) . ' seconds, worker: ' . $workerId . ').
|
||||
Attempting restart in 5 seconds (attempt #' . $attempts . ')'
|
||||
);
|
||||
Console::error('Pub/sub connection lost (lasted ' . (time() - $start) . ' seconds, worker: ' . $workerId . ').
|
||||
Attempting restart in 5 seconds (attempt #' . $attempts . ')');
|
||||
sleep(5); // 5 sec delay between connection attempts
|
||||
}
|
||||
|
||||
$start = time();
|
||||
|
||||
$pools = $container->get('pools');
|
||||
$pool = $pools['pools-pubsub-pubsub']['pool'];
|
||||
|
||||
/** @var Connections $connections */
|
||||
$connections = $container->get('connections');
|
||||
$connection = $pool->get();
|
||||
$connections->add($connection, $pool);
|
||||
|
||||
$redis = $connection;
|
||||
|
||||
/** @var Redis $redis */
|
||||
$redis = $register->get('pools')->get('pubsub')->pop()->getResource(); /** @var Redis $redis */
|
||||
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
|
||||
|
||||
if ($redis->ping(true)) {
|
||||
@@ -292,7 +375,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats
|
||||
Console::error('Pub/sub failed (worker: ' . $workerId . ')');
|
||||
}
|
||||
|
||||
$redis->subscribe(['realtime'], function (Redis $redis, string $channel, string $payload) use ($server, $workerId, $stats, $realtime, $authorization, $container) {
|
||||
$redis->subscribe(['realtime'], function (Redis $redis, string $channel, string $payload) use ($server, $workerId, $stats, $register, $realtime) {
|
||||
$event = json_decode($payload, true);
|
||||
|
||||
if ($event['permissionsChanged'] && isset($event['userId'])) {
|
||||
@@ -301,24 +384,25 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats
|
||||
|
||||
if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) {
|
||||
$connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId]));
|
||||
$dbForConsole = $container->get('dbForConsole');
|
||||
$consoleDatabase = getConsoleDB();
|
||||
$project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
|
||||
$database = getProjectDB($project);
|
||||
|
||||
$project = $authorization->skip(fn () => $dbForConsole->getDocument('projects', $projectId));
|
||||
$dbForProject = $container->get('getProjectDB')($project);
|
||||
$user = $database->getDocument('users', $userId);
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
$roles = Auth::getRoles($user, $authorization);
|
||||
$roles = Auth::getRoles($user);
|
||||
$channels = $realtime->connections[$connection]['channels'];
|
||||
|
||||
$realtime->unsubscribe($connection);
|
||||
$realtime->subscribe($projectId, $connection, $roles, $channels);
|
||||
|
||||
$register->get('pools')->reclaim();
|
||||
}
|
||||
}
|
||||
|
||||
$receivers = $realtime->getSubscribers($event);
|
||||
|
||||
if (Http::isDevelopment() && !empty($receivers)) {
|
||||
if (App::isDevelopment() && !empty($receivers)) {
|
||||
Console::log("[Debug][Worker {$workerId}] Receivers: " . count($receivers));
|
||||
Console::log("[Debug][Worker {$workerId}] Receivers Connection IDs: " . json_encode($receivers));
|
||||
Console::log("[Debug][Worker {$workerId}] Event: " . $payload);
|
||||
@@ -344,40 +428,30 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats
|
||||
sleep(DATABASE_RECONNECT_SLEEP);
|
||||
continue;
|
||||
} finally {
|
||||
($container->get('connections'))->reclaim();
|
||||
$container->refresh('dbForConsole');
|
||||
$register->get('pools')->reclaim();
|
||||
}
|
||||
}
|
||||
|
||||
Console::error('Failed to restart pub/sub...');
|
||||
});
|
||||
|
||||
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $container, $stats, &$realtime, $logError) {
|
||||
$authorization = $container->get('authorization');
|
||||
|
||||
$request = new Request(new UtopiaRequest($request));
|
||||
$response = new Response(new UtopiaResponse(new SwooleResponse()));
|
||||
|
||||
$requestInjection = new Dependency();
|
||||
$responseInjection = new Dependency();
|
||||
|
||||
$requestInjection->setName('request')->setCallback(fn () => $request);
|
||||
$responseInjection->setName('response')->setCallback(fn () => $response);
|
||||
|
||||
$container->set($requestInjection);
|
||||
$container->set($responseInjection);
|
||||
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $logError) {
|
||||
$app = new App('UTC');
|
||||
$request = new Request($request);
|
||||
$response = new Response(new SwooleResponse());
|
||||
|
||||
Console::info("Connection open (user: {$connection})");
|
||||
|
||||
App::setResource('pools', fn () => $register->get('pools'));
|
||||
App::setResource('request', fn () => $request);
|
||||
App::setResource('response', fn () => $response);
|
||||
|
||||
try {
|
||||
|
||||
/** @var Document $project */
|
||||
$project = $container->refresh('project')->get('project');
|
||||
|
||||
$container->refresh('dbForProject');
|
||||
$project = $app->getResource('project');
|
||||
|
||||
/*
|
||||
* Project Check
|
||||
* Project Check
|
||||
*/
|
||||
if (empty($project->getId())) {
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing or unknown project ID');
|
||||
@@ -386,16 +460,15 @@ $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()))
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
|
||||
$dbForProject = $container->get('getProjectDB')($project);
|
||||
/** @var Document $console */
|
||||
$console = $container->get('console');
|
||||
/** @var Document $user */
|
||||
$user = $container->refresh('user')->get('user');
|
||||
$dbForProject = getProjectDB($project);
|
||||
$console = $app->getResource('console'); /** @var Document $console */
|
||||
$user = $app->getResource('user'); /** @var Document $user */
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
*
|
||||
@@ -424,8 +497,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
|
||||
}
|
||||
|
||||
$authorization = $container->get('authorization');
|
||||
$roles = Auth::getRoles($user, $authorization);
|
||||
$roles = Auth::getRoles($user);
|
||||
|
||||
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
|
||||
|
||||
@@ -474,33 +546,25 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$server->send([$connection], json_encode($response));
|
||||
$server->close($connection, $code);
|
||||
|
||||
if (Http::isDevelopment()) {
|
||||
if (App::isDevelopment()) {
|
||||
Console::error('[Error] Connection Error');
|
||||
Console::error('[Error] Code: ' . $response['data']['code']);
|
||||
Console::error('[Error] Message: ' . $response['data']['message']);
|
||||
}
|
||||
} finally {
|
||||
$connections = $container->get('connections');
|
||||
$connections->reclaim();
|
||||
$register->get('pools')->reclaim();
|
||||
}
|
||||
});
|
||||
|
||||
$server->onWorkerStop(function (int $workerId) use ($container) {
|
||||
$connections = $container->get('connections');
|
||||
$connections->reclaim();
|
||||
});
|
||||
|
||||
$server->onMessage(function (int $connection, string $message) use ($server, $container, $realtime, $containerId) {
|
||||
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
|
||||
try {
|
||||
$response = new Response(new HttpResponse(new SwooleHttpResponse()));
|
||||
$response = new Response(new SwooleResponse());
|
||||
$projectId = $realtime->connections[$connection]['projectId'];
|
||||
$database = $container->get('dbForConsole');
|
||||
$authorization = $container->get('authorization');
|
||||
$authentication = $container->get('authentication');
|
||||
$database = getConsoleDB();
|
||||
|
||||
if ($projectId !== 'console') {
|
||||
$project = $authorization->skip(fn () => $database->getDocument('projects', $projectId));
|
||||
$database = $container->get('getProjectDB')($project);
|
||||
$project = Authorization::skip(fn () => $database->getDocument('projects', $projectId));
|
||||
$database = getProjectDB($project);
|
||||
} else {
|
||||
$project = null;
|
||||
}
|
||||
@@ -538,21 +602,20 @@ $server->onMessage(function (int $connection, string $message) use ($server, $co
|
||||
}
|
||||
|
||||
$session = Auth::decodeSession($message['data']['session']);
|
||||
Auth::$unique = $session['id'] ?? '';
|
||||
Auth::$secret = $session['secret'] ?? '';
|
||||
|
||||
$authentication->setUnique($session['id'] ?? '');
|
||||
$authentication->setSecret($session['secret'] ?? '');
|
||||
|
||||
$user = $database->getDocument('users', $authentication->getUnique());
|
||||
$user = $database->getDocument('users', Auth::$unique);
|
||||
|
||||
if (
|
||||
empty($user->getId()) // Check a document has been found in the DB
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), $authentication->getSecret()) // Validate user has valid login token
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // 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, $authorization);
|
||||
$roles = Auth::getRoles($user);
|
||||
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
|
||||
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
|
||||
|
||||
@@ -586,8 +649,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $co
|
||||
$server->close($connection, $th->getCode());
|
||||
}
|
||||
} finally {
|
||||
($container->get('connections'))->reclaim();
|
||||
$container->refresh('dbForConsole');
|
||||
$register->get('pools')->reclaim();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ $httpsPort = $this->getParam('httpsPort', '');
|
||||
$version = $this->getParam('version', '');
|
||||
$organization = $this->getParam('organization', '');
|
||||
$image = $this->getParam('image', '');
|
||||
?>
|
||||
services:
|
||||
?>services:
|
||||
traefik:
|
||||
image: traefik:2.11
|
||||
container_name: appwrite-traefik
|
||||
@@ -532,6 +531,8 @@ services:
|
||||
- _APP_SMTP_USERNAME
|
||||
- _APP_SMTP_PASSWORD
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_DOMAIN
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
|
||||
appwrite-worker-messaging:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
@@ -852,7 +853,7 @@ services:
|
||||
- MYSQL_USER=${_APP_DB_USER}
|
||||
- MYSQL_PASSWORD=${_APP_DB_PASS}
|
||||
- MARIADB_AUTO_UPGRADE=1
|
||||
command: 'mysqld --innodb-flush-method=fsync --max_connections=5000'
|
||||
command: 'mysqld --innodb-flush-method=fsync'
|
||||
|
||||
redis:
|
||||
image: redis:7.2.4-alpine
|
||||
|
||||
+274
-102
@@ -2,107 +2,283 @@
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Certificate;
|
||||
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\Usage;
|
||||
use Appwrite\Event\UsageDump;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\Utopia\Queue\Connections;
|
||||
use Swoole\Runtime;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DI\Dependency;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Service;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Queue\Worker;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Queue\Server;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\System\System;
|
||||
|
||||
global $registry, $container;
|
||||
|
||||
Authorization::disable();
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
$project = new Dependency();
|
||||
$register = new Dependency();
|
||||
$dbForProject = new Dependency();
|
||||
$abuseRetention = new Dependency();
|
||||
$deviceForCache = new Dependency();
|
||||
$auditRetention = new Dependency();
|
||||
$queueForUsageDump = new Dependency();
|
||||
$executionRetention = new Dependency();
|
||||
$deviceForLocalFiles = new Dependency();
|
||||
Server::setResource('register', fn () => $register);
|
||||
|
||||
$register
|
||||
->setName('register')
|
||||
->setCallback(fn () => $registry);
|
||||
Server::setResource('dbForConsole', function (Cache $cache, Registry $register) {
|
||||
$pools = $register->get('pools');
|
||||
$database = $pools
|
||||
->get('console')
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$project
|
||||
->setName('project')
|
||||
->inject('message')
|
||||
->inject('dbForConsole')
|
||||
->setCallback(function (Message $message, Database $dbForConsole) {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
$project = new Document($payload['project'] ?? []);
|
||||
$adapter = new Database($database, $cache);
|
||||
$adapter->setNamespace('_console');
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
return $project;
|
||||
return $adapter;
|
||||
}, ['cache', 'register']);
|
||||
|
||||
Server::setResource('project', function (Message $message, Database $dbForConsole) {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
$project = new Document($payload['project'] ?? []);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
return $project;
|
||||
}
|
||||
|
||||
return $dbForConsole->getDocument('projects', $project->getId());
|
||||
}, ['message', 'dbForConsole']);
|
||||
|
||||
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForConsole) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForConsole;
|
||||
}
|
||||
|
||||
$pools = $register->get('pools');
|
||||
|
||||
try {
|
||||
$dsn = new DSN($project->getAttribute('database'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
// TODO: Temporary until all projects are using shared tables
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
$adapter = $pools
|
||||
->get($dsn->getHost())
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
try {
|
||||
$dsn = new DSN($project->getAttribute('database'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
// TODO: Temporary until all projects are using shared tables
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getInternalId());
|
||||
}
|
||||
|
||||
return $database;
|
||||
}, ['cache', 'register', 'message', 'project', 'dbForConsole']);
|
||||
|
||||
Server::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases): Database {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForConsole;
|
||||
}
|
||||
|
||||
return $dbForConsole->getDocument('projects', $project->getId());
|
||||
});
|
||||
try {
|
||||
$dsn = new DSN($project->getAttribute('database'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
// TODO: Temporary until all projects are using shared tables
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
$abuseRetention
|
||||
->setName('abuseRetention')
|
||||
->setCallback(function () {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400));
|
||||
});
|
||||
if (isset($databases[$dsn->getHost()])) {
|
||||
$database = $databases[$dsn->getHost()];
|
||||
|
||||
$auditRetention
|
||||
->setName('auditRetention')
|
||||
->setCallback(function () {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600));
|
||||
});
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getInternalId());
|
||||
}
|
||||
|
||||
$executionRetention
|
||||
->setName('executionRetention')
|
||||
->setCallback(function () {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600));
|
||||
});
|
||||
return $database;
|
||||
}
|
||||
|
||||
$queueForUsageDump
|
||||
->setName('queueForUsageDump')
|
||||
->inject('queue')
|
||||
->setCallback(function (Connection $queue) {
|
||||
return new UsageDump($queue);
|
||||
});
|
||||
$dbAdapter = $pools
|
||||
->get($dsn->getHost())
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$deviceForCache
|
||||
->setName('deviceForCache')
|
||||
->inject('project')
|
||||
->setCallback(function (Document $project) {
|
||||
return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId());
|
||||
});
|
||||
$database = new Database($dbAdapter, $cache);
|
||||
|
||||
$deviceForLocalFiles
|
||||
->setName('deviceForLocalFiles')
|
||||
->inject('project')
|
||||
->setCallback(function (Document $project) {
|
||||
return new Local(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
|
||||
});
|
||||
$databases[$dsn->getHost()] = $database;
|
||||
|
||||
$container->set($project);
|
||||
$container->set($register);
|
||||
$container->set($dbForProject);
|
||||
$container->set($abuseRetention);
|
||||
$container->set($auditRetention);
|
||||
$container->set($deviceForCache);
|
||||
$container->set($queueForUsageDump);
|
||||
$container->set($executionRetention);
|
||||
$container->set($deviceForLocalFiles);
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getInternalId());
|
||||
}
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForConsole', 'cache']);
|
||||
|
||||
Server::setResource('abuseRetention', function () {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400));
|
||||
});
|
||||
|
||||
Server::setResource('auditRetention', function () {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600));
|
||||
});
|
||||
|
||||
Server::setResource('executionRetention', function () {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600));
|
||||
});
|
||||
|
||||
Server::setResource('cache', function (Registry $register) {
|
||||
$pools = $register->get('pools');
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
$adapters = [];
|
||||
|
||||
foreach ($list as $value) {
|
||||
$adapters[] = $pools
|
||||
->get($value)
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
}
|
||||
|
||||
return new Cache(new Sharding($adapters));
|
||||
}, ['register']);
|
||||
|
||||
Server::setResource('log', fn () => new Log());
|
||||
|
||||
Server::setResource('queueForUsage', function (Connection $queue) {
|
||||
return new Usage($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queueForUsageDump', function (Connection $queue) {
|
||||
return new UsageDump($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queue', function (Group $pools) {
|
||||
return $pools->get('queue')->pop()->getResource();
|
||||
}, ['pools']);
|
||||
|
||||
Server::setResource('queueForDatabase', function (Connection $queue) {
|
||||
return new EventDatabase($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queueForMessaging', function (Connection $queue) {
|
||||
return new Messaging($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queueForMails', function (Connection $queue) {
|
||||
return new Mail($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queueForBuilds', function (Connection $queue) {
|
||||
return new Build($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queueForDeletes', function (Connection $queue) {
|
||||
return new Delete($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queueForEvents', function (Connection $queue) {
|
||||
return new Event($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queueForAudits', function (Connection $queue) {
|
||||
return new Audit($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queueForFunctions', function (Connection $queue) {
|
||||
return new Func($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queueForCertificates', function (Connection $queue) {
|
||||
return new Certificate($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queueForMigrations', function (Connection $queue) {
|
||||
return new Migration($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('logger', function (Registry $register) {
|
||||
return $register->get('logger');
|
||||
}, ['register']);
|
||||
|
||||
Server::setResource('pools', function (Registry $register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
|
||||
Server::setResource('deviceForFunctions', function (Document $project) {
|
||||
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
Server::setResource('deviceForFiles', function (Document $project) {
|
||||
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
Server::setResource('deviceForBuilds', function (Document $project) {
|
||||
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
Server::setResource('deviceForCache', function (Document $project) {
|
||||
return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
Server::setResource(
|
||||
'isResourceBlocked',
|
||||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
|
||||
);
|
||||
|
||||
|
||||
$pools = $register->get('pools');
|
||||
$platform = new Appwrite();
|
||||
$args = $platform->getEnv('argv');
|
||||
|
||||
@@ -121,13 +297,6 @@ if (\str_starts_with($workerName, 'databases')) {
|
||||
}
|
||||
|
||||
try {
|
||||
$connection = new Connection\Redis(
|
||||
System::getEnv('_APP_REDIS_HOST', 'redis'),
|
||||
System::getEnv('_APP_REDIS_PORT', '6379'),
|
||||
System::getEnv('_APP_REDIS_USER', ''),
|
||||
System::getEnv('_APP_REDIS_PASS', '')
|
||||
);
|
||||
|
||||
/**
|
||||
* Any worker can be configured with the following env vars:
|
||||
* - _APP_WORKERS_NUM The total number of worker processes
|
||||
@@ -136,35 +305,32 @@ try {
|
||||
*/
|
||||
$platform->init(Service::TYPE_WORKER, [
|
||||
'workersNum' => System::getEnv('_APP_WORKERS_NUM', 1),
|
||||
'connection' => $connection,
|
||||
'connection' => $pools->get('queue')->pop()->getResource(),
|
||||
'workerName' => strtolower($workerName) ?? null,
|
||||
'queueName' => $queueName
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine());
|
||||
Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine());
|
||||
}
|
||||
|
||||
Worker::init()
|
||||
->inject('authorization')
|
||||
->action(function (Authorization $authorization) {
|
||||
$authorization->disable();
|
||||
$worker = $platform->getWorker();
|
||||
|
||||
$worker
|
||||
->shutdown()
|
||||
->inject('pools')
|
||||
->action(function (Group $pools) {
|
||||
$pools->reclaim();
|
||||
});
|
||||
|
||||
Worker::shutdown()
|
||||
->inject('connections')
|
||||
->action(function (Connections $connections) {
|
||||
$connections->reclaim();
|
||||
});
|
||||
|
||||
Worker::error()
|
||||
$worker
|
||||
->error()
|
||||
->inject('error')
|
||||
->inject('logger')
|
||||
->inject('log')
|
||||
->inject('connections')
|
||||
->inject('pools')
|
||||
->inject('project')
|
||||
->inject('authorization')
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log, Connections $connections, Document $project, Authorization $authorization) use ($queueName) {
|
||||
$connections->reclaim();
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($queueName) {
|
||||
$pools->reclaim();
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
if ($logger) {
|
||||
@@ -180,13 +346,17 @@ Worker::error()
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('roles', $authorization->getRoles());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Usage stats log pushed with status code: ' . $responseCode);
|
||||
try {
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Error log pushed with status code: ' . $responseCode);
|
||||
} catch (Throwable $th) {
|
||||
Console::error('Error pushing log: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
@@ -195,7 +365,9 @@ Worker::error()
|
||||
Console::error('[Error] Line: ' . $error->getLine());
|
||||
});
|
||||
|
||||
$platform
|
||||
->getWorker()
|
||||
->setContainer($container)
|
||||
->start();
|
||||
$worker->workerStart()
|
||||
->action(function () use ($workerName) {
|
||||
Console::info("Worker $workerName started");
|
||||
});
|
||||
|
||||
$worker->start();
|
||||
|
||||
+21
-24
@@ -4,7 +4,6 @@
|
||||
"description": "End to end backend server for frontend and mobile apps.",
|
||||
"type": "project",
|
||||
"license": "BSD-3-Clause",
|
||||
"minimum-stability": "stable",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Eldad Fux",
|
||||
@@ -15,8 +14,7 @@
|
||||
"test": "vendor/bin/phpunit",
|
||||
"lint": "vendor/bin/pint --test",
|
||||
"format": "vendor/bin/pint",
|
||||
"bench": "vendor/bin/phpbench run --report=benchmark",
|
||||
"check": "./vendor/bin/phpstan analyse -c phpstan.neon --memory-limit 1G app src tests"
|
||||
"bench": "vendor/bin/phpbench run --report=benchmark"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -45,35 +43,35 @@
|
||||
"ext-openssl": "*",
|
||||
"ext-zlib": "*",
|
||||
"ext-sockets": "*",
|
||||
"appwrite/php-runtimes": "0.15.*",
|
||||
"appwrite/php-runtimes": "0.16.*",
|
||||
"appwrite/php-clamav": "2.0.*",
|
||||
"utopia-php/abuse": "0.44.*",
|
||||
"utopia-php/analytics": "0.13.*",
|
||||
"utopia-php/audit": "0.44.*",
|
||||
"utopia-php/abuse": "0.43.0",
|
||||
"utopia-php/analytics": "0.10.*",
|
||||
"utopia-php/audit": "0.43.0",
|
||||
"utopia-php/cache": "0.10.*",
|
||||
"utopia-php/cli": "0.19.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.54.*",
|
||||
"utopia-php/domains": "0.6.*",
|
||||
"utopia-php/dsn": "0.2.*",
|
||||
"utopia-php/framework": "1.0.*",
|
||||
"utopia-php/database": "0.53.8",
|
||||
"utopia-php/domains": "0.5.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/fetch": "0.2.*",
|
||||
"utopia-php/image": "0.6.*",
|
||||
"utopia-php/image": "0.7.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/logger": "0.6.*",
|
||||
"utopia-php/messaging": "0.12.*",
|
||||
"utopia-php/migration": "0.4.*",
|
||||
"utopia-php/orchestration": "0.15.*",
|
||||
"utopia-php/platform": "0.8.*",
|
||||
"utopia-php/view": "0.2.*",
|
||||
"utopia-php/migration": "0.6.*",
|
||||
"utopia-php/orchestration": "0.9.*",
|
||||
"utopia-php/platform": "0.7.*",
|
||||
"utopia-php/pools": "0.5.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/queue": "0.8.*",
|
||||
"utopia-php/queue": "0.7.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "0.19.*",
|
||||
"utopia-php/system": "0.8.*",
|
||||
"utopia-php/vcs": "0.9.*",
|
||||
"utopia-php/websocket": "0.2.*",
|
||||
"utopia-php/storage": "0.18.*",
|
||||
"utopia-php/swoole": "0.8.*",
|
||||
"utopia-php/system": "0.9.*",
|
||||
"utopia-php/vcs": "0.8.*",
|
||||
"utopia-php/websocket": "0.1.*",
|
||||
"matomo/device-detector": "6.1.*",
|
||||
"dragonmantank/cron-expression": "3.3.2",
|
||||
"phpmailer/phpmailer": "6.9.1",
|
||||
@@ -90,8 +88,7 @@
|
||||
"swoole/ide-helper": "5.1.2",
|
||||
"textalk/websocket": "1.5.7",
|
||||
"laravel/pint": "^1.14",
|
||||
"phpbench/phpbench": "^1.2",
|
||||
"phpstan/phpstan": "1.8.*"
|
||||
"phpbench/phpbench": "^1.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-phpiredis": "*"
|
||||
|
||||
Generated
+275
-420
File diff suppressed because it is too large
Load Diff
+2
-6
@@ -682,7 +682,6 @@ services:
|
||||
entrypoint: maintenance
|
||||
<<: *x-logging
|
||||
container_name: appwrite-task-maintenance
|
||||
restart: unless-stopped
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -783,7 +782,6 @@ services:
|
||||
entrypoint: schedule-functions
|
||||
<<: *x-logging
|
||||
container_name: appwrite-task-scheduler-functions
|
||||
restart: unless-stopped
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -812,7 +810,6 @@ services:
|
||||
entrypoint: schedule-executions
|
||||
<<: *x-logging
|
||||
container_name: appwrite-task-scheduler-executions
|
||||
restart: unless-stopped
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -840,7 +837,6 @@ services:
|
||||
entrypoint: schedule-messages
|
||||
<<: *x-logging
|
||||
container_name: appwrite-task-scheduler-messages
|
||||
restart: unless-stopped
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -867,7 +863,7 @@ services:
|
||||
|
||||
appwrite-assistant:
|
||||
container_name: appwrite-assistant
|
||||
image: appwrite/assistant:0.4.0
|
||||
image: appwrite/assistant:0.5.0
|
||||
networks:
|
||||
- appwrite
|
||||
environment:
|
||||
@@ -960,7 +956,7 @@ services:
|
||||
- MYSQL_USER=${_APP_DB_USER}
|
||||
- MYSQL_PASSWORD=${_APP_DB_PASS}
|
||||
- MARIADB_AUTO_UPGRADE=1
|
||||
command: 'mysqld --innodb-flush-method=fsync --max_connections=5000'
|
||||
command: "mysqld --innodb-flush-method=fsync"
|
||||
|
||||
redis:
|
||||
image: redis:7.2.4-alpine
|
||||
|
||||
+15
-15
@@ -8,7 +8,7 @@ Setting an alias allows the route to be also accessible from the alias URL.
|
||||
The first parameter specifies the alias URL, the second parameter specifies default values for route parameters.
|
||||
|
||||
```php
|
||||
Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->alias('/v1/storage/files', ['bucketId' => 'default'])
|
||||
```
|
||||
|
||||
@@ -17,7 +17,7 @@ Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
Used as an abstract description of the route.
|
||||
|
||||
```php
|
||||
Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->desc('Create File')
|
||||
```
|
||||
|
||||
@@ -26,14 +26,14 @@ Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
Groups array is used to group one or more routes with one or more hooks functionality.
|
||||
|
||||
```php
|
||||
Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->groups(['api'])
|
||||
```
|
||||
|
||||
In the above example groups() is used to define the current route as part of the routes that shares a common init middleware hook.
|
||||
|
||||
```php
|
||||
Http::init()
|
||||
App::init()
|
||||
->groups(['api'])
|
||||
->action(
|
||||
some code.....
|
||||
@@ -52,7 +52,7 @@ Appwrite uses different labels to achieve different things, for example:
|
||||
- scope - Defines the route permissions scope.
|
||||
|
||||
```php
|
||||
Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->label('scope', 'files.write')
|
||||
```
|
||||
|
||||
@@ -66,7 +66,7 @@ Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
- audits.resource - Signals the extraction part of the resource.
|
||||
|
||||
```php
|
||||
Http::post('/v1/account/create')
|
||||
App::post('/v1/account/create')
|
||||
->label('audits.event', 'account.create')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
->label('audits.userId', '{response.$id}')
|
||||
@@ -84,7 +84,7 @@ Http::post('/v1/account/create')
|
||||
* sdk.offline.response.key - JSON property name that has the ID. Defaults to $id
|
||||
|
||||
```php
|
||||
Http::post('/v1/account/jwt')
|
||||
App::post('/v1/account/jwt')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'createJWT')
|
||||
@@ -100,7 +100,7 @@ Http::post('/v1/account/jwt')
|
||||
- cache.resource - Identifies the cached resource.
|
||||
|
||||
```php
|
||||
Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'file/{request.fileId}')
|
||||
```
|
||||
@@ -115,7 +115,7 @@ When using the example below, we configure the abuse mechanism to allow this key
|
||||
constructed from the combination of the ip, http method, url, userId to hit the route maximum 60 times in 1 hour (60 seconds \* 60 minutes).
|
||||
|
||||
```php
|
||||
Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 3600)
|
||||
@@ -127,7 +127,7 @@ Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
Placeholders marked as `[]` are parsed and replaced with their real values.
|
||||
|
||||
```php
|
||||
Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->label('event', 'buckets.[bucketId].files.[fileId].create')
|
||||
```
|
||||
|
||||
@@ -145,7 +145,7 @@ As the name implies, `param()` is used to define a request parameter.
|
||||
- An array of injections
|
||||
|
||||
```php
|
||||
Http::get('/v1/account/logs')
|
||||
App::get('/v1/account/logs')
|
||||
->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)
|
||||
```
|
||||
|
||||
@@ -154,14 +154,14 @@ Http::get('/v1/account/logs')
|
||||
inject is used to inject dependencies pre-bounded to the app.
|
||||
|
||||
```php
|
||||
Http::post('/v1/storage/buckets/:bucketId/files')
|
||||
App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->inject('user')
|
||||
```
|
||||
|
||||
In the example above, the user object is injected into the route pre-bounded using `Http::setResource()`.
|
||||
In the example above, the user object is injected into the route pre-bounded using `App::setResource()`.
|
||||
|
||||
```php
|
||||
Http::setResource('user', function() {
|
||||
App::setResource('user', function() {
|
||||
some code...
|
||||
});
|
||||
```
|
||||
@@ -170,7 +170,7 @@ some code...
|
||||
Action populates the actual route code and has to be very clear and understandable. A good route stays simple and doesn't contain complex logic. An action is where we describe our business needs in code, and combine different libraries to work together and tell our story.
|
||||
|
||||
```php
|
||||
Http::post('/v1/account/sessions/anonymous')
|
||||
App::post('/v1/account/sessions/anonymous')
|
||||
->action(function (Request $request) {
|
||||
some code...
|
||||
});
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
parameters:
|
||||
level: 0
|
||||
scanDirectories:
|
||||
- vendor/swoole/ide-helper
|
||||
excludePaths:
|
||||
- tests/resources
|
||||
ignoreErrors:
|
||||
- '#Parameter \$geodb of anonymous function has invalid type MaxMind\\Db\\Reader\.#'
|
||||
- '#Parameter \$geodb of function router\(\) has invalid type MaxMind\\Db\\Reader\.#'
|
||||
- '#Instantiated class MaxMind\\Db\\Reader not found.#'
|
||||
- '#Function scrypt not found\.#'
|
||||
@@ -91,6 +91,37 @@ class Auth
|
||||
*/
|
||||
public const MFA_RECENT_DURATION = 1800; // 30 mins
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static $cookieName = 'a_session';
|
||||
|
||||
/**
|
||||
* User Unique ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $unique = '';
|
||||
|
||||
/**
|
||||
* User Secret Key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $secret = '';
|
||||
|
||||
/**
|
||||
* Set Cookie Name.
|
||||
*
|
||||
* @param $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function setCookieName($string)
|
||||
{
|
||||
return self::$cookieName = $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode Session.
|
||||
*
|
||||
@@ -408,14 +439,13 @@ class Auth
|
||||
* Returns all roles for a user.
|
||||
*
|
||||
* @param Document $user
|
||||
* @param Authorization $auth
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function getRoles(Document $user, Authorization $auth): array
|
||||
public static function getRoles(Document $user): array
|
||||
{
|
||||
$roles = [];
|
||||
|
||||
if (!self::isPrivilegedUser($auth->getRoles()) && !self::isAppUser($auth->getRoles())) {
|
||||
if (!self::isPrivilegedUser(Authorization::getRoles()) && !self::isAppUser(Authorization::getRoles())) {
|
||||
if ($user->getId()) {
|
||||
$roles[] = Role::user($user->getId())->toString();
|
||||
$roles[] = Role::users()->toString();
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth;
|
||||
|
||||
class Authentication
|
||||
{
|
||||
/**
|
||||
* User Unique ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $unique = '';
|
||||
|
||||
/**
|
||||
* User Secret Key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $secret = '';
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $cookieName = 'a_session';
|
||||
|
||||
public function setCookieName($string): string
|
||||
{
|
||||
return $this->cookieName = $string;
|
||||
}
|
||||
|
||||
public function getCookieName(): string
|
||||
{
|
||||
return $this->cookieName;
|
||||
}
|
||||
|
||||
public function getUnique(): string
|
||||
{
|
||||
return $this->unique;
|
||||
}
|
||||
|
||||
public function setUnique(string $unique): void
|
||||
{
|
||||
$this->unique = $unique;
|
||||
}
|
||||
|
||||
public function getSecret(): string
|
||||
{
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
public function setSecret(string $secret): void
|
||||
{
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Utopia\Http\Validator;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
/**
|
||||
* MockNumber.
|
||||
@@ -52,7 +52,6 @@ class MockNumber extends Validator
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->message = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Utopia\Http\Validator;
|
||||
use Utopia\Validator;
|
||||
|
||||
/**
|
||||
* Password.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Utopia\Http\Validator;
|
||||
use Utopia\Validator;
|
||||
|
||||
/**
|
||||
* Phone.
|
||||
|
||||
@@ -8,6 +8,8 @@ use Utopia\Queue\Connection;
|
||||
|
||||
class Func extends Event
|
||||
{
|
||||
public const TYPE_ASYNC_WRITE = 'async_write';
|
||||
|
||||
protected string $jwt = '';
|
||||
protected string $type = '';
|
||||
protected string $body = '';
|
||||
@@ -175,9 +177,9 @@ class Func extends Event
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBody(): string
|
||||
public function getData(): string
|
||||
{
|
||||
return $this->body;
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -81,7 +81,7 @@ class Migration extends Event
|
||||
return $client->enqueue([
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'migration' => $this->migration
|
||||
'migration' => $this->migration,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace Appwrite\Event\Validator;
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Http\Validator;
|
||||
use Utopia\Validator;
|
||||
|
||||
class Event extends Validator
|
||||
{
|
||||
|
||||
@@ -39,6 +39,7 @@ class Exception extends \Exception
|
||||
public const GENERAL_UNKNOWN = 'general_unknown';
|
||||
public const GENERAL_MOCK = 'general_mock';
|
||||
public const GENERAL_ACCESS_FORBIDDEN = 'general_access_forbidden';
|
||||
public const GENERAL_RESOURCE_BLOCKED = 'general_resource_blocked';
|
||||
public const GENERAL_UNKNOWN_ORIGIN = 'general_unknown_origin';
|
||||
public const GENERAL_API_DISABLED = 'general_api_disabled';
|
||||
public const GENERAL_SERVICE_DISABLED = 'general_service_disabled';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Functions\Validator;
|
||||
|
||||
use Utopia\Http\Validator;
|
||||
use Utopia\Validator;
|
||||
|
||||
/**
|
||||
* Headers.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Functions\Validator;
|
||||
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Payload extends Text
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Functions\Validator;
|
||||
|
||||
use Utopia\Http\Validator;
|
||||
use Utopia\Validator;
|
||||
|
||||
class RuntimeSpecification extends Validator
|
||||
{
|
||||
|
||||
@@ -6,12 +6,9 @@ use Appwrite\GraphQL\Exception as GQLException;
|
||||
use Appwrite\Promises\Swoole;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\App;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Request as UtopiaHttpRequest;
|
||||
use Utopia\Http\Response as UtopiaHttpResponse;
|
||||
use Utopia\Http\Route;
|
||||
use Utopia\Route;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Resolvers
|
||||
@@ -19,21 +16,24 @@ class Resolvers
|
||||
/**
|
||||
* Create a resolver for a given API {@see Route}.
|
||||
*
|
||||
* @param Http $http
|
||||
* @param App $utopia
|
||||
* @param ?Route $route
|
||||
* @return callable
|
||||
*/
|
||||
public function api(
|
||||
Http $http,
|
||||
public static function api(
|
||||
App $utopia,
|
||||
?Route $route,
|
||||
UtopiaHttpRequest $request,
|
||||
UtopiaHttpResponse $response,
|
||||
Container $container,
|
||||
): callable {
|
||||
$resolver = $this;
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $route, $args, $context, $info) {
|
||||
/** @var App $utopia */
|
||||
/** @var Response $response */
|
||||
/** @var Request $request */
|
||||
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
return fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($http, $route, $args, $context, $container, $info, $request, $response, $resolver) {
|
||||
$path = $route->getPath();
|
||||
foreach ($args as $key => $value) {
|
||||
if (\str_contains($path, '/:' . $key)) {
|
||||
@@ -46,13 +46,14 @@ class Resolvers
|
||||
|
||||
switch ($route->getMethod()) {
|
||||
case 'GET':
|
||||
$request->setQuery($args);
|
||||
$request->setQueryString($args);
|
||||
break;
|
||||
default:
|
||||
$request->setPayload($args);
|
||||
break;
|
||||
}
|
||||
$resolver->resolve($http, $request, $response, $container, $resolve, $reject);
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -60,20 +61,20 @@ class Resolvers
|
||||
/**
|
||||
* Create a resolver for a document in a specified database and collection with a specific method type.
|
||||
*
|
||||
* @param Http $http
|
||||
* @param App $utopia
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param string $methodType
|
||||
* @return callable
|
||||
*/
|
||||
public function document(
|
||||
Http $http,
|
||||
public static function document(
|
||||
App $utopia,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
string $methodType,
|
||||
): callable {
|
||||
return [self::class, 'document' . \ucfirst($methodType)](
|
||||
$http,
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId
|
||||
);
|
||||
@@ -82,28 +83,28 @@ class Resolvers
|
||||
/**
|
||||
* Create a resolver for getting a document in a specified database and collection.
|
||||
*
|
||||
* @param Http $http
|
||||
* @param App $utopia
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param callable $url
|
||||
* @return callable
|
||||
*/
|
||||
public function documentGet(
|
||||
Http $http,
|
||||
public static function documentGet(
|
||||
App $utopia,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
callable $url,
|
||||
UtopiaHttpRequest $request,
|
||||
UtopiaHttpResponse $response,
|
||||
Container $container,
|
||||
): callable {
|
||||
$resolver = $this;
|
||||
return fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $type, $args, $container, $request, $response, $resolver) {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
$request->setMethod('GET');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
|
||||
$resolver->resolve($http, $request, $response, $container, $resolve, $reject);
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -111,35 +112,35 @@ class Resolvers
|
||||
/**
|
||||
* Create a resolver for listing documents in a specified database and collection.
|
||||
*
|
||||
* @param Http $http
|
||||
* @param App $utopia
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param callable $url
|
||||
* @param callable $params
|
||||
* @return callable
|
||||
*/
|
||||
public function documentList(
|
||||
Http $http,
|
||||
public static function documentList(
|
||||
App $utopia,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
callable $url,
|
||||
callable $params,
|
||||
UtopiaHttpRequest $request,
|
||||
UtopiaHttpResponse $response,
|
||||
Container $container,
|
||||
): callable {
|
||||
$resolver = $this;
|
||||
return fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $params, $type, $args, $container, $request, $response, $resolver) {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
$request->setMethod('GET');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
$request->setQuery($params($databaseId, $collectionId, $args));
|
||||
$request->setQueryString($params($databaseId, $collectionId, $args));
|
||||
|
||||
$beforeResolve = function ($payload) {
|
||||
return $payload['documents'];
|
||||
};
|
||||
|
||||
$resolver->resolve($http, $request, $response, $container, $resolve, $reject, $beforeResolve);
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject, $beforeResolve);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -147,31 +148,31 @@ class Resolvers
|
||||
/**
|
||||
* Create a resolver for creating a document in a specified database and collection.
|
||||
*
|
||||
* @param Http $http
|
||||
* @param App $utopia
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param callable $url
|
||||
* @param callable $params
|
||||
* @return callable
|
||||
*/
|
||||
public function documentCreate(
|
||||
Http $http,
|
||||
public static function documentCreate(
|
||||
App $utopia,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
callable $url,
|
||||
callable $params,
|
||||
UtopiaHttpRequest $request,
|
||||
UtopiaHttpResponse $response,
|
||||
Container $container,
|
||||
): callable {
|
||||
$resolver = $this;
|
||||
return fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $params, $type, $args, $container, $request, $response, $resolver) {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
$request->setMethod('POST');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
$request->setPayload($params($databaseId, $collectionId, $args));
|
||||
|
||||
$resolver->resolve($http, $request, $response, $container, $resolve, $reject);
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -179,31 +180,31 @@ class Resolvers
|
||||
/**
|
||||
* Create a resolver for updating a document in a specified database and collection.
|
||||
*
|
||||
* @param Http $http
|
||||
* @param App $utopia
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param callable $url
|
||||
* @param callable $params
|
||||
* @return callable
|
||||
*/
|
||||
public function documentUpdate(
|
||||
Http $http,
|
||||
public static function documentUpdate(
|
||||
App $utopia,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
callable $url,
|
||||
callable $params,
|
||||
UtopiaHttpRequest $request,
|
||||
UtopiaHttpResponse $response,
|
||||
Container $container,
|
||||
): callable {
|
||||
$resolver = $this;
|
||||
return fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $params, $type, $args, $container, $request, $response, $resolver) {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
$request->setMethod('PATCH');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
$request->setPayload($params($databaseId, $collectionId, $args));
|
||||
|
||||
$resolver->resolve($http, $request, $response, $container, $resolve, $reject);
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -211,34 +212,34 @@ class Resolvers
|
||||
/**
|
||||
* Create a resolver for deleting a document in a specified database and collection.
|
||||
*
|
||||
* @param Http $http
|
||||
* @param App $utopia
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param callable $url
|
||||
* @return callable
|
||||
*/
|
||||
public function documentDelete(
|
||||
Http $http,
|
||||
public static function documentDelete(
|
||||
App $utopia,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
callable $url,
|
||||
UtopiaHttpRequest $request,
|
||||
UtopiaHttpResponse $response,
|
||||
Container $container,
|
||||
): callable {
|
||||
$resolver = $this;
|
||||
return fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $type, $args, $container, $request, $response, $resolver) {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
$request->setMethod('DELETE');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
|
||||
$resolver->resolve($http, $request, $response, $container, $resolve, $reject);
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Http $http
|
||||
* @param App $utopia
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param callable $resolve
|
||||
@@ -248,11 +249,10 @@ class Resolvers
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function resolve(
|
||||
Http $http,
|
||||
private static function resolve(
|
||||
App $utopia,
|
||||
Request $request,
|
||||
Response $response,
|
||||
Container $context,
|
||||
callable $resolve,
|
||||
callable $reject,
|
||||
?callable $beforeResolve = null,
|
||||
@@ -263,16 +263,14 @@ class Resolvers
|
||||
$request->removeHeader('content-type');
|
||||
}
|
||||
|
||||
$request = clone $request;
|
||||
$utopia->setResource('request', static fn () => $request);
|
||||
$response->setContentType(Response::CONTENT_TYPE_NULL);
|
||||
|
||||
try {
|
||||
$context
|
||||
->refresh('cache')
|
||||
->refresh('dbForProject')
|
||||
->refresh('dbForConsole')
|
||||
->refresh('getProjectDb');
|
||||
$route = $utopia->match($request, fresh: true);
|
||||
|
||||
$http->run(clone $context);
|
||||
$utopia->execute($route, $request, $response);
|
||||
} catch (\Throwable $e) {
|
||||
if ($beforeReject) {
|
||||
$e = $beforeReject($e);
|
||||
@@ -287,12 +285,10 @@ class Resolvers
|
||||
if ($beforeReject) {
|
||||
$payload = $beforeReject($payload);
|
||||
}
|
||||
$reject(
|
||||
new GQLException(
|
||||
message: $payload['message'],
|
||||
code: $response->getStatusCode()
|
||||
)
|
||||
);
|
||||
$reject(new GQLException(
|
||||
message: $payload['message'],
|
||||
code: $response->getStatusCode()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,63 +3,54 @@
|
||||
namespace Appwrite\GraphQL;
|
||||
|
||||
use Appwrite\GraphQL\Types\Mapper;
|
||||
use Appwrite\Utopia\Response\Models;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema as GQLSchema;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\App;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Request;
|
||||
use Utopia\Http\Response as UtopiaHttpResponse;
|
||||
use Utopia\Http\Route;
|
||||
use Utopia\Route;
|
||||
|
||||
class Schema
|
||||
{
|
||||
protected ?GQLSchema $schema = null;
|
||||
protected array $dirty = [];
|
||||
protected static ?GQLSchema $schema = null;
|
||||
protected static array $dirty = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param Http $http
|
||||
* @param callable $complexity Function to calculate complexity
|
||||
* @param callable $attributes Function to get attributes
|
||||
* @param array $urls Array of functions to get urls for specific method types
|
||||
* @param array $params Array of functions to build parameters for specific method types
|
||||
* @param App $utopia
|
||||
* @param callable $complexity Function to calculate complexity
|
||||
* @param callable $attributes Function to get attributes
|
||||
* @param array $urls Array of functions to get urls for specific method types
|
||||
* @param array $params Array of functions to build parameters for specific method types
|
||||
* @return GQLSchema
|
||||
* @throws Exception
|
||||
*/
|
||||
public function build(
|
||||
Http $http,
|
||||
Request $request,
|
||||
UtopiaHttpResponse $response,
|
||||
Container $container,
|
||||
public static function build(
|
||||
App $utopia,
|
||||
callable $complexity,
|
||||
callable $attributes,
|
||||
array $urls,
|
||||
array $params,
|
||||
): GQLSchema {
|
||||
if (!empty($this->schema)) {
|
||||
return $this->schema;
|
||||
App::setResource('utopia:graphql', static function () use ($utopia) {
|
||||
return $utopia;
|
||||
});
|
||||
|
||||
if (!empty(self::$schema)) {
|
||||
return self::$schema;
|
||||
}
|
||||
|
||||
$api = $this->api(
|
||||
$http,
|
||||
$request,
|
||||
$response,
|
||||
$container,
|
||||
$api = static::api(
|
||||
$utopia,
|
||||
$complexity
|
||||
);
|
||||
// $collections = $this->collections(
|
||||
// $http,
|
||||
// $complexity,
|
||||
// $request,
|
||||
// $response,
|
||||
// $attributes,
|
||||
// $urls,
|
||||
// $params,
|
||||
// );
|
||||
//$collections = static::collections(
|
||||
// $utopia,
|
||||
// $complexity,
|
||||
// $attributes,
|
||||
// $urls,
|
||||
// $params,
|
||||
//);
|
||||
|
||||
$queries = \array_merge_recursive(
|
||||
$api['query'],
|
||||
@@ -73,7 +64,7 @@ class Schema
|
||||
\ksort($queries);
|
||||
\ksort($mutations);
|
||||
|
||||
return $this->schema = new GQLSchema([
|
||||
return static::$schema = new GQLSchema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => $queries
|
||||
@@ -89,23 +80,21 @@ class Schema
|
||||
* This function iterates all API routes and builds a GraphQL
|
||||
* schema defining types and resolvers for all response models.
|
||||
*
|
||||
* @param Http $http
|
||||
* @param Request $request
|
||||
* @param UtopiaSwooleResponse $response
|
||||
* @param App $utopia
|
||||
* @param callable $complexity
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function api(Http $http, Request $request, UtopiaHttpResponse $response, Container $container, callable $complexity): array
|
||||
protected static function api(App $utopia, callable $complexity): array
|
||||
{
|
||||
Mapper::init(Models::getModels());
|
||||
|
||||
$mapper = new Mapper();
|
||||
Mapper::init($utopia
|
||||
->getResource('response')
|
||||
->getModels());
|
||||
|
||||
$queries = [];
|
||||
$mutations = [];
|
||||
|
||||
foreach ($http->getRoutes() as $routes) {
|
||||
foreach ($utopia->getRoutes() as $routes) {
|
||||
foreach ($routes as $route) {
|
||||
/** @var Route $route */
|
||||
|
||||
@@ -117,7 +106,7 @@ class Schema
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($mapper->route($http, $route, $request, $response, $container, $complexity) as $field) {
|
||||
foreach (Mapper::route($utopia, $route, $complexity) as $field) {
|
||||
switch ($route->getMethod()) {
|
||||
case 'GET':
|
||||
$queries[$name] = $field;
|
||||
@@ -145,7 +134,7 @@ class Schema
|
||||
* Iterates all of a projects attributes and builds GraphQL
|
||||
* queries and mutations for the collections they make up.
|
||||
*
|
||||
* @param Http $http
|
||||
* @param App $utopia
|
||||
* @param callable $complexity
|
||||
* @param callable $attributes
|
||||
* @param array $urls
|
||||
@@ -154,7 +143,7 @@ class Schema
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected static function collections(
|
||||
Http $http,
|
||||
App $utopia,
|
||||
callable $complexity,
|
||||
callable $attributes,
|
||||
array $urls,
|
||||
@@ -205,36 +194,36 @@ class Schema
|
||||
$queryFields[$collectionId . 'Get'] = [
|
||||
'type' => $objectType,
|
||||
'args' => Mapper::args('id'),
|
||||
/*'resolve' => Resolvers::documentGet(
|
||||
$http,
|
||||
'resolve' => Resolvers::documentGet(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['get'],
|
||||
)*/
|
||||
)
|
||||
];
|
||||
$queryFields[$collectionId . 'List'] = [
|
||||
'type' => Type::listOf($objectType),
|
||||
'args' => Mapper::args('list'),
|
||||
/*'resolve' => Resolvers::documentList(
|
||||
$http,
|
||||
'resolve' => Resolvers::documentList(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['list'],
|
||||
$params['list'],
|
||||
),*/
|
||||
),
|
||||
'complexity' => $complexity,
|
||||
];
|
||||
|
||||
$mutationFields[$collectionId . 'Create'] = [
|
||||
'type' => $objectType,
|
||||
'args' => $attributes,
|
||||
/*'resolve' => Resolvers::documentCreate(
|
||||
$http,
|
||||
'resolve' => Resolvers::documentCreate(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['create'],
|
||||
$params['create'],
|
||||
)*/
|
||||
)
|
||||
];
|
||||
$mutationFields[$collectionId . 'Update'] = [
|
||||
'type' => $objectType,
|
||||
@@ -245,23 +234,23 @@ class Schema
|
||||
$attributes
|
||||
)
|
||||
),
|
||||
/*'resolve' => Resolvers::documentUpdate(
|
||||
$http,
|
||||
'resolve' => Resolvers::documentUpdate(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['update'],
|
||||
$params['update'],
|
||||
)*/
|
||||
)
|
||||
];
|
||||
$mutationFields[$collectionId . 'Delete'] = [
|
||||
'type' => Mapper::model('none'),
|
||||
'args' => Mapper::args('id'),
|
||||
/*'resolve' => Resolvers::documentDelete(
|
||||
$http,
|
||||
'resolve' => Resolvers::documentDelete(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['delete'],
|
||||
)*/
|
||||
)
|
||||
];
|
||||
}
|
||||
$offset += $limit;
|
||||
@@ -273,8 +262,8 @@ class Schema
|
||||
];
|
||||
}
|
||||
|
||||
public function setDirty(string $projectId): void
|
||||
public static function setDirty(string $projectId): void
|
||||
{
|
||||
$this->dirty[$projectId] = true;
|
||||
self::$dirty[$projectId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,10 @@ use Exception;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Request;
|
||||
use Utopia\Http\Route;
|
||||
use Utopia\Http\Validator;
|
||||
use Utopia\Http\Validator\Nullable;
|
||||
use Utopia\App;
|
||||
use Utopia\Route;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Mapper
|
||||
{
|
||||
@@ -53,6 +50,7 @@ class Mapper
|
||||
$defaults = [
|
||||
'boolean' => Type::boolean(),
|
||||
'string' => Type::string(),
|
||||
'payload' => Type::string(),
|
||||
'integer' => Type::int(),
|
||||
'double' => Type::float(),
|
||||
'datetime' => Type::string(),
|
||||
@@ -77,15 +75,12 @@ class Mapper
|
||||
return self::$args[$key] ?? [];
|
||||
}
|
||||
|
||||
public function route(
|
||||
Http $http,
|
||||
public static function route(
|
||||
App $utopia,
|
||||
Route $route,
|
||||
Request $request,
|
||||
UtopiaSwooleResponse $response,
|
||||
Container $container,
|
||||
callable $complexity
|
||||
): iterable {
|
||||
foreach (static::$blacklist as $blacklist) {
|
||||
foreach (self::$blacklist as $blacklist) {
|
||||
if (\str_starts_with($route->getPath(), $blacklist)) {
|
||||
return;
|
||||
}
|
||||
@@ -107,7 +102,7 @@ class Mapper
|
||||
$list = true;
|
||||
}
|
||||
$parameterType = Mapper::param(
|
||||
$container,
|
||||
$utopia,
|
||||
$parameter['validator'],
|
||||
!$parameter['optional'],
|
||||
$parameter['injections']
|
||||
@@ -122,7 +117,7 @@ class Mapper
|
||||
'type' => $type,
|
||||
'description' => $description,
|
||||
'args' => $params,
|
||||
'resolve' => (new Resolvers())->api($http, $route, $request, $response, $container)
|
||||
'resolve' => Resolvers::api($utopia, $route)
|
||||
];
|
||||
|
||||
if ($list) {
|
||||
@@ -211,7 +206,7 @@ class Mapper
|
||||
/**
|
||||
* Map a {@see Route} parameter to a GraphQL Type
|
||||
*
|
||||
* @param Container $container
|
||||
* @param App $utopia
|
||||
* @param Validator|callable $validator
|
||||
* @param bool $required
|
||||
* @param array $injections
|
||||
@@ -219,13 +214,13 @@ class Mapper
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function param(
|
||||
Container $container,
|
||||
App $utopia,
|
||||
Validator|callable $validator,
|
||||
bool $required,
|
||||
array $injections
|
||||
): Type {
|
||||
$validator = \is_callable($validator)
|
||||
? \call_user_func_array($validator, array_map(fn ($injection) => $container->get($injection), $injections))
|
||||
? \call_user_func_array($validator, $utopia->getResources($injections))
|
||||
: $validator;
|
||||
|
||||
$isNullable = $validator instanceof Nullable;
|
||||
@@ -238,20 +233,20 @@ class Mapper
|
||||
case 'Appwrite\Network\Validator\CNAME':
|
||||
case 'Appwrite\Task\Validator\Cron':
|
||||
case 'Appwrite\Utopia\Database\Validator\CustomId':
|
||||
case 'Utopia\Http\Validator\Domain':
|
||||
case 'Utopia\Validator\Domain':
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
case 'Appwrite\Event\Validator\Event':
|
||||
case 'Appwrite\Event\Validator\FunctionEvent':
|
||||
case 'Utopia\Http\Validator\HexColor':
|
||||
case 'Utopia\Http\Validator\Host':
|
||||
case 'Utopia\Http\Validator\IP':
|
||||
case 'Utopia\Validator\HexColor':
|
||||
case 'Utopia\Validator\Host':
|
||||
case 'Utopia\Validator\IP':
|
||||
case 'Utopia\Database\Validator\Key':
|
||||
case 'Utopia\Http\Validator\Origin':
|
||||
case 'Utopia\Validator\Origin':
|
||||
case 'Appwrite\Auth\Validator\Password':
|
||||
case 'Utopia\Http\Validator\Text':
|
||||
case 'Utopia\Validator\Text':
|
||||
case 'Utopia\Database\Validator\UID':
|
||||
case 'Utopia\Http\Validator\URL':
|
||||
case 'Utopia\Http\Validator\WhiteList':
|
||||
case 'Utopia\Validator\URL':
|
||||
case 'Utopia\Validator\WhiteList':
|
||||
default:
|
||||
$type = Type::string();
|
||||
break;
|
||||
@@ -279,29 +274,29 @@ class Mapper
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Variables':
|
||||
$type = Type::listOf(Type::string());
|
||||
break;
|
||||
case 'Utopia\Http\Validator\Boolean':
|
||||
case 'Utopia\Validator\Boolean':
|
||||
$type = Type::boolean();
|
||||
break;
|
||||
case 'Utopia\Http\Validator\ArrayList':
|
||||
case 'Utopia\Validator\ArrayList':
|
||||
$type = Type::listOf(self::param(
|
||||
$container,
|
||||
$utopia,
|
||||
$validator->getValidator(),
|
||||
$required,
|
||||
$injections
|
||||
));
|
||||
break;
|
||||
case 'Utopia\Http\Validator\Integer':
|
||||
case 'Utopia\Http\Validator\Numeric':
|
||||
case 'Utopia\Http\Validator\Range':
|
||||
case 'Utopia\Validator\Integer':
|
||||
case 'Utopia\Validator\Numeric':
|
||||
case 'Utopia\Validator\Range':
|
||||
$type = Type::int();
|
||||
break;
|
||||
case 'Utopia\Http\Validator\FloatValidator':
|
||||
case 'Utopia\Validator\FloatValidator':
|
||||
$type = Type::float();
|
||||
break;
|
||||
case 'Utopia\Http\Validator\Assoc':
|
||||
case 'Utopia\Validator\Assoc':
|
||||
$type = Types::assoc();
|
||||
break;
|
||||
case 'Utopia\Http\Validator\JSON':
|
||||
case 'Utopia\Validator\JSON':
|
||||
$type = Types::json();
|
||||
break;
|
||||
case 'Utopia\Storage\Validator\File':
|
||||
|
||||
@@ -243,7 +243,11 @@ class Realtime extends Adapter
|
||||
* @param string $event
|
||||
* @param Document $payload
|
||||
* @param Document|null $project
|
||||
* @param Document|null $database
|
||||
* @param Document|null $collection
|
||||
* @param Document|null $bucket
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function fromPayload(string $event, Document $payload, Document $project = null, Document $database = null, Document $collection = null, Document $bucket = null): array
|
||||
{
|
||||
@@ -262,6 +266,13 @@ class Realtime extends Adapter
|
||||
break;
|
||||
case 'rules':
|
||||
$channels[] = 'console';
|
||||
$channels[] = 'projects.' . $project->getId();
|
||||
$projectId = 'console';
|
||||
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
|
||||
break;
|
||||
case 'projects':
|
||||
$channels[] = 'console';
|
||||
$channels[] = 'projects.' . $parts[1];
|
||||
$projectId = 'console';
|
||||
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
|
||||
break;
|
||||
@@ -280,6 +291,7 @@ class Realtime extends Adapter
|
||||
case 'databases':
|
||||
if (in_array($parts[4] ?? [], ['attributes', 'indexes'])) {
|
||||
$channels[] = 'console';
|
||||
$channels[] = 'projects.' . $project->getId();
|
||||
$projectId = 'console';
|
||||
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
|
||||
} elseif (($parts[4] ?? '') === 'documents') {
|
||||
@@ -319,6 +331,7 @@ class Realtime extends Adapter
|
||||
if ($parts[2] === 'executions') {
|
||||
if (!empty($payload->getRead())) {
|
||||
$channels[] = 'console';
|
||||
$channels[] = 'projects.' . $project->getId();
|
||||
$channels[] = 'executions';
|
||||
$channels[] = 'executions.' . $payload->getId();
|
||||
$channels[] = 'functions.' . $payload->getAttribute('functionId');
|
||||
@@ -326,6 +339,7 @@ class Realtime extends Adapter
|
||||
}
|
||||
} elseif ($parts[2] === 'deployments') {
|
||||
$channels[] = 'console';
|
||||
$channels[] = 'projects.' . $project->getId();
|
||||
$projectId = 'console';
|
||||
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
|
||||
}
|
||||
@@ -333,6 +347,7 @@ class Realtime extends Adapter
|
||||
break;
|
||||
case 'migrations':
|
||||
$channels[] = 'console';
|
||||
$channels[] = 'projects.' . $project->getId();
|
||||
$projectId = 'console';
|
||||
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
|
||||
break;
|
||||
|
||||
@@ -98,10 +98,10 @@ abstract class Migration
|
||||
*/
|
||||
protected array $collections;
|
||||
|
||||
public function __construct(Authorization $auth)
|
||||
public function __construct()
|
||||
{
|
||||
$auth->disable();
|
||||
$auth->setDefaultStatus(false);
|
||||
Authorization::disable();
|
||||
Authorization::setDefaultStatus(false);
|
||||
|
||||
$this->collections = Config::getParam('collections', []);
|
||||
|
||||
@@ -176,23 +176,25 @@ abstract class Migration
|
||||
Console::log('Migrating Collection ' . $collection['$id'] . ':');
|
||||
|
||||
foreach ($this->documentsIterator($collection['$id']) as $document) {
|
||||
if (empty($document->getId()) || empty($document->getCollection())) {
|
||||
return;
|
||||
}
|
||||
go(function (Document $document, callable $callback) {
|
||||
if (empty($document->getId()) || empty($document->getCollection())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$old = $document->getArrayCopy();
|
||||
$new = call_user_func($callback, $document);
|
||||
$old = $document->getArrayCopy();
|
||||
$new = call_user_func($callback, $document);
|
||||
|
||||
if (is_null($new) || $new->getArrayCopy() == $old) {
|
||||
return;
|
||||
}
|
||||
if (is_null($new) || $new->getArrayCopy() == $old) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->projectDB->updateDocument($document->getCollection(), $document->getId(), $document);
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed to update document: ' . $th->getMessage());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$this->projectDB->updateDocument($document->getCollection(), $document->getId(), $document);
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed to update document: ' . $th->getMessage());
|
||||
return;
|
||||
}
|
||||
}, $document, $callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Network\Validator;
|
||||
|
||||
use Utopia\Http\Validator;
|
||||
use Utopia\Validator;
|
||||
|
||||
class CNAME extends Validator
|
||||
{
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
namespace Appwrite\Network\Validator;
|
||||
|
||||
use Utopia\Http\Validator;
|
||||
use Utopia\Validator;
|
||||
|
||||
/**
|
||||
* Email
|
||||
*
|
||||
* Validate that an variable is a valid email address
|
||||
*
|
||||
* @package Utopia\Http\Validator
|
||||
* @package Utopia\Validator
|
||||
*/
|
||||
class Email extends Validator
|
||||
{
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Network\Validator;
|
||||
|
||||
use Utopia\Http\Validator;
|
||||
use Utopia\Http\Validator\Hostname;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Hostname;
|
||||
|
||||
class Origin extends Validator
|
||||
{
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\ClamAV\Network;
|
||||
use Appwrite\Utopia\Queue\Connections;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Adapter\MySQL;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Connection\Redis;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Storage;
|
||||
@@ -31,11 +27,10 @@ class Doctor extends Action
|
||||
$this
|
||||
->desc('Validate server health')
|
||||
->inject('register')
|
||||
->inject('connections')
|
||||
->callback(fn (Registry $register, Connections $connections) => $this->action($register, $connections));
|
||||
->callback(fn (Registry $register) => $this->action($register));
|
||||
}
|
||||
|
||||
public function action(Registry $register, Connections $connections): void
|
||||
public function action(Registry $register): void
|
||||
{
|
||||
Console::log(" __ ____ ____ _ _ ____ __ ____ ____ __ __
|
||||
/ _\ ( _ \( _ \/ )( \( _ \( )(_ _)( __) ( )/ \
|
||||
@@ -130,73 +125,17 @@ class Doctor extends Action
|
||||
//throw $th;
|
||||
}
|
||||
|
||||
/** @var array $pools */
|
||||
$pools = $register->get('pools');
|
||||
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
|
||||
|
||||
$configs = [
|
||||
'Console.DB' => [
|
||||
'prefix' => 'console',
|
||||
'databases' => Config::getParam('pools-console')
|
||||
],
|
||||
'Database.DB' => [
|
||||
'prefix' => 'database',
|
||||
'databases' => Config::getParam('pools-database')
|
||||
],
|
||||
'Console.DB' => Config::getParam('pools-console'),
|
||||
'Projects.DB' => Config::getParam('pools-database'),
|
||||
];
|
||||
|
||||
|
||||
foreach ($configs as $key => $config) {
|
||||
foreach ($config['databases'] as $database) {
|
||||
foreach ($config as $database) {
|
||||
try {
|
||||
$pool = $pools['pools-' . $config['prefix'] . '-' . $database]['pool'];
|
||||
$dsn = $pools['pools-' . $config['prefix'] . '-' . $database]['dsn'];
|
||||
|
||||
$connection = $pool->get();
|
||||
$connections->add($connection, $pool);
|
||||
$adapter = match ($dsn->getScheme()) {
|
||||
'mariadb' => new MariaDB($connection),
|
||||
'mysql' => new MySQL($connection),
|
||||
default => null
|
||||
};
|
||||
$adapter->setDatabase($dsn->getPath());
|
||||
|
||||
|
||||
if ($adapter->ping()) {
|
||||
Console::success('🟢 ' . str_pad("$key({$database})", 50, '.') . 'connected');
|
||||
} else {
|
||||
Console::error('🔴 ' . str_pad("$key({$database})", 47, '.') . 'disconnected');
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('🔴 ' . str_pad("{$key}.({$database})", 47, '.') . 'disconnected');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var array $pools */
|
||||
$pools = $register->get('pools');
|
||||
$configs = [
|
||||
'Cache' => [
|
||||
'prefix' => 'cache',
|
||||
'databases' => Config::getParam('pools-cache')
|
||||
],
|
||||
'Queue' => [
|
||||
'prefix' => 'queue',
|
||||
'databases' => Config::getParam('pools-queue')
|
||||
],
|
||||
'PubSub' => [
|
||||
'prefix' => 'pubsub',
|
||||
'databases' => Config::getParam('pools-pubsub')
|
||||
],
|
||||
];
|
||||
foreach ($configs as $key => $config) {
|
||||
foreach ($config['databases'] as $database) {
|
||||
try {
|
||||
$pool = $pools['pools-' . $config['prefix'] . '-' . $database]['pool'];
|
||||
$dsn = $pools['pools-' . $config['prefix'] . '-' . $database]['dsn'];
|
||||
$connection = $pool->get();
|
||||
$connections->add($connection, $pool);
|
||||
|
||||
$adapter = new Redis($dsn->getHost(), $dsn->getPort());
|
||||
$adapter = $pools->get($database)->pop()->getResource();
|
||||
|
||||
if ($adapter->ping()) {
|
||||
Console::success('🟢 ' . str_pad("{$key}({$database})", 50, '.') . 'connected');
|
||||
@@ -204,7 +143,30 @@ class Doctor extends Action
|
||||
Console::error('🔴 ' . str_pad("{$key}({$database})", 47, '.') . 'disconnected');
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('🔴 ' . str_pad("{$key}({$database})", 47, '.') . 'disconnected');
|
||||
Console::error('🔴 ' . str_pad("{$key}.({$database})", 47, '.') . 'disconnected');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
|
||||
$configs = [
|
||||
'Cache' => Config::getParam('pools-cache'),
|
||||
'Queue' => Config::getParam('pools-queue'),
|
||||
'PubSub' => Config::getParam('pools-pubsub'),
|
||||
];
|
||||
|
||||
foreach ($configs as $key => $config) {
|
||||
foreach ($config as $pool) {
|
||||
try {
|
||||
$adapter = $pools->get($pool)->pop()->getResource();
|
||||
|
||||
if ($adapter->ping()) {
|
||||
Console::success('🟢 ' . str_pad("{$key}({$pool})", 50, '.') . 'connected');
|
||||
} else {
|
||||
Console::error('🔴 ' . str_pad("{$key}({$pool})", 47, '.') . 'disconnected');
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('🔴 ' . str_pad("{$key}({$pool})", 47, '.') . 'disconnected');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,7 +258,7 @@ class Doctor extends Action
|
||||
}
|
||||
|
||||
try {
|
||||
if (Http::isProduction()) {
|
||||
if (App::isProduction()) {
|
||||
Console::log('');
|
||||
$version = \json_decode(@\file_get_contents(System::getEnv('_APP_HOME', 'http://localhost') . '/version'), true);
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ use Appwrite\Docker\Env;
|
||||
use Appwrite\Utopia\View;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Http\Validator\Boolean;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Install extends Action
|
||||
{
|
||||
@@ -213,7 +213,8 @@ class Install extends Action
|
||||
}
|
||||
|
||||
$env = '';
|
||||
$output = '';
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
|
||||
foreach ($input as $key => $value) {
|
||||
if ($value) {
|
||||
@@ -224,13 +225,13 @@ class Install extends Action
|
||||
$exit = 0;
|
||||
if (!$noStart) {
|
||||
Console::log("Running \"docker compose up -d --remove-orphans --renew-anon-volumes\"");
|
||||
$exit = Console::execute("$env docker compose --project-directory $this->path up -d --remove-orphans --renew-anon-volumes", '', $output);
|
||||
$exit = Console::execute("$env docker compose --project-directory $this->path up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr);
|
||||
}
|
||||
|
||||
if ($exit !== 0) {
|
||||
$message = 'Failed to install Appwrite dockers';
|
||||
Console::error($message);
|
||||
Console::error($output);
|
||||
Console::error($stderr);
|
||||
Console::exit($exit);
|
||||
} else {
|
||||
$message = 'Appwrite installed successfully';
|
||||
|
||||
@@ -49,12 +49,7 @@ class Maintenance extends Action
|
||||
$this->foreachProject($dbForConsole, function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) {
|
||||
$queueForDeletes->setProject($project);
|
||||
|
||||
$this->notifyDeleteTargets($queueForDeletes);
|
||||
$this->notifyDeleteExecutionLogs($queueForDeletes);
|
||||
$this->notifyDeleteAbuseLogs($queueForDeletes);
|
||||
$this->notifyDeleteAuditLogs($queueForDeletes);
|
||||
$this->notifyDeleteUsageStats($usageStatsRetentionHourly, $queueForDeletes);
|
||||
$this->notifyDeleteExpiredSessions($queueForDeletes);
|
||||
$this->notifyProjects($queueForDeletes, $usageStatsRetentionHourly);
|
||||
});
|
||||
|
||||
$this->notifyDeleteConnections($queueForDeletes);
|
||||
@@ -64,6 +59,19 @@ class Maintenance extends Action
|
||||
}, $interval, $delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to allow sub-classes to extend project-level maintenance functionality.
|
||||
*/
|
||||
protected function notifyProjects(Delete $queueForDeletes, int $usageStatsRetentionHourly): void
|
||||
{
|
||||
$this->notifyDeleteTargets($queueForDeletes);
|
||||
$this->notifyDeleteExecutionLogs($queueForDeletes);
|
||||
$this->notifyDeleteAbuseLogs($queueForDeletes);
|
||||
$this->notifyDeleteAuditLogs($queueForDeletes);
|
||||
$this->notifyDeleteUsageStats($usageStatsRetentionHourly, $queueForDeletes);
|
||||
$this->notifyDeleteExpiredSessions($queueForDeletes);
|
||||
}
|
||||
|
||||
protected function foreachProject(Database $dbForConsole, callable $callback): void
|
||||
{
|
||||
// TODO: @Meldiron name of this method no longer matches. It does not delete, and it gives whole document
|
||||
|
||||
@@ -10,10 +10,10 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Migrate extends Action
|
||||
{
|
||||
@@ -30,15 +30,12 @@ class Migrate extends Action
|
||||
->desc('Migrate Appwrite to new version')
|
||||
/** @TODO APP_VERSION_STABLE needs to be defined */
|
||||
->param('version', APP_VERSION_STABLE, new Text(8), 'Version to migrate to.', true)
|
||||
->inject('cache')
|
||||
->inject('dbForConsole')
|
||||
->inject('getProjectDB')
|
||||
->inject('register')
|
||||
->inject('authorization')
|
||||
->inject('console')
|
||||
->callback(function ($version, $dbForConsole, $getProjectDB, Registry $register, Authorization $authorization, Document $console) {
|
||||
\Co\run(function () use ($version, $dbForConsole, $getProjectDB, $register, $authorization, $console) {
|
||||
$this->action($version, $dbForConsole, $getProjectDB, $register, $authorization, $console);
|
||||
->callback(function ($version, $dbForConsole, $getProjectDB, Registry $register) {
|
||||
\Co\run(function () use ($version, $dbForConsole, $getProjectDB, $register) {
|
||||
$this->action($version, $dbForConsole, $getProjectDB, $register);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -61,12 +58,13 @@ class Migrate extends Action
|
||||
}
|
||||
}
|
||||
|
||||
public function action(string $version, Database $dbForConsole, callable $getProjectDB, Registry $register, Authorization $auth, Document $console)
|
||||
public function action(string $version, Database $dbForConsole, callable $getProjectDB, Registry $register)
|
||||
{
|
||||
$auth->disable();
|
||||
Authorization::disable();
|
||||
if (!array_key_exists($version, Migration::$versions)) {
|
||||
Console::error("Version {$version} not found.");
|
||||
Console::exit(1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,8 +77,12 @@ class Migrate extends Action
|
||||
10
|
||||
);
|
||||
|
||||
$app = new App('UTC');
|
||||
|
||||
Console::success('Starting Data Migration to version ' . $version);
|
||||
|
||||
$console = $app->getResource('console');
|
||||
|
||||
$limit = 30;
|
||||
$sum = 30;
|
||||
$offset = 0;
|
||||
@@ -99,7 +101,7 @@ class Migrate extends Action
|
||||
|
||||
$class = 'Appwrite\\Migration\\Version\\' . Migration::$versions[$version];
|
||||
/** @var Migration $migration */
|
||||
$migration = new $class($auth, );
|
||||
$migration = new $class();
|
||||
|
||||
while (!empty($projects)) {
|
||||
foreach ($projects as $project) {
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class QueueCount extends Action
|
||||
{
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\Wildcard;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Wildcard;
|
||||
|
||||
class QueueRetry extends Action
|
||||
{
|
||||
|
||||
@@ -5,10 +5,10 @@ namespace Appwrite\Platform\Tasks;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Http\Validator\Boolean;
|
||||
use Utopia\Http\Validator\Hostname;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
|
||||
class SSL extends Action
|
||||
{
|
||||
|
||||
@@ -2,44 +2,41 @@
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Utopia\Queue\Connections;
|
||||
use Swoole\Timer;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\System\System;
|
||||
|
||||
use function Swoole\Coroutine\run;
|
||||
|
||||
abstract class ScheduleBase extends Action
|
||||
{
|
||||
protected const UPDATE_TIMER = 10; //seconds
|
||||
protected const ENQUEUE_TIMER = 60; //seconds
|
||||
|
||||
protected array $schedules = [];
|
||||
protected Connections $connections;
|
||||
|
||||
abstract public static function getName(): string;
|
||||
|
||||
abstract public static function getSupportedResource(): string;
|
||||
|
||||
abstract protected function enqueueResources(
|
||||
array $pools,
|
||||
callable $getConsoleDB
|
||||
);
|
||||
abstract public static function getCollectionId(): string;
|
||||
abstract protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->connections = new Connections();
|
||||
$type = static::getSupportedResource();
|
||||
|
||||
$this
|
||||
->desc("Execute {$type}s scheduled in Appwrite")
|
||||
->inject('pools')
|
||||
->inject('getConsoleDB')
|
||||
->inject('dbForConsole')
|
||||
->inject('getProjectDB')
|
||||
->callback(fn (array $pools, callable $getConsoleDB, callable $getProjectDB) => $this->action($pools, $getConsoleDB, $getProjectDB));
|
||||
->callback(fn (Group $pools, Database $dbForConsole, callable $getProjectDB) => $this->action($pools, $dbForConsole, $getProjectDB));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,12 +44,11 @@ abstract class ScheduleBase extends Action
|
||||
* 2. Create timer that sync all changes from 'schedules' collection to local copy. Only reading changes thanks to 'resourceUpdatedAt' attribute
|
||||
* 3. Create timer that prepares coroutines for soon-to-execute schedules. When it's ready, coroutine sleeps until exact time before sending request to worker.
|
||||
*/
|
||||
public function action(array $pools, callable $getConsoleDB, callable $getProjectDB): void
|
||||
public function action(Group $pools, Database $dbForConsole, callable $getProjectDB): void
|
||||
{
|
||||
Console::title(\ucfirst(static::getSupportedResource()) . ' scheduler V1');
|
||||
Console::success(APP_NAME . ' ' . \ucfirst(static::getSupportedResource()) . ' scheduler v1 has started');
|
||||
|
||||
[$_, $_, $dbForConsole] = $getConsoleDB();
|
||||
/**
|
||||
* Extract only necessary attributes to lower memory used.
|
||||
*
|
||||
@@ -63,14 +59,8 @@ abstract class ScheduleBase extends Action
|
||||
$getSchedule = function (Document $schedule) use ($dbForConsole, $getProjectDB): array {
|
||||
$project = $dbForConsole->getDocument('projects', $schedule->getAttribute('projectId'));
|
||||
|
||||
$collectionId = match ($schedule->getAttribute('resourceType')) {
|
||||
'function' => 'functions',
|
||||
'message' => 'messages',
|
||||
'execution' => 'executions'
|
||||
};
|
||||
|
||||
$resource = $getProjectDB($project)->getDocument(
|
||||
$collectionId,
|
||||
static::getCollectionId(),
|
||||
$schedule->getAttribute('resourceId')
|
||||
);
|
||||
|
||||
@@ -114,12 +104,7 @@ abstract class ScheduleBase extends Action
|
||||
try {
|
||||
$this->schedules[$document->getInternalId()] = $getSchedule($document);
|
||||
} catch (\Throwable $th) {
|
||||
$collectionId = match ($document->getAttribute('resourceType')) {
|
||||
'function' => 'functions',
|
||||
'message' => 'messages',
|
||||
'execution' => 'executions'
|
||||
};
|
||||
|
||||
$collectionId = static::getCollectionId();
|
||||
Console::error("Failed to load schedule for project {$document['projectId']} {$collectionId} {$document['resourceId']}");
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
@@ -128,74 +113,76 @@ abstract class ScheduleBase extends Action
|
||||
$latestDocument = \end($results);
|
||||
}
|
||||
|
||||
$pools->reclaim();
|
||||
|
||||
Console::success("{$total} resources were loaded in " . (\microtime(true) - $loadStart) . " seconds");
|
||||
|
||||
Console::success("Starting timers at " . DateTime::now());
|
||||
|
||||
run(function () use ($dbForConsole, &$lastSyncUpdate, $getSchedule, $pools, $getProjectDB) {
|
||||
/**
|
||||
* The timer synchronize $schedules copy with database collection.
|
||||
*/
|
||||
Timer::tick(static::UPDATE_TIMER * 1000, function () use ($dbForConsole, &$lastSyncUpdate, $getSchedule, $pools) {
|
||||
$time = DateTime::now();
|
||||
$timerStart = \microtime(true);
|
||||
|
||||
Timer::tick(static::UPDATE_TIMER * 1000, function () use ($getConsoleDB, &$lastSyncUpdate, $getSchedule, $pools) {
|
||||
[$connection,$pool, $dbForConsole] = $getConsoleDB();
|
||||
$connections = new Connections();
|
||||
$connections->add($connection, $pool);
|
||||
$limit = 1000;
|
||||
$sum = $limit;
|
||||
$total = 0;
|
||||
$latestDocument = null;
|
||||
|
||||
$time = DateTime::now();
|
||||
$timerStart = \microtime(true);
|
||||
Console::log("Sync tick: Running at $time");
|
||||
|
||||
$limit = 1000;
|
||||
$sum = $limit;
|
||||
$total = 0;
|
||||
$latestDocument = null;
|
||||
while ($sum === $limit) {
|
||||
$paginationQueries = [Query::limit($limit)];
|
||||
|
||||
Console::log("Sync tick: Running at $time");
|
||||
|
||||
while ($sum === $limit) {
|
||||
$paginationQueries = [Query::limit($limit)];
|
||||
|
||||
if ($latestDocument) {
|
||||
$paginationQueries[] = Query::cursorAfter($latestDocument);
|
||||
}
|
||||
|
||||
$results = $dbForConsole->find('schedules', \array_merge($paginationQueries, [
|
||||
Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
|
||||
Query::equal('resourceType', [static::getSupportedResource()]),
|
||||
Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate),
|
||||
]));
|
||||
|
||||
$sum = count($results);
|
||||
$total = $total + $sum;
|
||||
|
||||
foreach ($results as $document) {
|
||||
$localDocument = $this->schedules[$document->getInternalId()] ?? null;
|
||||
|
||||
// Check if resource has been updated since last sync
|
||||
$org = $localDocument !== null ? \strtotime($localDocument['resourceUpdatedAt']) : null;
|
||||
$new = \strtotime($document['resourceUpdatedAt']);
|
||||
|
||||
if (!$document['active']) {
|
||||
Console::info("Removing: {$document['resourceId']}");
|
||||
unset($this->schedules[$document->getInternalId()]);
|
||||
} elseif ($new !== $org) {
|
||||
Console::info("Updating: {$document['resourceId']}");
|
||||
$this->schedules[$document->getInternalId()] = $getSchedule($document);
|
||||
if ($latestDocument) {
|
||||
$paginationQueries[] = Query::cursorAfter($latestDocument);
|
||||
}
|
||||
|
||||
$results = $dbForConsole->find('schedules', \array_merge($paginationQueries, [
|
||||
Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
|
||||
Query::equal('resourceType', [static::getSupportedResource()]),
|
||||
Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate),
|
||||
]));
|
||||
|
||||
$sum = count($results);
|
||||
$total = $total + $sum;
|
||||
|
||||
foreach ($results as $document) {
|
||||
$localDocument = $this->schedules[$document->getInternalId()] ?? null;
|
||||
|
||||
// Check if resource has been updated since last sync
|
||||
$org = $localDocument !== null ? \strtotime($localDocument['resourceUpdatedAt']) : null;
|
||||
$new = \strtotime($document['resourceUpdatedAt']);
|
||||
|
||||
if (!$document['active']) {
|
||||
Console::info("Removing: {$document['resourceType']}::{$document['resourceId']}");
|
||||
unset($this->schedules[$document->getInternalId()]);
|
||||
} elseif ($new !== $org) {
|
||||
Console::info("Updating: {$document['resourceType']}::{$document['resourceId']}");
|
||||
$this->schedules[$document->getInternalId()] = $getSchedule($document);
|
||||
}
|
||||
}
|
||||
|
||||
$latestDocument = \end($results);
|
||||
}
|
||||
|
||||
$latestDocument = \end($results);
|
||||
}
|
||||
$lastSyncUpdate = $time;
|
||||
$timerEnd = \microtime(true);
|
||||
|
||||
$lastSyncUpdate = $time;
|
||||
$timerEnd = \microtime(true);
|
||||
$pools->reclaim();
|
||||
|
||||
$connections->reclaim();
|
||||
Console::log("Sync tick: {$total} schedules were updated in " . ($timerEnd - $timerStart) . " seconds");
|
||||
Console::log("Sync tick: {$total} schedules were updated in " . ($timerEnd - $timerStart) . " seconds");
|
||||
});
|
||||
|
||||
Timer::tick(
|
||||
static::ENQUEUE_TIMER * 1000,
|
||||
fn () => $this->enqueueResources($pools, $dbForConsole, $getProjectDB)
|
||||
);
|
||||
|
||||
$this->enqueueResources($pools, $dbForConsole, $getProjectDB);
|
||||
});
|
||||
|
||||
Timer::tick(
|
||||
static::ENQUEUE_TIMER * 1000,
|
||||
fn () => $this->enqueueResources($pools, $getConsoleDB)
|
||||
);
|
||||
|
||||
$this->enqueueResources($pools, $getConsoleDB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Event\Func;
|
||||
use Swoole\Coroutine as Co;
|
||||
use Utopia\Queue\Connection\Redis;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Pools\Group;
|
||||
|
||||
class ScheduleExecutions extends ScheduleBase
|
||||
{
|
||||
@@ -21,16 +22,16 @@ class ScheduleExecutions extends ScheduleBase
|
||||
return 'execution';
|
||||
}
|
||||
|
||||
protected function enqueueResources(array $pools, callable $getConsoleDB): void
|
||||
public static function getCollectionId(): string
|
||||
{
|
||||
[$connection,$pool, $dbForConsole] = $getConsoleDB();
|
||||
$this->connections->add($connection, $pool);
|
||||
return 'executions';
|
||||
}
|
||||
|
||||
$queuePool = $pools['pools-queue-queue']['pool'];
|
||||
$queueConnection = $queuePool->get();
|
||||
$this->connections->add($queueConnection, $queuePool);
|
||||
|
||||
$queueForFunctions = new Func(new Redis($queueConnection));
|
||||
protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void
|
||||
{
|
||||
$queue = $pools->get('queue')->pop();
|
||||
$connection = $queue->getResource();
|
||||
$queueForFunctions = new Func($connection);
|
||||
$intervalEnd = (new \DateTime())->modify('+' . self::ENQUEUE_TIMER . ' seconds');
|
||||
|
||||
foreach ($this->schedules as $schedule) {
|
||||
@@ -56,7 +57,7 @@ class ScheduleExecutions extends ScheduleBase
|
||||
|
||||
$delay = $scheduledAt->getTimestamp() - (new \DateTime())->getTimestamp();
|
||||
|
||||
\go(function () use ($queueForFunctions, $schedule, $delay, $data, $dbForConsole) {
|
||||
\go(function () use ($queueForFunctions, $schedule, $delay, $data) {
|
||||
Co::sleep($delay);
|
||||
|
||||
$queueForFunctions->setType('schedule')
|
||||
@@ -71,16 +72,16 @@ class ScheduleExecutions extends ScheduleBase
|
||||
->setProject($schedule['project'])
|
||||
->setUserId($data['userId'] ?? '')
|
||||
->trigger();
|
||||
|
||||
$dbForConsole->deleteDocument(
|
||||
'schedules',
|
||||
$schedule['$id'],
|
||||
);
|
||||
});
|
||||
|
||||
$dbForConsole->deleteDocument(
|
||||
'schedules',
|
||||
$schedule['$id'],
|
||||
);
|
||||
|
||||
unset($this->schedules[$schedule['$internalId']]);
|
||||
}
|
||||
|
||||
$this->connections->reclaim();
|
||||
$queue->reclaim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ namespace Appwrite\Platform\Tasks;
|
||||
use Appwrite\Event\Func;
|
||||
use Cron\CronExpression;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Queue\Connection\Redis;
|
||||
use Utopia\Pools\Group;
|
||||
|
||||
class ScheduleFunctions extends ScheduleBase
|
||||
{
|
||||
@@ -25,7 +26,12 @@ class ScheduleFunctions extends ScheduleBase
|
||||
return 'function';
|
||||
}
|
||||
|
||||
protected function enqueueResources(array $pools, callable $getConsoleDB): void
|
||||
public static function getCollectionId(): string
|
||||
{
|
||||
return 'functions';
|
||||
}
|
||||
|
||||
protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void
|
||||
{
|
||||
$timerStart = \microtime(true);
|
||||
$time = DateTime::now();
|
||||
@@ -67,11 +73,8 @@ class ScheduleFunctions extends ScheduleBase
|
||||
\go(function () use ($delay, $scheduleKeys, $pools) {
|
||||
\sleep($delay); // in seconds
|
||||
|
||||
$pool = $pools['pools-queue-queue']['pool'];
|
||||
$connection = $pool->get();
|
||||
$this->connections->add($connection, $pool);
|
||||
|
||||
$queueConnection = new Redis($connection);
|
||||
$queue = $pools->get('queue')->pop();
|
||||
$connection = $queue->getResource();
|
||||
|
||||
foreach ($scheduleKeys as $scheduleKey) {
|
||||
// Ensure schedule was not deleted
|
||||
@@ -81,7 +84,7 @@ class ScheduleFunctions extends ScheduleBase
|
||||
|
||||
$schedule = $this->schedules[$scheduleKey];
|
||||
|
||||
$queueForFunctions = new Func($queueConnection);
|
||||
$queueForFunctions = new Func($connection);
|
||||
|
||||
$queueForFunctions
|
||||
->setType('schedule')
|
||||
@@ -92,8 +95,7 @@ class ScheduleFunctions extends ScheduleBase
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$this->connections->reclaim();
|
||||
// $queue->reclaim(); // TODO: Do in try/catch/finally, or add to connectons resource
|
||||
$queue->reclaim();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Event\Messaging;
|
||||
use Utopia\Queue\Connection\Redis;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Pools\Group;
|
||||
|
||||
class ScheduleMessages extends ScheduleBase
|
||||
{
|
||||
@@ -20,11 +21,13 @@ class ScheduleMessages extends ScheduleBase
|
||||
return 'message';
|
||||
}
|
||||
|
||||
protected function enqueueResources(array $pools, callable $getConsoleDB): void
|
||||
public static function getCollectionId(): string
|
||||
{
|
||||
[$connection,$pool, $dbForConsole] = $getConsoleDB();
|
||||
$this->connections->add($connection, $pool);
|
||||
return 'messages';
|
||||
}
|
||||
|
||||
protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void
|
||||
{
|
||||
foreach ($this->schedules as $schedule) {
|
||||
if (!$schedule['active']) {
|
||||
continue;
|
||||
@@ -37,15 +40,10 @@ class ScheduleMessages extends ScheduleBase
|
||||
continue;
|
||||
}
|
||||
|
||||
\go(function () use ($now, $schedule, $pools, $dbForConsole) {
|
||||
$pool = $pools['pools-queue-queue']['pool'];
|
||||
$dsn = $pools['pools-queue-queue']['dsn'];
|
||||
$connection = $pool->get();
|
||||
$this->connections->add($connection, $pool);
|
||||
|
||||
$queueConnection = new Redis($dsn->getHost(), $dsn->getPort());
|
||||
|
||||
$queueForMessaging = new Messaging($queueConnection);
|
||||
\go(function () use ($schedule, $pools, $dbForConsole) {
|
||||
$queue = $pools->get('queue')->pop();
|
||||
$connection = $queue->getResource();
|
||||
$queueForMessaging = new Messaging($connection);
|
||||
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
@@ -58,7 +56,8 @@ class ScheduleMessages extends ScheduleBase
|
||||
$schedule['$id'],
|
||||
);
|
||||
|
||||
$this->connections->reclaim();
|
||||
$queue->reclaim();
|
||||
|
||||
unset($this->schedules[$schedule['$internalId']]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,27 +5,25 @@ namespace Appwrite\Platform\Tasks;
|
||||
use Appwrite\Specification\Format\OpenAPI3;
|
||||
use Appwrite\Specification\Format\Swagger2;
|
||||
use Appwrite\Specification\Specification;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Models;
|
||||
use Appwrite\Utopia\Request as AppwriteRequest;
|
||||
use Appwrite\Utopia\Response as AppwriteResponse;
|
||||
use Exception;
|
||||
use Swoole\Http\Request as SwooleHttpRequest;
|
||||
use Swoole\Http\Response as SwooleHttpResponse;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\None;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\MySQL;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\DI\Dependency;
|
||||
use Utopia\Http\Adapter\FPM\Server;
|
||||
use Utopia\Http\Adapter\Swoole\Request;
|
||||
use Utopia\Http\Adapter\Swoole\Response as HttpResponse;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Http\Validator\WhiteList;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Request as UtopiaRequest;
|
||||
use Utopia\Response as UtopiaResponse;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Specs extends Action
|
||||
{
|
||||
@@ -34,38 +32,37 @@ class Specs extends Action
|
||||
return 'specs';
|
||||
}
|
||||
|
||||
public function getRequest(): UtopiaRequest
|
||||
{
|
||||
return new AppwriteRequest(new SwooleRequest());
|
||||
}
|
||||
|
||||
public function getResponse(): UtopiaResponse
|
||||
{
|
||||
return new AppwriteResponse(new SwooleResponse());
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Generate Appwrite API specifications')
|
||||
->param('version', 'latest', new Text(16), 'Spec version', true)
|
||||
->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true)
|
||||
->inject('context')
|
||||
->callback(fn (string $version, string $mode, Container $context) => $this->action($version, $mode, $context));
|
||||
->inject('register')
|
||||
->callback(fn (string $version, string $mode, Registry $register) => $this->action($version, $mode, $register));
|
||||
}
|
||||
|
||||
public function action(string $version, string $mode, Container $container): void
|
||||
public function action(string $version, string $mode, Registry $register): void
|
||||
{
|
||||
$appRoutes = Http::getRoutes();
|
||||
$response = new Response(new HttpResponse(new SwooleHttpResponse()));
|
||||
$appRoutes = App::getRoutes();
|
||||
$response = $this->getResponse();
|
||||
$mocks = ($mode === 'mocks');
|
||||
|
||||
$requestDependency = new Dependency();
|
||||
$responseDependency = new Dependency();
|
||||
$dbForConsole = new Dependency();
|
||||
$dbForProject = new Dependency();
|
||||
|
||||
// Mock dependencies
|
||||
$requestDependency->setName('request')->setCallback(fn () => new Request(new SwooleHttpRequest()));
|
||||
$responseDependency->setName('response')->setCallback(fn () => $response);
|
||||
$dbForConsole->setName('dbForConsole')->setCallback(fn () => new Database(new MySQL(''), new Cache(new None())));
|
||||
$dbForProject->setName('dbForProject')->setCallback(fn () => new Database(new MySQL(''), new Cache(new None())));
|
||||
|
||||
$container
|
||||
->set($requestDependency)
|
||||
->set($responseDependency)
|
||||
->set($dbForProject)
|
||||
->set($dbForConsole);
|
||||
App::setResource('request', fn () => $this->getRequest());
|
||||
App::setResource('response', fn () => $response);
|
||||
App::setResource('dbForConsole', fn () => new Database(new MySQL(''), new Cache(new None())));
|
||||
App::setResource('dbForProject', fn () => new Database(new MySQL(''), new Cache(new None())));
|
||||
|
||||
$platforms = [
|
||||
'client' => APP_PLATFORM_CLIENT,
|
||||
@@ -199,10 +196,8 @@ class Specs extends Action
|
||||
case APP_AUTH_TYPE_SESSION:
|
||||
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
||||
break;
|
||||
case APP_AUTH_TYPE_KEY:
|
||||
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||
break;
|
||||
case APP_AUTH_TYPE_JWT:
|
||||
case APP_AUTH_TYPE_KEY:
|
||||
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||
break;
|
||||
case APP_AUTH_TYPE_ADMIN:
|
||||
@@ -257,7 +252,7 @@ class Specs extends Action
|
||||
];
|
||||
}
|
||||
|
||||
$models = Models::getModels();
|
||||
$models = $response->getModels();
|
||||
|
||||
foreach ($models as $key => $value) {
|
||||
if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
|
||||
@@ -265,7 +260,7 @@ class Specs extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$arguments = [new Http(new Server(), $container, 'UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0];
|
||||
$arguments = [new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0];
|
||||
foreach (['swagger2', 'open-api3'] as $format) {
|
||||
$formatInstance = match ($format) {
|
||||
'swagger2' => new Swagger2(...$arguments),
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Http\Validator\Boolean;
|
||||
use Utopia\Http\Validator\Text;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Upgrade extends Install
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user