mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
398
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
610b9518fb | ||
|
|
c1af9f4201 | ||
|
|
c60524e11f | ||
|
|
ea3b9ca0ec | ||
|
|
14e2488f98 | ||
|
|
aae672d3be | ||
|
|
298d6ea917 | ||
|
|
fa7f9be6f7 | ||
|
|
1595d41490 | ||
|
|
38a3c08772 | ||
|
|
f2d20cc5c2 | ||
|
|
f127ca651f | ||
|
|
4e7b61b6c8 | ||
|
|
d79411813a | ||
|
|
4d0242b48b | ||
|
|
eb87b951c6 | ||
|
|
fa6f9acaed | ||
|
|
65b1e1e948 | ||
|
|
62808bfb8b | ||
|
|
8218e95f17 | ||
|
|
b6fabf80ef | ||
|
|
11a9583c72 | ||
|
|
49bb3444bf | ||
|
|
e382a9d58b | ||
|
|
d4ff696173 | ||
|
|
0382b2250a | ||
|
|
799fe2acca | ||
|
|
f3544485e5 | ||
|
|
4b7676158e | ||
|
|
5cb818066f | ||
|
|
d34050a5df | ||
|
|
8bec64b2a2 | ||
|
|
dea3e74b6a | ||
|
|
1a496bbf71 | ||
|
|
540ff6ee2e | ||
|
|
5179c4d08a | ||
|
|
6201edc65d | ||
|
|
1720acb2b4 | ||
|
|
aa6a1a2e31 | ||
|
|
022f0a663e | ||
|
|
7e625ba653 | ||
|
|
72b4c78c9f | ||
|
|
0dca07f6fc | ||
|
|
39df6e443a | ||
|
|
8eb8351d5f | ||
|
|
9d31d8d950 | ||
|
|
f81d675609 | ||
|
|
28c61acec6 | ||
|
|
9a12ab8f8a | ||
|
|
da3798e52f | ||
|
|
b2381798ab | ||
|
|
8b14d96151 | ||
|
|
6ad4566b6b | ||
|
|
a7833421ef | ||
|
|
37e1e3adaf | ||
|
|
a96e571258 | ||
|
|
91e29fb974 | ||
|
|
926e1f3fc8 | ||
|
|
81902f0555 | ||
|
|
f9783ccba3 | ||
|
|
695d6f4065 | ||
|
|
03e8a29e8d | ||
|
|
e16387e463 | ||
|
|
a76efe7d4e | ||
|
|
0873093441 | ||
|
|
446e1c0979 | ||
|
|
b68be491b9 | ||
|
|
9183f2a875 | ||
|
|
2a2ff34e20 | ||
|
|
f0e900a3b5 | ||
|
|
55df51fe94 | ||
|
|
ab35b75c37 | ||
|
|
2a54fd64ee | ||
|
|
274ef1664b | ||
|
|
228b813994 | ||
|
|
93f58fb876 | ||
|
|
7902defe69 | ||
|
|
505091c9e7 | ||
|
|
827ad84f53 | ||
|
|
cf1c914edb | ||
|
|
9329287982 | ||
|
|
40ec98569e | ||
|
|
eeb0eed29b | ||
|
|
e079d4a8b5 | ||
|
|
174449ce49 | ||
|
|
5a715ff68c | ||
|
|
18994d308a | ||
|
|
881b92eeb8 | ||
|
|
8013d79ae0 | ||
|
|
8c9a662e14 | ||
|
|
dceb1858b0 | ||
|
|
c34dc4dae0 | ||
|
|
fccbe405db | ||
|
|
02b6659b35 | ||
|
|
7cce033682 | ||
|
|
8e911dc83e | ||
|
|
e145ad4cb4 | ||
|
|
6826a9efe0 | ||
|
|
37e1e25170 | ||
|
|
90ea00398b | ||
|
|
5a06c015da | ||
|
|
e36dc817fb | ||
|
|
bc09b50c11 | ||
|
|
6b5e734dfa | ||
|
|
31d5b75034 | ||
|
|
7efecbca8b | ||
|
|
7766279025 | ||
|
|
42a92933a8 | ||
|
|
df38a8efd1 | ||
|
|
115ca23a58 | ||
|
|
74604a1a60 | ||
|
|
2e70ed59af | ||
|
|
94931ff4ce | ||
|
|
c3015340ce | ||
|
|
f6408cd945 | ||
|
|
1c4fea0fc3 | ||
|
|
29cd164448 | ||
|
|
7e1b618769 | ||
|
|
c2e5849c49 | ||
|
|
141b864a56 | ||
|
|
2572cb43c2 | ||
|
|
c133bccfa9 | ||
|
|
fb6455b783 | ||
|
|
4f2f76db22 | ||
|
|
89c097eed3 | ||
|
|
78eb5105e7 | ||
|
|
be85b173e7 | ||
|
|
3d9ee8bc52 | ||
|
|
60c0f4c973 | ||
|
|
dd0bf66212 | ||
|
|
be6b31b43f | ||
|
|
b9007a5956 | ||
|
|
967854d34a | ||
|
|
a029287e5a | ||
|
|
5871178fc0 | ||
|
|
573a035f92 | ||
|
|
97757c9984 | ||
|
|
4d54e7bb36 | ||
|
|
2d9ac9bac1 | ||
|
|
17919ecfbe | ||
|
|
7e8778c185 | ||
|
|
f1ea4f761e | ||
|
|
c014e40c54 | ||
|
|
f70b418c5c | ||
|
|
e2fa5356bc | ||
|
|
acc49eb998 | ||
|
|
d33b8c2ce1 | ||
|
|
56310bc4fa | ||
|
|
5087ae61e7 | ||
|
|
0c264a107a | ||
|
|
fcf9e9c4d0 | ||
|
|
2bf247053d | ||
|
|
d64d56069f | ||
|
|
780dffd973 | ||
|
|
2baadb6e95 | ||
|
|
54e5985940 | ||
|
|
dd9d8010c1 | ||
|
|
bae9da5695 | ||
|
|
05ab73ee37 | ||
|
|
e461a4f14b | ||
|
|
0f3c438976 | ||
|
|
365c520267 | ||
|
|
698bce2c36 | ||
|
|
93d3cb7bcf | ||
|
|
fd7b354113 | ||
|
|
8feb01d857 | ||
|
|
888c7bf39c | ||
|
|
2d2400fdff | ||
|
|
c079d2f978 | ||
|
|
f4cdd0accc | ||
|
|
8dff9b9b49 | ||
|
|
9acc23049c | ||
|
|
f53fbc19d4 | ||
|
|
d5d7440a68 | ||
|
|
44cbfb2769 | ||
|
|
cb512710f2 | ||
|
|
e4191f206d | ||
|
|
7e37c973ce | ||
|
|
8db73a3431 | ||
|
|
018800c7ae | ||
|
|
55a73efeab | ||
|
|
dfc8c919d4 | ||
|
|
75669b9b2e | ||
|
|
4440859c05 | ||
|
|
61c5f371b7 | ||
|
|
8b24c44fb9 | ||
|
|
2292143750 | ||
|
|
1429c82aa0 | ||
|
|
e4bde92b35 | ||
|
|
0ecdeed329 | ||
|
|
19e5235491 | ||
|
|
bcdac2f803 | ||
|
|
30b8ff9166 | ||
|
|
03da0380ea | ||
|
|
9f7bbe4869 | ||
|
|
9fc7c1384a | ||
|
|
305c781206 | ||
|
|
4a3b8fb70e | ||
|
|
9be32184d6 | ||
|
|
8e1afa11f9 | ||
|
|
23631f8282 | ||
|
|
d921c03ee1 | ||
|
|
b15dac16c8 | ||
|
|
2d045335ff | ||
|
|
e158d134b0 | ||
|
|
49e23e8916 | ||
|
|
d95e4790a0 | ||
|
|
8798d183bd | ||
|
|
64630490a5 | ||
|
|
281b50ba4a | ||
|
|
3652ab61d8 | ||
|
|
74450b1178 | ||
|
|
7110836beb | ||
|
|
358fdb6878 | ||
|
|
755699de8a | ||
|
|
311fc0f0c4 | ||
|
|
7c8a9ad4d2 | ||
|
|
c4e22b1085 | ||
|
|
ce6d873cf4 | ||
|
|
1809140659 | ||
|
|
9fded16d3f | ||
|
|
42288f3070 | ||
|
|
0a6ee966a2 | ||
|
|
744f2fbc1c | ||
|
|
e8110953b5 | ||
|
|
68486d4795 | ||
|
|
fbb82ceb81 | ||
|
|
4eab2a01f7 | ||
|
|
c1df289df7 | ||
|
|
255659f009 | ||
|
|
c3b7b5af80 | ||
|
|
3abb74c937 | ||
|
|
24824e1582 | ||
|
|
106f4cfc45 | ||
|
|
75484fa018 | ||
|
|
917a4e9ab1 | ||
|
|
bcdebee598 | ||
|
|
9a6d5f8101 | ||
|
|
71807635b3 | ||
|
|
07ef5535ef | ||
|
|
e17bb94e39 | ||
|
|
21220a2051 | ||
|
|
6d237853e6 | ||
|
|
9164ed1045 | ||
|
|
30abfb01b7 | ||
|
|
cba20d8b96 | ||
|
|
b2d385944a | ||
|
|
bb7278dd12 | ||
|
|
36fcc6c293 | ||
|
|
0a1a2af218 | ||
|
|
84559b8f56 | ||
|
|
3700751592 | ||
|
|
2ee505b3f8 | ||
|
|
740d9def8e | ||
|
|
94ff3baa9c | ||
|
|
7c1ab91ebc | ||
|
|
fc4fa977a0 | ||
|
|
cb03ad1a15 | ||
|
|
836ce852ed | ||
|
|
3206cee36f | ||
|
|
70a3820009 | ||
|
|
ab760ba46b | ||
|
|
3caa49a547 | ||
|
|
c81b4576b4 | ||
|
|
4becf5138d | ||
|
|
16fe42bca9 | ||
|
|
d16e3f2f56 | ||
|
|
45a0954b24 | ||
|
|
fc348b2055 | ||
|
|
7e6378b23b | ||
|
|
0f43aa6872 | ||
|
|
2b939a57d0 | ||
|
|
c8788e2a13 | ||
|
|
09912592cf | ||
|
|
c9c688c592 | ||
|
|
5d634384c8 | ||
|
|
a4c59b838a | ||
|
|
c3a80dcebc | ||
|
|
831122679f | ||
|
|
1d9b94ee4d | ||
|
|
8628e94d0c | ||
|
|
89236955fb | ||
|
|
995c404f35 | ||
|
|
2d897889c9 | ||
|
|
822596b2c9 | ||
|
|
191235ecaf | ||
|
|
2910e87c7a | ||
|
|
404ad8653d | ||
|
|
fa0d9139c1 | ||
|
|
f9a3ae1421 | ||
|
|
db52e047c6 | ||
|
|
17de0d1ea7 | ||
|
|
5732171a2a | ||
|
|
7d10174193 | ||
|
|
8db372a7d3 | ||
|
|
5069aef6ac | ||
|
|
fbcbd0d430 | ||
|
|
3a53dce467 | ||
|
|
3e2b6c4344 | ||
|
|
84ea81f56d | ||
|
|
3addf76a2a | ||
|
|
ce8c7e1527 | ||
|
|
342deb4989 | ||
|
|
0411f9f530 | ||
|
|
ad4db114bc | ||
|
|
c0460289b7 | ||
|
|
a09c4281c8 | ||
|
|
c7435e7e78 | ||
|
|
2cf8639b68 | ||
|
|
97c1099961 | ||
|
|
fc34d2ff7e | ||
|
|
45c44bb51c | ||
|
|
14f35589b3 | ||
|
|
76e2b39c1a | ||
|
|
e2eccaca8b | ||
|
|
765391cd7c | ||
|
|
453ce03881 | ||
|
|
d1deef43c5 | ||
|
|
c0f099e176 | ||
|
|
42ce9d881a | ||
|
|
1787e57448 | ||
|
|
b0b9bd3268 | ||
|
|
9f3561aaa3 | ||
|
|
6141d02e16 | ||
|
|
175879a352 | ||
|
|
397bdd50f4 | ||
|
|
413209d990 | ||
|
|
3a80d58353 | ||
|
|
eac911a006 | ||
|
|
979054e5ca | ||
|
|
2914a4d1c5 | ||
|
|
bb0744842d | ||
|
|
9fbf10f9f5 | ||
|
|
80574838a4 | ||
|
|
64d582a43a | ||
|
|
62ce68f1b5 | ||
|
|
23a1da58fb | ||
|
|
1489f77499 | ||
|
|
96f389cee5 | ||
|
|
b420c570bc | ||
|
|
5956c4c247 | ||
|
|
5274f1234b | ||
|
|
d4b53e5014 | ||
|
|
fe7aa60a11 | ||
|
|
f18d33a0d6 | ||
|
|
66ec7f2e1d | ||
|
|
615bd5148e | ||
|
|
bb75036efe | ||
|
|
291654bee9 | ||
|
|
dbd6dbcb5b | ||
|
|
1f97bb39b3 | ||
|
|
bd2999037d | ||
|
|
9cb1cf418c | ||
|
|
e5c828fbbd | ||
|
|
c12999dc74 | ||
|
|
a2c9c8b82e | ||
|
|
9b0a2fdc73 | ||
|
|
5771ad35ed | ||
|
|
56a3b3df99 | ||
|
|
6b47999120 | ||
|
|
f07f246f8d | ||
|
|
ab5fa96668 | ||
|
|
8a28c9ddbc | ||
|
|
85fcddcead | ||
|
|
ff4816a436 | ||
|
|
1c9dfb903d | ||
|
|
238089409a | ||
|
|
e70d5beb0d | ||
|
|
e390cf0d70 | ||
|
|
29e056c71b | ||
|
|
0a2874458b | ||
|
|
0ddf036bd5 | ||
|
|
29d0820b10 | ||
|
|
889942ee76 | ||
|
|
01aaa6b337 | ||
|
|
eaf94aa670 | ||
|
|
1a93aa4085 | ||
|
|
0f67c1151f | ||
|
|
10f6cc7b0b | ||
|
|
733b7ed612 | ||
|
|
1415727f10 | ||
|
|
ebb4bc7e6b | ||
|
|
21357bfe7b | ||
|
|
5cb14fbec5 | ||
|
|
5793a11480 | ||
|
|
2ed6327c1e | ||
|
|
cf030457e0 | ||
|
|
27f77759ed | ||
|
|
dec4f4722b | ||
|
|
8bd822d171 | ||
|
|
13c6666883 | ||
|
|
fa99c4584e | ||
|
|
2aad9e622b | ||
|
|
22c8781047 | ||
|
|
57e146be2b | ||
|
|
1e07fa96a3 | ||
|
|
2eae37d802 | ||
|
|
addc88ccdc |
@@ -91,7 +91,7 @@ _APP_GRAPHQL_MAX_DEPTH=3
|
||||
_APP_DOCKER_HUB_USERNAME=
|
||||
_APP_DOCKER_HUB_PASSWORD=
|
||||
_APP_VCS_GITHUB_APP_NAME=
|
||||
_APP_VCS_GITHUB_PRIVATE_KEY=""
|
||||
_APP_VCS_GITHUB_PRIVATE_KEY=disabled
|
||||
_APP_VCS_GITHUB_APP_ID=
|
||||
_APP_VCS_GITHUB_CLIENT_ID=
|
||||
_APP_VCS_GITHUB_CLIENT_SECRET=
|
||||
|
||||
@@ -18,6 +18,10 @@ jobs:
|
||||
|
||||
- run: git checkout HEAD^2
|
||||
|
||||
- name: Validate composer.json and composer.lock
|
||||
run: |
|
||||
docker run --rm -v $PWD:/app composer sh -c \
|
||||
"composer validate"
|
||||
- name: Run Linter
|
||||
run: |
|
||||
docker run --rm -v $PWD:/app composer sh -c \
|
||||
|
||||
+104
-42
@@ -4,57 +4,119 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
IMAGE: appwrite-dev
|
||||
CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }}
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Unit & E2E
|
||||
setup:
|
||||
name: Setup & Build Appwrite Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build Appwrite
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: ${{ env.IMAGE }}
|
||||
load: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar
|
||||
build-args: |
|
||||
DEBUG=false
|
||||
TESTING=true
|
||||
VERSION=dev
|
||||
|
||||
- name: Cache Docker Image
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
|
||||
unit_test:
|
||||
name: Unit Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
# Fetch submodules
|
||||
submodules: recursive
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
# This is a separate action that sets up buildx runner
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Load and Start Appwrite
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
|
||||
- name: Build Appwrite
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: appwrite-dev
|
||||
load: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
DEBUG=false
|
||||
TESTING=true
|
||||
VERSION=dev
|
||||
- name: Doctor
|
||||
run: docker compose exec -T appwrite doctor
|
||||
|
||||
- name: Start Appwrite
|
||||
run: |
|
||||
docker compose up -d
|
||||
sleep 30
|
||||
- name: Environment Variables
|
||||
run: docker compose exec -T appwrite vars
|
||||
|
||||
- name: Doctor
|
||||
run: |
|
||||
docker compose logs appwrite
|
||||
docker compose exec -T appwrite doctor
|
||||
- name: Run Unit Tests
|
||||
run: docker compose exec appwrite test /usr/src/code/tests/unit
|
||||
|
||||
- name: Environment Variables
|
||||
run: docker compose exec -T appwrite vars
|
||||
e2e_test:
|
||||
name: E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
service:
|
||||
[
|
||||
Account,
|
||||
Avatars,
|
||||
Console,
|
||||
Databases,
|
||||
Functions,
|
||||
GraphQL,
|
||||
Health,
|
||||
Locale,
|
||||
Projects,
|
||||
Realtime,
|
||||
Storage,
|
||||
Teams,
|
||||
Users,
|
||||
Webhooks,
|
||||
VCS,
|
||||
]
|
||||
|
||||
- name: Run Tests
|
||||
run: docker compose exec -T appwrite test --debug
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
|
||||
- name: Run ${{matrix.service}} Tests
|
||||
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
[submodule "app/console"]
|
||||
path = app/console
|
||||
url = https://github.com/appwrite/console
|
||||
branch = 3.2.3
|
||||
branch = 3.2.15
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ tasks:
|
||||
command: |
|
||||
docker run --rm --interactive --tty \
|
||||
--volume $PWD:/app \
|
||||
composer update \
|
||||
composer install \
|
||||
--ignore-platform-reqs \
|
||||
--optimize-autoloader \
|
||||
--no-plugins \
|
||||
|
||||
+88
@@ -1,3 +1,91 @@
|
||||
# Version 1.4.13
|
||||
|
||||
## Notable changes
|
||||
|
||||
* Change enum size validation in update controller [#7164](https://github.com/appwrite/appwrite/pull/7164)
|
||||
* Bump console to version 3.2.8 in [#7167](https://github.com/appwrite/appwrite/pull/7167)
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fix error after adding bigger enum [#7162](https://github.com/appwrite/appwrite/pull/7162)
|
||||
* Add chunkId to abuse key to prevent rate limit for SDKs [#7154](https://github.com/appwrite/appwrite/pull/7154)
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
* Fix enum test case [#7163](https://github.com/appwrite/appwrite/pull/7163)
|
||||
* Add flag to send logs to logger [#7155](https://github.com/appwrite/appwrite/pull/7155)
|
||||
* Add a CI task to validate composer file and lock [#7142](https://github.com/appwrite/appwrite/pull/7142)
|
||||
|
||||
# Version 1.4.12
|
||||
|
||||
## Miscellaneous
|
||||
* Bump console to version 3.2.7 [#7148](https://github.com/appwrite/appwrite/pull/7148)
|
||||
* Chore update database to 0.45.2 [#7138](https://github.com/appwrite/appwrite/pull/7138)
|
||||
* Implement queue thresholds for the health API [#7123](https://github.com/appwrite/appwrite/pull/7123)
|
||||
* Add Authorization::skip to the usage worker [#7124](https://github.com/appwrite/appwrite/pull/7124)
|
||||
|
||||
## Bug fixes
|
||||
* fix: use queueForDeletes in git installation delete endpoint [#7140](https://github.com/appwrite/appwrite/pull/7140)
|
||||
* fix: patch script, make errors silent [#7134](https://github.com/appwrite/appwrite/pull/7134)
|
||||
* fix: repositories recreation script [#7133](https://github.com/appwrite/appwrite/pull/7133)
|
||||
* fix: Only delete repositories linked to the particular project [#7131](https://github.com/appwrite/appwrite/pull/7131)
|
||||
|
||||
# Version 1.4.11
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
* Update database by @abnegate in [#7111](https://github.com/appwrite/appwrite/pull/7111)
|
||||
|
||||
# Version 1.4.10
|
||||
|
||||
## Bug fixes
|
||||
* Handle cases where password history could contain NULLs [#7092](https://github.com/appwrite/appwrite/pull/7092)
|
||||
* Missing functionId error on create execution [#7091](https://github.com/appwrite/appwrite/pull/7091)
|
||||
* Ensure usage endpoints don't throw 500 when usage is disabled [#7087](https://github.com/appwrite/appwrite/pull/7087)
|
||||
* Missing sessionId error when deleting all user sessions [#7085](https://github.com/appwrite/appwrite/pull/7085)
|
||||
* Domain validation in Create Proxy rule results in 500 error [#7084](https://github.com/appwrite/appwrite/pull/7084)
|
||||
* Fix optional services [#7078](https://github.com/appwrite/appwrite/pull/7078)
|
||||
* Fix regression from worker refactor [#7074](https://github.com/appwrite/appwrite/pull/7074)
|
||||
* Use getQueueSize() in the Health service's get X queue endpoints [#7073](https://github.com/appwrite/appwrite/pull/7073)
|
||||
* Delete linked VCS repos and comments [#7066](https://github.com/appwrite/appwrite/pull/7066)
|
||||
|
||||
# Version 1.4.9
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fix 400 error on function domain execution in [#7059](https://github.com/appwrite/appwrite/pull/7059)
|
||||
|
||||
# Version 1.4.8
|
||||
|
||||
## Notable changes
|
||||
|
||||
* Fix certificate emails and add support for variables in email template subject in [#6495](https://github.com/appwrite/appwrite/)pull/6495
|
||||
* Bump console to version 3.2.5 in [#7027](https://github.com/appwrite/appwrite/pull/7027)
|
||||
* Bump utopia database and storage versions in [#7002](https://github.com/appwrite/appwrite/pull/7002)
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fixes cookie headers not being passed properly by router in [#7024](https://github.com/appwrite/appwrite/pull/7024)
|
||||
* Fix permission problem in deletes worker in [#7013](https://github.com/appwrite/appwrite/pull/7013)
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
* Improve error handling in the realtime service in [#6998](https://github.com/appwrite/appwrite/pull/6998)
|
||||
* Update the error code for unsupported protocol in [#7006](https://github.com/appwrite/appwrite/pull/7006)
|
||||
* Improve CI tests by executing them in parallel in [#6198](https://github.com/appwrite/appwrite/pull/6198)
|
||||
* Update README.md to add links to orchestration tools in [#7011](https://github.com/appwrite/appwrite/pull/7011)
|
||||
* Update gitpod setup to install instead of update dependencies in [#6938](https://github.com/appwrite/appwrite/pull/6938)
|
||||
* Remove analytics from install script in [#7017](https://github.com/appwrite/appwrite/pull/7017)
|
||||
* Improve database logging in [#7003](https://github.com/appwrite/appwrite/pull/7003)
|
||||
* Add VCS tests in [#6894](https://github.com/appwrite/appwrite/pull/6894)
|
||||
* Improve error messages in [#6487](https://github.com/appwrite/appwrite/pull/6487)
|
||||
* Add command to delete orphaned projects in [#7015](https://github.com/appwrite/appwrite/pull/7015)
|
||||
|
||||
# Version 1.4.7
|
||||
|
||||
## Fixes
|
||||
- Fix missing body in async function execution in [#6988](https://github.com/appwrite/appwrite/pull/6988)
|
||||
|
||||
# Version 1.4.6
|
||||
|
||||
## Changes
|
||||
|
||||
+2
-2
@@ -89,7 +89,7 @@ $ git push origin [name_of_your_new_branch]
|
||||
|
||||
To set up a working **development environment**, just fork the project git repository and install the backend and frontend dependencies using the proper package manager and create run the docker-compose stack.
|
||||
|
||||
> If you just want to install Appwrite for day-to-day use and not as a contributor, you can reference the [installation guide](https://github.com/appwrite/appwrite#installation), the [getting started guide](https://appwrite.io/docs/getting-started-for-web), or the main [README](README.md) file.
|
||||
> If you just want to install Appwrite for day-to-day use and not as a contributor, you can reference the [installation guide](https://github.com/appwrite/appwrite#installation), the [getting started guide](https://appwrite.io/docs/quick-starts), or the main [README](README.md) file.
|
||||
|
||||
```bash
|
||||
git clone git@github.com:[YOUR_FORK_HERE]/appwrite.git
|
||||
@@ -454,7 +454,7 @@ From time to time, our team will add tutorials that will help contributors find
|
||||
|
||||
- [Adding Support for a New OAuth2 Provider](./docs/tutorials/add-oauth2-provider.md)
|
||||
- [Appwrite Environment Variables](./docs/tutorials/add-environment-variable.md)
|
||||
- [Running in Production](https://appwrite.io/docs/production)
|
||||
- [Running in Production](https://appwrite.io/docs/advanced/self-hosting/production)
|
||||
- [Adding Storage Adapter](./docs/tutorials/add-storage-adapter.md)
|
||||
|
||||
## Other Ways to Help
|
||||
|
||||
+6
-2
@@ -94,16 +94,20 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/worker-mails && \
|
||||
chmod +x /usr/local/bin/worker-messaging && \
|
||||
chmod +x /usr/local/bin/worker-webhooks && \
|
||||
chmod +x /usr/local/bin/worker-migrations
|
||||
chmod +x /usr/local/bin/worker-migrations && \
|
||||
chmod +x /usr/local/bin/worker-hamster
|
||||
|
||||
# Cloud Executabless
|
||||
RUN chmod +x /usr/local/bin/hamster && \
|
||||
chmod +x /usr/local/bin/volume-sync && \
|
||||
chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \
|
||||
chmod +x /usr/local/bin/patch-recreate-repositories-documents && \
|
||||
chmod +x /usr/local/bin/patch-delete-project-collections && \
|
||||
chmod +x /usr/local/bin/delete-orphaned-projects && \
|
||||
chmod +x /usr/local/bin/clear-card-cache && \
|
||||
chmod +x /usr/local/bin/calc-users-stats && \
|
||||
chmod +x /usr/local/bin/calc-tier-stats
|
||||
chmod +x /usr/local/bin/calc-tier-stats && \
|
||||
chmod +x /usr/local/bin/get-migration-stats
|
||||
|
||||
# Letsencrypt Permissions
|
||||
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
||||
|
||||
+4
-4
@@ -55,7 +55,7 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
|
||||
|
||||
## 安装
|
||||
|
||||
Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 Kubernetes、Docker Swarm 或 Rancher)上运行 Appwrite。
|
||||
Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 [Kubernetes](https://kubernetes.io/docs/home/)、[Docker Swarm](https://docs.docker.com/engine/swarm/) 或 [Rancher](https://rancher.com/docs/))上运行 Appwrite。
|
||||
|
||||
启动 Appwrite 服务器的最简单方法是运行我们的 docker-compose 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://dockerdocs.cn/get-docker/index.html):
|
||||
|
||||
@@ -66,7 +66,7 @@ docker run -it --rm \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.4.6
|
||||
appwrite/appwrite:1.4.13
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -78,7 +78,7 @@ docker run -it --rm ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.4.6
|
||||
appwrite/appwrite:1.4.13
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -88,7 +88,7 @@ docker run -it --rm `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.4.6
|
||||
appwrite/appwrite:1.4.13
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
|
||||
@@ -65,7 +65,7 @@ Table of Contents:
|
||||
|
||||
## Installation
|
||||
|
||||
Appwrite is designed to run in a containerized environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool, such as Kubernetes, Docker Swarm, or Rancher.
|
||||
Appwrite is designed to run in a containerized environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool, such as [Kubernetes](https://kubernetes.io/docs/home/), [Docker Swarm](https://docs.docker.com/engine/swarm/), or [Rancher](https://rancher.com/docs/).
|
||||
|
||||
The easiest way to start running your Appwrite server is by running our docker-compose file. Before running the installation command, make sure you have [Docker](https://www.docker.com/products/docker-desktop) installed on your machine:
|
||||
|
||||
@@ -76,7 +76,7 @@ docker run -it --rm \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.4.6
|
||||
appwrite/appwrite:1.4.13
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -88,7 +88,7 @@ docker run -it --rm ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.4.6
|
||||
appwrite/appwrite:1.4.13
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -98,7 +98,7 @@ docker run -it --rm `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.4.6
|
||||
appwrite/appwrite:1.4.13
|
||||
```
|
||||
|
||||
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
|
||||
|
||||
+13
-2
@@ -6,6 +6,7 @@ require_once __DIR__ . '/controllers/general.php';
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Hamster;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Utopia\CLI\CLI;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
@@ -61,7 +62,11 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
|
||||
->getResource();
|
||||
|
||||
$dbForConsole = new Database($dbAdapter, $cache);
|
||||
$dbForConsole->setNamespace('_console');
|
||||
|
||||
$dbForConsole
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console');
|
||||
|
||||
// Ensure tables exist
|
||||
$collections = Config::getParam('collections', [])['console'];
|
||||
@@ -111,7 +116,10 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
||||
|
||||
$databases[$databaseName] = $database;
|
||||
|
||||
$database->setNamespace('_' . $project->getInternalId());
|
||||
$database
|
||||
->setNamespace('_' . $project->getInternalId())
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
return $database;
|
||||
};
|
||||
@@ -147,6 +155,9 @@ CLI::setResource('queue', function (Group $pools) {
|
||||
CLI::setResource('queueForFunctions', function (Connection $queue) {
|
||||
return new Func($queue);
|
||||
}, ['queue']);
|
||||
CLI::setResource('queueForHamster', function (Connection $queue) {
|
||||
return new Hamster($queue);
|
||||
}, ['queue']);
|
||||
CLI::setResource('queueForDeletes', function (Connection $queue) {
|
||||
return new Delete($queue);
|
||||
}, ['queue']);
|
||||
|
||||
+48
-7
@@ -86,7 +86,7 @@ return [
|
||||
Exception::GENERAL_PROTOCOL_UNSUPPORTED => [
|
||||
'name' => Exception::GENERAL_PROTOCOL_UNSUPPORTED,
|
||||
'description' => 'The request cannot be fulfilled with the current protocol. Please check the value of the _APP_OPTIONS_FORCE_HTTPS environment variable.',
|
||||
'code' => 500,
|
||||
'code' => 426,
|
||||
],
|
||||
Exception::GENERAL_CODES_DISABLED => [
|
||||
'name' => Exception::GENERAL_CODES_DISABLED,
|
||||
@@ -209,6 +209,7 @@ return [
|
||||
'name' => Exception::USER_AUTH_METHOD_UNSUPPORTED,
|
||||
'description' => 'The requested authentication method is either disabled or unsupported. Please check the supported authentication methods in the Appwrite console.',
|
||||
'code' => 501,
|
||||
'publish' => false,
|
||||
],
|
||||
Exception::USER_PHONE_ALREADY_EXISTS => [
|
||||
'name' => Exception::USER_PHONE_ALREADY_EXISTS,
|
||||
@@ -240,6 +241,16 @@ return [
|
||||
'description' => 'OAuth2 provider returned some error.',
|
||||
'code' => 424,
|
||||
],
|
||||
Exception::USER_EMAIL_ALREADY_VERIFIED => [
|
||||
'name' => Exception::USER_EMAIL_ALREADY_VERIFIED,
|
||||
'description' => 'User email is already verified',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::USER_PHONE_ALREADY_VERIFIED => [
|
||||
'name' => Exception::USER_PHONE_ALREADY_VERIFIED,
|
||||
'description' => 'User phone is already verified',
|
||||
'code' => 409
|
||||
],
|
||||
|
||||
/** Teams */
|
||||
Exception::TEAM_NOT_FOUND => [
|
||||
@@ -355,7 +366,7 @@ return [
|
||||
],
|
||||
Exception::STORAGE_BUCKET_ALREADY_EXISTS => [
|
||||
'name' => Exception::STORAGE_BUCKET_ALREADY_EXISTS,
|
||||
'description' => 'A storage bucket with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
|
||||
'description' => 'A storage bucket with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::STORAGE_BUCKET_NOT_FOUND => [
|
||||
@@ -479,7 +490,7 @@ return [
|
||||
],
|
||||
Exception::COLLECTION_ALREADY_EXISTS => [
|
||||
'name' => Exception::COLLECTION_ALREADY_EXISTS,
|
||||
'description' => 'A collection with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
|
||||
'description' => 'A collection with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::COLLECTION_LIMIT_EXCEEDED => [
|
||||
@@ -511,7 +522,7 @@ return [
|
||||
],
|
||||
Exception::DOCUMENT_ALREADY_EXISTS => [
|
||||
'name' => Exception::DOCUMENT_ALREADY_EXISTS,
|
||||
'description' => 'Document with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
|
||||
'description' => 'Document with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::DOCUMENT_UPDATE_CONFLICT => [
|
||||
@@ -553,7 +564,7 @@ return [
|
||||
],
|
||||
Exception::ATTRIBUTE_ALREADY_EXISTS => [
|
||||
'name' => Exception::ATTRIBUTE_ALREADY_EXISTS,
|
||||
'description' => 'Attribute with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
|
||||
'description' => 'Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::ATTRIBUTE_LIMIT_EXCEEDED => [
|
||||
@@ -585,7 +596,7 @@ return [
|
||||
],
|
||||
Exception::INDEX_ALREADY_EXISTS => [
|
||||
'name' => Exception::INDEX_ALREADY_EXISTS,
|
||||
'description' => 'Index with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
|
||||
'description' => 'Index with the requested key already exists. Try again with a different key.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::INDEX_INVALID => [
|
||||
@@ -602,7 +613,7 @@ return [
|
||||
],
|
||||
Exception::PROJECT_ALREADY_EXISTS => [
|
||||
'name' => Exception::PROJECT_ALREADY_EXISTS,
|
||||
'description' => 'Project with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
|
||||
'description' => 'Project with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::PROJECT_UNKNOWN => [
|
||||
@@ -732,4 +743,34 @@ return [
|
||||
'description' => 'Migration is already in progress. You can check the status of the migration in your Appwrite Console\'s "Settings" > "Migrations".',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Realtime */
|
||||
Exception::REALTIME_MESSAGE_FORMAT_INVALID => [
|
||||
'name' => Exception::REALTIME_MESSAGE_FORMAT_INVALID,
|
||||
'description' => 'Message format is not valid.',
|
||||
'code' => 1003,
|
||||
],
|
||||
Exception::REALTIME_POLICY_VIOLATION => [
|
||||
'name' => Exception::REALTIME_POLICY_VIOLATION,
|
||||
'description' => 'Policy violation.',
|
||||
'code' => 1008,
|
||||
],
|
||||
Exception::REALTIME_TOO_MANY_MESSAGES => [
|
||||
'name' => Exception::REALTIME_TOO_MANY_MESSAGES,
|
||||
'description' => 'Too many messages.',
|
||||
'code' => 1013,
|
||||
],
|
||||
Exception::MIGRATION_PROVIDER_ERROR => [
|
||||
'name' => Exception::MIGRATION_PROVIDER_ERROR,
|
||||
'description' => 'An error occurred on the provider\'s side. Please try again later.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Health */
|
||||
Exception::QUEUE_SIZE_EXCEEDED => [
|
||||
'name' => Exception::QUEUE_SIZE_EXCEEDED,
|
||||
'description' => 'Queue size threshold hit.',
|
||||
'code' => 503,
|
||||
'publish' => false
|
||||
],
|
||||
];
|
||||
|
||||
+16
-16
@@ -15,7 +15,7 @@ return [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '13.0.0',
|
||||
'version' => '13.0.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-web',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -63,7 +63,7 @@ return [
|
||||
[
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'version' => '11.0.0',
|
||||
'version' => '11.0.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-flutter',
|
||||
'package' => 'https://pub.dev/packages/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -81,7 +81,7 @@ return [
|
||||
[
|
||||
'key' => 'apple',
|
||||
'name' => 'Apple',
|
||||
'version' => '4.0.1',
|
||||
'version' => '4.0.2',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'enabled' => true,
|
||||
@@ -116,7 +116,7 @@ return [
|
||||
[
|
||||
'key' => 'android',
|
||||
'name' => 'Android',
|
||||
'version' => '4.0.0',
|
||||
'version' => '4.0.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-android',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
|
||||
'enabled' => true,
|
||||
@@ -203,7 +203,7 @@ return [
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '4.1.0',
|
||||
'version' => '4.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
@@ -231,7 +231,7 @@ return [
|
||||
[
|
||||
'key' => 'nodejs',
|
||||
'name' => 'Node.js',
|
||||
'version' => '11.0.0',
|
||||
'version' => '11.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-node',
|
||||
'package' => 'https://www.npmjs.com/package/node-appwrite',
|
||||
'enabled' => true,
|
||||
@@ -249,7 +249,7 @@ return [
|
||||
[
|
||||
'key' => 'deno',
|
||||
'name' => 'Deno',
|
||||
'version' => '9.0.0',
|
||||
'version' => '9.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-deno',
|
||||
'package' => 'https://deno.land/x/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -267,7 +267,7 @@ return [
|
||||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '10.0.0',
|
||||
'version' => '10.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -285,7 +285,7 @@ return [
|
||||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '4.0.0',
|
||||
'version' => '4.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
@@ -303,7 +303,7 @@ return [
|
||||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '10.0.0',
|
||||
'version' => '10.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -321,7 +321,7 @@ return [
|
||||
[
|
||||
'key' => 'go',
|
||||
'name' => 'Go',
|
||||
'version' => '3.0.0',
|
||||
'version' => '3.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'package' => '',
|
||||
'enabled' => false,
|
||||
@@ -339,7 +339,7 @@ return [
|
||||
[
|
||||
'key' => 'java',
|
||||
'name' => 'Java',
|
||||
'version' => '3.0.0',
|
||||
'version' => '3.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-java',
|
||||
'package' => '',
|
||||
'enabled' => false,
|
||||
@@ -357,7 +357,7 @@ return [
|
||||
[
|
||||
'key' => 'dotnet',
|
||||
'name' => '.NET',
|
||||
'version' => '0.6.0',
|
||||
'version' => '0.7.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
|
||||
'package' => 'https://www.nuget.org/packages/Appwrite',
|
||||
'enabled' => true,
|
||||
@@ -375,7 +375,7 @@ return [
|
||||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '10.0.0',
|
||||
'version' => '10.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_appwrite',
|
||||
'enabled' => true,
|
||||
@@ -393,7 +393,7 @@ return [
|
||||
[
|
||||
'key' => 'kotlin',
|
||||
'name' => 'Kotlin',
|
||||
'version' => '4.0.0',
|
||||
'version' => '4.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
|
||||
'enabled' => true,
|
||||
@@ -415,7 +415,7 @@ return [
|
||||
[
|
||||
'key' => 'swift',
|
||||
'name' => 'Swift',
|
||||
'version' => '4.0.1',
|
||||
'version' => '4.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'enabled' => true,
|
||||
|
||||
@@ -170,7 +170,7 @@ return [
|
||||
'docs' => false,
|
||||
'docsUrl' => '',
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
'functions' => [
|
||||
@@ -196,7 +196,7 @@ return [
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/proxy',
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'optional' => false,
|
||||
'icon' => '/images/services/proxy.png',
|
||||
],
|
||||
'mock' => [
|
||||
@@ -248,7 +248,7 @@ return [
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/migrations',
|
||||
'tests' => true,
|
||||
'optional' => true,
|
||||
'optional' => false,
|
||||
'icon' => '/images/services/migrations.png',
|
||||
],
|
||||
];
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
Submodule app/console updated: e965738987...94e4c1a730
@@ -1000,7 +1000,12 @@ App::post('/v1/account/sessions/magic-url')
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.magicSession-' . $locale->default] ?? [];
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message->setParam('{{body}}', $body);
|
||||
$message
|
||||
->setParam('{{body}}', $body)
|
||||
->setParam('{{hello}}', $locale->getText("emails.magicSession.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.magicSession.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.magicSession.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.magicSession.signature"));
|
||||
$body = $message->render();
|
||||
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
@@ -1050,16 +1055,7 @@ App::post('/v1/account/sessions/magic-url')
|
||||
}
|
||||
|
||||
$emailVariables = [
|
||||
'subject' => $subject,
|
||||
'hello' => $locale->getText("emails.magicSession.hello"),
|
||||
'body' => $body,
|
||||
'footer' => $locale->getText("emails.magicSession.footer"),
|
||||
'thanks' => $locale->getText("emails.magicSession.thanks"),
|
||||
'signature' => $locale->getText("emails.magicSession.signature"),
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'bg-body' => '#f7f7f7',
|
||||
'bg-content' => '#ffffff',
|
||||
'text-content' => '#000000',
|
||||
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
|
||||
'user' => '',
|
||||
'team' => '',
|
||||
@@ -2457,7 +2453,12 @@ App::post('/v1/account/recovery')
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.recovery-' . $locale->default] ?? [];
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message->setParam('{{body}}', $body);
|
||||
$message
|
||||
->setParam('{{body}}', $body)
|
||||
->setParam('{{hello}}', $locale->getText("emails.recovery.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.recovery.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.recovery.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.recovery.signature"));
|
||||
$body = $message->render();
|
||||
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
@@ -2507,16 +2508,7 @@ App::post('/v1/account/recovery')
|
||||
}
|
||||
|
||||
$emailVariables = [
|
||||
'subject' => $subject,
|
||||
'hello' => $locale->getText("emails.recovery.hello"),
|
||||
'body' => $body,
|
||||
'footer' => $locale->getText("emails.recovery.footer"),
|
||||
'thanks' => $locale->getText("emails.recovery.thanks"),
|
||||
'signature' => $locale->getText("emails.recovery.signature"),
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'bg-body' => '#f7f7f7',
|
||||
'bg-content' => '#ffffff',
|
||||
'text-content' => '#000000',
|
||||
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
|
||||
'user' => $profile->getAttribute('name'),
|
||||
'team' => '',
|
||||
@@ -2524,7 +2516,6 @@ App::post('/v1/account/recovery')
|
||||
'redirect' => $url
|
||||
];
|
||||
|
||||
|
||||
$queueForMails
|
||||
->setRecipient($profile->getAttribute('email', ''))
|
||||
->setName($profile->getAttribute('name'))
|
||||
@@ -2671,6 +2662,10 @@ App::post('/v1/account/verification')
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
|
||||
}
|
||||
|
||||
if ($user->getAttribute('emailVerification')) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED);
|
||||
}
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
@@ -2709,7 +2704,12 @@ App::post('/v1/account/verification')
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.verification-' . $locale->default] ?? [];
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message->setParam('{{body}}', $body);
|
||||
$message
|
||||
->setParam('{{body}}', $body)
|
||||
->setParam('{{hello}}', $locale->getText("emails.verification.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.verification.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.verification.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.verification.signature"));
|
||||
$body = $message->render();
|
||||
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
@@ -2759,16 +2759,7 @@ App::post('/v1/account/verification')
|
||||
}
|
||||
|
||||
$emailVariables = [
|
||||
'subject' => $subject,
|
||||
'hello' => $locale->getText("emails.verification.hello"),
|
||||
'body' => $body,
|
||||
'footer' => $locale->getText("emails.verification.footer"),
|
||||
'thanks' => $locale->getText("emails.verification.thanks"),
|
||||
'signature' => $locale->getText("emails.verification.signature"),
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'bg-body' => '#f7f7f7',
|
||||
'bg-content' => '#ffffff',
|
||||
'text-content' => '#000000',
|
||||
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
|
||||
'user' => $user->getAttribute('name'),
|
||||
'team' => '',
|
||||
@@ -2896,6 +2887,10 @@ App::post('/v1/account/verification/phone')
|
||||
throw new Exception(Exception::USER_PHONE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($user->getAttribute('phoneVerification')) {
|
||||
throw new Exception(Exception::USER_PHONE_ALREADY_VERIFIED);
|
||||
}
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
@@ -1231,7 +1231,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('elements', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE, min: 0), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.')
|
||||
->param('elements', [], new ArrayList(new Text(DATABASE::LENGTH_KEY), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . DATABASE::LENGTH_KEY . ' characters long.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Text(0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
@@ -1240,16 +1240,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
|
||||
// use length of longest string as attribute size
|
||||
$size = 0;
|
||||
foreach ($elements as $element) {
|
||||
$length = \strlen($element);
|
||||
if ($length === 0) {
|
||||
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Each enum element must not be empty');
|
||||
}
|
||||
$size = ($length > $size) ? $length : $size;
|
||||
}
|
||||
|
||||
if (!is_null($default) && !in_array($default, $elements)) {
|
||||
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Default value not found in elements');
|
||||
}
|
||||
@@ -1257,7 +1247,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
|
||||
$attribute = createAttribute($databaseId, $collectionId, new Document([
|
||||
'key' => $key,
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => $size,
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'required' => $required,
|
||||
'default' => $default,
|
||||
'array' => $array,
|
||||
@@ -1930,7 +1920,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('elements', null, new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.')
|
||||
->param('elements', null, new ArrayList(new Text(DATABASE::LENGTH_KEY), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . DATABASE::LENGTH_KEY . ' characters long.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Text(0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->inject('response')
|
||||
@@ -3632,7 +3622,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
|
||||
App::get('/v1/databases/usage')
|
||||
->desc('Get usage stats for the database')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'usage'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'databases')
|
||||
@@ -3750,7 +3740,7 @@ App::get('/v1/databases/usage')
|
||||
|
||||
App::get('/v1/databases/:databaseId/usage')
|
||||
->desc('Get usage stats for the database')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'usage'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'databases')
|
||||
@@ -3860,7 +3850,7 @@ App::get('/v1/databases/:databaseId/usage')
|
||||
App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
|
||||
->alias('/v1/database/:collectionId/usage', ['databaseId' => 'default'])
|
||||
->desc('Get usage stats for a collection')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'usage'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'databases')
|
||||
|
||||
@@ -46,6 +46,7 @@ use Utopia\Validator\Boolean;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
include_once __DIR__ . '/../shared/api.php';
|
||||
|
||||
@@ -58,7 +59,14 @@ $redeployVcs = function (Request $request, Document $function, Document $project
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$providerRepositoryId = $function->getAttribute('providerRepositoryId', '');
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
$providerBranch = $function->getAttribute('providerBranch', 'main');
|
||||
$authorUrl = "https://github.com/$owner";
|
||||
$repositoryUrl = "https://github.com/$owner/$repositoryName";
|
||||
@@ -234,12 +242,16 @@ App::post('/v1/functions')
|
||||
|
||||
// Git connect logic
|
||||
if (!empty($providerRepositoryId)) {
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
|
||||
$repository = $dbForConsole->createDocument('repositories', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
||||
],
|
||||
'installationId' => $installation->getId(),
|
||||
'installationInternalId' => $installation->getInternalId(),
|
||||
@@ -287,9 +299,8 @@ App::post('/v1/functions')
|
||||
$ruleModel = new Rule();
|
||||
$ruleCreate =
|
||||
$queueForEvents
|
||||
->setClass(Event::WEBHOOK_CLASS_NAME)
|
||||
->setQueue(Event::WEBHOOK_QUEUE_NAME)
|
||||
;
|
||||
->setClass(Event::WEBHOOK_CLASS_NAME)
|
||||
->setQueue(Event::WEBHOOK_QUEUE_NAME);
|
||||
|
||||
$ruleCreate
|
||||
->setProject($project)
|
||||
@@ -870,8 +881,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||
->setContentType('application/gzip')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"')
|
||||
;
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
|
||||
|
||||
$size = $deviceFunctions->getFileSize($path);
|
||||
$rangeHeader = $request->getHeader('range');
|
||||
@@ -899,7 +909,6 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||
}
|
||||
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
$response->addHeader('Content-Length', $deviceFunctions->getFileSize($path));
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
$response->chunk(
|
||||
$deviceFunctions->read(
|
||||
@@ -1653,6 +1662,8 @@ App::post('/v1/functions/:functionId/executions')
|
||||
->setJWT($jwt)
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('executionId', $execution->getId())
|
||||
->trigger();
|
||||
|
||||
return $response
|
||||
|
||||
+122
-35
@@ -14,6 +14,7 @@ use Utopia\Registry\Registry;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
App::get('/v1/health')
|
||||
@@ -87,22 +88,18 @@ App::get('/v1/health/db')
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
} else {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
$failure[] = $database;
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
$failure[] = $database;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($failure)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'DB failure on: ' . implode(", ", $failure));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'statuses' => $output,
|
||||
'total' => count($output),
|
||||
@@ -348,11 +345,20 @@ App::get('/v1/health/queue/webhooks')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('response')
|
||||
->action(function (Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::WEBHOOK_QUEUE_NAME, $queue);
|
||||
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
|
||||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/logs')
|
||||
@@ -366,11 +372,20 @@ App::get('/v1/health/queue/logs')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('response')
|
||||
->action(function (Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::AUDITS_QUEUE_NAME, $queue);
|
||||
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
|
||||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/certificates')
|
||||
@@ -384,11 +399,20 @@ App::get('/v1/health/queue/certificates')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('response')
|
||||
->action(function (Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::CERTIFICATES_QUEUE_NAME, $queue);
|
||||
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
|
||||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/builds')
|
||||
@@ -402,11 +426,20 @@ App::get('/v1/health/queue/builds')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('response')
|
||||
->action(function (Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::BUILDS_QUEUE_NAME, $queue);
|
||||
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
|
||||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/databases')
|
||||
@@ -421,11 +454,20 @@ App::get('/v1/health/queue/databases')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->param('name', 'database_db_main', new Text(256), 'Queue name for which to check the queue size', true)
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('response')
|
||||
->action(function (string $name, Connection $queue, Response $response) {
|
||||
->action(function (string $name, int|string $threshold, Connection $queue, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client($name, $queue);
|
||||
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
|
||||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/deletes')
|
||||
@@ -439,11 +481,20 @@ App::get('/v1/health/queue/deletes')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('response')
|
||||
->action(function (Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::DELETE_QUEUE_NAME, $queue);
|
||||
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
|
||||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/mails')
|
||||
@@ -457,11 +508,20 @@ App::get('/v1/health/queue/mails')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('response')
|
||||
->action(function (Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::MAILS_QUEUE_NAME, $queue);
|
||||
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
|
||||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/messaging')
|
||||
@@ -475,11 +535,20 @@ App::get('/v1/health/queue/messaging')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('response')
|
||||
->action(function (Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::MESSAGING_QUEUE_NAME, $queue);
|
||||
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
|
||||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/migrations')
|
||||
@@ -493,11 +562,20 @@ App::get('/v1/health/queue/migrations')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('response')
|
||||
->action(function (Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::MIGRATIONS_QUEUE_NAME, $queue);
|
||||
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
|
||||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/functions')
|
||||
@@ -511,11 +589,20 @@ App::get('/v1/health/queue/functions')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queue')
|
||||
->inject('response')
|
||||
->action(function (Connection $queue, Response $response) {
|
||||
->action(function (int|string $threshold, Connection $queue, Response $response) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client(Event::FUNCTIONS_QUEUE_NAME, $queue);
|
||||
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
|
||||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/storage/local')
|
||||
@@ -536,10 +623,10 @@ App::get('/v1/health/storage/local')
|
||||
|
||||
foreach (
|
||||
[
|
||||
'Uploads' => APP_STORAGE_UPLOADS,
|
||||
'Cache' => APP_STORAGE_CACHE,
|
||||
'Config' => APP_STORAGE_CONFIG,
|
||||
'Certs' => APP_STORAGE_CERTIFICATES
|
||||
'Uploads' => APP_STORAGE_UPLOADS,
|
||||
'Cache' => APP_STORAGE_CACHE,
|
||||
'Config' => APP_STORAGE_CONFIG,
|
||||
'Certs' => APP_STORAGE_CERTIFICATES
|
||||
] as $key => $volume
|
||||
) {
|
||||
$device = new Local($volume);
|
||||
@@ -601,7 +688,7 @@ App::get('/v1/health/anti-virus')
|
||||
});
|
||||
|
||||
App::get('/v1/health/stats') // Currently only used internally
|
||||
->desc('Get system stats')
|
||||
->desc('Get system stats')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'root')
|
||||
// ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
|
||||
@@ -208,6 +208,16 @@ App::post('/v1/migrations/firebase')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$serviceAccountData = json_decode($serviceAccount, true);
|
||||
|
||||
if (empty($serviceAccountData)) {
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
|
||||
}
|
||||
|
||||
if (!isset($serviceAccountData['project_id']) || !isset($serviceAccountData['client_email']) || !isset($serviceAccountData['private_key'])) {
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
|
||||
}
|
||||
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
@@ -449,15 +459,26 @@ App::get('/v1/migrations/appwrite/report')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->action(function (array $resources, string $endpoint, string $projectID, string $key, Response $response) {
|
||||
try {
|
||||
$appwrite = new Appwrite($projectID, $endpoint, $key);
|
||||
$appwrite = new Appwrite($projectID, $endpoint, $key);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($appwrite->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||
try {
|
||||
$report = $appwrite->report($resources);
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
switch ($e->getCode()) {
|
||||
case 401:
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
|
||||
case 429:
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
|
||||
case 500:
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/firebase/report')
|
||||
@@ -475,15 +496,36 @@ App::get('/v1/migrations/firebase/report')
|
||||
->param('serviceAccount', '', new Text(65536), 'JSON of the Firebase service account credentials')
|
||||
->inject('response')
|
||||
->action(function (array $resources, string $serviceAccount, Response $response) {
|
||||
try {
|
||||
$firebase = new Firebase(json_decode($serviceAccount, true));
|
||||
$serviceAccount = json_decode($serviceAccount, true);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($firebase->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
if (empty($serviceAccount)) {
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
|
||||
}
|
||||
|
||||
if (!isset($serviceAccount['project_id']) || !isset($serviceAccount['client_email']) || !isset($serviceAccount['private_key'])) {
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
|
||||
}
|
||||
|
||||
$firebase = new Firebase($serviceAccount);
|
||||
|
||||
try {
|
||||
$report = $firebase->report($resources);
|
||||
} catch (\Throwable $e) {
|
||||
switch ($e->getCode()) {
|
||||
case 401:
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
|
||||
case 429:
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
|
||||
case 500:
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/firebase/report/oauth')
|
||||
@@ -869,15 +911,26 @@ App::get('/v1/migrations/supabase/report')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response) {
|
||||
try {
|
||||
$supabase = new Supabase($endpoint, $apiKey, $databaseHost, 'postgres', $username, $password, $port);
|
||||
$supabase = new Supabase($endpoint, $apiKey, $databaseHost, 'postgres', $username, $password, $port);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($supabase->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
try {
|
||||
$report = $supabase->report($resources);
|
||||
} catch (\Throwable $e) {
|
||||
switch ($e->getCode()) {
|
||||
case 401:
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
|
||||
case 429:
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
|
||||
case 500:
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/nhost/report')
|
||||
@@ -901,15 +954,26 @@ App::get('/v1/migrations/nhost/report')
|
||||
->param('port', 5432, new Integer(true), 'Source\'s Database Port.', true)
|
||||
->inject('response')
|
||||
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response) {
|
||||
try {
|
||||
$nhost = new NHost($subdomain, $region, $adminSecret, $database, $username, $password, $port);
|
||||
$nhost = new NHost($subdomain, $region, $adminSecret, $database, $username, $password, $port);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($nhost->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
try {
|
||||
$report = $nhost->report($resources);
|
||||
} catch (\Throwable $e) {
|
||||
switch ($e->getCode()) {
|
||||
case 401:
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
|
||||
case 429:
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
|
||||
case 500:
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::patch('/v1/migrations/:migrationId')
|
||||
|
||||
@@ -100,11 +100,6 @@ App::post('/v1/projects')
|
||||
$backups['database_db_fra1_v14x_07'] = ['from' => '00:00', 'to' => '02:00'];
|
||||
|
||||
$databases = Config::getParam('pools-database', []);
|
||||
$databaseSelfHosted = 'database_db_fra1_self_hosted_0_0';
|
||||
$selfHostedIndex = array_search($databaseSelfHosted, $databases);
|
||||
if ($selfHostedIndex !== false) {
|
||||
unset($databases[$selfHostedIndex]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove databases from the list that are currently undergoing an backup
|
||||
@@ -175,16 +170,6 @@ App::post('/v1/projects')
|
||||
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update database with self-managed db every $mod projects
|
||||
*/
|
||||
$mod = 20;
|
||||
if ($project->getInternalId() % $mod === 0 && $selfHostedIndex !== false) {
|
||||
$database = $databaseSelfHosted;
|
||||
$project->setAttribute('database', $database);
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project);
|
||||
}
|
||||
|
||||
$dbForProject = new Database($pools->get($database)->pop()->getResource(), $cache);
|
||||
$dbForProject->setNamespace("_{$project->getInternalId()}");
|
||||
$dbForProject->create();
|
||||
@@ -1718,7 +1703,6 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message
|
||||
->setParam('{{hello}}', $localeObj->getText("emails.{$type}.hello"))
|
||||
->setParam('{{user}}', '')
|
||||
->setParam('{{footer}}', $localeObj->getText("emails.{$type}.footer"))
|
||||
->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'))
|
||||
->setParam('{{thanks}}', $localeObj->getText("emails.{$type}.thanks"))
|
||||
|
||||
@@ -87,7 +87,11 @@ App::post('/v1/proxy/rules')
|
||||
$resourceInternalId = $function->getInternalId();
|
||||
}
|
||||
|
||||
$domain = new Domain($domain);
|
||||
try {
|
||||
$domain = new Domain($domain);
|
||||
} catch (\Exception) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
|
||||
}
|
||||
|
||||
$ruleId = ID::unique();
|
||||
$rule = new Document([
|
||||
|
||||
@@ -339,7 +339,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->label('audits.resource', 'file/{response.$id}')
|
||||
->label('usage.metric', 'files.{scope}.requests.create')
|
||||
->label('usage.params', ['bucketId:{request.bucketId}'])
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId},chunkId:{chunkId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
@@ -1109,7 +1109,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
}
|
||||
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
$response->addHeader('Content-Length', $deviceFiles->getFileSize($path));
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
$response->chunk(
|
||||
$deviceFiles->read(
|
||||
@@ -1262,7 +1261,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
|
||||
$size = $deviceFiles->getFileSize($path);
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
$response->addHeader('Content-Length', $deviceFiles->getFileSize($path));
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
$response->chunk(
|
||||
$deviceFiles->read(
|
||||
|
||||
@@ -555,7 +555,12 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.invitation-' . $locale->default] ?? [];
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message->setParam('{{body}}', $body);
|
||||
$message
|
||||
->setParam('{{body}}', $body)
|
||||
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"));
|
||||
$body = $message->render();
|
||||
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
@@ -606,16 +611,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
|
||||
$emailVariables = [
|
||||
'owner' => $user->getAttribute('name'),
|
||||
'subject' => $subject,
|
||||
'hello' => $locale->getText("emails.invitation.hello"),
|
||||
'body' => $body,
|
||||
'footer' => $locale->getText("emails.invitation.footer"),
|
||||
'thanks' => $locale->getText("emails.invitation.thanks"),
|
||||
'signature' => $locale->getText("emails.invitation.signature"),
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'bg-body' => '#f7f7f7',
|
||||
'bg-content' => '#ffffff',
|
||||
'text-content' => '#000000',
|
||||
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
|
||||
'user' => $user->getAttribute('name'),
|
||||
'team' => $team->getAttribute('name'),
|
||||
|
||||
@@ -85,7 +85,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
||||
'status' => true,
|
||||
'labels' => [],
|
||||
'password' => $password,
|
||||
'passwordHistory' => is_null($password) && $passwordHistory === 0 ? [] : [$password],
|
||||
'passwordHistory' => is_null($password) || $passwordHistory === 0 ? [] : [$password],
|
||||
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
|
||||
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
|
||||
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash],
|
||||
@@ -1127,7 +1127,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
App::delete('/v1/users/:userId/sessions')
|
||||
->desc('Delete user sessions')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].sessions.[sessionId].delete')
|
||||
->label('event', 'users.[userId].sessions.delete')
|
||||
->label('scope', 'users.write')
|
||||
->label('audits.event', 'session.delete')
|
||||
->label('audits.resource', 'user/{user.$id}')
|
||||
|
||||
+67
-30
@@ -22,7 +22,6 @@ use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Detector\Adapter\Bun;
|
||||
use Utopia\Detector\Adapter\CPP;
|
||||
use Utopia\Detector\Adapter\Dart;
|
||||
@@ -36,6 +35,7 @@ use Utopia\Detector\Adapter\Ruby;
|
||||
use Utopia\Detector\Adapter\Swift;
|
||||
use Utopia\Detector\Detector;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
use function Swoole\Coroutine\batch;
|
||||
|
||||
@@ -66,7 +66,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
}
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
@@ -154,7 +161,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$message = 'Authorization required for external contributor.';
|
||||
|
||||
$providerRepositoryId = $resource->getAttribute('providerRepositoryId');
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, 'failure', $message, $authorizeUrl, $name);
|
||||
continue;
|
||||
@@ -206,7 +220,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$message = 'Starting...';
|
||||
|
||||
$providerRepositoryId = $resource->getAttribute('providerRepositoryId');
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
|
||||
$providerTargetUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$projectId/functions/function-$functionId";
|
||||
@@ -273,7 +294,7 @@ App::get('/v1/vcs/github/callback')
|
||||
->label('scope', 'public')
|
||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||
->param('installation_id', '', new Text(256, 0), 'GitHub installation ID', true)
|
||||
->param('setup_action', '', new Text(256, 0), 'GitHub setup actuon type', true)
|
||||
->param('setup_action', '', new Text(256, 0), 'GitHub setup action type', true)
|
||||
->param('state', '', new Text(2048), 'GitHub state. Contains info sent when starting authorization flow.', true)
|
||||
->param('code', '', new Text(2048, 0), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true)
|
||||
->inject('gitHub')
|
||||
@@ -458,9 +479,12 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
|
||||
if (empty($repositoryName)) {
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -720,9 +744,12 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
|
||||
if (empty($repositoryName)) {
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -766,9 +793,12 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
|
||||
if (empty($repositoryName)) {
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -827,10 +857,10 @@ App::post('/v1/vcs/github/events')
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
//find functionId from functions table
|
||||
$repositories = $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) {
|
||||
@@ -847,13 +877,13 @@ App::post('/v1/vcs/github/events')
|
||||
]);
|
||||
|
||||
foreach ($installations as $installation) {
|
||||
$repositories = $dbForConsole->find('repositories', [
|
||||
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
|
||||
Query::equal('installationInternalId', [$installation->getInternalId()]),
|
||||
Query::limit(1000)
|
||||
]);
|
||||
]));
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
$dbForConsole->deleteDocument('repositories', $repository->getId());
|
||||
Authorization::skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId()));
|
||||
}
|
||||
|
||||
$dbForConsole->deleteDocument('installations', $installation->getId());
|
||||
@@ -885,10 +915,10 @@ App::post('/v1/vcs/github/events')
|
||||
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
|
||||
$providerCommitMessage = $commitDetails["commitMessage"] ?? '';
|
||||
|
||||
$repositories = $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);
|
||||
} elseif ($parsedPayload["action"] == "closed") {
|
||||
@@ -899,10 +929,10 @@ App::post('/v1/vcs/github/events')
|
||||
$external = $parsedPayload["external"] ?? true;
|
||||
|
||||
if ($external) {
|
||||
$repositories = $dbForConsole->find('repositories', [
|
||||
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::orderDesc('$createdAt')
|
||||
]);
|
||||
]));
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
$providerPullRequestIds = $repository->getAttribute('providerPullRequestIds', []);
|
||||
@@ -1016,8 +1046,8 @@ App::delete('/v1/vcs/installations/:installationId')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('deletes')
|
||||
->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole, Delete $deletes) {
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole, Delete $queueForDeletes) {
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
@@ -1028,7 +1058,7 @@ App::delete('/v1/vcs/installations/:installationId')
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove installation from DB');
|
||||
}
|
||||
|
||||
$deletes
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($installation);
|
||||
|
||||
@@ -1062,9 +1092,9 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$repository = $dbForConsole->getDocument('repositories', $repositoryId, [
|
||||
$repository = Authorization::skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()])
|
||||
]);
|
||||
]));
|
||||
|
||||
if ($repository->isEmpty()) {
|
||||
throw new Exception(Exception::REPOSITORY_NOT_FOUND);
|
||||
@@ -1079,7 +1109,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
|
||||
// TODO: Delete from array when PR is closed
|
||||
|
||||
$repository = $dbForConsole->updateDocument('repositories', $repository->getId(), $repository);
|
||||
$repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
|
||||
|
||||
$privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
@@ -1090,7 +1120,14 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
$providerRepositoryId = $repository->getAttribute('providerRepositoryId');
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
$pullRequestResponse = $github->getPullRequest($owner, $repositoryName, $providerPullRequestId);
|
||||
|
||||
$providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? '';
|
||||
|
||||
@@ -117,12 +117,14 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
|
||||
$path .= '?' . $query;
|
||||
}
|
||||
|
||||
$requestHeaders = $request->getHeaders();
|
||||
|
||||
$body = \json_encode([
|
||||
'async' => false,
|
||||
'body' => $swooleRequest->getContent() ?? '',
|
||||
'method' => $swooleRequest->server['request_method'],
|
||||
'path' => $path,
|
||||
'headers' => $swooleRequest->header
|
||||
'headers' => $requestHeaders
|
||||
]);
|
||||
|
||||
$headers = [
|
||||
@@ -606,8 +608,13 @@ App::error()
|
||||
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->getRoute();
|
||||
$publish = true;
|
||||
|
||||
if ($logger) {
|
||||
if ($error instanceof AppwriteException) {
|
||||
$publish = $error->isPublishable();
|
||||
}
|
||||
|
||||
if ($logger && $publish) {
|
||||
if ($error->getCode() >= 500 || $error->getCode() === 0) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
|
||||
+63
-496
@@ -9,506 +9,13 @@ use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Storage\Validator\File;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
App::get('/v1/mock/tests/foo')
|
||||
->desc('Get Foo')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'foo')
|
||||
->label('sdk.method', 'get')
|
||||
->label('sdk.description', 'Mock a get request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/foo')
|
||||
->desc('Post Foo')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'foo')
|
||||
->label('sdk.method', 'post')
|
||||
->label('sdk.description', 'Mock a post request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
App::patch('/v1/mock/tests/foo')
|
||||
->desc('Patch Foo')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'foo')
|
||||
->label('sdk.method', 'patch')
|
||||
->label('sdk.description', 'Mock a patch request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
App::put('/v1/mock/tests/foo')
|
||||
->desc('Put Foo')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'foo')
|
||||
->label('sdk.method', 'put')
|
||||
->label('sdk.description', 'Mock a put request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
App::delete('/v1/mock/tests/foo')
|
||||
->desc('Delete Foo')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'foo')
|
||||
->label('sdk.method', 'delete')
|
||||
->label('sdk.description', 'Mock a delete request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/bar')
|
||||
->desc('Get Bar')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'bar')
|
||||
->label('sdk.method', 'get')
|
||||
->label('sdk.description', 'Mock a get request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/bar')
|
||||
->desc('Post Bar')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'bar')
|
||||
->label('sdk.method', 'post')
|
||||
->label('sdk.description', 'Mock a post request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.offline.model', '/mock/tests/bar')
|
||||
->label('sdk.offline.key', '{required}')
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
App::patch('/v1/mock/tests/bar')
|
||||
->desc('Patch Bar')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'bar')
|
||||
->label('sdk.method', 'patch')
|
||||
->label('sdk.description', 'Mock a patch request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
App::put('/v1/mock/tests/bar')
|
||||
->desc('Put Bar')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'bar')
|
||||
->label('sdk.method', 'put')
|
||||
->label('sdk.description', 'Mock a put request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
App::delete('/v1/mock/tests/bar')
|
||||
->desc('Delete Bar')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'bar')
|
||||
->label('sdk.method', 'delete')
|
||||
->label('sdk.description', 'Mock a delete request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/headers')
|
||||
->desc('Get headers')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'headers')
|
||||
->label('sdk.description', 'Return headers from the request')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
$res = [
|
||||
'x-sdk-name' => $request->getHeader('x-sdk-name'),
|
||||
'x-sdk-platform' => $request->getHeader('x-sdk-platform'),
|
||||
'x-sdk-language' => $request->getHeader('x-sdk-language'),
|
||||
'x-sdk-version' => $request->getHeader('x-sdk-version'),
|
||||
];
|
||||
$res = array_map(function ($key, $value) {
|
||||
return $key . ': ' . $value;
|
||||
}, array_keys($res), $res);
|
||||
$res = implode("; ", $res);
|
||||
|
||||
$response->dynamic(new Document(['result' => $res]), Response::MODEL_MOCK);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/download')
|
||||
->desc('Download File')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'download')
|
||||
->label('sdk.methodType', 'location')
|
||||
->label('sdk.description', 'Mock a file download request.')
|
||||
->label('sdk.response.type', '*/*')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response
|
||||
->setContentType('text/plain')
|
||||
->addHeader('Content-Disposition', 'attachment; filename="test.txt"')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->send("GET:/v1/mock/tests/general/download:passed")
|
||||
;
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/general/upload')
|
||||
->desc('Upload File')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'upload')
|
||||
->label('sdk.description', 'Mock a file upload request.')
|
||||
->label('sdk.request.type', 'multipart/form-data')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->param('file', [], new File(), 'Sample file param', skipValidation: true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (string $x, int $y, array $z, mixed $file, Request $request, Response $response) {
|
||||
|
||||
$file = $request->getFiles('file');
|
||||
|
||||
$contentRange = $request->getHeader('content-range');
|
||||
|
||||
$chunkSize = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
if (!empty($contentRange)) {
|
||||
$start = $request->getContentRangeStart();
|
||||
$end = $request->getContentRangeEnd();
|
||||
$size = $request->getContentRangeSize();
|
||||
$id = $request->getHeader('x-appwrite-id', '');
|
||||
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
|
||||
|
||||
if (is_null($start) || is_null($end) || is_null($size) || $end >= $size) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
|
||||
}
|
||||
|
||||
if ($start > $end || $end > $size) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
|
||||
}
|
||||
|
||||
if ($start === 0 && !empty($id)) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'First chunked request cannot have id header');
|
||||
}
|
||||
|
||||
if ($start !== 0 && $id !== 'newfileid') {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'All chunked request must have id header (except first)');
|
||||
}
|
||||
|
||||
if ($end !== $size - 1 && $end - $start + 1 !== $chunkSize) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB (except last chunk)');
|
||||
}
|
||||
|
||||
if ($end !== $size - 1 && $file['size'] !== $chunkSize) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Wrong chunk size');
|
||||
}
|
||||
|
||||
if ($file['size'] > $chunkSize) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB or less');
|
||||
}
|
||||
|
||||
if ($end !== $size - 1) {
|
||||
$response->json([
|
||||
'$id' => ID::custom('newfileid'),
|
||||
'chunksTotal' => (int) ceil($size / ($end + 1 - $start)),
|
||||
'chunksUploaded' => ceil($start / $chunkSize) + 1
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$file['tmp_name'] = (\is_array($file['tmp_name'])) ? $file['tmp_name'][0] : $file['tmp_name'];
|
||||
$file['name'] = (\is_array($file['name'])) ? $file['name'][0] : $file['name'];
|
||||
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
|
||||
|
||||
if ($file['name'] !== 'file.png') {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file name');
|
||||
}
|
||||
|
||||
if ($file['size'] !== 38756) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file size');
|
||||
}
|
||||
|
||||
if (\md5(\file_get_contents($file['tmp_name'])) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file uploaded');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/redirect')
|
||||
->desc('Redirect')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'redirect')
|
||||
->label('sdk.description', 'Mock a redirect request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_HTML)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->redirect('/v1/mock/tests/general/redirect/done');
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/redirect/done')
|
||||
->desc('Redirected')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'redirected')
|
||||
->label('sdk.description', 'Mock a redirected request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->action(function () {
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/set-cookie')
|
||||
->desc('Set Cookie')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'setCookie')
|
||||
->label('sdk.description', 'Mock a set cookie request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->action(function (Response $response, Request $request) {
|
||||
|
||||
$response->addCookie('cookieName', 'cookieValue', \time() + 31536000, '/', $request->getHostname(), true, true);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/get-cookie')
|
||||
->desc('Get Cookie')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'getCookie')
|
||||
->label('sdk.description', 'Mock a cookie response.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('request')
|
||||
->action(function (Request $request) {
|
||||
|
||||
if ($request->getCookie('cookieName', '') !== 'cookieValue') {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Missing cookie value');
|
||||
}
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/empty')
|
||||
->desc('Empty Response')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'empty')
|
||||
->label('sdk.description', 'Mock an empty response.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/general/nullable')
|
||||
->desc('Nullable Test')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'nullable')
|
||||
->label('sdk.description', 'Mock a nullable parameter.')
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('nullable', '', new Nullable(new Text(100)), 'Sample string param')
|
||||
->param('optional', '', new Text(100), 'Sample string param', true)
|
||||
->action(function (string $required, string $nullable, ?string $optional) {
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/general/enum')
|
||||
->desc('Enum Test')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'enum')
|
||||
->label('sdk.description', 'Mock an enum parameter.')
|
||||
->label('sdk.mock', true)
|
||||
->param('mockType', '', new WhiteList(['first', 'second', 'third']), 'Sample enum param')
|
||||
->action(function (string $mockType) {
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/400-error')
|
||||
->desc('400 Error')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'error400')
|
||||
->label('sdk.description', 'Mock a 400 failed request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_BAD_REQUEST)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ERROR)
|
||||
->label('sdk.mock', true)
|
||||
->action(function () {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Mock 400 error');
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/500-error')
|
||||
->desc('500 Error')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'error500')
|
||||
->label('sdk.description', 'Mock a 500 failed request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_INTERNAL_SERVER_ERROR)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ERROR)
|
||||
->label('sdk.mock', true)
|
||||
->action(function () {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Mock 500 error', 500);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/502-error')
|
||||
->desc('502 Error')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'error502')
|
||||
->label('sdk.description', 'Mock a 502 bad gateway.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_BAD_GATEWAY)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_TEXT)
|
||||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response
|
||||
->setStatusCode(502)
|
||||
->text('This is a text error')
|
||||
;
|
||||
});
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
|
||||
App::get('/v1/mock/tests/general/oauth2')
|
||||
->desc('OAuth Login')
|
||||
@@ -646,6 +153,66 @@ App::patch('/v1/mock/functions-v2')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::get('/v1/mock/github/callback')
|
||||
->desc('Create installation document using GitHub installation id')
|
||||
->groups(['mock', 'api', 'vcs'])
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->param('providerInstallationId', '', new UID(), 'GitHub installation ID')
|
||||
->param('projectId', '', new UID(), 'Project ID of the project where app is to be installed')
|
||||
->inject('gitHub')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForConsole) {
|
||||
$isDevelopment = App::getEnv('_APP_ENV', 'development') === 'development';
|
||||
|
||||
if (!$isDevelopment) {
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
$error = 'Project with the ID from state could not be found.';
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND, $error);
|
||||
}
|
||||
|
||||
if (!empty($providerInstallationId)) {
|
||||
$privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
|
||||
$projectInternalId = $project->getInternalId();
|
||||
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
|
||||
$installation = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
||||
],
|
||||
'providerInstallationId' => $providerInstallationId,
|
||||
'projectId' => $projectId,
|
||||
'projectInternalId' => $projectInternalId,
|
||||
'provider' => 'github',
|
||||
'organization' => $owner,
|
||||
'personal' => false
|
||||
]);
|
||||
|
||||
$installation = $dbForConsole->createDocument('installations', $installation);
|
||||
}
|
||||
|
||||
$response->json([
|
||||
'installationId' => $installation->getId(),
|
||||
]);
|
||||
});
|
||||
|
||||
App::shutdown()
|
||||
->groups(['mock'])
|
||||
->inject('utopia')
|
||||
|
||||
@@ -121,13 +121,16 @@ App::init()
|
||||
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
|
||||
|
||||
foreach ($abuseKeyLabel as $abuseKey) {
|
||||
$start = $request->getContentRangeStart();
|
||||
$end = $request->getContentRangeEnd();
|
||||
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
|
||||
$timeLimit
|
||||
->setParam('{userId}', $user->getId())
|
||||
->setParam('{userAgent}', $request->getUserAgent(''))
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getHostname() . $route->getPath())
|
||||
->setParam('{method}', $request->getMethod());
|
||||
->setParam('{method}', $request->getMethod())
|
||||
->setParam('{chunkId}', (int) ($start / ($end + 1 - $start)));
|
||||
$timeLimitArray[] = $timeLimit;
|
||||
}
|
||||
|
||||
@@ -554,3 +557,11 @@ App::shutdown()
|
||||
->submit();
|
||||
}
|
||||
});
|
||||
|
||||
App::init()
|
||||
->groups(['usage'])
|
||||
->action(function () {
|
||||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') !== 'enabled') {
|
||||
throw new Exception(Exception::GENERAL_USAGE_DISABLED);
|
||||
}
|
||||
});
|
||||
|
||||
+32
-2
@@ -109,8 +109,8 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return
|
||||
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 514;
|
||||
const APP_VERSION_STABLE = '1.4.6';
|
||||
const APP_CACHE_BUSTER = 329;
|
||||
const APP_VERSION_STABLE = '1.4.13';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
@@ -767,6 +767,25 @@ $register->set('pools', function () {
|
||||
return $group;
|
||||
});
|
||||
|
||||
$register->set('db', function () {
|
||||
// This is usually for our workers or CLI commands scope
|
||||
$dbHost = App::getEnv('_APP_DB_HOST', '');
|
||||
$dbPort = App::getEnv('_APP_DB_PORT', '');
|
||||
$dbUser = App::getEnv('_APP_DB_USER', '');
|
||||
$dbPass = App::getEnv('_APP_DB_PASS', '');
|
||||
$dbScheme = App::getEnv('_APP_DB_SCHEMA', '');
|
||||
|
||||
$pdo = new PDO("mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", $dbUser, $dbPass, array(
|
||||
PDO::ATTR_TIMEOUT => 3, // Seconds
|
||||
PDO::ATTR_PERSISTENT => true,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_EMULATE_PREPARES => true,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => true,
|
||||
));
|
||||
|
||||
return $pdo;
|
||||
});
|
||||
$register->set('influxdb', function () {
|
||||
|
||||
// Register DB connection
|
||||
@@ -1049,6 +1068,9 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
}
|
||||
}
|
||||
|
||||
$dbForProject->setMetadata('user', $user->getId());
|
||||
$dbForConsole->setMetadata('user', $user->getId());
|
||||
|
||||
return $user;
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']);
|
||||
|
||||
@@ -1122,6 +1144,8 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole,
|
||||
|
||||
$database
|
||||
->setNamespace('_' . $project->getInternalId())
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId())
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
|
||||
|
||||
return $database;
|
||||
@@ -1138,6 +1162,8 @@ App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
|
||||
|
||||
$database
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
|
||||
|
||||
return $database;
|
||||
@@ -1158,6 +1184,8 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
||||
|
||||
$database
|
||||
->setNamespace('_' . $project->getInternalId())
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId())
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
|
||||
|
||||
return $database;
|
||||
@@ -1174,6 +1202,8 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
||||
|
||||
$database
|
||||
->setNamespace('_' . $project->getInternalId())
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId())
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
|
||||
|
||||
return $database;
|
||||
|
||||
+37
-25
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
@@ -20,7 +22,6 @@ use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
@@ -28,6 +29,9 @@ use Utopia\Database\Database;
|
||||
use Utopia\WebSocket\Server;
|
||||
use Utopia\WebSocket\Adapter;
|
||||
|
||||
/**
|
||||
* @var \Utopia\Registry\Registry $register
|
||||
*/
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
@@ -47,7 +51,10 @@ function getConsoleDB(): Database
|
||||
|
||||
$database = new Database($dbAdapter, getCache());
|
||||
|
||||
$database->setNamespace('_console');
|
||||
$database
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', '_console');
|
||||
|
||||
return $database;
|
||||
}
|
||||
@@ -70,7 +77,11 @@ function getProjectDB(Document $project): Database
|
||||
;
|
||||
|
||||
$database = new Database($dbAdapter, getCache());
|
||||
$database->setNamespace('_' . $project->getInternalId());
|
||||
|
||||
$database
|
||||
->setNamespace('_' . $project->getInternalId())
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
return $database;
|
||||
}
|
||||
@@ -122,12 +133,12 @@ $server = new Server($adapter);
|
||||
$logError = function (Throwable $error, string $action) use ($register) {
|
||||
$logger = $register->get('logger');
|
||||
|
||||
if ($logger) {
|
||||
if ($logger && !$error instanceof Exception) {
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace("realtime");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setServer(gethostname());
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
@@ -182,7 +193,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
|
||||
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
|
||||
break;
|
||||
} catch (\Throwable $th) {
|
||||
} catch (Throwable) {
|
||||
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
|
||||
sleep(DATABASE_RECONNECT_SLEEP);
|
||||
}
|
||||
@@ -210,7 +221,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
->setAttribute('value', json_encode($payload));
|
||||
|
||||
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
} catch (\Throwable $th) {
|
||||
} catch (Throwable $th) {
|
||||
call_user_func($logError, $th, "updateWorkerDocument");
|
||||
} finally {
|
||||
$register->get('pools')->reclaim();
|
||||
@@ -362,7 +373,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
$stats->incr($event['project'], 'messages', $num);
|
||||
}
|
||||
});
|
||||
} catch (\Throwable $th) {
|
||||
} catch (Throwable $th) {
|
||||
call_user_func($logError, $th, "pubSubConnection");
|
||||
|
||||
Console::error('Pub/sub error: ' . $th->getMessage());
|
||||
@@ -389,19 +400,19 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
App::setResource('response', fn() => $response);
|
||||
|
||||
try {
|
||||
/** @var \Utopia\Database\Document $project */
|
||||
/** @var Document $project */
|
||||
$project = $app->getResource('project');
|
||||
|
||||
/*
|
||||
* Project Check
|
||||
*/
|
||||
if (empty($project->getId())) {
|
||||
throw new Exception('Missing or unknown project ID', 1008);
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing or unknown project ID');
|
||||
}
|
||||
|
||||
$dbForProject = getProjectDB($project);
|
||||
$console = $app->getResource('console'); /** @var \Utopia\Database\Document $console */
|
||||
$user = $app->getResource('user'); /** @var \Utopia\Database\Document $user */
|
||||
$console = $app->getResource('console'); /** @var Document $console */
|
||||
$user = $app->getResource('user'); /** @var Document $user */
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
@@ -416,7 +427,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$abuse = new Abuse($timeLimit);
|
||||
|
||||
if (App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled' && $abuse->check()) {
|
||||
throw new Exception('Too many requests', 1013);
|
||||
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many requests');
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -425,10 +436,10 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
* Skip this check for non-web platforms which are not required to send an origin header.
|
||||
*/
|
||||
$origin = $request->getOrigin();
|
||||
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
|
||||
$originValidator = new Origin(array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
|
||||
|
||||
if (!$originValidator->isValid($origin) && $project->getId() !== 'console') {
|
||||
throw new Exception($originValidator->getDescription(), 1008);
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
|
||||
}
|
||||
|
||||
$roles = Auth::getRoles($user);
|
||||
@@ -439,7 +450,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
* Channels Check
|
||||
*/
|
||||
if (empty($channels)) {
|
||||
throw new Exception('Missing channels', 1008);
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing channels');
|
||||
}
|
||||
|
||||
$realtime->subscribe($project->getId(), $connection, $roles, $channels);
|
||||
@@ -460,7 +471,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
]);
|
||||
$stats->incr($project->getId(), 'connections');
|
||||
$stats->incr($project->getId(), 'connectionsTotal');
|
||||
} catch (\Throwable $th) {
|
||||
} catch (Throwable $th) {
|
||||
call_user_func($logError, $th, "initServer");
|
||||
|
||||
$response = [
|
||||
@@ -486,7 +497,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
|
||||
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
|
||||
try {
|
||||
$app = new App('UTC');
|
||||
$response = new Response(new SwooleResponse());
|
||||
$projectId = $realtime->connections[$connection]['projectId'];
|
||||
$database = getConsoleDB();
|
||||
@@ -494,6 +504,8 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
if ($projectId !== 'console') {
|
||||
$project = Authorization::skip(fn() => $database->getDocument('projects', $projectId));
|
||||
$database = getProjectDB($project);
|
||||
} else {
|
||||
$project = null;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -510,22 +522,22 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
$abuse = new Abuse($timeLimit);
|
||||
|
||||
if ($abuse->check() && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
|
||||
throw new Exception('Too many messages', 1013);
|
||||
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many messages.');
|
||||
}
|
||||
|
||||
$message = json_decode($message, true);
|
||||
|
||||
if (is_null($message) || (!array_key_exists('type', $message) && !array_key_exists('data', $message))) {
|
||||
throw new Exception('Message format is not valid.', 1003);
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message format is not valid.');
|
||||
}
|
||||
|
||||
switch ($message['type']) {
|
||||
/**
|
||||
/**
|
||||
* This type is used to authenticate.
|
||||
*/
|
||||
case 'authentication':
|
||||
if (!array_key_exists('session', $message['data'])) {
|
||||
throw new Exception('Payload is not valid.', 1003);
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
|
||||
}
|
||||
|
||||
$session = Auth::decodeSession($message['data']['session']);
|
||||
@@ -540,7 +552,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret, $authDuration) // Validate user has valid login token
|
||||
) {
|
||||
// cookie not valid
|
||||
throw new Exception('Session is not valid.', 1003);
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
|
||||
}
|
||||
|
||||
$roles = Auth::getRoles($user);
|
||||
@@ -560,9 +572,9 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Message type is not valid.', 1003);
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message type is not valid.');
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
} catch (Throwable $th) {
|
||||
$response = [
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
|
||||
+4
-1
@@ -9,6 +9,7 @@ use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Hamster;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Migration;
|
||||
@@ -36,7 +37,6 @@ use Utopia\Queue\Connection;
|
||||
Authorization::disable();
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
|
||||
Server::setResource('register', fn () => $register);
|
||||
|
||||
Server::setResource('dbForConsole', function (Cache $cache, Registry $register) {
|
||||
@@ -156,6 +156,9 @@ Server::setResource('queueForCertificates', function (Connection $queue) {
|
||||
Server::setResource('queueForMigrations', function (Connection $queue) {
|
||||
return new Migration($queue);
|
||||
}, ['queue']);
|
||||
Server::setResource('queueForHamster', function (Connection $queue) {
|
||||
return new Hamster($queue);
|
||||
}, ['queue']);
|
||||
Server::setResource('logger', function (Registry $register) {
|
||||
return $register->get('logger');
|
||||
}, ['register']);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php delete-orphaned-projects $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php get-migration-stats $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php patch-recreate-repositories-documents $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/worker.php hamster $@
|
||||
+7
-7
@@ -43,20 +43,20 @@
|
||||
"ext-sockets": "*",
|
||||
"appwrite/php-runtimes": "0.13.*",
|
||||
"appwrite/php-clamav": "2.0.*",
|
||||
"utopia-php/abuse": "0.32.*",
|
||||
"utopia-php/abuse": "0.33.*",
|
||||
"utopia-php/analytics": "0.10.*",
|
||||
"utopia-php/audit": "0.34.*",
|
||||
"utopia-php/audit": "0.35.*",
|
||||
"utopia-php/cache": "0.8.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.44.*",
|
||||
"utopia-php/database": "0.45.*",
|
||||
"utopia-php/domains": "0.3.*",
|
||||
"utopia-php/dsn": "0.1.*",
|
||||
"utopia-php/framework": "0.31.0",
|
||||
"utopia-php/image": "0.5.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/logger": "0.3.*",
|
||||
"utopia-php/messaging": "0.1.*",
|
||||
"utopia-php/messaging": "0.2.*",
|
||||
"utopia-php/migration": "0.3.*",
|
||||
"utopia-php/orchestration": "0.9.*",
|
||||
"utopia-php/platform": "0.5.*",
|
||||
@@ -64,9 +64,9 @@
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/queue": "0.5.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "0.17.*",
|
||||
"utopia-php/storage": "0.18.*",
|
||||
"utopia-php/swoole": "0.5.*",
|
||||
"utopia-php/vcs": "0.5.*",
|
||||
"utopia-php/vcs": "0.6.*",
|
||||
"utopia-php/websocket": "0.1.*",
|
||||
"matomo/device-detector": "6.1.*",
|
||||
"dragonmantank/cron-expression": "3.3.2",
|
||||
@@ -86,7 +86,7 @@
|
||||
],
|
||||
"require-dev": {
|
||||
"ext-fileinfo": "*",
|
||||
"appwrite/sdk-generator": "0.35.*",
|
||||
"appwrite/sdk-generator": "0.36.*",
|
||||
"phpunit/phpunit": "9.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.7",
|
||||
"swoole/ide-helper": "5.0.2",
|
||||
|
||||
Generated
+64
-131
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6ff937a260c3e0c09de9eb5e073d830d",
|
||||
"content-hash": "eff47c9354bb55140097067b71425f91",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -851,72 +851,6 @@
|
||||
},
|
||||
"time": "2021-10-08T21:21:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/pint",
|
||||
"version": "v1.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/pint.git",
|
||||
"reference": "e60e2112ee779ce60f253695b273d1646a17d6f1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/pint/zipball/e60e2112ee779ce60f253695b273d1646a17d6f1",
|
||||
"reference": "e60e2112ee779ce60f253695b273d1646a17d6f1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"ext-xml": "*",
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.11.0",
|
||||
"illuminate/view": "^9.32.0",
|
||||
"laravel-zero/framework": "^9.2.0",
|
||||
"mockery/mockery": "^1.5.1",
|
||||
"nunomaduro/larastan": "^2.2.0",
|
||||
"nunomaduro/termwind": "^1.14.0",
|
||||
"pestphp/pest": "^1.22.1"
|
||||
},
|
||||
"bin": [
|
||||
"builds/pint"
|
||||
],
|
||||
"type": "project",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Seeders\\": "database/seeders/",
|
||||
"Database\\Factories\\": "database/factories/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nuno Maduro",
|
||||
"email": "enunomaduro@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "An opinionated code formatter for PHP.",
|
||||
"homepage": "https://laravel.com",
|
||||
"keywords": [
|
||||
"format",
|
||||
"formatter",
|
||||
"lint",
|
||||
"linter",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/pint/issues",
|
||||
"source": "https://github.com/laravel/pint"
|
||||
},
|
||||
"time": "2022-11-29T16:25:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/csv",
|
||||
"version": "9.7.1",
|
||||
@@ -1681,23 +1615,23 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/abuse",
|
||||
"version": "0.32.0",
|
||||
"version": "0.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/abuse.git",
|
||||
"reference": "9717ffb2d7711f3fd621bb6df3edf5724c08ea78"
|
||||
"reference": "1ba8d5f2793885cbf779e3b5b9d886968af43d2c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/9717ffb2d7711f3fd621bb6df3edf5724c08ea78",
|
||||
"reference": "9717ffb2d7711f3fd621bb6df3edf5724c08ea78",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/1ba8d5f2793885cbf779e3b5b9d886968af43d2c",
|
||||
"reference": "1ba8d5f2793885cbf779e3b5b9d886968af43d2c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "0.44.*"
|
||||
"utopia-php/database": "0.45.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.5.*",
|
||||
@@ -1724,9 +1658,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/abuse/issues",
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.32.0"
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.33.0"
|
||||
},
|
||||
"time": "2023-10-18T07:28:55+00:00"
|
||||
"time": "2023-11-01T08:51:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/analytics",
|
||||
@@ -1776,21 +1710,21 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/audit",
|
||||
"version": "0.34.0",
|
||||
"version": "0.35.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/audit.git",
|
||||
"reference": "cf34cc3f9f20da4e574a9be4517e1a11025a858f"
|
||||
"reference": "ed9366ef05556da040de7a8b570f4160c7d8ea4a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/cf34cc3f9f20da4e574a9be4517e1a11025a858f",
|
||||
"reference": "cf34cc3f9f20da4e574a9be4517e1a11025a858f",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/ed9366ef05556da040de7a8b570f4160c7d8ea4a",
|
||||
"reference": "ed9366ef05556da040de7a8b570f4160c7d8ea4a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "0.44.*"
|
||||
"utopia-php/database": "0.45.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.5.*",
|
||||
@@ -1817,9 +1751,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/audit/issues",
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.34.0"
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.35.0"
|
||||
},
|
||||
"time": "2023-10-18T07:43:25+00:00"
|
||||
"time": "2023-11-01T08:51:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cache",
|
||||
@@ -1972,16 +1906,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "0.44.2",
|
||||
"version": "0.45.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "591cadbc2c622a3304aae9a16ebfdbe75ef33a06"
|
||||
"reference": "dc789f2c1fd8b5ee07ff883e11c9ad7970824788"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/591cadbc2c622a3304aae9a16ebfdbe75ef33a06",
|
||||
"reference": "591cadbc2c622a3304aae9a16ebfdbe75ef33a06",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/dc789f2c1fd8b5ee07ff883e11c9ad7970824788",
|
||||
"reference": "dc789f2c1fd8b5ee07ff883e11c9ad7970824788",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1994,7 +1928,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.14",
|
||||
"laravel/pint": "1.4.*",
|
||||
"laravel/pint": "1.13.*",
|
||||
"pcov/clobber": "^2.0",
|
||||
"phpstan/phpstan": "1.10.*",
|
||||
"phpunit/phpunit": "^9.4",
|
||||
@@ -2022,9 +1956,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/0.44.2"
|
||||
"source": "https://github.com/utopia-php/database/tree/0.45.2"
|
||||
},
|
||||
"time": "2023-10-19T07:39:00+00:00"
|
||||
"time": "2023-11-15T03:38:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
@@ -2336,16 +2270,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/messaging",
|
||||
"version": "0.1.1",
|
||||
"version": "0.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/messaging.git",
|
||||
"reference": "a75d66ddd59b834ab500a4878a2c084e6572604a"
|
||||
"reference": "2d0f474a106bb1da285f85e105c29b46085d3a43"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/a75d66ddd59b834ab500a4878a2c084e6572604a",
|
||||
"reference": "a75d66ddd59b834ab500a4878a2c084e6572604a",
|
||||
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/2d0f474a106bb1da285f85e105c29b46085d3a43",
|
||||
"reference": "2d0f474a106bb1da285f85e105c29b46085d3a43",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2354,8 +2288,8 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.2",
|
||||
"phpmailer/phpmailer": "6.6.*",
|
||||
"phpunit/phpunit": "9.5.*"
|
||||
"phpmailer/phpmailer": "6.8.*",
|
||||
"phpunit/phpunit": "9.6.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -2378,22 +2312,22 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/messaging/issues",
|
||||
"source": "https://github.com/utopia-php/messaging/tree/0.1.1"
|
||||
"source": "https://github.com/utopia-php/messaging/tree/0.2.0"
|
||||
},
|
||||
"time": "2023-02-07T05:42:46+00:00"
|
||||
"time": "2023-09-14T20:48:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
"version": "0.3.5",
|
||||
"version": "0.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "b2fd3a8310296f4e44ff0e85b0eb0230ad9a2f83"
|
||||
"reference": "f78273b38bade23db5866e5c7cb5f55427ba82af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/b2fd3a8310296f4e44ff0e85b0eb0230ad9a2f83",
|
||||
"reference": "b2fd3a8310296f4e44ff0e85b0eb0230ad9a2f83",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/f78273b38bade23db5866e5c7cb5f55427ba82af",
|
||||
"reference": "f78273b38bade23db5866e5c7cb5f55427ba82af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2426,9 +2360,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/0.3.5"
|
||||
"source": "https://github.com/utopia-php/migration/tree/0.3.6"
|
||||
},
|
||||
"time": "2023-09-25T16:51:47+00:00"
|
||||
"time": "2023-11-02T15:13:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
@@ -2808,16 +2742,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/storage",
|
||||
"version": "0.17.0",
|
||||
"version": "0.18.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/storage.git",
|
||||
"reference": "efec5376c02d3d8330f1beb1469e6d6e313e21ee"
|
||||
"reference": "983e6dee137012f9f57f126d3c79aab54e4e8824"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/storage/zipball/efec5376c02d3d8330f1beb1469e6d6e313e21ee",
|
||||
"reference": "efec5376c02d3d8330f1beb1469e6d6e313e21ee",
|
||||
"url": "https://api.github.com/repos/utopia-php/storage/zipball/983e6dee137012f9f57f126d3c79aab54e4e8824",
|
||||
"reference": "983e6dee137012f9f57f126d3c79aab54e4e8824",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2857,9 +2791,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/storage/issues",
|
||||
"source": "https://github.com/utopia-php/storage/tree/0.17.0"
|
||||
"source": "https://github.com/utopia-php/storage/tree/0.18.1"
|
||||
},
|
||||
"time": "2023-08-21T11:28:36+00:00"
|
||||
"time": "2023-10-24T14:44:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/swoole",
|
||||
@@ -2914,26 +2848,25 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/system",
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/system.git",
|
||||
"reference": "01bf0d283aded0ee0a7a6e5ff540acf64270ab27"
|
||||
"reference": "4593d4d334b0c15879c4744a826e0362924c5d66"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/system/zipball/01bf0d283aded0ee0a7a6e5ff540acf64270ab27",
|
||||
"reference": "01bf0d283aded0ee0a7a6e5ff540acf64270ab27",
|
||||
"url": "https://api.github.com/repos/utopia-php/system/zipball/4593d4d334b0c15879c4744a826e0362924c5d66",
|
||||
"reference": "4593d4d334b0c15879c4744a826e0362924c5d66",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"laravel/pint": "1.2.*",
|
||||
"php": ">=8.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
"vimeo/psalm": "4.0.1"
|
||||
"laravel/pint": "1.13.*",
|
||||
"phpstan/phpstan": "1.10.*",
|
||||
"phpunit/phpunit": "9.6.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -2965,22 +2898,22 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/system/issues",
|
||||
"source": "https://github.com/utopia-php/system/tree/0.7.1"
|
||||
"source": "https://github.com/utopia-php/system/tree/0.7.2"
|
||||
},
|
||||
"time": "2023-08-30T09:14:37+00:00"
|
||||
"time": "2023-10-20T01:39:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/vcs",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/vcs.git",
|
||||
"reference": "47144f272030b7ed1b05471f2cb3aabeb8cb831c"
|
||||
"reference": "f135291b87cb45335fc6608722e7f89894bc33ee"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/47144f272030b7ed1b05471f2cb3aabeb8cb831c",
|
||||
"reference": "47144f272030b7ed1b05471f2cb3aabeb8cb831c",
|
||||
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/f135291b87cb45335fc6608722e7f89894bc33ee",
|
||||
"reference": "f135291b87cb45335fc6608722e7f89894bc33ee",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3014,9 +2947,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/vcs/issues",
|
||||
"source": "https://github.com/utopia-php/vcs/tree/0.5.0"
|
||||
"source": "https://github.com/utopia-php/vcs/tree/0.6.2"
|
||||
},
|
||||
"time": "2023-09-13T19:05:52+00:00"
|
||||
"time": "2023-11-08T15:36:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/websocket",
|
||||
@@ -3203,16 +3136,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "0.35.2",
|
||||
"version": "0.36.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "2dfe0430a64ffd2a07078d83b20144b871acac3b"
|
||||
"reference": "3a10f1f895ed71120442ff71eb6adec3fd6b4e8a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/2dfe0430a64ffd2a07078d83b20144b871acac3b",
|
||||
"reference": "2dfe0430a64ffd2a07078d83b20144b871acac3b",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/3a10f1f895ed71120442ff71eb6adec3fd6b4e8a",
|
||||
"reference": "3a10f1f895ed71120442ff71eb6adec3fd6b4e8a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3248,9 +3181,9 @@
|
||||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/0.35.2"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/0.36.0"
|
||||
},
|
||||
"time": "2023-09-14T14:59:50+00:00"
|
||||
"time": "2023-11-20T10:03:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
@@ -5889,5 +5822,5 @@
|
||||
"platform-overrides": {
|
||||
"php": "8.0"
|
||||
},
|
||||
"plugin-api-version": "2.2.0"
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
||||
@@ -717,6 +717,63 @@ services:
|
||||
environment:
|
||||
- _APP_ASSISTANT_OPENAI_API_KEY
|
||||
|
||||
appwrite-worker-hamster:
|
||||
entrypoint: worker-hamster
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-hamster
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
- redis
|
||||
- mariadb
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_MIXPANEL_TOKEN
|
||||
|
||||
appwrite-hamster-scheduler:
|
||||
entrypoint: hamster
|
||||
<<: *x-logging
|
||||
container_name: appwrite-hamster-scheduler
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
- redis
|
||||
- mariadb
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_HAMSTER_TIME
|
||||
- _APP_HAMSTER_INTERVAL
|
||||
|
||||
openruntimes-executor:
|
||||
container_name: openruntimes-executor
|
||||
hostname: appwrite-executor
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
appwrite health getQueueBuilds
|
||||
appwrite health getQueueBuilds \
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
appwrite health getQueueCertificates
|
||||
appwrite health getQueueCertificates \
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
appwrite health getQueueDatabases \
|
||||
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
appwrite health getQueueDeletes
|
||||
appwrite health getQueueDeletes \
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
appwrite health getQueueFunctions
|
||||
appwrite health getQueueFunctions \
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
appwrite health getQueueLogs
|
||||
appwrite health getQueueLogs \
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
appwrite health getQueueMails
|
||||
appwrite health getQueueMails \
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
appwrite health getQueueMessaging
|
||||
appwrite health getQueueMessaging \
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
appwrite health getQueueMigrations
|
||||
appwrite health getQueueMigrations \
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
appwrite health getQueueWebhooks
|
||||
appwrite health getQueueWebhooks \
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
## 10.1.0
|
||||
|
||||
* Add new queue health endpoints
|
||||
* Fix between queries
|
||||
|
||||
## 10.0.0
|
||||
|
||||
* Parameter `url` is now optional in the `createMembership` endpoint
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 11.0.1
|
||||
|
||||
* Fix between queries
|
||||
|
||||
## 11.0.0
|
||||
|
||||
* Parameter `url` is now optional in the `createMembership` endpoint
|
||||
|
||||
@@ -44,7 +44,7 @@ class PasswordHistory extends Password
|
||||
public function isValid($value): bool
|
||||
{
|
||||
foreach ($this->history as $hash) {
|
||||
if (Auth::passwordVerify($value, $hash, $this->algo, $this->algoOptions)) {
|
||||
if (!empty($hash) && Auth::passwordVerify($value, $hash, $this->algo, $this->algoOptions)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ class Event
|
||||
public const MIGRATIONS_QUEUE_NAME = 'v1-migrations';
|
||||
public const MIGRATIONS_CLASS_NAME = 'MigrationsV1';
|
||||
|
||||
public const HAMSTER_QUEUE_NAME = 'v1-hamster';
|
||||
public const HAMSTER_CLASS_NAME = 'HamsterV1';
|
||||
|
||||
protected string $queue = '';
|
||||
protected string $class = '';
|
||||
protected string $event = '';
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
|
||||
class Hamster extends Event
|
||||
{
|
||||
protected string $type = '';
|
||||
protected ?Document $project = null;
|
||||
protected ?Document $organization = null;
|
||||
protected ?Document $user = null;
|
||||
|
||||
public const TYPE_PROJECT = 'project';
|
||||
public const TYPE_ORGANISATION = 'organisation';
|
||||
public const TYPE_USER = 'user';
|
||||
|
||||
public function __construct(protected Connection $connection)
|
||||
{
|
||||
parent::__construct($connection);
|
||||
|
||||
$this
|
||||
->setQueue(Event::HAMSTER_QUEUE_NAME)
|
||||
->setClass(Event::HAMSTER_CLASS_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type for the hamster event.
|
||||
*
|
||||
* @param string $type
|
||||
* @return self
|
||||
*/
|
||||
public function setType(string $type): self
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set type for the hamster event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the project for the hamster event.
|
||||
*
|
||||
* @param Document $project
|
||||
*/
|
||||
public function setProject(Document $project): self
|
||||
{
|
||||
$this->project = $project;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set project for the hamster event.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function getProject(): Document
|
||||
{
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the organization for the hamster event.
|
||||
*
|
||||
* @param Document $organization
|
||||
*/
|
||||
public function setOrganization(Document $organization): self
|
||||
{
|
||||
$this->organization = $organization;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set organization for the hamster event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOrganization(): Document
|
||||
{
|
||||
return $this->organization;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user for the hamster event.
|
||||
*
|
||||
* @param Document $user
|
||||
*/
|
||||
public function setUser(Document $user): self
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set user for the hamster event.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function getUser(): Document
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the function event and sends it to the functions worker.
|
||||
*
|
||||
* @return string|bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function trigger(): string|bool
|
||||
{
|
||||
if ($this->paused) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$client = new Client($this->queue, $this->connection);
|
||||
|
||||
$events = $this->getEvent() ? Event::generateEvents($this->getEvent(), $this->getParams()) : null;
|
||||
|
||||
return $client->enqueue([
|
||||
'type' => $this->type,
|
||||
'project' => $this->project,
|
||||
'organization' => $this->organization,
|
||||
'user' => $this->user,
|
||||
'events' => $events,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a function event from a base event
|
||||
*
|
||||
* @param Event $event
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
*/
|
||||
public function from(Event $event): self
|
||||
{
|
||||
$this->event = $event->getEvent();
|
||||
$this->params = $event->getParams();
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,8 @@ class Exception extends \Exception
|
||||
public const USER_OAUTH2_BAD_REQUEST = 'user_oauth2_bad_request';
|
||||
public const USER_OAUTH2_UNAUTHORIZED = 'user_oauth2_unauthorized';
|
||||
public const USER_OAUTH2_PROVIDER_ERROR = 'user_oauth2_provider_error';
|
||||
public const USER_EMAIL_ALREADY_VERIFIED = 'user_email_alread_verified';
|
||||
public const USER_PHONE_ALREADY_VERIFIED = 'user_phone_already_verified';
|
||||
|
||||
/** Teams */
|
||||
public const TEAM_NOT_FOUND = 'team_not_found';
|
||||
@@ -224,9 +226,19 @@ class Exception extends \Exception
|
||||
public const MIGRATION_NOT_FOUND = 'migration_not_found';
|
||||
public const MIGRATION_ALREADY_EXISTS = 'migration_already_exists';
|
||||
public const MIGRATION_IN_PROGRESS = 'migration_in_progress';
|
||||
public const MIGRATION_PROVIDER_ERROR = 'migration_provider_error';
|
||||
|
||||
protected $type = '';
|
||||
protected $errors = [];
|
||||
/** Realtime */
|
||||
public const REALTIME_MESSAGE_FORMAT_INVALID = 'realtime_message_format_invalid';
|
||||
public const REALTIME_TOO_MANY_MESSAGES = 'realtime_too_many_messages';
|
||||
public const REALTIME_POLICY_VIOLATION = 'realtime_policy_violation';
|
||||
|
||||
/** Health */
|
||||
public const QUEUE_SIZE_EXCEEDED = 'queue_size_exceeded';
|
||||
|
||||
protected string $type = '';
|
||||
protected array $errors = [];
|
||||
protected bool $publish = true;
|
||||
|
||||
public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int $code = null, \Throwable $previous = null)
|
||||
{
|
||||
@@ -236,6 +248,7 @@ class Exception extends \Exception
|
||||
if (isset($this->errors[$type])) {
|
||||
$this->code = $this->errors[$type]['code'];
|
||||
$this->message = $this->errors[$type]['description'];
|
||||
$this->publish = $this->errors[$type]['publish'] ?? true;
|
||||
}
|
||||
|
||||
$this->message = $message ?? $this->message;
|
||||
@@ -265,4 +278,14 @@ class Exception extends \Exception
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the log is publishable for the exception.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPublishable(): bool
|
||||
{
|
||||
return $this->publish;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,13 @@ abstract class Migration
|
||||
'1.4.4' => 'V19',
|
||||
'1.4.5' => 'V19',
|
||||
'1.4.6' => 'V19',
|
||||
'1.4.7' => 'V19',
|
||||
'1.4.8' => 'V19',
|
||||
'1.4.9' => 'V19',
|
||||
'1.4.10' => 'V19',
|
||||
'1.4.11' => 'V19',
|
||||
'1.4.12' => 'V19',
|
||||
'1.4.13' => 'V19'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -188,17 +195,24 @@ abstract class Migration
|
||||
* @return iterable<Document>
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function documentsIterator(string $collectionId): iterable
|
||||
public function documentsIterator(string $collectionId, $queries = []): iterable
|
||||
{
|
||||
$sum = 0;
|
||||
$nextDocument = null;
|
||||
$collectionCount = $this->projectDB->count($collectionId);
|
||||
$queries[] = Query::limit($this->limit);
|
||||
|
||||
do {
|
||||
$queries = [Query::limit($this->limit)];
|
||||
if ($nextDocument !== null) {
|
||||
$queries[] = Query::cursorAfter($nextDocument);
|
||||
$cursorQueryIndex = \array_search('cursorAfter', \array_map(fn (Query $query) => $query->getMethod(), $queries));
|
||||
|
||||
if ($cursorQueryIndex !== false) {
|
||||
$queries[$cursorQueryIndex] = Query::cursorAfter($nextDocument);
|
||||
} else {
|
||||
$queries[] = Query::cursorAfter($nextDocument);
|
||||
}
|
||||
}
|
||||
|
||||
$documents = $this->projectDB->find($collectionId, $queries);
|
||||
$count = count($documents);
|
||||
$sum += $count;
|
||||
|
||||
@@ -10,6 +10,7 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class V19 extends Migration
|
||||
{
|
||||
@@ -41,6 +42,11 @@ class V19 extends Migration
|
||||
Console::info('Migrating Buckets');
|
||||
$this->migrateBuckets();
|
||||
|
||||
if ($this->project->getId() !== 'console') {
|
||||
Console::info('Migrating Enum Attribute Size');
|
||||
$this->migrateEnumAttributeSize();
|
||||
}
|
||||
|
||||
Console::info('Migrating Documents');
|
||||
$this->forEachDocument([$this, 'fixDocument']);
|
||||
|
||||
@@ -640,6 +646,22 @@ class V19 extends Migration
|
||||
return $commands;
|
||||
}
|
||||
|
||||
private function migrateEnumAttributeSize(): void
|
||||
{
|
||||
foreach (
|
||||
$this->documentsIterator('attributes', [
|
||||
Query::equal('format', ['enum']),
|
||||
Query::lessThan('size', Database::LENGTH_KEY)
|
||||
]) as $attribute
|
||||
) {
|
||||
$attribute->setAttribute('size', Database::LENGTH_KEY);
|
||||
$this->projectDB->updateDocument('attributes', $attribute->getId(), $attribute);
|
||||
$databaseInternalId = $attribute->getAttribute('databaseInternalId');
|
||||
$collectionInternalId = $attribute->getAttribute('collectionInternalId');
|
||||
$this->projectDB->updateAttribute('database_' . $databaseInternalId . '_collection_' . $collectionInternalId, $attribute->getAttribute('key'), size: 255);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix run on each document
|
||||
*
|
||||
|
||||
@@ -18,6 +18,9 @@ use Appwrite\Platform\Tasks\Version;
|
||||
use Appwrite\Platform\Tasks\VolumeSync;
|
||||
use Appwrite\Platform\Tasks\CalcTierStats;
|
||||
use Appwrite\Platform\Tasks\Upgrade;
|
||||
use Appwrite\Platform\Tasks\DeleteOrphanedProjects;
|
||||
use Appwrite\Platform\Tasks\GetMigrationStats;
|
||||
use Appwrite\Platform\Tasks\PatchRecreateRepositoriesDocuments;
|
||||
|
||||
class Tasks extends Service
|
||||
{
|
||||
@@ -40,6 +43,10 @@ class Tasks extends Service
|
||||
->addAction(VolumeSync::getName(), new VolumeSync())
|
||||
->addAction(Specs::getName(), new Specs())
|
||||
->addAction(CalcTierStats::getName(), new CalcTierStats())
|
||||
->addAction(DeleteOrphanedProjects::getName(), new DeleteOrphanedProjects())
|
||||
->addAction(PatchRecreateRepositoriesDocuments::getName(), new PatchRecreateRepositoriesDocuments())
|
||||
->addAction(GetMigrationStats::getName(), new GetMigrationStats())
|
||||
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use Appwrite\Platform\Workers\Databases;
|
||||
use Appwrite\Platform\Workers\Functions;
|
||||
use Appwrite\Platform\Workers\Builds;
|
||||
use Appwrite\Platform\Workers\Deletes;
|
||||
use Appwrite\Platform\Workers\Hamster;
|
||||
use Appwrite\Platform\Workers\Migrations;
|
||||
|
||||
class Workers extends Service
|
||||
@@ -30,6 +31,7 @@ class Workers extends Service
|
||||
->addAction(Builds::getName(), new Builds())
|
||||
->addAction(Deletes::getName(), new Deletes())
|
||||
->addAction(Migrations::getName(), new Migrations())
|
||||
->addAction(Hamster::getName(), new Hamster())
|
||||
|
||||
;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ class CalcTierStats extends Action
|
||||
'Functions',
|
||||
'Deployments',
|
||||
'Executions',
|
||||
'Migrations',
|
||||
];
|
||||
|
||||
protected string $directory = '/usr/local';
|
||||
@@ -99,8 +100,8 @@ class CalcTierStats extends Action
|
||||
|
||||
$projects = [$console];
|
||||
$count = 0;
|
||||
$limit = 30;
|
||||
$sum = 30;
|
||||
$limit = 100;
|
||||
$sum = 100;
|
||||
$offset = 0;
|
||||
while (!empty($projects)) {
|
||||
foreach ($projects as $project) {
|
||||
@@ -200,7 +201,7 @@ class CalcTierStats extends Action
|
||||
|
||||
try {
|
||||
/** Get Domains */
|
||||
$stats['Domains'] = $dbForConsole->count('domains', [
|
||||
$stats['Domains'] = $dbForConsole->count('rules', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
} catch (\Throwable) {
|
||||
@@ -290,6 +291,13 @@ class CalcTierStats extends Action
|
||||
$stats['Executions'] = 0;
|
||||
}
|
||||
|
||||
/** Get Total Migrations */
|
||||
try {
|
||||
$stats['Migrations'] = $dbForProject->count('migrations', []);
|
||||
} catch (\Throwable) {
|
||||
$stats['Migrations'] = 0;
|
||||
}
|
||||
|
||||
$csv->insertOne(array_values($stats));
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed on project ("' . $project->getId() . '") version with error on File: ' . $th->getFile() . ' line no: ' . $th->getline() . ' with message: ' . $th->getMessage());
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
class DeleteOrphanedProjects extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'delete-orphaned-projects';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$this
|
||||
->desc('Delete orphaned projects')
|
||||
->param('commit', false, new Boolean(true), 'Commit project deletion', true)
|
||||
->inject('pools')
|
||||
->inject('cache')
|
||||
->inject('dbForConsole')
|
||||
->inject('register')
|
||||
->callback(function (bool $commit, Group $pools, Cache $cache, Database $dbForConsole, Registry $register) {
|
||||
$this->action($commit, $pools, $cache, $dbForConsole, $register);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function action(bool $commit, Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void
|
||||
{
|
||||
|
||||
Console::title('Delete orphaned projects V1');
|
||||
Console::success(APP_NAME . ' Delete orphaned projects started');
|
||||
|
||||
/** @var array $collections */
|
||||
$collectionsConfig = Config::getParam('collections', [])['projects'] ?? [];
|
||||
|
||||
$collectionsConfig = array_merge([
|
||||
'audit' => [
|
||||
'$id' => ID::custom('audit'),
|
||||
'$collection' => Database::METADATA
|
||||
],
|
||||
'abuse' => [
|
||||
'$id' => ID::custom('abuse'),
|
||||
'$collection' => Database::METADATA
|
||||
]
|
||||
], $collectionsConfig);
|
||||
|
||||
/* Initialise new Utopia app */
|
||||
$app = new App('UTC');
|
||||
$console = $app->getResource('console');
|
||||
$projects = [$console];
|
||||
|
||||
/** Database connections */
|
||||
$totalProjects = $dbForConsole->count('projects');
|
||||
Console::success("Found a total of: {$totalProjects} projects");
|
||||
|
||||
$orphans = 1;
|
||||
$cnt = 0;
|
||||
$count = 0;
|
||||
$limit = 30;
|
||||
$sum = 30;
|
||||
$offset = 0;
|
||||
while (!empty($projects)) {
|
||||
foreach ($projects as $project) {
|
||||
|
||||
/**
|
||||
* Skip user projects with id 'console'
|
||||
*/
|
||||
if ($project->getId() === 'console') {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = $project->getAttribute('database');
|
||||
$adapter = $pools
|
||||
->get($db)
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$dbForProject = new Database($adapter, $cache);
|
||||
$dbForProject->setDefaultDatabase('appwrite');
|
||||
$dbForProject->setNamespace('_' . $project->getInternalId());
|
||||
|
||||
$collectionsCreated = 0;
|
||||
$cnt++;
|
||||
if ($dbForProject->exists($dbForProject->getDefaultDatabase(), Database::METADATA)) {
|
||||
$collectionsCreated = $dbForProject->count(Database::METADATA);
|
||||
}
|
||||
|
||||
$msg = '(' . $cnt . ') found (' . $collectionsCreated . ') collections on project (' . $project->getInternalId() . ') , database (' . $project['database'] . ')';
|
||||
|
||||
if ($collectionsCreated >= count($collectionsConfig)) {
|
||||
Console::log($msg . ' ignoring....');
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::log($msg);
|
||||
|
||||
if ($collectionsCreated > 0) {
|
||||
$collections = $dbForProject->find(Database::METADATA, []);
|
||||
foreach ($collections as $collection) {
|
||||
if ($commit) {
|
||||
$dbForProject->deleteCollection($collection->getId());
|
||||
$dbForConsole->deleteCachedCollection($collection->getId());
|
||||
}
|
||||
Console::info('--Deleting collection (' . $collection->getId() . ') project no (' . $project->getInternalId() . ')');
|
||||
}
|
||||
}
|
||||
|
||||
if ($commit) {
|
||||
$dbForConsole->deleteDocument('projects', $project->getId());
|
||||
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
||||
|
||||
if ($dbForProject->exists($dbForProject->getDefaultDatabase(), Database::METADATA)) {
|
||||
try {
|
||||
$dbForProject->deleteCollection(Database::METADATA);
|
||||
$dbForProject->deleteCachedCollection(Database::METADATA);
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning('Metadata collection does not exist');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console::info('--Deleting project no (' . $project->getInternalId() . ')');
|
||||
|
||||
$orphans++;
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Error: ' . $th->getMessage() . ' ' . $th->getTraceAsString());
|
||||
} finally {
|
||||
$pools
|
||||
->get($db)
|
||||
->reclaim();
|
||||
}
|
||||
}
|
||||
|
||||
$sum = \count($projects);
|
||||
|
||||
$projects = $dbForConsole->find('projects', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
]);
|
||||
|
||||
$offset = $offset + $limit;
|
||||
$count = $count + $sum;
|
||||
}
|
||||
|
||||
Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects found ' . $orphans - 1 . ' orphans');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Exception;
|
||||
use League\Csv\CannotInsertRecord;
|
||||
use Utopia\App;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use League\Csv\Writer;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Registry\Registry;
|
||||
|
||||
class GetMigrationStats extends Action
|
||||
{
|
||||
/*
|
||||
* Csv cols headers
|
||||
*/
|
||||
private array $columns = [
|
||||
'Project ID',
|
||||
'$id',
|
||||
'$createdAt',
|
||||
'status',
|
||||
'stage',
|
||||
'source'
|
||||
];
|
||||
|
||||
protected string $directory = '/usr/local';
|
||||
protected string $path;
|
||||
protected string $date;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'get-migration-stats';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$this
|
||||
->desc('Get stats for projects')
|
||||
->inject('pools')
|
||||
->inject('cache')
|
||||
->inject('dbForConsole')
|
||||
->inject('register')
|
||||
->callback(function (Group $pools, Cache $cache, Database $dbForConsole, Registry $register) {
|
||||
$this->action($pools, $cache, $dbForConsole, $register);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Utopia\Exception
|
||||
* @throws CannotInsertRecord
|
||||
*/
|
||||
public function action(Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void
|
||||
{
|
||||
//docker compose exec -t appwrite get-migration-stats
|
||||
|
||||
Console::title('Migration stats calculation V1');
|
||||
Console::success(APP_NAME . ' Migration stats calculation has started');
|
||||
|
||||
/* Initialise new Utopia app */
|
||||
$app = new App('UTC');
|
||||
$console = $app->getResource('console');
|
||||
|
||||
/** CSV stuff */
|
||||
$this->date = date('Y-m-d');
|
||||
$this->path = "{$this->directory}/migration_stats_{$this->date}.csv";
|
||||
$csv = Writer::createFromPath($this->path, 'w');
|
||||
$csv->insertOne($this->columns);
|
||||
|
||||
/** Database connections */
|
||||
$totalProjects = $dbForConsole->count('projects');
|
||||
Console::success("Found a total of: {$totalProjects} projects");
|
||||
|
||||
$projects = [$console];
|
||||
$count = 0;
|
||||
$limit = 100;
|
||||
$sum = 100;
|
||||
$offset = 0;
|
||||
while (!empty($projects)) {
|
||||
foreach ($projects as $project) {
|
||||
|
||||
/**
|
||||
* Skip user projects with id 'console'
|
||||
*/
|
||||
if ($project->getId() === 'console') {
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::info("Getting stats for {$project->getId()}");
|
||||
|
||||
try {
|
||||
$db = $project->getAttribute('database');
|
||||
$adapter = $pools
|
||||
->get($db)
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$dbForProject = new Database($adapter, $cache);
|
||||
$dbForProject->setDefaultDatabase('appwrite');
|
||||
$dbForProject->setNamespace('_' . $project->getInternalId());
|
||||
|
||||
/** Get Project ID */
|
||||
$stats['Project ID'] = $project->getId();
|
||||
|
||||
/** Get Migration details */
|
||||
$migrations = $dbForProject->find('migrations', [
|
||||
Query::limit(500)
|
||||
]);
|
||||
|
||||
$migrations = array_map(function ($migration) use ($project) {
|
||||
return [
|
||||
$project->getId(),
|
||||
$migration->getAttribute('$id'),
|
||||
$migration->getAttribute('$createdAt'),
|
||||
$migration->getAttribute('status'),
|
||||
$migration->getAttribute('stage'),
|
||||
$migration->getAttribute('source'),
|
||||
];
|
||||
}, $migrations);
|
||||
|
||||
if (!empty($migrations)) {
|
||||
$csv->insertAll($migrations);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed on project ("' . $project->getId() . '") with error on File: ' . $th->getFile() . ' line no: ' . $th->getline() . ' with message: ' . $th->getMessage());
|
||||
} finally {
|
||||
$pools
|
||||
->get($db)
|
||||
->reclaim();
|
||||
}
|
||||
}
|
||||
|
||||
$sum = \count($projects);
|
||||
|
||||
$projects = $dbForConsole->find('projects', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
]);
|
||||
|
||||
$offset = $offset + $limit;
|
||||
$count = $count + $sum;
|
||||
}
|
||||
|
||||
Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects...');
|
||||
|
||||
$pools
|
||||
->get('console')
|
||||
->reclaim();
|
||||
|
||||
/** @var PHPMailer $mail */
|
||||
$mail = $register->get('smtp');
|
||||
|
||||
$mail->clearAddresses();
|
||||
$mail->clearAllRecipients();
|
||||
$mail->clearReplyTos();
|
||||
$mail->clearAttachments();
|
||||
$mail->clearBCCs();
|
||||
$mail->clearCCs();
|
||||
|
||||
try {
|
||||
/** Addresses */
|
||||
$mail->setFrom(App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), 'Appwrite Cloud Hamster');
|
||||
$recipients = explode(',', App::getEnv('_APP_USERS_STATS_RECIPIENTS', ''));
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
$mail->addAddress($recipient);
|
||||
}
|
||||
|
||||
/** Attachments */
|
||||
$mail->addAttachment($this->path);
|
||||
|
||||
/** Content */
|
||||
$mail->Subject = "Migration Report for {$this->date}";
|
||||
$mail->Body = "Please find the migration report atttached";
|
||||
$mail->send();
|
||||
Console::success('Email has been sent!');
|
||||
} catch (Exception $e) {
|
||||
Console::error("Message could not be sent. Mailer Error: {$mail->ErrorInfo}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,45 +2,17 @@
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Event\Hamster as EventHamster;
|
||||
use Exception;
|
||||
use Utopia\App;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Analytics\Adapter\Mixpanel;
|
||||
use Utopia\Analytics\Event;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Pools\Group;
|
||||
|
||||
class Hamster extends Action
|
||||
{
|
||||
private array $metrics = [
|
||||
'usage_files' => 'files.$all.count.total',
|
||||
'usage_buckets' => 'buckets.$all.count.total',
|
||||
'usage_databases' => 'databases.$all.count.total',
|
||||
'usage_documents' => 'documents.$all.count.total',
|
||||
'usage_collections' => 'collections.$all.count.total',
|
||||
'usage_storage' => 'project.$all.storage.size',
|
||||
'usage_requests' => 'project.$all.network.requests',
|
||||
'usage_bandwidth' => 'project.$all.network.bandwidth',
|
||||
'usage_users' => 'users.$all.count.total',
|
||||
'usage_sessions' => 'sessions.email.requests.create',
|
||||
'usage_executions' => 'executions.$all.compute.total',
|
||||
];
|
||||
|
||||
protected string $directory = '/usr/local';
|
||||
|
||||
protected string $path;
|
||||
|
||||
protected string $date;
|
||||
|
||||
protected Mixpanel $mixpanel;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'hamster';
|
||||
@@ -48,263 +20,31 @@ class Hamster extends Action
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->mixpanel = new Mixpanel(App::getEnv('_APP_MIXPANEL_TOKEN', ''));
|
||||
|
||||
$this
|
||||
->desc('Get stats for projects')
|
||||
->inject('pools')
|
||||
->inject('cache')
|
||||
->inject('queueForHamster')
|
||||
->inject('dbForConsole')
|
||||
->callback(function (Group $pools, Cache $cache, Database $dbForConsole) {
|
||||
$this->action($pools, $cache, $dbForConsole);
|
||||
->callback(function (EventHamster $queueForHamster, Database $dbForConsole) {
|
||||
$this->action($queueForHamster, $dbForConsole);
|
||||
});
|
||||
}
|
||||
|
||||
private function getStatsPerProject(Group $pools, Cache $cache, Database $dbForConsole)
|
||||
public function action(EventHamster $queueForHamster, Database $dbForConsole): void
|
||||
{
|
||||
$this->calculateByGroup('projects', $dbForConsole, function (Database $dbForConsole, Document $project) use ($pools, $cache) {
|
||||
/**
|
||||
* Skip user projects with id 'console'
|
||||
*/
|
||||
if ($project->getId() === 'console') {
|
||||
Console::info("Skipping project console");
|
||||
return;
|
||||
}
|
||||
|
||||
Console::log("Getting stats for {$project->getId()}");
|
||||
|
||||
try {
|
||||
$db = $project->getAttribute('database');
|
||||
$adapter = $pools
|
||||
->get($db)
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$dbForProject = new Database($adapter, $cache);
|
||||
$dbForProject->setDefaultDatabase('appwrite');
|
||||
$dbForProject->setNamespace('_' . $project->getInternalId());
|
||||
|
||||
$statsPerProject = [];
|
||||
|
||||
$statsPerProject['time'] = microtime(true);
|
||||
|
||||
/** Get Project ID */
|
||||
$statsPerProject['project_id'] = $project->getId();
|
||||
|
||||
/** Get project created time */
|
||||
$statsPerProject['project_created'] = $project->getAttribute('$createdAt');
|
||||
|
||||
/** Get Project Name */
|
||||
$statsPerProject['project_name'] = $project->getAttribute('name');
|
||||
|
||||
/** Total Project Variables */
|
||||
$statsPerProject['custom_variables'] = $dbForProject->count('variables', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Total Migrations */
|
||||
$statsPerProject['custom_migrations'] = $dbForProject->count('migrations', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Custom SMTP */
|
||||
$smtp = $project->getAttribute('smtp', null);
|
||||
if ($smtp) {
|
||||
$statsPerProject['custom_smtp_status'] = $smtp['enabled'] === true ? 'enabled' : 'disabled';
|
||||
|
||||
/** Get Custom Templates Count */
|
||||
$templates = array_keys($project->getAttribute('templates', []));
|
||||
$statsPerProject['custom_email_templates'] = array_filter($templates, function ($template) {
|
||||
return str_contains($template, 'email');
|
||||
});
|
||||
$statsPerProject['custom_sms_templates'] = array_filter($templates, function ($template) {
|
||||
return str_contains($template, 'sms');
|
||||
});
|
||||
}
|
||||
|
||||
/** Get total relationship attributes */
|
||||
$statsPerProject['custom_relationship_attributes'] = $dbForProject->count('attributes', [
|
||||
Query::equal('type', ['relationship'])
|
||||
], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Total Functions */
|
||||
$statsPerProject['custom_functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT);
|
||||
|
||||
foreach (\array_keys(Config::getParam('runtimes')) as $runtime) {
|
||||
$statsPerProject['custom_functions_' . $runtime] = $dbForProject->count('functions', [
|
||||
Query::equal('runtime', [$runtime]),
|
||||
], APP_LIMIT_COUNT);
|
||||
}
|
||||
|
||||
/** Get Total Deployments */
|
||||
$statsPerProject['custom_deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT);
|
||||
$statsPerProject['custom_deployments_manual'] = $dbForProject->count('deployments', [
|
||||
Query::equal('type', ['manual'])
|
||||
], APP_LIMIT_COUNT);
|
||||
$statsPerProject['custom_deployments_git'] = $dbForProject->count('deployments', [
|
||||
Query::equal('type', ['vcs'])
|
||||
], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get VCS repos connected */
|
||||
$statsPerProject['custom_vcs_repositories'] = $dbForConsole->count('repositories', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()])
|
||||
], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Total Teams */
|
||||
$statsPerProject['custom_teams'] = $dbForProject->count('teams', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Total Members */
|
||||
$teamInternalId = $project->getAttribute('teamInternalId', null);
|
||||
if ($teamInternalId) {
|
||||
$statsPerProject['custom_organization_members'] = $dbForConsole->count('memberships', [
|
||||
Query::equal('teamInternalId', [$teamInternalId])
|
||||
], APP_LIMIT_COUNT);
|
||||
} else {
|
||||
$statsPerProject['custom_organization_members'] = 0;
|
||||
}
|
||||
|
||||
/** Get Email and Name of the project owner */
|
||||
if ($teamInternalId) {
|
||||
$membership = $dbForConsole->findOne('memberships', [
|
||||
Query::equal('teamInternalId', [$teamInternalId]),
|
||||
]);
|
||||
|
||||
if (!$membership || $membership->isEmpty()) {
|
||||
throw new Exception('Membership not found. Skipping project : ' . $project->getId());
|
||||
}
|
||||
|
||||
$userId = $membership->getAttribute('userId', null);
|
||||
if ($userId) {
|
||||
$user = $dbForConsole->getDocument('users', $userId);
|
||||
$statsPerProject['email'] = $user->getAttribute('email', null);
|
||||
$statsPerProject['name'] = $user->getAttribute('name', null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Get Domains */
|
||||
$statsPerProject['custom_domains'] = $dbForConsole->count('rules', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
/** Get Platforms */
|
||||
$platforms = $dbForConsole->find('platforms', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
$statsPerProject['custom_platforms_web'] = sizeof(array_filter($platforms, function ($platform) {
|
||||
return $platform['type'] === 'web';
|
||||
}));
|
||||
|
||||
$statsPerProject['custom_platforms_android'] = sizeof(array_filter($platforms, function ($platform) {
|
||||
return $platform['type'] === 'android';
|
||||
}));
|
||||
|
||||
$statsPerProject['custom_platforms_apple'] = sizeof(array_filter($platforms, function ($platform) {
|
||||
return str_contains($platform['type'], 'apple');
|
||||
}));
|
||||
|
||||
$statsPerProject['custom_platforms_flutter'] = sizeof(array_filter($platforms, function ($platform) {
|
||||
return str_contains($platform['type'], 'flutter');
|
||||
}));
|
||||
|
||||
$flutterPlatforms = [Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_FLUTTER_LINUX];
|
||||
|
||||
foreach ($flutterPlatforms as $flutterPlatform) {
|
||||
$statsPerProject['custom_platforms_' . $flutterPlatform] = sizeof(array_filter($platforms, function ($platform) use ($flutterPlatform) {
|
||||
return $platform['type'] === $flutterPlatform;
|
||||
}));
|
||||
}
|
||||
|
||||
$statsPerProject['custom_platforms_api_keys'] = $dbForConsole->count('keys', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
/** Get Usage $statsPerProject */
|
||||
$periods = [
|
||||
'infinity' => [
|
||||
'period' => '1d',
|
||||
'limit' => 90,
|
||||
],
|
||||
'24h' => [
|
||||
'period' => '1h',
|
||||
'limit' => 24,
|
||||
],
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $periods, &$statsPerProject) {
|
||||
foreach ($this->metrics as $key => $metric) {
|
||||
foreach ($periods as $periodKey => $periodValue) {
|
||||
$limit = $periodValue['limit'];
|
||||
$period = $periodValue['period'];
|
||||
|
||||
$requestDocs = $dbForProject->find('stats', [
|
||||
Query::equal('period', [$period]),
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
|
||||
$statsPerProject[$key . '_' . $periodKey] = [];
|
||||
foreach ($requestDocs as $requestDoc) {
|
||||
$statsPerProject[$key . '_' . $periodKey][] = [
|
||||
'value' => $requestDoc->getAttribute('value'),
|
||||
'date' => $requestDoc->getAttribute('time'),
|
||||
];
|
||||
}
|
||||
|
||||
$statsPerProject[$key . '_' . $periodKey] = array_reverse($statsPerProject[$key . '_' . $periodKey]);
|
||||
// Calculate aggregate of each metric
|
||||
$statsPerProject[$key . '_' . $periodKey] = array_sum(array_column($statsPerProject[$key . '_' . $periodKey], 'value'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (isset($statsPerProject['email'])) {
|
||||
/** Send data to mixpanel */
|
||||
$res = $this->mixpanel->createProfile($statsPerProject['email'], '', [
|
||||
'name' => $statsPerProject['name'],
|
||||
'email' => $statsPerProject['email']
|
||||
]);
|
||||
|
||||
if (!$res) {
|
||||
Console::error('Failed to create user profile for project: ' . $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
$event = new Event();
|
||||
$event
|
||||
->setName('Project Daily Usage')
|
||||
->setProps($statsPerProject);
|
||||
$res = $this->mixpanel->createEvent($event);
|
||||
|
||||
if (!$res) {
|
||||
Console::error('Failed to create event for project: ' . $project->getId());
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Console::error('Failed to send stats for project: ' . $project->getId());
|
||||
Console::error($e->getMessage());
|
||||
} finally {
|
||||
$pools
|
||||
->get($db)
|
||||
->reclaim();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function action(Group $pools, Cache $cache, Database $dbForConsole): void
|
||||
{
|
||||
|
||||
Console::title('Cloud Hamster V1');
|
||||
Console::success(APP_NAME . ' cloud hamster process has started');
|
||||
|
||||
$sleep = (int) App::getEnv('_APP_HAMSTER_INTERVAL', '30'); // 30 seconds (by default)
|
||||
|
||||
$jobInitTime = App::getEnv('_APP_HAMSTER_TIME', '22:00'); // (hour:minutes)
|
||||
|
||||
$now = new \DateTime();
|
||||
$now->setTimezone(new \DateTimeZone(date_default_timezone_get()));
|
||||
|
||||
$next = new \DateTime($now->format("Y-m-d $jobInitTime"));
|
||||
$next->setTimezone(new \DateTimeZone(date_default_timezone_get()));
|
||||
$delay = $next->getTimestamp() - $now->getTimestamp();
|
||||
|
||||
$delay = $next->getTimestamp() - $now->getTimestamp();
|
||||
/**
|
||||
* If time passed for the target day.
|
||||
*/
|
||||
@@ -315,29 +55,22 @@ class Hamster extends Action
|
||||
|
||||
Console::log('[' . $now->format("Y-m-d H:i:s.v") . '] Delaying for ' . $delay . ' setting loop to [' . $next->format("Y-m-d H:i:s.v") . ']');
|
||||
|
||||
Console::loop(function () use ($pools, $cache, $dbForConsole, $sleep) {
|
||||
Console::loop(function () use ($queueForHamster, $dbForConsole, $sleep) {
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Getting Cloud Usage Stats every {$sleep} seconds");
|
||||
Console::info("[{$now}] Queuing Cloud Usage Stats every {$sleep} seconds");
|
||||
$loopStart = microtime(true);
|
||||
|
||||
/* Initialise new Utopia app */
|
||||
$app = new App('UTC');
|
||||
Console::info('Queuing stats for all projects');
|
||||
$this->getStatsPerProject($queueForHamster, $dbForConsole, $loopStart);
|
||||
Console::success('Completed queuing stats for all projects');
|
||||
|
||||
Console::info('Getting stats for all projects');
|
||||
$this->getStatsPerProject($pools, $cache, $dbForConsole);
|
||||
Console::success('Completed getting stats for all projects');
|
||||
Console::info('Queuing stats for all organizations');
|
||||
$this->getStatsPerOrganization($queueForHamster, $dbForConsole, $loopStart);
|
||||
Console::success('Completed queuing stats for all organizations');
|
||||
|
||||
Console::info('Getting stats for all organizations');
|
||||
$this->getStatsPerOrganization($dbForConsole);
|
||||
Console::success('Completed getting stats for all organizations');
|
||||
|
||||
Console::info('Getting stats for all users');
|
||||
$this->getStatsPerUser($dbForConsole);
|
||||
Console::success('Completed getting stats for all users');
|
||||
|
||||
$pools
|
||||
->get('console')
|
||||
->reclaim();
|
||||
Console::info('Queuing stats for all users');
|
||||
$this->getStatsPerUser($queueForHamster, $dbForConsole, $loopStart);
|
||||
Console::success('Completed queuing stats for all users');
|
||||
|
||||
$loopTook = microtime(true) - $loopStart;
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
@@ -345,7 +78,7 @@ class Hamster extends Action
|
||||
}, $sleep, $delay);
|
||||
}
|
||||
|
||||
protected function calculateByGroup(string $collection, Database $dbForConsole, callable $callback)
|
||||
protected function calculateByGroup(string $collection, Database $database, callable $callback)
|
||||
{
|
||||
$count = 0;
|
||||
$chunk = 0;
|
||||
@@ -358,7 +91,7 @@ class Hamster extends Action
|
||||
while ($sum === $limit) {
|
||||
$chunk++;
|
||||
|
||||
$results = $dbForConsole->find($collection, \array_merge([
|
||||
$results = $database->find($collection, \array_merge([
|
||||
Query::limit($limit),
|
||||
Query::offset($count)
|
||||
]));
|
||||
@@ -368,7 +101,7 @@ class Hamster extends Action
|
||||
Console::log('Processing chunk #' . $chunk . '. Found ' . $sum . ' documents');
|
||||
|
||||
foreach ($results as $document) {
|
||||
call_user_func($callback, $dbForConsole, $document);
|
||||
call_user_func($callback, $database, $document);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
@@ -378,96 +111,45 @@ class Hamster extends Action
|
||||
Console::log("Processed {$count} document by group in " . ($executionEnd - $executionStart) . " seconds");
|
||||
}
|
||||
|
||||
protected function getStatsPerOrganization(Database $dbForConsole)
|
||||
protected function getStatsPerOrganization(EventHamster $hamster, Database $dbForConsole, float $loopStart)
|
||||
{
|
||||
|
||||
$this->calculateByGroup('teams', $dbForConsole, function (Database $dbForConsole, Document $document) {
|
||||
$this->calculateByGroup('teams', $dbForConsole, function (Database $dbForConsole, Document $organization) use ($hamster, $loopStart) {
|
||||
try {
|
||||
$statsPerOrganization = [];
|
||||
|
||||
/** Organization name */
|
||||
$statsPerOrganization['name'] = $document->getAttribute('name');
|
||||
|
||||
/** Get Email and of the organization owner */
|
||||
$membership = $dbForConsole->findOne('memberships', [
|
||||
Query::equal('teamInternalId', [$document->getInternalId()]),
|
||||
]);
|
||||
|
||||
if (!$membership || $membership->isEmpty()) {
|
||||
throw new Exception('Membership not found. Skipping organization : ' . $document->getId());
|
||||
}
|
||||
|
||||
$userId = $membership->getAttribute('userId', null);
|
||||
if ($userId) {
|
||||
$user = $dbForConsole->getDocument('users', $userId);
|
||||
$statsPerOrganization['email'] = $user->getAttribute('email', null);
|
||||
}
|
||||
|
||||
/** Organization Creation Date */
|
||||
$statsPerOrganization['created'] = $document->getAttribute('$createdAt');
|
||||
|
||||
/** Number of team members */
|
||||
$statsPerOrganization['members'] = $document->getAttribute('total');
|
||||
|
||||
/** Number of projects in this organization */
|
||||
$statsPerOrganization['projects'] = $dbForConsole->count('projects', [
|
||||
Query::equal('teamId', [$document->getId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
if (!isset($statsPerOrganization['email'])) {
|
||||
throw new Exception('Email not found. Skipping organization : ' . $document->getId());
|
||||
}
|
||||
|
||||
$event = new Event();
|
||||
$event
|
||||
->setName('Organization Daily Usage')
|
||||
->setProps($statsPerOrganization);
|
||||
$res = $this->mixpanel->createEvent($event);
|
||||
if (!$res) {
|
||||
throw new Exception('Failed to create event for organization : ' . $document->getId());
|
||||
}
|
||||
$organization->setAttribute('$time', $loopStart);
|
||||
$hamster
|
||||
->setType(EventHamster::TYPE_ORGANISATION)
|
||||
->setOrganization($organization)
|
||||
->trigger();
|
||||
} catch (Exception $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function getStatsPerUser(Database $dbForConsole)
|
||||
private function getStatsPerProject(EventHamster $hamster, Database $dbForConsole, float $loopStart)
|
||||
{
|
||||
$this->calculateByGroup('users', $dbForConsole, function (Database $dbForConsole, Document $document) {
|
||||
$this->calculateByGroup('projects', $dbForConsole, function (Database $dbForConsole, Document $project) use ($hamster, $loopStart) {
|
||||
try {
|
||||
$statsPerUser = [];
|
||||
$project->setAttribute('$time', $loopStart);
|
||||
$hamster
|
||||
->setType(EventHamster::TYPE_PROJECT)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
} catch (Exception $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Organization name */
|
||||
$statsPerUser['name'] = $document->getAttribute('name');
|
||||
|
||||
/** Organization ID (needs to be stored as an email since mixpanel uses the email attribute as a distinctID) */
|
||||
$statsPerUser['email'] = $document->getAttribute('email');
|
||||
|
||||
/** Organization Creation Date */
|
||||
$statsPerUser['created'] = $document->getAttribute('$createdAt');
|
||||
|
||||
/** Number of teams this user is a part of */
|
||||
$statsPerUser['memberships'] = $dbForConsole->count('memberships', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
if (!isset($statsPerUser['email'])) {
|
||||
throw new Exception('User has no email: ' . $document->getId());
|
||||
}
|
||||
|
||||
/** Send data to mixpanel */
|
||||
$event = new Event();
|
||||
$event
|
||||
->setName('User Daily Usage')
|
||||
->setProps($statsPerUser);
|
||||
$res = $this->mixpanel->createEvent($event);
|
||||
|
||||
if (!$res) {
|
||||
throw new Exception('Failed to create user profile for user: ' . $document->getId());
|
||||
}
|
||||
protected function getStatsPerUser(EventHamster $hamster, Database $dbForConsole, float $loopStart)
|
||||
{
|
||||
$this->calculateByGroup('users', $dbForConsole, function (Database $dbForConsole, Document $user) use ($hamster, $loopStart) {
|
||||
try {
|
||||
$user->setAttribute('$time', $loopStart);
|
||||
$hamster
|
||||
->setType(EventHamster::TYPE_USER)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
} catch (Exception $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ use Appwrite\Auth\Auth;
|
||||
use Appwrite\Docker\Compose;
|
||||
use Appwrite\Docker\Env;
|
||||
use Appwrite\Utopia\View;
|
||||
use Utopia\Analytics\Adapter;
|
||||
use Utopia\Analytics\Adapter\GoogleAnalytics;
|
||||
use Utopia\Analytics\Event;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Validator\Text;
|
||||
@@ -43,12 +40,6 @@ class Install extends Action
|
||||
/** @var array<string, array<string, string>> $vars array whre key is variable name and value is variable */
|
||||
$vars = [];
|
||||
|
||||
/**
|
||||
* We are using a random value every execution for identification.
|
||||
* This allows us to collect information without invading the privacy of our users.
|
||||
*/
|
||||
$analytics = new GoogleAnalytics('UA-26264668-9', uniqid('server.', true));
|
||||
|
||||
foreach ($config as $category) {
|
||||
foreach ($category['variables'] ?? [] as $var) {
|
||||
$vars[$var['name']] = $var;
|
||||
@@ -82,7 +73,7 @@ class Install extends Action
|
||||
file_put_contents($this->path . '/docker-compose.yml.' . $time . '.backup', $data);
|
||||
$compose = new Compose($data);
|
||||
$appwrite = $compose->getService('appwrite');
|
||||
$oldVersion = ($appwrite) ? $appwrite->getImageVersion() : null;
|
||||
$oldVersion = $appwrite?->getImageVersion();
|
||||
try {
|
||||
$ports = $compose->getService('traefik')->getPorts();
|
||||
} catch (\Throwable $th) {
|
||||
@@ -209,14 +200,12 @@ class Install extends Action
|
||||
|
||||
if (!file_put_contents($this->path . '/docker-compose.yml', $templateForCompose->render(false))) {
|
||||
$message = 'Failed to save Docker Compose file';
|
||||
$this->sendEvent($analytics, $message);
|
||||
Console::error($message);
|
||||
Console::exit(1);
|
||||
}
|
||||
|
||||
if (!file_put_contents($this->path . '/.env', $templateForEnv->render(false))) {
|
||||
$message = 'Failed to save environment variables file';
|
||||
$this->sendEvent($analytics, $message);
|
||||
Console::error($message);
|
||||
Console::exit(1);
|
||||
}
|
||||
@@ -237,29 +226,12 @@ class Install extends Action
|
||||
|
||||
if ($exit !== 0) {
|
||||
$message = 'Failed to install Appwrite dockers';
|
||||
$this->sendEvent($analytics, $message);
|
||||
Console::error($message);
|
||||
Console::error($stderr);
|
||||
Console::exit($exit);
|
||||
} else {
|
||||
$message = 'Appwrite installed successfully';
|
||||
$this->sendEvent($analytics, $message);
|
||||
Console::success($message);
|
||||
}
|
||||
}
|
||||
|
||||
private function sendEvent(Adapter $analytics, string $message): void
|
||||
{
|
||||
$event = new Event();
|
||||
$event->setName(APP_VERSION_STABLE);
|
||||
$event->setValue($message);
|
||||
$event->setUrl('http://localhost/');
|
||||
$event->setProps([
|
||||
'category' => 'install/server',
|
||||
'action' => 'install',
|
||||
]);
|
||||
$event->setType('install/server');
|
||||
|
||||
$analytics->createEvent($event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Migrate extends Action
|
||||
@@ -29,7 +30,8 @@ class Migrate extends Action
|
||||
->inject('cache')
|
||||
->inject('dbForConsole')
|
||||
->inject('getProjectDB')
|
||||
->callback(fn ($version, $cache, $dbForConsole, $getProjectDB) => $this->action($version, $cache, $dbForConsole, $getProjectDB));
|
||||
->inject('register')
|
||||
->callback(fn ($version, $cache, $dbForConsole, $getProjectDB, Registry $register) => $this->action($version, $cache, $dbForConsole, $getProjectDB, $register));
|
||||
}
|
||||
|
||||
private function clearProjectsCache(Cache $cache, Document $project)
|
||||
@@ -41,7 +43,7 @@ class Migrate extends Action
|
||||
}
|
||||
}
|
||||
|
||||
public function action(string $version, Cache $cache, Database $dbForConsole, callable $getProjectDB)
|
||||
public function action(string $version, Cache $cache, Database $dbForConsole, callable $getProjectDB, Registry $register)
|
||||
{
|
||||
Authorization::disable();
|
||||
if (!array_key_exists($version, Migration::$versions)) {
|
||||
@@ -89,9 +91,11 @@ class Migrate extends Action
|
||||
|
||||
try {
|
||||
// TODO: Iterate through all project DBs
|
||||
/** @var Database $projectDB */
|
||||
$projectDB = $getProjectDB($project);
|
||||
$migration
|
||||
->setProject($project, $projectDB, $dbForConsole)
|
||||
->setPDO($register->get('db', true))
|
||||
->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed to update project ("' . $project->getId() . '") version with error: ' . $th->getMessage());
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\CLI\Console;
|
||||
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\Query;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class PatchRecreateRepositoriesDocuments extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'patch-recreate-repositories-documents';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Recreate missing repositories in consoleDB from projectDBs. They can be missing if you used Appwrite 1.4.10 or 1.4.11, and deleted a function.')
|
||||
->param('after', '', new Text(36), 'After cursor', true)
|
||||
->param('projectId', '', new Text(36), 'Select project to validate', true)
|
||||
->inject('dbForConsole')
|
||||
->inject('getProjectDB')
|
||||
->callback(fn ($after, $projectId, $dbForConsole, $getProjectDB) => $this->action($after, $projectId, $dbForConsole, $getProjectDB));
|
||||
}
|
||||
|
||||
public function action($after, $projectId, Database $dbForConsole, callable $getProjectDB): void
|
||||
{
|
||||
Console::info("Starting the patch");
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
if (!empty($projectId)) {
|
||||
try {
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
$dbForProject = call_user_func($getProjectDB, $project);
|
||||
$this->recreateRepositories($dbForConsole, $dbForProject, $project);
|
||||
} catch (\Throwable $th) {
|
||||
Console::error("Unexpected error occured with Project ID {$projectId}");
|
||||
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());
|
||||
}
|
||||
} else {
|
||||
$queries = [];
|
||||
if (!empty($after)) {
|
||||
Console::info("Iterating remaining projects after project with ID {$after}");
|
||||
$project = $dbForConsole->getDocument('projects', $after);
|
||||
$queries = [Query::cursorAfter($project)];
|
||||
} else {
|
||||
Console::info("Iterating all projects");
|
||||
}
|
||||
$this->foreachDocument($dbForConsole, 'projects', $queries, function (Document $project) use ($getProjectDB, $dbForConsole) {
|
||||
$projectId = $project->getId();
|
||||
|
||||
try {
|
||||
$dbForProject = call_user_func($getProjectDB, $project);
|
||||
$this->recreateRepositories($dbForConsole, $dbForProject, $project);
|
||||
} catch (\Throwable $th) {
|
||||
Console::error("Unexpected error occured with Project ID {$projectId}");
|
||||
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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$endTime = microtime(true);
|
||||
$timeTaken = $endTime - $startTime;
|
||||
|
||||
$hours = (int)($timeTaken / 3600);
|
||||
$timeTaken -= $hours * 3600;
|
||||
$minutes = (int)($timeTaken / 60);
|
||||
$timeTaken -= $minutes * 60;
|
||||
$seconds = (int)$timeTaken;
|
||||
$milliseconds = ($timeTaken - $seconds) * 1000;
|
||||
Console::info("Recreate patch completed in $hours h, $minutes m, $seconds s, $milliseconds mis ( total $timeTaken milliseconds)");
|
||||
}
|
||||
|
||||
protected function foreachDocument(Database $database, string $collection, array $queries = [], callable $callback = null): void
|
||||
{
|
||||
$limit = 1000;
|
||||
$results = [];
|
||||
$sum = $limit;
|
||||
$latestDocument = null;
|
||||
|
||||
while ($sum === $limit) {
|
||||
$newQueries = $queries;
|
||||
|
||||
if ($latestDocument != null) {
|
||||
array_unshift($newQueries, Query::cursorAfter($latestDocument));
|
||||
}
|
||||
$newQueries[] = Query::limit($limit);
|
||||
$results = $database->find($collection, $newQueries);
|
||||
|
||||
if (empty($results)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sum = count($results);
|
||||
|
||||
foreach ($results as $document) {
|
||||
if (is_callable($callback)) {
|
||||
$callback($document);
|
||||
}
|
||||
}
|
||||
$latestDocument = $results[array_key_last($results)];
|
||||
}
|
||||
}
|
||||
|
||||
public function recreateRepositories(Database $dbForConsole, Database $dbForProject, Document $project): void
|
||||
{
|
||||
$projectId = $project->getId();
|
||||
Console::log("Running patch for project {$projectId}");
|
||||
|
||||
$this->foreachDocument($dbForProject, 'functions', [], function (Document $function) use ($dbForProject, $dbForConsole, $project) {
|
||||
$isConnected = !empty($function->getAttribute('providerRepositoryId', ''));
|
||||
|
||||
if ($isConnected) {
|
||||
$repository = $dbForConsole->getDocument('repositories', $function->getAttribute('repositoryId', ''));
|
||||
|
||||
if ($repository->isEmpty()) {
|
||||
$projectId = $project->getId();
|
||||
$functionId = $function->getId();
|
||||
Console::success("Recreating repositories document for project ID {$projectId}, function ID {$functionId}");
|
||||
|
||||
$repository = $dbForConsole->createDocument('repositories', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'installationId' => $function->getAttribute('installationId', ''),
|
||||
'installationInternalId' => $function->getAttribute('installationInternalId', ''),
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'providerRepositoryId' => $function->getAttribute('providerRepositoryId', ''),
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceInternalId' => $function->getInternalId(),
|
||||
'resourceType' => 'function',
|
||||
'providerPullRequestIds' => []
|
||||
]));
|
||||
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), $function
|
||||
->setAttribute('repositoryId', $repository->getId())
|
||||
->setAttribute('repositoryInternalId', $repository->getInternalId()));
|
||||
|
||||
$this->foreachDocument($dbForProject, 'deployments', [
|
||||
Query::equal('resourceInternalId', [$function->getInternalId()]),
|
||||
Query::equal('resourceType', ['functions'])
|
||||
], function (Document $deployment) use ($dbForProject, $repository) {
|
||||
$dbForProject->updateDocument('deployments', $deployment->getId(), $deployment
|
||||
->setAttribute('repositoryId', $repository->getId())
|
||||
->setAttribute('repositoryInternalId', $repository->getInternalId()));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -235,6 +235,11 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
'X-Appwrite-Response-Format' => '1.4.0',
|
||||
]);
|
||||
|
||||
// Make sure we have a clean slate.
|
||||
// Otherwise, all files in this dir will be pushed,
|
||||
// regardless of whether they were just generated or not.
|
||||
\exec('rm -rf ' . $result);
|
||||
|
||||
try {
|
||||
$sdk->generate($result);
|
||||
} catch (Exception $exception) {
|
||||
|
||||
@@ -426,29 +426,31 @@ class Certificates extends Action
|
||||
$locale->setDefault('en');
|
||||
}
|
||||
|
||||
$body = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base.tpl');
|
||||
|
||||
$subject = \sprintf($locale->getText("emails.certificate.subject"), $domain);
|
||||
$body
|
||||
->setParam('{{domain}}', $domain)
|
||||
->setParam('{{error}}', $errorMessage)
|
||||
->setParam('{{attempt}}', $attempt)
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.certificate.hello"))
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-inner-base.tpl');
|
||||
$message
|
||||
->setParam('{{body}}', $locale->getText("emails.certificate.body"))
|
||||
->setParam('{{redirect}}', 'https://' . $domain)
|
||||
->setParam('{{hello}}', $locale->getText("emails.certificate.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.certificate.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.certificate.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.certificate.signature"))
|
||||
->setParam('{{project}}', 'Console')
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
->setParam('{{signature}}', $locale->getText("emails.certificate.signature"));
|
||||
$body = $message->render();
|
||||
|
||||
$emailVariables = [
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'domain' => $domain,
|
||||
'error' => '<br><pre>' . $errorMessage . '</pre>',
|
||||
'attempt' => $attempt,
|
||||
'project' => 'Console',
|
||||
'redirect' => 'https://' . $domain,
|
||||
];
|
||||
|
||||
$queueForMails
|
||||
->setRecipient(App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'))
|
||||
->setBody($body->render())
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setVariables($emailVariables)
|
||||
->setName('Appwrite Administrator')
|
||||
->trigger();
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class Deletes extends Action
|
||||
->inject('getFunctionsDevice')
|
||||
->inject('getBuildsDevice')
|
||||
->inject('getCacheDevice')
|
||||
->callback(fn($message, $dbForConsole, callable $getProjectDB, callable $getFilesDevice, callable $getFunctionsDevice, callable $getBuildsDevice, callable $getCacheDevice) => $this->action($message, $dbForConsole, $getProjectDB, $getFilesDevice, $getFunctionsDevice, $getBuildsDevice, $getCacheDevice));
|
||||
->callback(fn ($message, $dbForConsole, callable $getProjectDB, callable $getFilesDevice, callable $getFunctionsDevice, callable $getBuildsDevice, callable $getCacheDevice) => $this->action($message, $dbForConsole, $getProjectDB, $getFilesDevice, $getFunctionsDevice, $getBuildsDevice, $getCacheDevice));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +178,8 @@ class Deletes extends Action
|
||||
$project = $dbForConsole->getDocument('projects', $document->getAttribute('projectId'));
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
Console::warning('Unable to delete schedule for function ' . $document->getAttribute('resourceId'));
|
||||
$dbForConsole->deleteDocument('schedules', $document->getId());
|
||||
Console::success('Deleted schedule for deleted project ' . $document->getAttribute('projectId'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -450,6 +451,21 @@ class Deletes extends Action
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete VCS Installations
|
||||
$this->deleteByGroup('installations', [
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete VCS Repositories
|
||||
$this->deleteByGroup('repositories', [
|
||||
Query::equal('projectInternalId', [$projectInternalId]),
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete VCS commments
|
||||
$this->deleteByGroup('vcsComments', [
|
||||
Query::equal('projectInternalId', [$projectInternalId]),
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete metadata tables
|
||||
try {
|
||||
$dbForProject->deleteCollection('_metadata');
|
||||
@@ -688,7 +704,7 @@ class Deletes extends Action
|
||||
$this->deleteDeploymentFiles($functionsStorage, $document);
|
||||
});
|
||||
|
||||
/**
|
||||
/**
|
||||
* Delete builds
|
||||
*/
|
||||
Console::info("Deleting builds for function " . $functionId);
|
||||
@@ -709,6 +725,23 @@ class Deletes extends Action
|
||||
Query::equal('functionInternalId', [$functionInternalId])
|
||||
], $dbForProject);
|
||||
|
||||
/**
|
||||
* Delete VCS Repositories and VCS Comments
|
||||
*/
|
||||
Console::info("Deleting VCS repositories and comments linked to function " . $functionId);
|
||||
$this->deleteByGroup('repositories', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::equal('resourceInternalId', [$functionInternalId]),
|
||||
Query::equal('resourceType', ['function']),
|
||||
], $dbForConsole, function (Document $document) use ($dbForConsole) {
|
||||
$providerRepositoryId = $document->getAttribute('providerRepositoryId', '');
|
||||
$projectInternalId = $document->getAttribute('projectInternalId', '');
|
||||
$this->deleteByGroup('vcsComments', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::equal('projectInternalId', [$projectInternalId]),
|
||||
], $dbForConsole);
|
||||
});
|
||||
|
||||
/**
|
||||
* Request executor to delete all deployment containers
|
||||
*/
|
||||
@@ -920,17 +953,28 @@ class Deletes extends Action
|
||||
$count = 0;
|
||||
$chunk = 0;
|
||||
$limit = 50;
|
||||
$results = [];
|
||||
$sum = $limit;
|
||||
$cursor = null;
|
||||
|
||||
$executionStart = \microtime(true);
|
||||
|
||||
while ($sum === $limit) {
|
||||
$chunk++;
|
||||
|
||||
$results = $database->find($collection, \array_merge([Query::limit($limit)], $queries));
|
||||
$mergedQueries = \array_merge([Query::limit($limit)], $queries);
|
||||
if ($cursor instanceof Document) {
|
||||
$mergedQueries[] = Query::cursorAfter($cursor);
|
||||
}
|
||||
|
||||
$results = $database->find($collection, $mergedQueries);
|
||||
|
||||
$sum = count($results);
|
||||
|
||||
if ($sum > 0) {
|
||||
$cursor = $results[$sum - 1];
|
||||
}
|
||||
|
||||
foreach ($results as $document) {
|
||||
if (is_callable($callback)) {
|
||||
$callback($document);
|
||||
|
||||
@@ -77,7 +77,7 @@ class Functions extends Action
|
||||
|
||||
$type = $payload['type'] ?? '';
|
||||
$events = $payload['events'] ?? [];
|
||||
$data = $payload['data'] ?? '';
|
||||
$data = $payload['body'] ?? '';
|
||||
$eventData = $payload['payload'] ?? '';
|
||||
$project = new Document($payload['project'] ?? []);
|
||||
$function = new Document($payload['function'] ?? []);
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Event\Hamster as EventHamster;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Utopia\Analytics\Adapter\Mixpanel;
|
||||
use Utopia\Analytics\Event as AnalyticsEvent;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Pools\Group;
|
||||
|
||||
class Hamster extends Action
|
||||
{
|
||||
private array $metrics = [
|
||||
'usage_files' => 'files.$all.count.total',
|
||||
'usage_buckets' => 'buckets.$all.count.total',
|
||||
'usage_databases' => 'databases.$all.count.total',
|
||||
'usage_documents' => 'documents.$all.count.total',
|
||||
'usage_collections' => 'collections.$all.count.total',
|
||||
'usage_storage' => 'project.$all.storage.size',
|
||||
'usage_requests' => 'project.$all.network.requests',
|
||||
'usage_bandwidth' => 'project.$all.network.bandwidth',
|
||||
'usage_users' => 'users.$all.count.total',
|
||||
'usage_sessions' => 'sessions.email.requests.create',
|
||||
'usage_executions' => 'executions.$all.compute.total',
|
||||
];
|
||||
|
||||
protected Mixpanel $mixpanel;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'hamster';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Hamster worker')
|
||||
->inject('message')
|
||||
->inject('pools')
|
||||
->inject('cache')
|
||||
->inject('dbForConsole')
|
||||
->callback(fn (Message $message, Group $pools, Cache $cache, Database $dbForConsole) => $this->action($message, $pools, $cache, $dbForConsole));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @param Group $pools
|
||||
* @param Cache $cache
|
||||
* @param Database $dbForConsole
|
||||
*
|
||||
* @return void
|
||||
* @throws \Utopia\Database\Exception
|
||||
*/
|
||||
public function action(Message $message, Group $pools, Cache $cache, Database $dbForConsole): void
|
||||
{
|
||||
$token = App::getEnv('_APP_MIXPANEL_TOKEN', '');
|
||||
if (empty($token)) {
|
||||
throw new \Exception('Missing MixPanel Token');
|
||||
}
|
||||
$this->mixpanel = new Mixpanel($token);
|
||||
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
if (empty($payload)) {
|
||||
throw new \Exception('Missing payload');
|
||||
}
|
||||
|
||||
$type = $payload['type'] ?? '';
|
||||
|
||||
switch ($type) {
|
||||
case EventHamster::TYPE_PROJECT:
|
||||
$this->getStatsForProject(new Document($payload['project']), $pools, $cache, $dbForConsole);
|
||||
break;
|
||||
case EventHamster::TYPE_ORGANISATION:
|
||||
$this->getStatsForOrganization(new Document($payload['organization']), $dbForConsole);
|
||||
break;
|
||||
case EventHamster::TYPE_USER:
|
||||
$this->getStatsPerUser(new Document($payload['user']), $dbForConsole);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param Group $pools
|
||||
* @param Cache $cache
|
||||
* @param Database $dbForConsole
|
||||
* @throws \Utopia\Database\Exception
|
||||
*/
|
||||
private function getStatsForProject(Document $project, Group $pools, Cache $cache, Database $dbForConsole): void
|
||||
{
|
||||
/**
|
||||
* Skip user projects with id 'console'
|
||||
*/
|
||||
if ($project->getId() === 'console') {
|
||||
Console::info("Skipping project console");
|
||||
return;
|
||||
}
|
||||
|
||||
Console::log("Getting stats for Project {$project->getId()}");
|
||||
|
||||
try {
|
||||
$db = $project->getAttribute('database');
|
||||
$adapter = $pools
|
||||
->get($db)
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$dbForProject = new Database($adapter, $cache);
|
||||
$dbForProject->setDefaultDatabase('appwrite');
|
||||
$dbForProject->setNamespace('_' . $project->getInternalId());
|
||||
|
||||
$statsPerProject = [];
|
||||
|
||||
$statsPerProject['time'] = $project->getAttribute('$time');
|
||||
|
||||
/** Get Project ID */
|
||||
$statsPerProject['project_id'] = $project->getId();
|
||||
|
||||
/** Get project created time */
|
||||
$statsPerProject['project_created'] = $project->getAttribute('$createdAt');
|
||||
|
||||
/** Get Project Name */
|
||||
$statsPerProject['project_name'] = $project->getAttribute('name');
|
||||
|
||||
/** Total Project Variables */
|
||||
$statsPerProject['custom_variables'] = $dbForProject->count('variables', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Total Migrations */
|
||||
$statsPerProject['custom_migrations'] = $dbForProject->count('migrations', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Custom SMTP */
|
||||
$smtp = $project->getAttribute('smtp', null);
|
||||
if ($smtp) {
|
||||
$statsPerProject['custom_smtp_status'] = $smtp['enabled'] === true ? 'enabled' : 'disabled';
|
||||
|
||||
/** Get Custom Templates Count */
|
||||
$templates = array_keys($project->getAttribute('templates', []));
|
||||
$statsPerProject['custom_email_templates'] = array_filter($templates, function ($template) {
|
||||
return str_contains($template, 'email');
|
||||
});
|
||||
$statsPerProject['custom_sms_templates'] = array_filter($templates, function ($template) {
|
||||
return str_contains($template, 'sms');
|
||||
});
|
||||
}
|
||||
|
||||
/** Get total relationship attributes */
|
||||
$statsPerProject['custom_relationship_attributes'] = $dbForProject->count('attributes', [
|
||||
Query::equal('type', ['relationship'])
|
||||
], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Total Functions */
|
||||
$statsPerProject['custom_functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT);
|
||||
|
||||
foreach (\array_keys(Config::getParam('runtimes')) as $runtime) {
|
||||
$statsPerProject['custom_functions_' . $runtime] = $dbForProject->count('functions', [
|
||||
Query::equal('runtime', [$runtime]),
|
||||
], APP_LIMIT_COUNT);
|
||||
}
|
||||
|
||||
/** Get Total Deployments */
|
||||
$statsPerProject['custom_deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT);
|
||||
$statsPerProject['custom_deployments_manual'] = $dbForProject->count('deployments', [
|
||||
Query::equal('type', ['manual'])
|
||||
], APP_LIMIT_COUNT);
|
||||
$statsPerProject['custom_deployments_git'] = $dbForProject->count('deployments', [
|
||||
Query::equal('type', ['vcs'])
|
||||
], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get VCS repos connected */
|
||||
$statsPerProject['custom_vcs_repositories'] = $dbForConsole->count('repositories', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()])
|
||||
], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Total Teams */
|
||||
$statsPerProject['custom_teams'] = $dbForProject->count('teams', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Total Members */
|
||||
$teamInternalId = $project->getAttribute('teamInternalId', null);
|
||||
if ($teamInternalId) {
|
||||
$statsPerProject['custom_organization_members'] = $dbForConsole->count('memberships', [
|
||||
Query::equal('teamInternalId', [$teamInternalId])
|
||||
], APP_LIMIT_COUNT);
|
||||
} else {
|
||||
$statsPerProject['custom_organization_members'] = 0;
|
||||
}
|
||||
|
||||
/** Get Email and Name of the project owner */
|
||||
if ($teamInternalId) {
|
||||
$membership = $dbForConsole->findOne('memberships', [
|
||||
Query::equal('teamInternalId', [$teamInternalId]),
|
||||
]);
|
||||
|
||||
if (!$membership || $membership->isEmpty()) {
|
||||
throw new \Exception('Membership not found. Skipping project : ' . $project->getId());
|
||||
}
|
||||
|
||||
$userId = $membership->getAttribute('userId', null);
|
||||
if ($userId) {
|
||||
$user = $dbForConsole->getDocument('users', $userId);
|
||||
$statsPerProject['email'] = $user->getAttribute('email', null);
|
||||
$statsPerProject['name'] = $user->getAttribute('name', null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Get Domains */
|
||||
$statsPerProject['custom_domains'] = $dbForConsole->count('rules', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
/** Get Platforms */
|
||||
$platforms = $dbForConsole->find('platforms', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
$statsPerProject['custom_platforms_web'] = sizeof(array_filter($platforms, function ($platform) {
|
||||
return $platform['type'] === 'web';
|
||||
}));
|
||||
|
||||
$statsPerProject['custom_platforms_android'] = sizeof(array_filter($platforms, function ($platform) {
|
||||
return $platform['type'] === 'android';
|
||||
}));
|
||||
|
||||
$statsPerProject['custom_platforms_apple'] = sizeof(array_filter($platforms, function ($platform) {
|
||||
return str_contains($platform['type'], 'apple');
|
||||
}));
|
||||
|
||||
$statsPerProject['custom_platforms_flutter'] = sizeof(array_filter($platforms, function ($platform) {
|
||||
return str_contains($platform['type'], 'flutter');
|
||||
}));
|
||||
|
||||
$flutterPlatforms = [Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_FLUTTER_LINUX];
|
||||
|
||||
foreach ($flutterPlatforms as $flutterPlatform) {
|
||||
$statsPerProject['custom_platforms_' . $flutterPlatform] = sizeof(array_filter($platforms, function ($platform) use ($flutterPlatform) {
|
||||
return $platform['type'] === $flutterPlatform;
|
||||
}));
|
||||
}
|
||||
|
||||
$statsPerProject['custom_platforms_api_keys'] = $dbForConsole->count('keys', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
/** Get Usage $statsPerProject */
|
||||
$periods = [
|
||||
'infinity' => [
|
||||
'period' => '1d',
|
||||
'limit' => 90,
|
||||
],
|
||||
'24h' => [
|
||||
'period' => '1h',
|
||||
'limit' => 24,
|
||||
],
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $periods, &$statsPerProject) {
|
||||
foreach ($this->metrics as $key => $metric) {
|
||||
foreach ($periods as $periodKey => $periodValue) {
|
||||
$limit = $periodValue['limit'];
|
||||
$period = $periodValue['period'];
|
||||
|
||||
$requestDocs = $dbForProject->find('stats', [
|
||||
Query::equal('period', [$period]),
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
|
||||
$statsPerProject[$key . '_' . $periodKey] = [];
|
||||
foreach ($requestDocs as $requestDoc) {
|
||||
$statsPerProject[$key . '_' . $periodKey][] = [
|
||||
'value' => $requestDoc->getAttribute('value'),
|
||||
'date' => $requestDoc->getAttribute('time'),
|
||||
];
|
||||
}
|
||||
|
||||
$statsPerProject[$key . '_' . $periodKey] = array_reverse($statsPerProject[$key . '_' . $periodKey]);
|
||||
// Calculate aggregate of each metric
|
||||
$statsPerProject[$key . '_' . $periodKey] = array_sum(array_column($statsPerProject[$key . '_' . $periodKey], 'value'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (isset($statsPerProject['email'])) {
|
||||
/** Send data to mixpanel */
|
||||
$res = $this->mixpanel->createProfile($statsPerProject['email'], '', [
|
||||
'name' => $statsPerProject['name'],
|
||||
'email' => $statsPerProject['email']
|
||||
]);
|
||||
|
||||
if (!$res) {
|
||||
Console::error('Failed to create user profile for project: ' . $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
$event = new AnalyticsEvent();
|
||||
$event
|
||||
->setName('Project Daily Usage')
|
||||
->setProps($statsPerProject);
|
||||
$res = $this->mixpanel->createEvent($event);
|
||||
|
||||
if (!$res) {
|
||||
Console::error('Failed to create event for project: ' . $project->getId());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Console::error('Failed to send stats for project: ' . $project->getId());
|
||||
Console::error($e->getMessage());
|
||||
} finally {
|
||||
$pools
|
||||
->get($db)
|
||||
->reclaim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $organization
|
||||
* @param Database $dbForConsole
|
||||
* @throws \Utopia\Database\Exception
|
||||
*/
|
||||
private function getStatsForOrganization(Document $organization, Database $dbForConsole): void
|
||||
{
|
||||
Console::log("Getting stats for Organization {$organization->getId()}");
|
||||
|
||||
try {
|
||||
$statsPerOrganization = [];
|
||||
|
||||
$statsPerOrganization['time'] = $organization->getAttribute('$time');
|
||||
|
||||
/** Organization name */
|
||||
$statsPerOrganization['name'] = $organization->getAttribute('name');
|
||||
|
||||
|
||||
/** Get Email and of the organization owner */
|
||||
$membership = $dbForConsole->findOne('memberships', [
|
||||
Query::equal('teamInternalId', [$organization->getInternalId()]),
|
||||
]);
|
||||
|
||||
if (!$membership || $membership->isEmpty()) {
|
||||
throw new \Exception('Membership not found. Skipping organization : ' . $organization->getId());
|
||||
}
|
||||
|
||||
$userId = $membership->getAttribute('userId', null);
|
||||
if ($userId) {
|
||||
$user = $dbForConsole->getDocument('users', $userId);
|
||||
$statsPerOrganization['email'] = $user->getAttribute('email', null);
|
||||
}
|
||||
|
||||
/** Organization Creation Date */
|
||||
$statsPerOrganization['created'] = $organization->getAttribute('$createdAt');
|
||||
|
||||
/** Number of team members */
|
||||
$statsPerOrganization['members'] = $organization->getAttribute('total');
|
||||
|
||||
/** Number of projects in this organization */
|
||||
$statsPerOrganization['projects'] = $dbForConsole->count('projects', [
|
||||
Query::equal('teamId', [$organization->getId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
if (!isset($statsPerOrganization['email'])) {
|
||||
throw new \Exception('Email not found. Skipping organization : ' . $organization->getId());
|
||||
}
|
||||
|
||||
$event = new AnalyticsEvent();
|
||||
$event
|
||||
->setName('Organization Daily Usage')
|
||||
->setProps($statsPerOrganization);
|
||||
$res = $this->mixpanel->createEvent($event);
|
||||
if (!$res) {
|
||||
throw new \Exception('Failed to create event for organization : ' . $organization->getId());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function getStatsPerUser(Document $user, Database $dbForConsole)
|
||||
{
|
||||
Console::log("Getting stats for User {$user->getId()}");
|
||||
|
||||
try {
|
||||
$statsPerUser = [];
|
||||
|
||||
$statsPerUser['time'] = $user->getAttribute('$time');
|
||||
|
||||
/** Organization name */
|
||||
$statsPerUser['name'] = $user->getAttribute('name');
|
||||
|
||||
/** Organization ID (needs to be stored as an email since mixpanel uses the email attribute as a distinctID) */
|
||||
$statsPerUser['email'] = $user->getAttribute('email');
|
||||
|
||||
/** Organization Creation Date */
|
||||
$statsPerUser['created'] = $user->getAttribute('$createdAt');
|
||||
|
||||
/** Number of teams this user is a part of */
|
||||
$statsPerUser['memberships'] = $dbForConsole->count('memberships', [
|
||||
Query::equal('userInternalId', [$user->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
if (!isset($statsPerUser['email'])) {
|
||||
throw new \Exception('User has no email: ' . $user->getId());
|
||||
}
|
||||
|
||||
/** Send data to mixpanel */
|
||||
$event = new AnalyticsEvent();
|
||||
$event
|
||||
->setName('User Daily Usage')
|
||||
->setProps($statsPerUser);
|
||||
|
||||
$res = $this->mixpanel->createEvent($event);
|
||||
|
||||
if (!$res) {
|
||||
throw new \Exception('Failed to create user profile for user: ' . $user->getId());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,13 +58,21 @@ class Mails extends Action
|
||||
$subject = $payload['subject'];
|
||||
$variables = $payload['variables'];
|
||||
$name = $payload['name'];
|
||||
$body = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base.tpl');
|
||||
$body = $payload['body'];
|
||||
|
||||
$bodyTemplate = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base.tpl');
|
||||
$bodyTemplate->setParam('{{body}}', $body);
|
||||
foreach ($variables as $key => $value) {
|
||||
$body->setParam('{{' . $key . '}}', $value);
|
||||
$bodyTemplate->setParam('{{' . $key . '}}', $value);
|
||||
}
|
||||
$body = $bodyTemplate->render();
|
||||
|
||||
$body = $body->render();
|
||||
$subjectTemplate = Template::fromString($subject);
|
||||
foreach ($variables as $key => $value) {
|
||||
$subjectTemplate->setParam('{{' . $key . '}}', $value);
|
||||
}
|
||||
// render() will return the subject in <p> tags, so use strip_tags() to remove them
|
||||
$subject = \strip_tags($subjectTemplate->render());
|
||||
|
||||
/** @var PHPMailer $mail */
|
||||
$mail = empty($smtp)
|
||||
|
||||
@@ -25,7 +25,7 @@ class Webhooks extends Action
|
||||
$this
|
||||
->desc('Webhooks worker')
|
||||
->inject('message')
|
||||
->callback(fn($message) => $this->action($message));
|
||||
->callback(fn ($message) => $this->action($message));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +48,7 @@ class Webhooks extends Action
|
||||
|
||||
foreach ($project->getAttribute('webhooks', []) as $webhook) {
|
||||
if (array_intersect($webhook->getAttribute('events', []), $events)) {
|
||||
$this->execute($events, $webhookPayload, $webhook, $user, $project);
|
||||
$this->execute($events, $webhookPayload, $webhook, $user, $project);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,9 @@ class Webhooks extends Action
|
||||
\curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
\curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
\curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
|
||||
\curl_setopt($ch, CURLOPT_TIMEOUT, 15);
|
||||
\curl_setopt($ch, CURLOPT_MAXFILESIZE, 5242880);
|
||||
\curl_setopt($ch, CURLOPT_USERAGENT, \sprintf(
|
||||
APP_USERAGENT,
|
||||
App::getEnv('_APP_VERSION', 'UNKNOWN'),
|
||||
@@ -88,16 +90,17 @@ class Webhooks extends Action
|
||||
$ch,
|
||||
CURLOPT_HTTPHEADER,
|
||||
[
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . \strlen($payload),
|
||||
'X-' . APP_NAME . '-Webhook-Id: ' . $webhook->getId(),
|
||||
'X-' . APP_NAME . '-Webhook-Events: ' . implode(',', $events),
|
||||
'X-' . APP_NAME . '-Webhook-Name: ' . $webhook->getAttribute('name', ''),
|
||||
'X-' . APP_NAME . '-Webhook-User-Id: ' . $user->getId(),
|
||||
'X-' . APP_NAME . '-Webhook-Project-Id: ' . $project->getId(),
|
||||
'X-' . APP_NAME . '-Webhook-Signature: ' . $signature,
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . \strlen($payload),
|
||||
'X-' . APP_NAME . '-Webhook-Id: ' . $webhook->getId(),
|
||||
'X-' . APP_NAME . '-Webhook-Events: ' . implode(',', $events),
|
||||
'X-' . APP_NAME . '-Webhook-Name: ' . $webhook->getAttribute('name', ''),
|
||||
'X-' . APP_NAME . '-Webhook-User-Id: ' . $user->getId(),
|
||||
'X-' . APP_NAME . '-Webhook-Project-Id: ' . $project->getId(),
|
||||
'X-' . APP_NAME . '-Webhook-Signature: ' . $signature,
|
||||
]
|
||||
);
|
||||
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
|
||||
|
||||
if (!$webhook->getAttribute('security', true)) {
|
||||
\curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
|
||||
@@ -8,6 +8,7 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use InfluxDB\Database as InfluxDatabase;
|
||||
use DateTime;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Registry\Registry;
|
||||
|
||||
class TimeSeries extends Calculator
|
||||
@@ -426,32 +427,34 @@ class TimeSeries extends Calculator
|
||||
$project = $this->database->getDocument('projects', $projectId);
|
||||
$database = call_user_func($this->getProjectDB, $project);
|
||||
|
||||
try {
|
||||
$document = $database->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$database->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'period' => $period,
|
||||
'time' => $time,
|
||||
'metric' => $metric,
|
||||
'value' => $value,
|
||||
'type' => $type,
|
||||
'region' => $this->region,
|
||||
]));
|
||||
} else {
|
||||
$database->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $value)
|
||||
);
|
||||
Authorization::skip(function () use ($database, $id, $period, $time, $metric, $value, $type, $projectId) {
|
||||
try {
|
||||
$document = $database->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$database->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'period' => $period,
|
||||
'time' => $time,
|
||||
'metric' => $metric,
|
||||
'value' => $value,
|
||||
'type' => $type,
|
||||
'region' => $this->region,
|
||||
]));
|
||||
} else {
|
||||
$database->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $value)
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) { // if projects are deleted this might fail
|
||||
if (is_callable($this->errorHandler)) {
|
||||
call_user_func($this->errorHandler, $e, "sync_project_{$projectId}_metric_{$metric}");
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) { // if projects are deleted this might fail
|
||||
if (is_callable($this->errorHandler)) {
|
||||
call_user_func($this->errorHandler, $e, "sync_project_{$projectId}_metric_{$metric}");
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$this->register->get('pools')->reclaim();
|
||||
}
|
||||
|
||||
@@ -95,4 +95,46 @@ class Request extends UtopiaRequest
|
||||
{
|
||||
return self::$route != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get headers
|
||||
*
|
||||
* Method for getting all HTTP header parameters, including cookies.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
$headers = $this->generateHeaders();
|
||||
|
||||
if (empty($this->swoole->cookie)) {
|
||||
return $headers;
|
||||
}
|
||||
|
||||
$cookieHeaders = [];
|
||||
foreach ($this->swoole->cookie as $key => $value) {
|
||||
$cookieHeaders[] = "{$key}={$value}";
|
||||
}
|
||||
|
||||
if (!empty($cookieHeaders)) {
|
||||
$headers['cookie'] = \implode('; ', $cookieHeaders);
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header
|
||||
*
|
||||
* Method for querying HTTP header parameters. If $key is not found $default value will be returned.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $default
|
||||
* @return string
|
||||
*/
|
||||
public function getHeader(string $key, string $default = ''): string
|
||||
{
|
||||
$headers = $this->getHeaders();
|
||||
return $headers[$key] ?? $default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,8 @@ trait ProjectCustom
|
||||
'locale.read',
|
||||
'avatars.read',
|
||||
'health.read',
|
||||
'rules.read',
|
||||
'rules.write'
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
@@ -945,6 +945,32 @@ trait AccountBase
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateAccountVerification
|
||||
*/
|
||||
public function testCreateAccountVerificationForVerifiedEmail($data): array
|
||||
{
|
||||
$email = $data['email'] ?? '';
|
||||
$name = $data['name'] ?? '';
|
||||
$session = $data['session'] ?? '';
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
'url' => 'http://localhost/verification',
|
||||
]);
|
||||
|
||||
$this->assertEquals(409, $response['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateAccountVerification
|
||||
*/
|
||||
|
||||
@@ -1068,4 +1068,27 @@ class AccountCustomClientTest extends Scope
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testPhoneVerification
|
||||
*/
|
||||
#[Retry(count: 1)]
|
||||
public function testPhoneVerificationForVerifiedPhone(array $data): array
|
||||
{
|
||||
$session = $data['session'] ?? '';
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/verification/phone', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]));
|
||||
|
||||
$this->assertEquals(409, $response['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -978,7 +978,7 @@ trait DatabasesBase
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $badEnum['headers']['status-code']);
|
||||
$this->assertEquals('Each enum element must not be empty', $badEnum['body']['message']);
|
||||
$this->assertEquals('Invalid `elements` param: Value must a valid array and Value must be a valid string and at least 1 chars and no longer than 255 chars', $badEnum['body']['message']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -1344,4 +1344,192 @@ class FunctionsCustomServerTest extends Scope
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCookieExecution()
|
||||
{
|
||||
$timeout = 5;
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php-cookie/code.tar.gz";
|
||||
$this->packageCode('php-cookie');
|
||||
|
||||
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test PHP Cookie executions',
|
||||
'runtime' => 'php-8.0',
|
||||
'entrypoint' => 'index.php',
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
$this->assertEquals(202, $deployment['headers']['status-code']);
|
||||
|
||||
// Poll until deployment is built
|
||||
while (true) {
|
||||
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
if (
|
||||
$deployment['headers']['status-code'] >= 400
|
||||
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
\sleep(1);
|
||||
}
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $deployment['headers']['status-code']);
|
||||
|
||||
// Wait a little for activation to finish
|
||||
sleep(5);
|
||||
|
||||
$cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5';
|
||||
|
||||
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'async' => false,
|
||||
'headers' => [
|
||||
'cookie' => $cookie
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $execution['headers']['status-code']);
|
||||
$this->assertEquals('completed', $execution['body']['status']);
|
||||
$this->assertEquals(200, $execution['body']['responseStatusCode']);
|
||||
$this->assertEquals($cookie, $execution['body']['responseBody']);
|
||||
|
||||
// Cleanup : Delete function
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testFunctionsDomain()
|
||||
{
|
||||
$timeout = 5;
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php-cookie/code.tar.gz";
|
||||
$this->packageCode('php-cookie');
|
||||
|
||||
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test PHP Cookie executions',
|
||||
'runtime' => 'php-8.0',
|
||||
'entrypoint' => 'index.php',
|
||||
'timeout' => $timeout,
|
||||
'execute' => ['any']
|
||||
]);
|
||||
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
|
||||
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [ 'equal("resourceId", "' . $functionId . '")', 'equal("resourceType", "function")' ]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(1, $rules['body']['total']);
|
||||
$this->assertCount(1, $rules['body']['rules']);
|
||||
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
|
||||
|
||||
$domain = $rules['body']['rules'][0]['domain'];
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
$this->assertEquals(202, $deployment['headers']['status-code']);
|
||||
|
||||
// Poll until deployment is built
|
||||
while (true) {
|
||||
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
if (
|
||||
$deployment['headers']['status-code'] >= 400
|
||||
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
\sleep(1);
|
||||
}
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $deployment['headers']['status-code']);
|
||||
|
||||
// Wait a little for activation to finish
|
||||
sleep(5);
|
||||
|
||||
$cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5';
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => $cookie
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($cookie, $response['body']);
|
||||
|
||||
// Cleanup : Delete function
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Utopia\CLI\Console;
|
||||
|
||||
trait Base
|
||||
{
|
||||
// Databases
|
||||
@@ -1933,4 +1935,13 @@ trait Base
|
||||
|
||||
throw new \InvalidArgumentException('Invalid query type');
|
||||
}
|
||||
|
||||
// Function-related methods
|
||||
protected string $stdout = '';
|
||||
protected string $stderr = '';
|
||||
|
||||
protected function packageCode($folder)
|
||||
{
|
||||
Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,11 @@ class FunctionsClientTest extends Scope
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php/code.tar.gz";
|
||||
|
||||
$folder = 'php';
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
|
||||
$this->packageCode($folder);
|
||||
|
||||
$gqlPayload = [
|
||||
'operations' => \json_encode([
|
||||
'query' => $query,
|
||||
|
||||
@@ -81,7 +81,11 @@ class FunctionsServerTest extends Scope
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php/code.tar.gz";
|
||||
|
||||
$folder = 'php';
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
|
||||
$this->packageCode($folder);
|
||||
|
||||
$gqlPayload = [
|
||||
'operations' => \json_encode([
|
||||
'query' => $query,
|
||||
|
||||
@@ -138,6 +138,15 @@ class HealthCustomServerTest extends Scope
|
||||
$this->assertIsInt($response['body']['size']);
|
||||
$this->assertLessThan(100, $response['body']['size']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/queue/webhooks?threshold=0', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
$this->assertEquals(503, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -155,6 +164,15 @@ class HealthCustomServerTest extends Scope
|
||||
$this->assertIsInt($response['body']['size']);
|
||||
$this->assertLessThan(100, $response['body']['size']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/queue/logs?threshold=0', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
$this->assertEquals(503, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -172,6 +190,15 @@ class HealthCustomServerTest extends Scope
|
||||
$this->assertIsInt($response['body']['size']);
|
||||
$this->assertLessThan(100, $response['body']['size']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/queue/certificates?threshold=0', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
$this->assertEquals(503, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -189,6 +216,15 @@ class HealthCustomServerTest extends Scope
|
||||
$this->assertIsInt($response['body']['size']);
|
||||
$this->assertLessThan(100, $response['body']['size']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/queue/functions?threshold=0', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
$this->assertEquals(503, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -206,6 +242,15 @@ class HealthCustomServerTest extends Scope
|
||||
$this->assertIsInt($response['body']['size']);
|
||||
$this->assertLessThan(100, $response['body']['size']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/queue/builds?threshold=0', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
$this->assertEquals(503, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -225,6 +270,18 @@ class HealthCustomServerTest extends Scope
|
||||
$this->assertIsInt($response['body']['size']);
|
||||
$this->assertLessThan(100, $response['body']['size']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/queue/databases', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'database_db_main',
|
||||
'threshold' => '0'
|
||||
]);
|
||||
$this->assertEquals(503, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -242,6 +299,15 @@ class HealthCustomServerTest extends Scope
|
||||
$this->assertIsInt($response['body']['size']);
|
||||
$this->assertLessThan(100, $response['body']['size']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/queue/deletes?threshold=0', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
$this->assertEquals(503, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -259,6 +325,15 @@ class HealthCustomServerTest extends Scope
|
||||
$this->assertIsInt($response['body']['size']);
|
||||
$this->assertLessThan(100, $response['body']['size']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/queue/mails?threshold=0', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
$this->assertEquals(503, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -276,6 +351,15 @@ class HealthCustomServerTest extends Scope
|
||||
$this->assertIsInt($response['body']['size']);
|
||||
$this->assertLessThan(100, $response['body']['size']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/queue/messaging?threshold=0', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
$this->assertEquals(503, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -293,6 +377,15 @@ class HealthCustomServerTest extends Scope
|
||||
$this->assertIsInt($response['body']['size']);
|
||||
$this->assertLessThan(100, $response['body']['size']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/queue/migrations?threshold=0', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
$this->assertEquals(503, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
@@ -1606,6 +1606,100 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(false, $response['body']['authPersonalDataCheck']);
|
||||
}
|
||||
|
||||
public function testUpdateProjectServicesAll(): void
|
||||
{
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]), [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Project Test',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $team['headers']['status-code']);
|
||||
$this->assertNotEmpty($team['body']['$id']);
|
||||
|
||||
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]), [
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'Project Test',
|
||||
'teamId' => $team['body']['$id'],
|
||||
'region' => 'default'
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $project['headers']['status-code']);
|
||||
$this->assertNotEmpty($project['body']['$id']);
|
||||
|
||||
$id = $project['body']['$id'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/service/all', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]), [
|
||||
'status' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
|
||||
$matches = [];
|
||||
$pattern = '/serviceStatusFor.*/';
|
||||
|
||||
foreach ($response['body'] as $key => $value) {
|
||||
if (\preg_match($pattern, $key)) {
|
||||
\var_dump('Matched key: ' . $key);
|
||||
$matches[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($matches as $value) {
|
||||
$this->assertFalse($value);
|
||||
}
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/service/all', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]), [
|
||||
'status' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$matches = [];
|
||||
foreach ($response['body'] as $key => $value) {
|
||||
if (\preg_match($pattern, $key)) {
|
||||
$matches[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($matches as $value) {
|
||||
$this->assertTrue($value);
|
||||
}
|
||||
}
|
||||
|
||||
public function testUpdateProjectServiceStatusAdmin(): array
|
||||
{
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\VCS;
|
||||
|
||||
use Utopia\App;
|
||||
|
||||
trait VCSBase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY') === 'disabled') {
|
||||
$this->markTestSkipped('VCS is not enabled.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\VCS;
|
||||
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\Cache\Adapter\None;
|
||||
use Utopia\Cache\Cache;
|
||||
|
||||
class VCSConsoleClientTest extends Scope
|
||||
{
|
||||
use VCSBase;
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
|
||||
public string $providerInstallationId = '42954928';
|
||||
public string $providerRepositoryId = '705764267';
|
||||
public string $providerRepositoryId2 = '708688544';
|
||||
|
||||
public function testGitHubAuthorize(): string
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/mock/github/callback', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'providerInstallationId' => $this->providerInstallationId,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($response['body']['installationId']);
|
||||
$installationId = $response['body']['installationId'];
|
||||
return $installationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testGetInstallation(string $installationId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$installation = $this->client->call(Client::METHOD_GET, '/vcs/installations/' . $installationId, array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $installation['headers']['status-code']);
|
||||
$this->assertEquals('github', $installation['body']['provider']);
|
||||
$this->assertEquals('appwrite-test', $installation['body']['organization']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testDetectRuntime(string $installationId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/detection', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $runtime['headers']['status-code']);
|
||||
$this->assertEquals($runtime['body']['runtime'], 'ruby-3.1');
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories/randomRepositoryId/detection', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(404, $runtime['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testListRepositories(string $installationId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $repositories['headers']['status-code']);
|
||||
$this->assertEquals($repositories['body']['total'], 3);
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][0]['name'], 'function1.4');
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][0]['organization'], 'appwrite-test');
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][0]['provider'], 'github');
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][1]['name'], 'appwrite');
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][2]['name'], 'ruby-starter');
|
||||
|
||||
|
||||
$searchedRepositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'search' => 'func'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $searchedRepositories['headers']['status-code']);
|
||||
$this->assertEquals($searchedRepositories['body']['total'], 1);
|
||||
$this->assertEquals($searchedRepositories['body']['providerRepositories'][0]['name'], 'function1.4');
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/randomInstallationId/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(404, $repositories['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testGetRepository(string $installationId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$repository = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId, array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $repository['headers']['status-code']);
|
||||
$this->assertEquals($repository['body']['name'], 'ruby-starter');
|
||||
$this->assertEquals($repository['body']['organization'], 'appwrite-test');
|
||||
$this->assertEquals($repository['body']['private'], false);
|
||||
|
||||
$repository = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId2, array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $repository['headers']['status-code']);
|
||||
$this->assertEquals($repository['body']['name'], 'function1.4');
|
||||
$this->assertEquals($repository['body']['organization'], 'appwrite-test');
|
||||
$this->assertEquals($repository['body']['private'], true);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$repository = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/randomRepositoryId', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(404, $repository['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testListRepositoryBranches(string $installationId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $repositoryBranches['headers']['status-code']);
|
||||
$this->assertEquals($repositoryBranches['body']['total'], 2);
|
||||
$this->assertEquals($repositoryBranches['body']['branches'][0]['name'], 'main');
|
||||
$this->assertEquals($repositoryBranches['body']['branches'][1]['name'], 'test');
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/randomRepositoryId/branches', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(404, $repositoryBranches['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testCreateFunctionUsingVCS(string $installationId): array
|
||||
{
|
||||
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'execute' => [Role::user($this->getUser()['$id'])->toString()],
|
||||
'runtime' => 'php-8.0',
|
||||
'entrypoint' => 'index.php',
|
||||
'events' => [
|
||||
'users.*.create',
|
||||
'users.*.delete',
|
||||
],
|
||||
'schedule' => '0 0 1 1 *',
|
||||
'timeout' => 10,
|
||||
'installationId' => $installationId,
|
||||
'providerRepositoryId' => $this->providerRepositoryId,
|
||||
'providerBranch' => 'main',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
$this->assertEquals('Test', $function['body']['name']);
|
||||
$this->assertEquals('php-8.0', $function['body']['runtime']);
|
||||
$this->assertEquals('index.php', $function['body']['entrypoint']);
|
||||
$this->assertEquals('705764267', $function['body']['providerRepositoryId']);
|
||||
$this->assertEquals('main', $function['body']['providerBranch']);
|
||||
|
||||
return [
|
||||
'installationId' => $installationId,
|
||||
'functionId' => $function['body']['$id']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunctionUsingVCS
|
||||
*/
|
||||
public function testUpdateFunctionUsingVCS(array $data): string
|
||||
{
|
||||
$function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'execute' => [Role::user($this->getUser()['$id'])->toString()],
|
||||
'runtime' => 'php-8.0',
|
||||
'entrypoint' => 'index.php',
|
||||
'events' => [
|
||||
'users.*.create',
|
||||
'users.*.delete',
|
||||
],
|
||||
'schedule' => '0 0 1 1 *',
|
||||
'timeout' => 10,
|
||||
'installationId' => $data['installationId'],
|
||||
'providerRepositoryId' => $this->providerRepositoryId2,
|
||||
'providerBranch' => 'main',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertEquals('Test', $function['body']['name']);
|
||||
$this->assertEquals('php-8.0', $function['body']['runtime']);
|
||||
$this->assertEquals('index.php', $function['body']['entrypoint']);
|
||||
$this->assertEquals('708688544', $function['body']['providerRepositoryId']);
|
||||
$this->assertEquals('main', $function['body']['providerBranch']);
|
||||
|
||||
return $function['body']['$id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testCreateRepository(string $installationId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$github = new GitHub(new Cache(new None()));
|
||||
$privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($this->providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
$repository = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'test-repo-1',
|
||||
'private' => true
|
||||
]);
|
||||
|
||||
$this->assertEquals('test-repo-1', $repository['body']['name']);
|
||||
$this->assertEquals('appwrite-test', $repository['body']['organization']);
|
||||
$this->assertEquals('github', $repository['body']['provider']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$repository = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'test-repo-1',
|
||||
'private' => true
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $repository['headers']['status-code']);
|
||||
$this->assertEquals('Provider Error: Repository creation failed. name already exists on this account', $repository['body']['message']);
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$result = $github->deleteRepository('appwrite-test', 'test-repo-1');
|
||||
$this->assertEquals($result, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\VCS;
|
||||
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Tests\E2E\Client;
|
||||
|
||||
class VCSCustomClientTest extends Scope
|
||||
{
|
||||
use VCSBase;
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
|
||||
public function testGetInstallation(string $installationId = 'randomString'): void
|
||||
{
|
||||
$installation = $this->client->call(Client::METHOD_GET, '/vcs/installations/' . $installationId, array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(401, $installation['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user