mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
409
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d89f8cc6b9 | ||
|
|
c5316681ca | ||
|
|
ccfe363905 | ||
|
|
e3ed9b43f4 | ||
|
|
f97f24eb16 | ||
|
|
92716ed72e | ||
|
|
43b8030f10 | ||
|
|
fd81c95e38 | ||
|
|
487d93bf04 | ||
|
|
d1a7d8f54a | ||
|
|
f95800de73 | ||
|
|
3f92987002 | ||
|
|
ff8ebb4f7e | ||
|
|
114a450f26 | ||
|
|
143b73deef | ||
|
|
7cff879e58 | ||
|
|
11fdefa2b8 | ||
|
|
8cad8f5533 | ||
|
|
d6be999c12 | ||
|
|
3859f037a1 | ||
|
|
9a3f6e7f71 | ||
|
|
b50ec49ac1 | ||
|
|
388f450d89 | ||
|
|
4bef6125f7 | ||
|
|
fefb60557d | ||
|
|
730e7319df | ||
|
|
0e5f6a34b0 | ||
|
|
a0051eaa81 | ||
|
|
c5c0c8a8e6 | ||
|
|
7b66cc5672 | ||
|
|
899bba64d1 | ||
|
|
fc4cbb8174 | ||
|
|
be761dd5cd | ||
|
|
293d85525c | ||
|
|
9f7ecb98d3 | ||
|
|
6d5b087605 | ||
|
|
048b4f76c8 | ||
|
|
6ff50103ac | ||
|
|
ccf437dac4 | ||
|
|
2452d3a479 | ||
|
|
479ed3d0b1 | ||
|
|
8974dcfb39 | ||
|
|
47213c50b6 | ||
|
|
f66a65f27b | ||
|
|
aa92257c76 | ||
|
|
008c3ac19a | ||
|
|
a792f3de1d | ||
|
|
26db6fa4c5 | ||
|
|
7c691b3c7c | ||
|
|
e3a039db49 | ||
|
|
7a25a3c529 | ||
|
|
76eacaf8e2 | ||
|
|
026f5dde2d | ||
|
|
39da1b8028 | ||
|
|
f875c6aa70 | ||
|
|
3469ebf7b0 | ||
|
|
7f038ec704 | ||
|
|
532ed5b340 | ||
|
|
4e08c6d02d | ||
|
|
e7e85bfb76 | ||
|
|
a3a7763340 | ||
|
|
ec930bf125 | ||
|
|
eb8543c690 | ||
|
|
75d8cc3327 | ||
|
|
5ee5ffb288 | ||
|
|
f8c44c8a50 | ||
|
|
803ba6182c | ||
|
|
5275d0b021 | ||
|
|
3d53249335 | ||
|
|
5ff5c930a5 | ||
|
|
5657e7d04e | ||
|
|
9adf124274 | ||
|
|
20e82b1b8b | ||
|
|
7d68c12f1b | ||
|
|
b3c2b54d09 | ||
|
|
061368fae0 | ||
|
|
f2dbda10c0 | ||
|
|
380b5f7f84 | ||
|
|
fe4362c29f | ||
|
|
47f442b98e | ||
|
|
ac397935a1 | ||
|
|
fcfff2ab04 | ||
|
|
440d924518 | ||
|
|
2ae9d0b710 | ||
|
|
446ebfffee | ||
|
|
f824c3c83e | ||
|
|
a2d8e6d44e | ||
|
|
eeba452fb5 | ||
|
|
5e6c94d307 | ||
|
|
a306cb87d8 | ||
|
|
979f6ee150 | ||
|
|
21d4b8feef | ||
|
|
2a6da11842 | ||
|
|
c849ff0b0f | ||
|
|
a75ce14218 | ||
|
|
795f1035ef | ||
|
|
c5512ae010 | ||
|
|
05221334e2 | ||
|
|
4fcb399867 | ||
|
|
24319b8417 | ||
|
|
17d85a478a | ||
|
|
f2cdd9cc1d | ||
|
|
310aa56e5d | ||
|
|
5c0df92346 | ||
|
|
a4bdf63afd | ||
|
|
f356f13ee6 | ||
|
|
993c9df840 | ||
|
|
b4c065a342 | ||
|
|
267f6bc752 | ||
|
|
a1d86c9b3f | ||
|
|
550837b572 | ||
|
|
22b3ea7e8d | ||
|
|
cea4e0d8b6 | ||
|
|
f482d96225 | ||
|
|
9c100758de | ||
|
|
15a6c510a0 | ||
|
|
b409efc492 | ||
|
|
54a02fbb96 | ||
|
|
dfac3d585a | ||
|
|
05f73fbe46 | ||
|
|
ed62a0ca4a | ||
|
|
57d7995e36 | ||
|
|
5e6f53d6aa | ||
|
|
5f4970befd | ||
|
|
060ec455b3 | ||
|
|
86dc85f3a4 | ||
|
|
064ac2ccf8 | ||
|
|
9e4250ad9f | ||
|
|
fc498fd80f | ||
|
|
9ea34ce068 | ||
|
|
45092a1049 | ||
|
|
868bba52aa | ||
|
|
0b4716721d | ||
|
|
2ffcb9b07e | ||
|
|
d91f6960f7 | ||
|
|
3542d360b0 | ||
|
|
9a821e250f | ||
|
|
d1715e3bf4 | ||
|
|
c8a3b709ee | ||
|
|
fed197a313 | ||
|
|
ec85ec9f2a | ||
|
|
1a59361200 | ||
|
|
658396b4a8 | ||
|
|
53438accce | ||
|
|
84f47dc1d2 | ||
|
|
4e2120952f | ||
|
|
1538f78c18 | ||
|
|
6acf6adf99 | ||
|
|
ac5d45d71c | ||
|
|
af3d6b2308 | ||
|
|
260928942b | ||
|
|
bbcb5e8a6e | ||
|
|
03d6eb7401 | ||
|
|
821e323a40 | ||
|
|
cab2233816 | ||
|
|
3a8c5a2e65 | ||
|
|
faeb9aa8d4 | ||
|
|
92dfe93033 | ||
|
|
3a524c24d9 | ||
|
|
b78725e650 | ||
|
|
e7a847a417 | ||
|
|
0ad44ceb94 | ||
|
|
9fe0a5a31b | ||
|
|
07f7271c51 | ||
|
|
d91a0e3048 | ||
|
|
ea484e593b | ||
|
|
ca1c83c2cf | ||
|
|
8c4473932f | ||
|
|
d5c24df28d | ||
|
|
141cdf4c82 | ||
|
|
f79a310965 | ||
|
|
65d99f8f90 | ||
|
|
c04f54da64 | ||
|
|
17888cc02d | ||
|
|
d2787c8555 | ||
|
|
42ca924557 | ||
|
|
a8098831a3 | ||
|
|
3f5e40ad46 | ||
|
|
10d882ce50 | ||
|
|
7a5d64a85f | ||
|
|
b03783c588 | ||
|
|
6567cff5dc | ||
|
|
db5c44c843 | ||
|
|
b2e1c162d2 | ||
|
|
e3c261b9e4 | ||
|
|
0dd2889de5 | ||
|
|
cc8705253e | ||
|
|
14e81c2e27 | ||
|
|
d60e7c641f | ||
|
|
f287e60e87 | ||
|
|
3f7d35065e | ||
|
|
abc67ab0c6 | ||
|
|
c2ae330888 | ||
|
|
1577be9172 | ||
|
|
22e7c58d7d | ||
|
|
505b481537 | ||
|
|
2ab6156db4 | ||
|
|
0f7c73c1ab | ||
|
|
928ae74ffe | ||
|
|
e4a86d9777 | ||
|
|
44a9aed48e | ||
|
|
ad78259c10 | ||
|
|
2c4a9e5ea9 | ||
|
|
3b1233eaa9 | ||
|
|
2a5df03c89 | ||
|
|
c3ddc6a05d | ||
|
|
db792999c1 | ||
|
|
ed4319314a | ||
|
|
2a5f21e982 | ||
|
|
7dbb1c6399 | ||
|
|
00db19257d | ||
|
|
f0a4cf78ab | ||
|
|
dae01f7077 | ||
|
|
26af9b7463 | ||
|
|
459db9b43e | ||
|
|
207811a224 | ||
|
|
84e4748625 | ||
|
|
7d38860d16 | ||
|
|
e1e5f5d96b | ||
|
|
219b28e9bf | ||
|
|
3476ab2627 | ||
|
|
cbf1085850 | ||
|
|
dac0e2a543 | ||
|
|
96317f0acb | ||
|
|
a0e82ccfab | ||
|
|
8c44627da1 | ||
|
|
c076dfe6f1 | ||
|
|
f5aebe6673 | ||
|
|
70effd1e99 | ||
|
|
e7ace61273 | ||
|
|
6b6f04d2d3 | ||
|
|
be761b31c1 | ||
|
|
eb75b527ad | ||
|
|
d4ad369a7e | ||
|
|
eb7e2fd23b | ||
|
|
36eabfa2dd | ||
|
|
2634c928c7 | ||
|
|
e462d73f65 | ||
|
|
e2baefe8c6 | ||
|
|
4ffde8e827 | ||
|
|
57a695367b | ||
|
|
681688e252 | ||
|
|
a8718bccb5 | ||
|
|
eba03cb126 | ||
|
|
622bdb7b2b | ||
|
|
c8720805c7 | ||
|
|
fdb11af6bb | ||
|
|
b09386727b | ||
|
|
f3e8994bb8 | ||
|
|
fb68dcc89e | ||
|
|
5abcb86fd7 | ||
|
|
ff93957922 | ||
|
|
179a045399 | ||
|
|
d0985db27d | ||
|
|
3788d5bceb | ||
|
|
f785ffbd71 | ||
|
|
c1fb4ecf39 | ||
|
|
e6a97bb36a | ||
|
|
a1539395c6 | ||
|
|
94468aa28f | ||
|
|
72ee7d7f34 | ||
|
|
8d7be399d3 | ||
|
|
f42a78ef9c | ||
|
|
5842f1950a | ||
|
|
ba4f708137 | ||
|
|
2fdd0656d0 | ||
|
|
790cdc297b | ||
|
|
990698e423 | ||
|
|
632f78140f | ||
|
|
243ddd4b33 | ||
|
|
abb1007385 | ||
|
|
93a99b561c | ||
|
|
a89b73d20b | ||
|
|
d5216d5b14 | ||
|
|
939aec866e | ||
|
|
6a5defb91c | ||
|
|
2f93dcc4df | ||
|
|
603b8ebd8c | ||
|
|
4dee937e36 | ||
|
|
bb92074093 | ||
|
|
a7e95588c6 | ||
|
|
3a5c233b59 | ||
|
|
8154b4c131 | ||
|
|
d4afe22396 | ||
|
|
fb127270ce | ||
|
|
11ceeff5ca | ||
|
|
8ed5489d9c | ||
|
|
1fefb7e133 | ||
|
|
6a6c3445fa | ||
|
|
6fe725ff9d | ||
|
|
4115537f72 | ||
|
|
26b2c27eb0 | ||
|
|
62bc540db0 | ||
|
|
1ef5c9bfcd | ||
|
|
ab80999d1e | ||
|
|
be829e2db4 | ||
|
|
bfe042dc3e | ||
|
|
5518ca165e | ||
|
|
13a879c274 | ||
|
|
7b5be4e154 | ||
|
|
3c7fae7843 | ||
|
|
44ed6826a9 | ||
|
|
36fddd4156 | ||
|
|
7a2ee683e3 | ||
|
|
359b3326d9 | ||
|
|
7a94bd143c | ||
|
|
9b7a5f55b7 | ||
|
|
e5dfed0aa3 | ||
|
|
215a139e87 | ||
|
|
64fde6cddd | ||
|
|
370704fafd | ||
|
|
6629fdd87f | ||
|
|
554bc89cb5 | ||
|
|
b08e3d4e7b | ||
|
|
90cf28389c | ||
|
|
8581df9c14 | ||
|
|
de96e0f3dc | ||
|
|
6ad63b6f7b | ||
|
|
4b23440c2d | ||
|
|
85950868f2 | ||
|
|
eb70990bf0 | ||
|
|
880526d6d3 | ||
|
|
1da476e5ac | ||
|
|
03f2041913 | ||
|
|
6b1330e400 | ||
|
|
faf39fcc81 | ||
|
|
06e385b346 | ||
|
|
7a2f7f5a17 | ||
|
|
671b1624d5 | ||
|
|
cc1272f80b | ||
|
|
9b2fbc5c52 | ||
|
|
9960912bb1 | ||
|
|
1869852985 | ||
|
|
336afc5ca2 | ||
|
|
33f92d9ab3 | ||
|
|
61e2b7a787 | ||
|
|
5a3fd99a28 | ||
|
|
eefbc68f6b | ||
|
|
7791cdc201 | ||
|
|
daec743e8c | ||
|
|
c2ce7d0aa1 | ||
|
|
9eea574df5 | ||
|
|
da504ba46c | ||
|
|
f4fedc832e | ||
|
|
92538c57f6 | ||
|
|
77a4300669 | ||
|
|
2884cb636b | ||
|
|
025a0292f4 | ||
|
|
0ea1418132 | ||
|
|
e5fca85d88 | ||
|
|
c2c645188d | ||
|
|
7d23b0af5f | ||
|
|
0853de0760 | ||
|
|
b2e4513af3 | ||
|
|
a26c9214cb | ||
|
|
d3b91b5ec5 | ||
|
|
a5ea32c237 | ||
|
|
88efb36ead | ||
|
|
902f20f20c | ||
|
|
f6281158a2 | ||
|
|
154e972217 | ||
|
|
0c2374e66a | ||
|
|
15863c2886 | ||
|
|
618c14524d | ||
|
|
6c4983f65f | ||
|
|
ea282a1761 | ||
|
|
8fed930b7d | ||
|
|
a66fc13ebc | ||
|
|
8d7ab130ff | ||
|
|
569d54bee3 | ||
|
|
5138d4c0f9 | ||
|
|
d37bfa0a5e | ||
|
|
2cce14418c | ||
|
|
e6b0d22e07 | ||
|
|
ba8cafbd45 | ||
|
|
e920c1f8b4 | ||
|
|
8ac7aca2a4 | ||
|
|
ec17284bfd | ||
|
|
2d123f60e1 | ||
|
|
7721f29d0e | ||
|
|
af87c24cfd | ||
|
|
4b7c13d3e2 | ||
|
|
bb9dd4423d | ||
|
|
303212fba8 | ||
|
|
acc408f5ab | ||
|
|
086116d0f0 | ||
|
|
e93681afc0 | ||
|
|
7d2b4503e3 | ||
|
|
0d314df561 | ||
|
|
006d4237f5 | ||
|
|
2c3b860e63 | ||
|
|
613a733169 | ||
|
|
0587aec643 | ||
|
|
b334f2e345 | ||
|
|
777c8dd587 | ||
|
|
4be8be1353 | ||
|
|
8cc1c0d6dc | ||
|
|
eaba2eb7e5 | ||
|
|
c6c7734c29 | ||
|
|
9ccaa5a883 | ||
|
|
93fa7496b1 | ||
|
|
45f16e560c | ||
|
|
ec93239630 | ||
|
|
2f7b0c9938 | ||
|
|
ef12b75013 | ||
|
|
bed88baa6c | ||
|
|
9562b95285 | ||
|
|
e9fda6168c | ||
|
|
a6768bdc32 |
@@ -73,11 +73,12 @@ _APP_EXECUTOR_SECRET=your-secret-key
|
||||
_APP_EXECUTOR_HOST=http://proxy/v1
|
||||
_APP_FUNCTIONS_RUNTIMES=php-8.0,node-18.0,python-3.9,ruby-3.1
|
||||
_APP_MAINTENANCE_INTERVAL=86400
|
||||
_APP_MAINTENANCE_DELAY=
|
||||
_APP_MAINTENANCE_RETENTION_CACHE=2592000
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE=86400
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
|
||||
_APP_USAGE_AGGREGATION_INTERVAL=60000
|
||||
_APP_USAGE_AGGREGATION_INTERVAL=20
|
||||
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
|
||||
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
|
||||
_APP_USAGE_STATS=enabled
|
||||
|
||||
@@ -17,6 +17,12 @@ jobs:
|
||||
fetch-depth: 2
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
@@ -35,11 +41,11 @@ jobs:
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
VERSION=${{ steps.meta.outputs.version }}
|
||||
VITE_APPWRITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
|
||||
VITE_GA_PROJECT=G-L7G2B6PLDS
|
||||
VITE_CONSOLE_MODE=cloud
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
|
||||
+2
-2
@@ -98,8 +98,8 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
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-usage
|
||||
|
||||
chmod +x /usr/local/bin/worker-usage && \
|
||||
chmod +x /usr/local/bin/worker-usage-dump
|
||||
|
||||
# Letsencrypt Permissions
|
||||
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
> Great news! Appwrite Cloud is now in public beta! Sign up at [cloud.appwrite.io](https://cloud.appwrite.io) for a hassle-free, hosted experience. Join us in the Cloud today! ☁️🎉
|
||||
> Our Appwrite Init event has concluded. You can check out all the new and upcoming features [on our Init website](https://appwrite.io/init) 🚀
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
|
||||
@@ -131,9 +131,6 @@ 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']);
|
||||
|
||||
+26
-4
@@ -31,7 +31,7 @@ return [
|
||||
Exception::GENERAL_SERVICE_DISABLED => [
|
||||
'name' => Exception::GENERAL_SERVICE_DISABLED,
|
||||
'description' => 'The requested service is disabled. You can enable the service from the Appwrite console.',
|
||||
'code' => 503,
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::GENERAL_UNAUTHORIZED_SCOPE => [
|
||||
'name' => Exception::GENERAL_UNAUTHORIZED_SCOPE,
|
||||
@@ -103,6 +103,11 @@ return [
|
||||
'description' => 'This method was not fully implemented yet. If you believe this is a mistake, please upgrade your Appwrite server version.',
|
||||
'code' => 405,
|
||||
],
|
||||
Exception::GENERAL_BAD_REQUEST => [
|
||||
'name' => Exception::GENERAL_BAD_REQUEST,
|
||||
'description' => 'There was an error processing your request. Please check the inputs and try again.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** User Errors */
|
||||
Exception::USER_COUNT_EXCEEDED => [
|
||||
@@ -251,6 +256,11 @@ return [
|
||||
'description' => 'User phone is already verified',
|
||||
'code' => 409
|
||||
],
|
||||
Exception::USER_DELETION_PROHIBITED => [
|
||||
'name' => Exception::USER_DELETION_PROHIBITED,
|
||||
'description' => 'User deletion is not allowed for users with active memberships. Please delete all confirmed memberships before deleting the account.',
|
||||
'code' => 400
|
||||
],
|
||||
|
||||
/** Teams */
|
||||
Exception::TEAM_NOT_FOUND => [
|
||||
@@ -629,7 +639,7 @@ return [
|
||||
Exception::PROJECT_PROVIDER_UNSUPPORTED => [
|
||||
'name' => Exception::PROJECT_PROVIDER_UNSUPPORTED,
|
||||
'description' => 'The chosen OAuth provider is unsupported. Please check the <a href="/docs/client/account?sdk=web-default#accountCreateOAuth2Session">Create OAuth2 Session docs</a> for the complete list of supported OAuth providers.',
|
||||
'code' => 501,
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::PROJECT_INVALID_SUCCESS_URL => [
|
||||
'name' => Exception::PROJECT_INVALID_SUCCESS_URL,
|
||||
@@ -768,10 +778,22 @@ return [
|
||||
],
|
||||
|
||||
/** Health */
|
||||
Exception::QUEUE_SIZE_EXCEEDED => [
|
||||
'name' => Exception::QUEUE_SIZE_EXCEEDED,
|
||||
Exception::HEALTH_QUEUE_SIZE_EXCEEDED => [
|
||||
'name' => Exception::HEALTH_QUEUE_SIZE_EXCEEDED,
|
||||
'description' => 'Queue size threshold hit.',
|
||||
'code' => 503,
|
||||
'publish' => false
|
||||
],
|
||||
|
||||
Exception::HEALTH_CERTIFICATE_EXPIRED => [
|
||||
'name' => Exception::HEALTH_CERTIFICATE_EXPIRED,
|
||||
'description' => 'The SSL certificate for the specified domain has expired and is no longer valid.',
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
Exception::HEALTH_INVALID_HOST => [
|
||||
'name' => Exception::HEALTH_INVALID_HOST,
|
||||
'description' => 'Failed to establish a connection to the specified domain. Please verify the domain name and ensure that the server is running and accessible.',
|
||||
'code' => 404,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -303,7 +303,7 @@ return [
|
||||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '10.1.0',
|
||||
'version' => '10.1.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
|
||||
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
@@ -408,7 +408,7 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_SMTP_SECURE',
|
||||
'description' => 'SMTP secure connection protocol. Empty by default, change to \'tls\' if running on a secure connection.',
|
||||
'description' => 'SMTP secure connection protocol. Empty by default, change to \'tls\' or \'ssl\' if running on a secure connection.',
|
||||
'introduction' => '',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
@@ -948,6 +948,15 @@ return [
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_DELAY',
|
||||
'description' => 'Delay value containing the number of seconds that the Appwrite maintenance process should wait before executing system cleanups and optimizations. The default value is 0 seconds.',
|
||||
'introduction' => '1.5.0',
|
||||
'default' => '0',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_CACHE',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain cached files. The default value is 2592000 seconds (30 days).',
|
||||
|
||||
@@ -110,7 +110,7 @@ App::post('/v1/account')
|
||||
Query::equal('providerEmail', [$email]),
|
||||
]);
|
||||
if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
|
||||
}
|
||||
|
||||
if ($project->getAttribute('auths', [])['personalDataCheck'] ?? false) {
|
||||
@@ -555,7 +555,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
|
||||
$identityWithMatchingEmail = $dbForProject->findOne('identities', [
|
||||
Query::equal('providerEmail', [$email]),
|
||||
Query::notEqual('userId', $userId),
|
||||
Query::notEqual('userInternalId', $user->getInternalId()),
|
||||
]);
|
||||
if (!empty($identityWithMatchingEmail)) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
@@ -637,7 +637,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
Query::equal('providerEmail', [$email]),
|
||||
]);
|
||||
if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -695,7 +695,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
Query::notEqual('userId', $user->getId()),
|
||||
]);
|
||||
if (!empty($identitiesWithMatchingEmail)) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
|
||||
}
|
||||
|
||||
$dbForProject->createDocument('identities', new Document([
|
||||
@@ -944,7 +944,7 @@ App::post('/v1/account/sessions/magic-url')
|
||||
Query::equal('providerEmail', [$email]),
|
||||
]);
|
||||
if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
|
||||
}
|
||||
|
||||
$userId = $userId === 'unique()' ? ID::unique() : $userId;
|
||||
@@ -1336,8 +1336,6 @@ App::post('/v1/account/sessions/phone')
|
||||
$message = $message->setParam('{{token}}', $secret);
|
||||
$message = $message->render();
|
||||
|
||||
var_dump($request->getIP());
|
||||
var_dump($project->getId());
|
||||
$queueForMessaging
|
||||
->setRecipient($phone)
|
||||
->setMessage($message)
|
||||
@@ -1987,10 +1985,10 @@ App::patch('/v1/account/email')
|
||||
// Makes sure this email is not already used in another identity
|
||||
$identityWithMatchingEmail = $dbForProject->findOne('identities', [
|
||||
Query::equal('providerEmail', [$email]),
|
||||
Query::notEqual('userId', $user->getId()),
|
||||
Query::notEqual('userInternalId', $user->getInternalId()),
|
||||
]);
|
||||
if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
|
||||
}
|
||||
|
||||
$user
|
||||
@@ -2009,7 +2007,7 @@ App::patch('/v1/account/email')
|
||||
try {
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
@@ -2528,7 +2526,7 @@ App::post('/v1/account/recovery')
|
||||
|
||||
$queueForMails
|
||||
->setRecipient($profile->getAttribute('email', ''))
|
||||
->setName($profile->getAttribute('name'))
|
||||
->setName($profile->getAttribute('name', ''))
|
||||
->setBody($body)
|
||||
->setVariables($emailVariables)
|
||||
->setSubject($subject)
|
||||
@@ -2939,8 +2937,6 @@ App::post('/v1/account/verification/phone')
|
||||
$message = $message->setParam('{{token}}', $secret);
|
||||
$message = $message->render();
|
||||
|
||||
var_dump($request->getIP());
|
||||
var_dump($project->getId());
|
||||
$queueForMessaging
|
||||
->setRecipient($user->getAttribute('phone'))
|
||||
->setMessage($message)
|
||||
@@ -3038,15 +3034,27 @@ App::delete('/v1/account')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->action(function (Document $user, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
|
||||
->action(function (Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
// get all memberships
|
||||
$memberships = $user->getAttribute('memberships', []);
|
||||
foreach ($memberships as $membership) {
|
||||
// prevent deletion if at least one active membership
|
||||
if ($membership->getAttribute('confirm', false)) {
|
||||
throw new Exception(Exception::USER_DELETION_PROHIBITED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dbForProject->deleteDocument('users', $user->getId());
|
||||
|
||||
$queueForDeletes
|
||||
|
||||
@@ -23,6 +23,7 @@ use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use Utopia\Fetch\Client;
|
||||
|
||||
$avatarCallback = function (string $type, string $code, int $width, int $height, int $quality, Response $response) {
|
||||
|
||||
@@ -283,14 +284,21 @@ App::get('/v1/avatars/image')
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$fetch = @\file_get_contents($url);
|
||||
$client = new Client();
|
||||
try {
|
||||
$res = $client
|
||||
->setAllowRedirects(false)
|
||||
->fetch($url);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
if (!$fetch) {
|
||||
if ($res->getStatusCode() !== 200) {
|
||||
throw new Exception(Exception::AVATAR_IMAGE_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$image = new Image($fetch);
|
||||
$image = new Image($res->getBody());
|
||||
} catch (\Exception $exception) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unable to parse image');
|
||||
}
|
||||
@@ -339,31 +347,27 @@ App::get('/v1/avatars/favicon')
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$curl = \curl_init();
|
||||
$client = new Client();
|
||||
try {
|
||||
$res = $client
|
||||
->setAllowRedirects(false)
|
||||
->setUserAgent(\sprintf(
|
||||
APP_USERAGENT,
|
||||
App::getEnv('_APP_VERSION', 'UNKNOWN'),
|
||||
App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
|
||||
))
|
||||
->fetch($url);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
\curl_setopt_array($curl, [
|
||||
CURLOPT_RETURNTRANSFER => 1,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 3,
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_USERAGENT => \sprintf(
|
||||
APP_USERAGENT,
|
||||
App::getEnv('_APP_VERSION', 'UNKNOWN'),
|
||||
App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
|
||||
),
|
||||
]);
|
||||
|
||||
$html = \curl_exec($curl);
|
||||
|
||||
\curl_close($curl);
|
||||
|
||||
if (!$html) {
|
||||
if ($res->getStatusCode() !== 200) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
$doc->strictErrorChecking = false;
|
||||
@$doc->loadHTML($html);
|
||||
@$doc->loadHTML($res->getBody());
|
||||
|
||||
$links = $doc->getElementsByTagName('link');
|
||||
$outputHref = '';
|
||||
@@ -418,9 +422,22 @@ App::get('/v1/avatars/favicon')
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
if ('ico' == $outputExt) { // Skip crop, Imagick isn\'t supporting icon files
|
||||
$data = @\file_get_contents($outputHref, false);
|
||||
$client = new Client();
|
||||
try {
|
||||
$res = $client
|
||||
->setAllowRedirects(false)
|
||||
->fetch($outputHref);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
if ($res->getStatusCode() !== 200) {
|
||||
throw new Exception(Exception::AVATAR_ICON_NOT_FOUND);
|
||||
}
|
||||
|
||||
$data = $res->getBody();
|
||||
|
||||
if ('ico' == $outputExt) { // Skip crop, Imagick isn\'t supporting icon files
|
||||
if (empty($data) || (\mb_substr($data, 0, 5) === '<html') || \mb_substr($data, 0, 5) === '<!doc') {
|
||||
throw new Exception(Exception::AVATAR_ICON_NOT_FOUND, 'Favicon not found');
|
||||
}
|
||||
@@ -430,13 +447,7 @@ App::get('/v1/avatars/favicon')
|
||||
->file($data);
|
||||
}
|
||||
|
||||
$fetch = @\file_get_contents($outputHref, false);
|
||||
|
||||
if (!$fetch) {
|
||||
throw new Exception(Exception::AVATAR_ICON_NOT_FOUND);
|
||||
}
|
||||
|
||||
$image = new Image($fetch);
|
||||
$image = new Image($data);
|
||||
$image->crop((int) $width, (int) $height);
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
@@ -19,16 +19,13 @@ use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Exception\Limit as LimitException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Exception\Restricted as RestrictedException;
|
||||
use Utopia\Database\Exception\Structure as StructureException;
|
||||
use Utopia\Database\Exception\Timeout as TimeoutException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
@@ -657,16 +654,10 @@ App::put('/v1/databases/:databaseId')
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$database = $dbForProject->updateDocument('databases', $databaseId, $database
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('enabled', $enabled)
|
||||
->setAttribute('search', implode(' ', [$databaseId, $name])));
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, 'Bad structure. ' . $exception->getMessage());
|
||||
}
|
||||
$database = $dbForProject->updateDocument('databases', $databaseId, $database
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('enabled', $enabled)
|
||||
->setAttribute('search', implode(' ', [$databaseId, $name])));
|
||||
|
||||
$queueForEvents->setParam('databaseId', $database->getId());
|
||||
|
||||
@@ -1015,19 +1006,14 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
|
||||
|
||||
$enabled ??= $collection->getAttribute('enabled', true);
|
||||
|
||||
try {
|
||||
$collection = $dbForProject->updateDocument('database_' . $database->getInternalId(), $collectionId, $collection
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('$permissions', $permissions)
|
||||
->setAttribute('documentSecurity', $documentSecurity)
|
||||
->setAttribute('enabled', $enabled)
|
||||
->setAttribute('search', implode(' ', [$collectionId, $name])));
|
||||
$dbForProject->updateCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $permissions, $documentSecurity);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, 'Bad structure. ' . $exception->getMessage());
|
||||
}
|
||||
$collection = $dbForProject->updateDocument('database_' . $database->getInternalId(), $collectionId, $collection
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('$permissions', $permissions)
|
||||
->setAttribute('documentSecurity', $documentSecurity)
|
||||
->setAttribute('enabled', $enabled)
|
||||
->setAttribute('search', implode(' ', [$collectionId, $name])));
|
||||
|
||||
$dbForProject->updateCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $permissions, $documentSecurity);
|
||||
|
||||
$queueForEvents
|
||||
->setContext('database', $database)
|
||||
@@ -2822,13 +2808,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
|
||||
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);
|
||||
|
||||
try {
|
||||
$document = $dbForProject->createDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $document);
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
|
||||
} catch (DuplicateException $exception) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
|
||||
@@ -2936,14 +2916,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
|
||||
$filters = Query::groupByType($queries)['filters'];
|
||||
|
||||
try {
|
||||
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries);
|
||||
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filters, APP_LIMIT_COUNT);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage());
|
||||
}
|
||||
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries);
|
||||
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filters, APP_LIMIT_COUNT);
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
$processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool {
|
||||
@@ -2997,6 +2971,33 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
$processDocument($collection, $document);
|
||||
}
|
||||
|
||||
$select = \array_reduce($queries, function ($result, $query) {
|
||||
return $result || ($query->getMethod() === Query::TYPE_SELECT);
|
||||
}, false);
|
||||
|
||||
// Check if the SELECT query includes $databaseId and $collectionId
|
||||
$hasDatabaseId = false;
|
||||
$hasCollectionId = false;
|
||||
if ($select) {
|
||||
$hasDatabaseId = \array_reduce($queries, function ($result, $query) {
|
||||
return $result || ($query->getMethod() === Query::TYPE_SELECT && \in_array('$databaseId', $query->getValues()));
|
||||
}, false);
|
||||
$hasCollectionId = \array_reduce($queries, function ($result, $query) {
|
||||
return $result || ($query->getMethod() === Query::TYPE_SELECT && \in_array('$collectionId', $query->getValues()));
|
||||
}, false);
|
||||
}
|
||||
|
||||
if ($select) {
|
||||
foreach ($documents as $document) {
|
||||
if (!$hasDatabaseId) {
|
||||
$document->removeAttribute('$databaseId');
|
||||
}
|
||||
if (!$hasCollectionId) {
|
||||
$document->removeAttribute('$collectionId');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $total,
|
||||
'documents' => $documents,
|
||||
@@ -3042,13 +3043,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
try {
|
||||
$document = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId, $queries);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage());
|
||||
}
|
||||
$document = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId, $queries);
|
||||
|
||||
if ($document->isEmpty()) {
|
||||
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
|
||||
@@ -3360,22 +3355,14 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
|
||||
$setCollection($collection, $newDocument);
|
||||
|
||||
try {
|
||||
$document = $dbForProject->withRequestTimestamp(
|
||||
$requestTimestamp,
|
||||
fn() => $dbForProject->updateDocument(
|
||||
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
|
||||
$document->getId(),
|
||||
$newDocument
|
||||
)
|
||||
);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
|
||||
}
|
||||
$document = $dbForProject->withRequestTimestamp(
|
||||
$requestTimestamp,
|
||||
fn() => $dbForProject->updateDocument(
|
||||
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
|
||||
$document->getId(),
|
||||
$newDocument
|
||||
)
|
||||
);
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
|
||||
@@ -3475,16 +3462,10 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
}
|
||||
|
||||
$dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) {
|
||||
try {
|
||||
$dbForProject->deleteDocument(
|
||||
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
|
||||
$documentId
|
||||
);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (RestrictedException) {
|
||||
throw new Exception(Exception::DOCUMENT_DELETE_RESTRICTED);
|
||||
}
|
||||
$dbForProject->deleteDocument(
|
||||
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
|
||||
$documentId
|
||||
);
|
||||
});
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
|
||||
+108
-14
@@ -7,6 +7,7 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Domains\Validator\PublicDomain;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
@@ -14,7 +15,9 @@ use Utopia\Registry\Registry;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Validator\Domain;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Multiple;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -356,7 +359,7 @@ App::get('/v1/health/queue/webhooks')
|
||||
$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}.");
|
||||
throw new Exception(Exception::HEALTH_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);
|
||||
@@ -383,12 +386,62 @@ App::get('/v1/health/queue/logs')
|
||||
$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}.");
|
||||
throw new Exception(Exception::HEALTH_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/certificate')
|
||||
->desc('Get the SSL certificate for a domain')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getCertificate')
|
||||
->label('sdk.description', '/docs/references/health/get-certificate.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_CERTIFICATE)
|
||||
->param('domain', null, new Multiple([new Domain(), new PublicDomain()]), Multiple::TYPE_STRING, 'Domain name')
|
||||
->inject('response')
|
||||
->action(function (string $domain, Response $response) {
|
||||
if (filter_var($domain, FILTER_VALIDATE_URL)) {
|
||||
$domain = parse_url($domain, PHP_URL_HOST);
|
||||
}
|
||||
|
||||
$sslContext = stream_context_create([
|
||||
"ssl" => [
|
||||
"capture_peer_cert" => true
|
||||
]
|
||||
]);
|
||||
$sslSocket = stream_socket_client("ssl://" . $domain . ":443", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $sslContext);
|
||||
if (!$sslSocket) {
|
||||
throw new Exception(Exception::HEALTH_INVALID_HOST);
|
||||
}
|
||||
|
||||
$streamContextParams = stream_context_get_params($sslSocket);
|
||||
$peerCertificate = $streamContextParams['options']['ssl']['peer_certificate'];
|
||||
$certificatePayload = openssl_x509_parse($peerCertificate);
|
||||
|
||||
|
||||
$sslExpiration = $certificatePayload['validTo_time_t'];
|
||||
$status = $sslExpiration < time() ? 'fail' : 'pass';
|
||||
|
||||
if ($status === 'fail') {
|
||||
throw new Exception(Exception::HEALTH_CERTIFICATE_EXPIRED);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'name' => $certificatePayload['name'],
|
||||
'subjectSN' => $certificatePayload['subject']['CN'],
|
||||
'issuerOrganisation' => $certificatePayload['issuer']['O'],
|
||||
'validFrom' => $certificatePayload['validFrom_time_t'],
|
||||
'validTo' => $certificatePayload['validTo_time_t'],
|
||||
'signatureTypeSN' => $certificatePayload['signatureTypeSN'],
|
||||
]), Response::MODEL_HEALTH_CERTIFICATE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/certificates')
|
||||
->desc('Get certificates queue')
|
||||
->groups(['api', 'health'])
|
||||
@@ -410,7 +463,7 @@ App::get('/v1/health/queue/certificates')
|
||||
$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}.");
|
||||
throw new Exception(Exception::HEALTH_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);
|
||||
@@ -437,7 +490,7 @@ App::get('/v1/health/queue/builds')
|
||||
$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}.");
|
||||
throw new Exception(Exception::HEALTH_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);
|
||||
@@ -465,7 +518,7 @@ App::get('/v1/health/queue/databases')
|
||||
$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}.");
|
||||
throw new Exception(Exception::HEALTH_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);
|
||||
@@ -492,7 +545,7 @@ App::get('/v1/health/queue/deletes')
|
||||
$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}.");
|
||||
throw new Exception(Exception::HEALTH_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);
|
||||
@@ -519,7 +572,7 @@ App::get('/v1/health/queue/mails')
|
||||
$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}.");
|
||||
throw new Exception(Exception::HEALTH_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);
|
||||
@@ -546,7 +599,7 @@ App::get('/v1/health/queue/messaging')
|
||||
$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}.");
|
||||
throw new Exception(Exception::HEALTH_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);
|
||||
@@ -573,7 +626,7 @@ App::get('/v1/health/queue/migrations')
|
||||
$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}.");
|
||||
throw new Exception(Exception::HEALTH_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);
|
||||
@@ -600,7 +653,7 @@ App::get('/v1/health/queue/functions')
|
||||
$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}.");
|
||||
throw new Exception(Exception::HEALTH_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);
|
||||
@@ -649,6 +702,47 @@ App::get('/v1/health/storage/local')
|
||||
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
|
||||
});
|
||||
|
||||
App::get('/v1/health/storage')
|
||||
->desc('Get storage')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getStorage')
|
||||
->label('sdk.description', '/docs/references/health/get-storage.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->inject('response')
|
||||
->inject('deviceFiles')
|
||||
->inject('deviceFunctions')
|
||||
->inject('deviceBuilds')
|
||||
->action(function (Response $response, Device $deviceFiles, Device $deviceFunctions, Device $deviceBuilds) {
|
||||
$devices = [$deviceFiles, $deviceFunctions, $deviceBuilds];
|
||||
$checkStart = \microtime(true);
|
||||
|
||||
foreach ($devices as $device) {
|
||||
if (!$device->write($device->getPath('health.txt'), 'test', 'text/plain')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed writing test file to ' . $device->getRoot());
|
||||
}
|
||||
|
||||
if ($device->read($device->getPath('health.txt')) !== 'test') {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed reading test file from ' . $device->getRoot());
|
||||
}
|
||||
|
||||
if (!$device->delete($device->getPath('health.txt'))) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed deleting test file from ' . $device->getRoot());
|
||||
}
|
||||
}
|
||||
|
||||
$output = [
|
||||
'status' => 'pass',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
];
|
||||
|
||||
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
|
||||
});
|
||||
|
||||
App::get('/v1/health/anti-virus')
|
||||
->desc('Get antivirus')
|
||||
->groups(['api', 'health'])
|
||||
@@ -695,19 +789,19 @@ App::get('/v1/health/queue/failed/:name')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getFailedJobs')
|
||||
->param('queueName', '', new WhiteList([
|
||||
->param('name', '', new WhiteList([
|
||||
Event::DATABASE_QUEUE_NAME,
|
||||
Event::DELETE_QUEUE_NAME,
|
||||
Event::AUDITS_QUEUE_NAME,
|
||||
Event::MAILS_QUEUE_NAME,
|
||||
Event::FUNCTIONS_QUEUE_NAME,
|
||||
Event::USAGE_QUEUE_NAME,
|
||||
Event::USAGE_DUMP_QUEUE_NAME,
|
||||
Event::WEBHOOK_CLASS_NAME,
|
||||
Event::CERTIFICATES_QUEUE_NAME,
|
||||
Event::BUILDS_QUEUE_NAME,
|
||||
Event::MESSAGING_QUEUE_NAME,
|
||||
Event::MIGRATIONS_QUEUE_NAME,
|
||||
Event::HAMSTER_CLASS_NAME
|
||||
Event::MIGRATIONS_QUEUE_NAME
|
||||
]), 'The name of the queue')
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->label('sdk.description', '/docs/references/health/get-failed-queue-jobs.md')
|
||||
@@ -723,7 +817,7 @@ App::get('/v1/health/queue/failed/:name')
|
||||
$failed = $client->countFailedJobs();
|
||||
|
||||
if ($failed >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue failed jobs threshold hit. Current size is {$failed} and threshold is {$threshold}.");
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue failed jobs threshold hit. Current size is {$failed} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $failed ]), Response::MODEL_HEALTH_QUEUE);
|
||||
|
||||
@@ -144,9 +144,30 @@ App::get('/v1/project/usage')
|
||||
];
|
||||
}, $dbForProject->find('buckets'));
|
||||
|
||||
// merge network inbound + outbound
|
||||
$projectBandwidth = [];
|
||||
foreach ($usage[METRIC_NETWORK_INBOUND] as $item) {
|
||||
$projectBandwidth[$item['date']] ??= 0;
|
||||
$projectBandwidth[$item['date']] += $item['value'];
|
||||
}
|
||||
|
||||
foreach ($usage[METRIC_NETWORK_OUTBOUND] as $item) {
|
||||
$projectBandwidth[$item['date']] ??= 0;
|
||||
$projectBandwidth[$item['date']] += $item['value'];
|
||||
}
|
||||
|
||||
|
||||
$network = [];
|
||||
foreach ($projectBandwidth as $date => $value) {
|
||||
$network[] = [
|
||||
'date' => $date,
|
||||
'value' => $value
|
||||
];
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'requests' => ($usage[METRIC_NETWORK_REQUESTS]),
|
||||
'network' => ($usage[METRIC_NETWORK_INBOUND] + $usage[METRIC_NETWORK_OUTBOUND]),
|
||||
'network' => $network,
|
||||
'users' => ($usage[METRIC_USERS]),
|
||||
'executions' => ($usage[METRIC_EXECUTIONS]),
|
||||
'executionsTotal' => $total[METRIC_EXECUTIONS],
|
||||
|
||||
@@ -4,6 +4,7 @@ use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Validator\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Hooks\Hooks;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Template\Template;
|
||||
@@ -75,7 +76,8 @@ App::post('/v1/projects')
|
||||
->inject('dbForConsole')
|
||||
->inject('cache')
|
||||
->inject('pools')
|
||||
->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Cache $cache, Group $pools) {
|
||||
->inject('hooks')
|
||||
->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Cache $cache, Group $pools, Hooks $hooks) {
|
||||
|
||||
|
||||
$team = $dbForConsole->getDocument('teams', $teamId);
|
||||
@@ -217,6 +219,8 @@ App::post('/v1/projects')
|
||||
$dbForProject->createCollection($key, $attributes, $indexes);
|
||||
}
|
||||
|
||||
$hooks->trigger('afterProjectCreation', [ $project, $pools, $cache ]);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($project, Response::MODEL_PROJECT);
|
||||
@@ -1446,7 +1450,7 @@ App::patch('/v1/projects/:projectId/smtp')
|
||||
->param('port', 587, new Integer(), 'SMTP server port', true)
|
||||
->param('username', '', new Text(0, 0), 'SMTP server username', true)
|
||||
->param('password', '', new Text(0, 0), 'SMTP server password', true)
|
||||
->param('secure', '', new WhiteList(['tls'], true), 'Does SMTP server use secure connection', true)
|
||||
->param('secure', '', new WhiteList(['tls', 'ssl'], true), 'Does SMTP server use secure connection', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $enabled, string $senderName, string $senderEmail, string $replyTo, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForConsole) {
|
||||
|
||||
+83
-107
@@ -581,111 +581,95 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
$openSSLIV = \bin2hex($iv);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($file->isEmpty()) {
|
||||
$doc = new Document([
|
||||
'$id' => $fileId,
|
||||
'$permissions' => $permissions,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'bucketInternalId' => $bucket->getInternalId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
'signature' => $fileHash,
|
||||
'mimeType' => $mimeType,
|
||||
'sizeOriginal' => $fileSize,
|
||||
'sizeActual' => $sizeActual,
|
||||
'algorithm' => $algorithm,
|
||||
'comment' => '',
|
||||
'chunksTotal' => $chunks,
|
||||
'chunksUploaded' => $chunksUploaded,
|
||||
'openSSLVersion' => $openSSLVersion,
|
||||
'openSSLCipher' => $openSSLCipher,
|
||||
'openSSLTag' => $openSSLTag,
|
||||
'openSSLIV' => $openSSLIV,
|
||||
'search' => implode(' ', [$fileId, $fileName]),
|
||||
'metadata' => $metadata,
|
||||
]);
|
||||
if ($file->isEmpty()) {
|
||||
$doc = new Document([
|
||||
'$id' => $fileId,
|
||||
'$permissions' => $permissions,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'bucketInternalId' => $bucket->getInternalId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
'signature' => $fileHash,
|
||||
'mimeType' => $mimeType,
|
||||
'sizeOriginal' => $fileSize,
|
||||
'sizeActual' => $sizeActual,
|
||||
'algorithm' => $algorithm,
|
||||
'comment' => '',
|
||||
'chunksTotal' => $chunks,
|
||||
'chunksUploaded' => $chunksUploaded,
|
||||
'openSSLVersion' => $openSSLVersion,
|
||||
'openSSLCipher' => $openSSLCipher,
|
||||
'openSSLTag' => $openSSLTag,
|
||||
'openSSLIV' => $openSSLIV,
|
||||
'search' => implode(' ', [$fileId, $fileName]),
|
||||
'metadata' => $metadata,
|
||||
]);
|
||||
|
||||
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
|
||||
} else {
|
||||
$file = $file
|
||||
->setAttribute('$permissions', $permissions)
|
||||
->setAttribute('signature', $fileHash)
|
||||
->setAttribute('mimeType', $mimeType)
|
||||
->setAttribute('sizeActual', $sizeActual)
|
||||
->setAttribute('algorithm', $algorithm)
|
||||
->setAttribute('openSSLVersion', $openSSLVersion)
|
||||
->setAttribute('openSSLCipher', $openSSLCipher)
|
||||
->setAttribute('openSSLTag', $openSSLTag)
|
||||
->setAttribute('openSSLIV', $openSSLIV)
|
||||
->setAttribute('metadata', $metadata)
|
||||
->setAttribute('chunksUploaded', $chunksUploaded);
|
||||
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
|
||||
} else {
|
||||
$file = $file
|
||||
->setAttribute('$permissions', $permissions)
|
||||
->setAttribute('signature', $fileHash)
|
||||
->setAttribute('mimeType', $mimeType)
|
||||
->setAttribute('sizeActual', $sizeActual)
|
||||
->setAttribute('algorithm', $algorithm)
|
||||
->setAttribute('openSSLVersion', $openSSLVersion)
|
||||
->setAttribute('openSSLCipher', $openSSLCipher)
|
||||
->setAttribute('openSSLTag', $openSSLTag)
|
||||
->setAttribute('openSSLIV', $openSSLIV)
|
||||
->setAttribute('metadata', $metadata)
|
||||
->setAttribute('chunksUploaded', $chunksUploaded);
|
||||
|
||||
/**
|
||||
* Validate create permission and skip authorization in updateDocument
|
||||
* Without this, the file creation will fail when user doesn't have update permission
|
||||
* However as with chunk upload even if we are updating, we are essentially creating a file
|
||||
* adding it's new chunk so we validate create permission instead of update
|
||||
*/
|
||||
$validator = new Authorization(Database::PERMISSION_CREATE);
|
||||
if (!$validator->isValid($bucket->getCreate())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
/**
|
||||
* Validate create permission and skip authorization in updateDocument
|
||||
* Without this, the file creation will fail when user doesn't have update permission
|
||||
* However as with chunk upload even if we are updating, we are essentially creating a file
|
||||
* adding it's new chunk so we validate create permission instead of update
|
||||
*/
|
||||
$validator = new Authorization(Database::PERMISSION_CREATE);
|
||||
if (!$validator->isValid($bucket->getCreate())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
$file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
if ($file->isEmpty()) {
|
||||
$doc = new Document([
|
||||
'$id' => ID::custom($fileId),
|
||||
'$permissions' => $permissions,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'bucketInternalId' => $bucket->getInternalId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
'signature' => '',
|
||||
'mimeType' => '',
|
||||
'sizeOriginal' => $fileSize,
|
||||
'sizeActual' => 0,
|
||||
'algorithm' => '',
|
||||
'comment' => '',
|
||||
'chunksTotal' => $chunks,
|
||||
'chunksUploaded' => $chunksUploaded,
|
||||
'search' => implode(' ', [$fileId, $fileName]),
|
||||
'metadata' => $metadata,
|
||||
]);
|
||||
if ($file->isEmpty()) {
|
||||
$doc = new Document([
|
||||
'$id' => ID::custom($fileId),
|
||||
'$permissions' => $permissions,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'bucketInternalId' => $bucket->getInternalId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
'signature' => '',
|
||||
'mimeType' => '',
|
||||
'sizeOriginal' => $fileSize,
|
||||
'sizeActual' => 0,
|
||||
'algorithm' => '',
|
||||
'comment' => '',
|
||||
'chunksTotal' => $chunks,
|
||||
'chunksUploaded' => $chunksUploaded,
|
||||
'search' => implode(' ', [$fileId, $fileName]),
|
||||
'metadata' => $metadata,
|
||||
]);
|
||||
|
||||
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
|
||||
} else {
|
||||
$file = $file
|
||||
->setAttribute('chunksUploaded', $chunksUploaded)
|
||||
->setAttribute('metadata', $metadata);
|
||||
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
|
||||
} else {
|
||||
$file = $file
|
||||
->setAttribute('chunksUploaded', $chunksUploaded)
|
||||
->setAttribute('metadata', $metadata);
|
||||
|
||||
/**
|
||||
* Validate create permission and skip authorization in updateDocument
|
||||
* Without this, the file creation will fail when user doesn't have update permission
|
||||
* However as with chunk upload even if we are updating, we are essentially creating a file
|
||||
* adding it's new chunk so we validate create permission instead of update
|
||||
*/
|
||||
$validator = new Authorization(Database::PERMISSION_CREATE);
|
||||
if (!$validator->isValid($bucket->getCreate())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
/**
|
||||
* Validate create permission and skip authorization in updateDocument
|
||||
* Without this, the file creation will fail when user doesn't have update permission
|
||||
* However as with chunk upload even if we are updating, we are essentially creating a file
|
||||
* adding it's new chunk so we validate create permission instead of update
|
||||
*/
|
||||
$validator = new Authorization(Database::PERMISSION_CREATE);
|
||||
if (!$validator->isValid($bucket->getCreate())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
$file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1383,11 +1367,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
}
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
try {
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
} else {
|
||||
$file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
}
|
||||
@@ -1471,11 +1451,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
;
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
try {
|
||||
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$deleted = Authorization::skip(fn() => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
@@ -380,7 +380,6 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
|
||||
->param('url', '', fn($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // TODO add our own built-in confirm page
|
||||
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
@@ -389,7 +388,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, EventPhone $queueForMessaging, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, EventPhone $queueForMessaging, Event $queueForEvents) {
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
@@ -639,8 +638,6 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
$message = $message->setParam('{{token}}', $url);
|
||||
$message = $message->render();
|
||||
|
||||
var_dump($request->getIP());
|
||||
var_dump($project->getId());
|
||||
$queueForMessaging
|
||||
->setRecipient($phone)
|
||||
->setMessage($message)
|
||||
@@ -697,7 +694,7 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
}
|
||||
|
||||
// Set internal queries
|
||||
$queries[] = Query::equal('teamId', [$teamId]);
|
||||
$queries[] = Query::equal('teamInternalId', [$team->getInternalId()]);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
@@ -897,16 +894,16 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($membership->getAttribute('teamId') !== $teamId) {
|
||||
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
|
||||
}
|
||||
|
||||
$team = Authorization::skip(fn() => $dbForProject->getDocument('teams', $teamId));
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($membership->getAttribute('teamInternalId') !== $team->getInternalId()) {
|
||||
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
|
||||
}
|
||||
|
||||
if (Auth::hash($secret) !== $membership->getAttribute('secret')) {
|
||||
throw new Exception(Exception::TEAM_INVALID_SECRET);
|
||||
}
|
||||
@@ -919,7 +916,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
$user->setAttributes($dbForProject->getDocument('users', $userId)->getArrayCopy()); // Get user
|
||||
}
|
||||
|
||||
if ($membership->getAttribute('userId') !== $user->getId()) {
|
||||
if ($membership->getAttribute('userInternalId') !== $user->getInternalId()) {
|
||||
throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user (' . $user->getAttribute('email') . ')');
|
||||
}
|
||||
|
||||
@@ -970,7 +967,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('total', $team->getAttribute('total', 0) + 1)));
|
||||
Authorization::skip(fn() => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('teamId', $team->getId())
|
||||
@@ -1023,10 +1020,6 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
throw new Exception(Exception::TEAM_INVITE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($membership->getAttribute('teamId') !== $teamId) {
|
||||
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
|
||||
}
|
||||
|
||||
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
@@ -1039,19 +1032,15 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$dbForProject->deleteDocument('memberships', $membership->getId());
|
||||
} catch (AuthorizationException $exception) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (\Exception $exception) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership from DB');
|
||||
if ($membership->getAttribute('teamInternalId') !== $team->getInternalId()) {
|
||||
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
|
||||
}
|
||||
|
||||
$dbForProject->deleteDocument('memberships', $membership->getId());
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
if ($membership->getAttribute('confirm')) { // Count only confirmed members
|
||||
$team->setAttribute('total', \max($team->getAttribute('total', 0) - 1, 0));
|
||||
Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team));
|
||||
Authorization::skip(fn() => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
|
||||
@@ -9,6 +9,7 @@ use Appwrite\Event\Event;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Identities;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Users;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
@@ -141,7 +142,6 @@ App::post('/v1/users')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
|
||||
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $queueForEvents, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
@@ -941,7 +941,7 @@ App::patch('/v1/users/:userId/email')
|
||||
// Makes sure this email is not already used in another identity
|
||||
$identityWithMatchingEmail = $dbForProject->findOne('identities', [
|
||||
Query::equal('providerEmail', [$email]),
|
||||
Query::notEqual('userId', $user->getId()),
|
||||
Query::notEqual('userInternalId', $user->getInternalId()),
|
||||
]);
|
||||
if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
|
||||
@@ -95,7 +95,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
|
||||
$latestCommentId = '';
|
||||
|
||||
if (!empty($providerPullRequestId)) {
|
||||
if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false) === false) {
|
||||
$latestComment = Authorization::skip(fn () => $dbForConsole->findOne('vcsComments', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::equal('providerPullRequestId', [$providerPullRequestId]),
|
||||
|
||||
+319
-93
@@ -9,9 +9,8 @@ use Utopia\Logger\Logger;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Pools\Group;
|
||||
use Appwrite\Utopia\Request;
|
||||
use MaxMind\Db\Reader;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\View;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
@@ -19,6 +18,8 @@ use Utopia\Config\Config;
|
||||
use Utopia\Domains\Domain;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Usage;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Utopia\Response\Filters\V11 as ResponseV11;
|
||||
use Appwrite\Utopia\Response\Filters\V12 as ResponseV12;
|
||||
@@ -31,6 +32,7 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Appwrite\Utopia\Request\Filters\V12 as RequestV12;
|
||||
@@ -38,6 +40,7 @@ use Appwrite\Utopia\Request\Filters\V13 as RequestV13;
|
||||
use Appwrite\Utopia\Request\Filters\V14 as RequestV14;
|
||||
use Appwrite\Utopia\Request\Filters\V15 as RequestV15;
|
||||
use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
|
||||
use Executor\Executor;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -45,7 +48,7 @@ Config::setParam('domainVerification', false);
|
||||
Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleRequest, Request $request, Response $response)
|
||||
function router(App $utopia, Database $dbForConsole, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, Usage $queueForUsage, Reader $geodb)
|
||||
{
|
||||
$utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
|
||||
|
||||
@@ -117,59 +120,218 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
|
||||
$path .= '?' . $query;
|
||||
}
|
||||
|
||||
|
||||
$body = $swooleRequest->getContent() ?? '';
|
||||
$method = $swooleRequest->server['request_method'];
|
||||
|
||||
$requestHeaders = $request->getHeaders();
|
||||
|
||||
$body = \json_encode([
|
||||
'async' => false,
|
||||
'body' => $swooleRequest->getContent() ?? '',
|
||||
'method' => $swooleRequest->server['request_method'],
|
||||
'path' => $path,
|
||||
'headers' => $requestHeaders
|
||||
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
|
||||
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
|
||||
|
||||
if ($function->isEmpty() || !$function->getAttribute('enabled')) {
|
||||
throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
|
||||
|
||||
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
|
||||
|
||||
if (\is_null($runtime)) {
|
||||
throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
|
||||
}
|
||||
|
||||
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
|
||||
throw new AppwriteException(AppwriteException::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
|
||||
}
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new AppwriteException(AppwriteException::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
|
||||
}
|
||||
|
||||
/** Check if build has completed */
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
if ($build->isEmpty()) {
|
||||
throw new AppwriteException(AppwriteException::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($build->getAttribute('status') !== 'ready') {
|
||||
throw new AppwriteException(AppwriteException::BUILD_NOT_READY);
|
||||
}
|
||||
|
||||
$permissions = $function->getAttribute('execute');
|
||||
|
||||
if (!(\in_array('any', $permissions)) && !(\in_array('guests', $permissions))) {
|
||||
throw new AppwriteException(AppwriteException::USER_UNAUTHORIZED, 'To execute function using domain, execute permissions must include "any" or "guests"');
|
||||
}
|
||||
|
||||
$headers = \array_merge([], $requestHeaders);
|
||||
$headers['x-appwrite-trigger'] = 'http';
|
||||
$headers['x-appwrite-user-id'] = '';
|
||||
$headers['x-appwrite-user-jwt'] = '';
|
||||
$headers['x-appwrite-country-code'] = '';
|
||||
$headers['x-appwrite-continent-code'] = '';
|
||||
$headers['x-appwrite-continent-eu'] = 'false';
|
||||
|
||||
$ip = $headers['x-real-ip'] ?? '';
|
||||
if (!empty($ip)) {
|
||||
$record = $geodb->get($ip);
|
||||
|
||||
if ($record) {
|
||||
$eu = Config::getParam('locale-eu');
|
||||
|
||||
$headers['x-appwrite-country-code'] = $record['country']['iso_code'] ?? '';
|
||||
$headers['x-appwrite-continent-code'] = $record['continent']['code'] ?? '';
|
||||
$headers['x-appwrite-continent-eu'] = (\in_array($record['country']['iso_code'], $eu)) ? 'true' : 'false';
|
||||
}
|
||||
}
|
||||
|
||||
$headersFiltered = [];
|
||||
foreach ($headers as $key => $value) {
|
||||
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_REQUEST)) {
|
||||
$headersFiltered[] = ['name' => $key, 'value' => $value];
|
||||
}
|
||||
}
|
||||
|
||||
$executionId = ID::unique();
|
||||
|
||||
$execution = new Document([
|
||||
'$id' => $executionId,
|
||||
'$permissions' => [],
|
||||
'functionInternalId' => $function->getInternalId(),
|
||||
'functionId' => $function->getId(),
|
||||
'deploymentInternalId' => $deployment->getInternalId(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'trigger' => 'http', // http / schedule / event
|
||||
'status' => 'processing', // waiting / processing / completed / failed
|
||||
'responseStatusCode' => 0,
|
||||
'responseHeaders' => [],
|
||||
'requestPath' => $path,
|
||||
'requestMethod' => $method,
|
||||
'requestHeaders' => $headersFiltered,
|
||||
'errors' => '',
|
||||
'logs' => '',
|
||||
'duration' => 0.0,
|
||||
'search' => implode(' ', [$functionId, $executionId]),
|
||||
]);
|
||||
|
||||
$headers = [
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . \strlen($body),
|
||||
'X-Appwrite-Project: ' . $projectId
|
||||
];
|
||||
$queueForEvents
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('executionId', $execution->getId())
|
||||
->setContext('function', $function);
|
||||
|
||||
$ch = \curl_init();
|
||||
\curl_setopt($ch, CURLOPT_URL, "http://localhost/v1/functions/{$functionId}/executions");
|
||||
\curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
|
||||
\curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
\curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
// \curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
\curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
||||
\curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||
$durationStart = \microtime(true);
|
||||
|
||||
$executionResponse = \curl_exec($ch);
|
||||
$statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = \curl_error($ch);
|
||||
$errNo = \curl_errno($ch);
|
||||
$vars = [];
|
||||
|
||||
\curl_close($ch);
|
||||
|
||||
if ($errNo !== 0) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ARGUMENT_INVALID, "Internal error: " . $error);
|
||||
// V2 vars
|
||||
if ($version === 'v2') {
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'] ?? '',
|
||||
'APPWRITE_FUNCTION_DATA' => $body ?? '',
|
||||
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'] ?? '',
|
||||
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt'] ?? ''
|
||||
]);
|
||||
}
|
||||
|
||||
if ($statusCode >= 400) {
|
||||
$error = \json_decode($executionResponse, true)['message'];
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ARGUMENT_INVALID, "Execution error: " . $error);
|
||||
// Shared vars
|
||||
foreach ($function->getAttribute('varsProject', []) as $var) {
|
||||
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
|
||||
}
|
||||
|
||||
$execution = \json_decode($executionResponse, true);
|
||||
// Function vars
|
||||
foreach ($function->getAttribute('vars', []) as $var) {
|
||||
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
|
||||
}
|
||||
|
||||
$contentType = 'text/plain';
|
||||
foreach ($execution['responseHeaders'] as $header) {
|
||||
if (\strtolower($header['name']) === 'content-type') {
|
||||
$contentType = $header['value'];
|
||||
// Appwrite vars
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_FUNCTION_ID' => $functionId,
|
||||
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
|
||||
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
|
||||
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
|
||||
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
|
||||
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
|
||||
]);
|
||||
|
||||
/** Execute function */
|
||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||
try {
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$command = $runtime['startCommand'];
|
||||
$command = $version === 'v2' ? '' : 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $command . '"';
|
||||
$executionResponse = $executor->createExecution(
|
||||
projectId: $project->getId(),
|
||||
deploymentId: $deployment->getId(),
|
||||
body: \strlen($body) > 0 ? $body : null,
|
||||
variables: $vars,
|
||||
timeout: $function->getAttribute('timeout', 0),
|
||||
image: $runtime['image'],
|
||||
source: $build->getAttribute('path', ''),
|
||||
entrypoint: $deployment->getAttribute('entrypoint', ''),
|
||||
version: $version,
|
||||
path: $path,
|
||||
method: $method,
|
||||
headers: $headers,
|
||||
runtimeEntrypoint: $command,
|
||||
requestTimeout: 30
|
||||
);
|
||||
|
||||
$headersFiltered = [];
|
||||
foreach ($executionResponse['headers'] as $key => $value) {
|
||||
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_RESPONSE)) {
|
||||
$headersFiltered[] = ['name' => $key, 'value' => $value];
|
||||
}
|
||||
}
|
||||
|
||||
$response->setHeader($header['name'], $header['value']);
|
||||
/** Update execution status */
|
||||
$status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed';
|
||||
$execution->setAttribute('status', $status);
|
||||
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
|
||||
$execution->setAttribute('responseHeaders', $headersFiltered);
|
||||
$execution->setAttribute('logs', $executionResponse['logs']);
|
||||
$execution->setAttribute('errors', $executionResponse['errors']);
|
||||
$execution->setAttribute('duration', $executionResponse['duration']);
|
||||
} catch (\Throwable $th) {
|
||||
$durationEnd = \microtime(true);
|
||||
|
||||
$execution
|
||||
->setAttribute('duration', $durationEnd - $durationStart)
|
||||
->setAttribute('status', 'failed')
|
||||
->setAttribute('responseStatusCode', 500)
|
||||
->setAttribute('errors', $th->getMessage() . '\nError Code: ' . $th->getCode());
|
||||
Console::error($th->getMessage());
|
||||
} finally {
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_EXECUTIONS, 1)
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
|
||||
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
|
||||
;
|
||||
}
|
||||
|
||||
if ($function->getAttribute('logging')) {
|
||||
/** @var Document $execution */
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
|
||||
$execution->setAttribute('logs', '');
|
||||
$execution->setAttribute('errors', '');
|
||||
|
||||
$headers = [];
|
||||
foreach (($executionResponse['headers'] ?? []) as $key => $value) {
|
||||
$headers[] = ['name' => $key, 'value' => $value];
|
||||
}
|
||||
|
||||
$execution->setAttribute('responseBody', $executionResponse['body'] ?? '');
|
||||
$execution->setAttribute('responseHeaders', $headers);
|
||||
|
||||
$body = $execution['responseBody'] ?? '';
|
||||
|
||||
$encodingKey = \array_search('x-open-runtimes-encoding', \array_column($execution['responseHeaders'], 'name'));
|
||||
@@ -179,6 +341,15 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
|
||||
}
|
||||
}
|
||||
|
||||
$contentType = 'text/plain';
|
||||
foreach ($execution['responseHeaders'] as $header) {
|
||||
if (\strtolower($header['name']) === 'content-type') {
|
||||
$contentType = $header['value'];
|
||||
}
|
||||
|
||||
$response->setHeader($header['name'], $header['value']);
|
||||
}
|
||||
|
||||
$response
|
||||
->setContentType($contentType)
|
||||
->setStatusCode($execution['responseStatusCode'] ?? 200)
|
||||
@@ -196,6 +367,18 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
App::init()
|
||||
->groups(['api'])
|
||||
->inject('project')
|
||||
->inject('mode')
|
||||
->action(function (Document $project, string $mode) {
|
||||
if ($mode === APP_MODE_ADMIN && $project->getId() === 'console') {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_BAD_REQUEST, 'Admin mode is not allowed for console project');
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
App::init()
|
||||
->groups(['api', 'web'])
|
||||
->inject('utopia')
|
||||
@@ -205,13 +388,17 @@ App::init()
|
||||
->inject('console')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('getProjectDB')
|
||||
->inject('user')
|
||||
->inject('locale')
|
||||
->inject('localeCodes')
|
||||
->inject('clients')
|
||||
->inject('servers')
|
||||
->inject('queueForCertificates')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $localeCodes, array $clients, array $servers, Certificate $queueForCertificates) {
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('geodb')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Document $user, Locale $locale, array $localeCodes, array $clients, array $servers, Certificate $queueForCertificates, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
@@ -220,7 +407,7 @@ App::init()
|
||||
$mainDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain) {
|
||||
if (router($utopia, $dbForConsole, $swooleRequest, $request, $response)) {
|
||||
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -455,7 +642,7 @@ App::init()
|
||||
: Role::users()->toString();
|
||||
|
||||
// Add user roles
|
||||
$memberships = $user->find('teamId', $project->getAttribute('teamId'), 'memberships');
|
||||
$memberships = $user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships');
|
||||
|
||||
if ($memberships) {
|
||||
foreach ($memberships->getAttribute('roles', []) as $memberRole) {
|
||||
@@ -571,7 +758,11 @@ App::options()
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole) {
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('geodb')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
@@ -579,7 +770,7 @@ App::options()
|
||||
$mainDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain) {
|
||||
if (router($utopia, $dbForConsole, $swooleRequest, $request, $response)) {
|
||||
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -607,17 +798,93 @@ App::error()
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log) {
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->getRoute();
|
||||
$publish = true;
|
||||
$class = \get_class($error);
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
$file = $error->getFile();
|
||||
$line = $error->getLine();
|
||||
$trace = $error->getTrace();
|
||||
|
||||
if (php_sapi_name() === 'cli') {
|
||||
Console::error('[Error] Timestamp: ' . date('c', time()));
|
||||
|
||||
if ($route) {
|
||||
Console::error('[Error] Method: ' . $route->getMethod());
|
||||
Console::error('[Error] URL: ' . $route->getPath());
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
Console::error('[Error] Message: ' . $message);
|
||||
Console::error('[Error] File: ' . $file);
|
||||
Console::error('[Error] Line: ' . $line);
|
||||
}
|
||||
|
||||
switch ($class) {
|
||||
case 'Utopia\Exception':
|
||||
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
|
||||
switch ($code) {
|
||||
case 400:
|
||||
$error->setType(AppwriteException::GENERAL_ARGUMENT_INVALID);
|
||||
break;
|
||||
case 404:
|
||||
$error->setType(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'Utopia\Database\Exception\Conflict':
|
||||
$error = new AppwriteException(AppwriteException::DOCUMENT_UPDATE_CONFLICT, previous: $error);
|
||||
break;
|
||||
case 'Utopia\Database\Exception\Timeout':
|
||||
$error = new AppwriteException(AppwriteException::DATABASE_TIMEOUT, previous: $error);
|
||||
break;
|
||||
case 'Utopia\Database\Exception\Query':
|
||||
$error = new AppwriteException(AppwriteException::GENERAL_QUERY_INVALID, $error->getMessage(), previous: $error);
|
||||
break;
|
||||
case 'Utopia\Database\Exception\Structure':
|
||||
$error = new AppwriteException(AppwriteException::DOCUMENT_INVALID_STRUCTURE, $error->getMessage(), previous: $error);
|
||||
break;
|
||||
case 'Utopia\Database\Exception\Duplicate':
|
||||
$error = new AppwriteException(AppwriteException::DOCUMENT_ALREADY_EXISTS);
|
||||
break;
|
||||
case 'Utopia\Database\Exception\Restricted':
|
||||
$error = new AppwriteException(AppwriteException::DOCUMENT_DELETE_RESTRICTED);
|
||||
break;
|
||||
case 'Utopia\Database\Exception\Authorization':
|
||||
$error = new AppwriteException(AppwriteException::USER_UNAUTHORIZED);
|
||||
break;
|
||||
}
|
||||
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
|
||||
if ($error instanceof AppwriteException) {
|
||||
$publish = $error->isPublishable();
|
||||
} else {
|
||||
$publish = $error->getCode() === 0 || $error->getCode() >= 500;
|
||||
}
|
||||
|
||||
if ($logger && ($publish || $error->getCode() === 0)) {
|
||||
if ($error->getCode() >= 400 && $error->getCode() < 500) {
|
||||
// Register error logger
|
||||
$providerName = App::getEnv('_APP_EXPERIMENT_LOGGING_PROVIDER', '');
|
||||
$providerConfig = App::getEnv('_APP_EXPERIMENT_LOGGING_CONFIG', '');
|
||||
|
||||
if (!(empty($providerName) || empty($providerConfig))) {
|
||||
if (!Logger::hasProvider($providerName)) {
|
||||
throw new Exception("Logging provider not supported. Logging is disabled");
|
||||
}
|
||||
|
||||
$classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName);
|
||||
$adapter = new $classname($providerConfig);
|
||||
$logger = new Logger($adapter);
|
||||
$publish = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($logger && $publish) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $utopia->getResource('user');
|
||||
} catch (\Throwable $th) {
|
||||
} catch (\Throwable) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
@@ -656,47 +923,6 @@ App::error()
|
||||
Console::info('Log pushed with status code: ' . $responseCode);
|
||||
}
|
||||
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
$file = $error->getFile();
|
||||
$line = $error->getLine();
|
||||
$trace = $error->getTrace();
|
||||
|
||||
if (php_sapi_name() === 'cli') {
|
||||
Console::error('[Error] Timestamp: ' . date('c', time()));
|
||||
|
||||
if ($route) {
|
||||
Console::error('[Error] Method: ' . $route->getMethod());
|
||||
Console::error('[Error] URL: ' . $route->getPath());
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
Console::error('[Error] Message: ' . $message);
|
||||
Console::error('[Error] File: ' . $file);
|
||||
Console::error('[Error] Line: ' . $line);
|
||||
}
|
||||
|
||||
/** Handle Utopia Errors */
|
||||
if ($error instanceof Utopia\Exception) {
|
||||
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
|
||||
switch ($code) {
|
||||
case 400:
|
||||
$error->setType(AppwriteException::GENERAL_ARGUMENT_INVALID);
|
||||
break;
|
||||
case 404:
|
||||
$error->setType(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
break;
|
||||
}
|
||||
} elseif ($error instanceof Utopia\Database\Exception\Conflict) {
|
||||
$error = new AppwriteException(AppwriteException::DOCUMENT_UPDATE_CONFLICT, previous: $error);
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
} elseif ($error instanceof Utopia\Database\Exception\Timeout) {
|
||||
$error = new AppwriteException(AppwriteException::DATABASE_TIMEOUT, previous: $error);
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
}
|
||||
|
||||
/** Wrap all exceptions inside Appwrite\Extend\Exception */
|
||||
if (!($error instanceof AppwriteException)) {
|
||||
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
|
||||
@@ -756,10 +982,10 @@ App::error()
|
||||
->setParam('development', App::isDevelopment())
|
||||
->setParam('projectName', $project->getAttribute('name'))
|
||||
->setParam('projectURL', $project->getAttribute('url'))
|
||||
->setParam('message', $error->getMessage())
|
||||
->setParam('type', $type)
|
||||
->setParam('code', $code)
|
||||
->setParam('trace', $trace);
|
||||
->setParam('message', $output['message'] ?? '')
|
||||
->setParam('type', $output['type'] ?? '')
|
||||
->setParam('code', $output['code'] ?? '')
|
||||
->setParam('trace', $output['trace'] ?? []);
|
||||
|
||||
$response->html($layout->render());
|
||||
}
|
||||
|
||||
+9
-4
@@ -112,7 +112,7 @@ 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 = 329;
|
||||
const APP_CACHE_BUSTER = 331;
|
||||
const APP_VERSION_STABLE = '1.4.13';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
@@ -195,6 +195,7 @@ const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
|
||||
// Usage metrics
|
||||
const METRIC_TEAMS = 'teams';
|
||||
const METRIC_USERS = 'users';
|
||||
const METRIC_MESSAGES = 'messages';
|
||||
const METRIC_SESSIONS = 'sessions';
|
||||
const METRIC_DATABASES = 'databases';
|
||||
const METRIC_COLLECTIONS = 'collections';
|
||||
@@ -1033,7 +1034,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
}
|
||||
|
||||
if (APP_MODE_ADMIN === $mode) {
|
||||
if ($user->find('teamId', $project->getAttribute('teamId'), 'memberships')) {
|
||||
if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
} else {
|
||||
$user = new Document([]);
|
||||
@@ -1265,7 +1266,9 @@ function getDevice($root): Device
|
||||
case Storage::DEVICE_S3:
|
||||
return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl);
|
||||
case STORAGE::DEVICE_DO_SPACES:
|
||||
return new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl);
|
||||
$device = new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl);
|
||||
$device->setHttpVersion(S3::HTTP_VERSION_1_1);
|
||||
return $device;
|
||||
case Storage::DEVICE_BACKBLAZE:
|
||||
return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl);
|
||||
case Storage::DEVICE_LINODE:
|
||||
@@ -1294,7 +1297,9 @@ function getDevice($root): Device
|
||||
$doSpacesRegion = App::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
|
||||
$doSpacesBucket = App::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
|
||||
$doSpacesAcl = 'private';
|
||||
return new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
|
||||
$device = new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
|
||||
$device->setHttpVersion(S3::HTTP_VERSION_1_1);
|
||||
return $device;
|
||||
case Storage::DEVICE_BACKBLAZE:
|
||||
$backblazeAccessKey = App::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', '');
|
||||
$backblazeSecretKey = App::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', '');
|
||||
|
||||
+91
-82
@@ -36,74 +36,83 @@ require_once __DIR__ . '/init.php';
|
||||
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
function getConsoleDB(): Database
|
||||
{
|
||||
global $register;
|
||||
// Allows overriding
|
||||
if (!function_exists("getConsoleDB")) {
|
||||
function getConsoleDB(): Database
|
||||
{
|
||||
global $register;
|
||||
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
$pools = $register->get('pools');
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
$pools = $register->get('pools');
|
||||
|
||||
$dbAdapter = $pools
|
||||
->get('console')
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
|
||||
$database = new Database($dbAdapter, getCache());
|
||||
|
||||
$database
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', '_console');
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
||||
function getProjectDB(Document $project): Database
|
||||
{
|
||||
global $register;
|
||||
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
$pools = $register->get('pools');
|
||||
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return getConsoleDB();
|
||||
}
|
||||
|
||||
$dbAdapter = $pools
|
||||
->get($project->getAttribute('database'))
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
|
||||
$database = new Database($dbAdapter, getCache());
|
||||
|
||||
$database
|
||||
->setNamespace('_' . $project->getInternalId())
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
||||
function getCache(): Cache
|
||||
{
|
||||
global $register;
|
||||
|
||||
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
|
||||
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
$adapters = [];
|
||||
|
||||
foreach ($list as $value) {
|
||||
$adapters[] = $pools
|
||||
->get($value)
|
||||
$dbAdapter = $pools
|
||||
->get('console')
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
}
|
||||
|
||||
return new Cache(new Sharding($adapters));
|
||||
$database = new Database($dbAdapter, getCache());
|
||||
|
||||
$database
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', '_console');
|
||||
|
||||
return $database;
|
||||
}
|
||||
}
|
||||
|
||||
// Allows overriding
|
||||
if (!function_exists("getProjectDB")) {
|
||||
function getProjectDB(Document $project): Database
|
||||
{
|
||||
global $register;
|
||||
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
$pools = $register->get('pools');
|
||||
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return getConsoleDB();
|
||||
}
|
||||
|
||||
$dbAdapter = $pools
|
||||
->get($project->getAttribute('database'))
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
|
||||
$database = new Database($dbAdapter, getCache());
|
||||
|
||||
$database
|
||||
->setNamespace('_' . $project->getInternalId())
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
return $database;
|
||||
}
|
||||
}
|
||||
|
||||
// Allows overriding
|
||||
if (!function_exists("getCache")) {
|
||||
function getCache(): Cache
|
||||
{
|
||||
global $register;
|
||||
|
||||
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
|
||||
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
$adapters = [];
|
||||
|
||||
foreach ($list as $value) {
|
||||
$adapters[] = $pools
|
||||
->get($value)
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
}
|
||||
|
||||
return new Cache(new Sharding($adapters));
|
||||
}
|
||||
}
|
||||
|
||||
$realtime = new Realtime();
|
||||
@@ -204,29 +213,29 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
/**
|
||||
* Save current connections to the Database every 5 seconds.
|
||||
*/
|
||||
Timer::tick(5000, function () use ($register, $stats, &$statsDocument, $logError) {
|
||||
$payload = [];
|
||||
foreach ($stats as $projectId => $value) {
|
||||
$payload[$projectId] = $stats->get($projectId, 'connectionsTotal');
|
||||
}
|
||||
if (empty($payload) || empty($statsDocument)) {
|
||||
return;
|
||||
}
|
||||
// Timer::tick(5000, function () use ($register, $stats, &$statsDocument, $logError) {
|
||||
// $payload = [];
|
||||
// foreach ($stats as $projectId => $value) {
|
||||
// $payload[$projectId] = $stats->get($projectId, 'connectionsTotal');
|
||||
// }
|
||||
// if (empty($payload) || empty($statsDocument)) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
try {
|
||||
$database = getConsoleDB();
|
||||
// try {
|
||||
// $database = getConsoleDB();
|
||||
|
||||
$statsDocument
|
||||
->setAttribute('timestamp', DateTime::now())
|
||||
->setAttribute('value', json_encode($payload));
|
||||
// $statsDocument
|
||||
// ->setAttribute('timestamp', DateTime::now())
|
||||
// ->setAttribute('value', json_encode($payload));
|
||||
|
||||
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
} catch (Throwable $th) {
|
||||
call_user_func($logError, $th, "updateWorkerDocument");
|
||||
} finally {
|
||||
$register->get('pools')->reclaim();
|
||||
}
|
||||
});
|
||||
// Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
// } catch (Throwable $th) {
|
||||
// call_user_func($logError, $th, "updateWorkerDocument");
|
||||
// } finally {
|
||||
// $register->get('pools')->reclaim();
|
||||
// }
|
||||
// });
|
||||
});
|
||||
|
||||
$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime, $logError) {
|
||||
|
||||
@@ -59,7 +59,7 @@ $title = $this->getParam('title', '')
|
||||
<link rel="stylesheet" href="/fonts/main.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="content-security-policy" content="">
|
||||
<title><?php echo $this->print($title, self::FILTER_ESCAPE); ?></title>
|
||||
<title><?php echo $this->print($title); ?></title>
|
||||
|
||||
<style>
|
||||
@media(min-width:768px) {
|
||||
@@ -74,11 +74,11 @@ $title = $this->getParam('title', '')
|
||||
<div class="container u-margin-block-start-24">
|
||||
<article class="card u-padding-16">
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<h1 class="heading-level-4 u-trim-1">Error <?php echo $this->print($code, self::FILTER_ESCAPE); ?></h1>
|
||||
<p class="text"><?php echo $this->print($message, self::FILTER_ESCAPE); ?></p>
|
||||
<h1 class="heading-level-4 u-trim-1">Error <?php echo $this->print($code); ?></h1>
|
||||
<p class="text"><?php echo $this->print($message); ?></p>
|
||||
<div class="u-flex u-flex-vertical u-gap-8">
|
||||
<p class="text">Type</p>
|
||||
<p><code class="inline-code"><?php echo $this->print($type, self::FILTER_ESCAPE); ?></code></p>
|
||||
<p><code class="inline-code"><?php echo $this->print($type); ?></code></p>
|
||||
</div>
|
||||
<?php if ($development) : ?>
|
||||
<h2 class="heading-level-5 u-trim-1">Error Trace</h2>
|
||||
@@ -111,45 +111,6 @@ $title = $this->getParam('title', '')
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<!--section>
|
||||
<h1>Error <?php echo $this->print($code, self::FILTER_ESCAPE); ?></h1>
|
||||
|
||||
<p><?php echo $this->print($message, self::FILTER_ESCAPE); ?></p>
|
||||
|
||||
<small>Error ID: <?php echo $this->print($errorID, self::FILTER_ESCAPE); ?></small>
|
||||
|
||||
<?php if (!empty($projectURL)) : ?>
|
||||
<hr />
|
||||
|
||||
<p><a href="<?php echo $this->print($projectURL, self::FILTER_ESCAPE); ?>" rel="noopener">Back to <?php echo $this->print($projectName, self::FILTER_ESCAPE); ?></a></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($development) : ?>
|
||||
<div>
|
||||
|
||||
<h2>Error Trace</h2>
|
||||
|
||||
<?php foreach ($trace as $log) : ?>
|
||||
<table>
|
||||
<?php foreach ($log as $key => $value) : ?>
|
||||
<tr>
|
||||
<td style="width: 120px"><?php echo $this->print($key, self::FILTER_ESCAPE); ?></td>
|
||||
<td>
|
||||
<?php if (is_array($value)) : ?>
|
||||
<?php echo $this->print(var_export($value, true), self::FILTER_ESCAPE); ?>
|
||||
<?php else : ?>
|
||||
<?php echo $this->print($value, self::FILTER_ESCAPE); ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</section-->
|
||||
<script type="text/javascript">
|
||||
const app = (JSON.parse(localStorage.getItem('appwrite')) ?? {});
|
||||
const theme = app.theme ?? 'auto';
|
||||
|
||||
@@ -143,6 +143,7 @@ services:
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_MAINTENANCE_INTERVAL
|
||||
- _APP_MAINTENANCE_DELAY
|
||||
- _APP_MAINTENANCE_RETENTION_EXECUTION
|
||||
- _APP_MAINTENANCE_RETENTION_CACHE
|
||||
- _APP_MAINTENANCE_RETENTION_ABUSE
|
||||
|
||||
+11
-12
@@ -14,6 +14,7 @@ use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Phone;
|
||||
use Appwrite\Event\Usage;
|
||||
use Appwrite\Event\UsageDump;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Swoole\Runtime;
|
||||
use Utopia\App;
|
||||
@@ -146,6 +147,9 @@ Server::setResource('log', fn() => new Log());
|
||||
Server::setResource('queueForUsage', function (Connection $queue) {
|
||||
return new Usage($queue);
|
||||
}, ['queue']);
|
||||
Server::setResource('queueForUsageDump', function (Connection $queue) {
|
||||
return new UsageDump($queue);
|
||||
}, ['queue']);
|
||||
Server::setResource('queue', function (Group $pools) {
|
||||
return $pools->get('queue')->pop()->getResource();
|
||||
}, ['pools']);
|
||||
@@ -179,9 +183,6 @@ 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']);
|
||||
@@ -249,7 +250,6 @@ try {
|
||||
Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine());
|
||||
}
|
||||
|
||||
|
||||
$worker = $platform->getWorker();
|
||||
|
||||
$worker
|
||||
@@ -264,7 +264,8 @@ $worker
|
||||
->inject('error')
|
||||
->inject('logger')
|
||||
->inject('log')
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log) {
|
||||
->inject('project')
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log, Document $project) {
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
if ($error instanceof PDOException) {
|
||||
@@ -280,6 +281,7 @@ $worker
|
||||
$log->setAction('appwrite-queue-' . App::getEnv('QUEUE'));
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('projectId', $project->getId() ?? 'n/a');
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
@@ -299,12 +301,9 @@ $worker
|
||||
Console::error('[Error] Line: ' . $error->getLine());
|
||||
});
|
||||
|
||||
try {
|
||||
$workerStart = $worker->getWorkerStart();
|
||||
} catch (\Throwable $error) {
|
||||
$worker->workerStart();
|
||||
} finally {
|
||||
Console::info("Worker $workerName started");
|
||||
}
|
||||
$worker->workerStart()
|
||||
->action(function () use ($workerName) {
|
||||
Console::info("Worker $workerName started");
|
||||
});
|
||||
|
||||
$worker->start();
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php create-inf-metric $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php patch-delete-project-collections $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php patch-delete-schedule-updated-at-attribute $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php patch-recreate-repositories-documents $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php volume-sync $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/worker.php usage-dump $@
|
||||
+7
-5
@@ -1,4 +1,5 @@
|
||||
{
|
||||
|
||||
"name": "appwrite/server-ce",
|
||||
"description": "End to end backend server for frontend and mobile apps.",
|
||||
"type": "project",
|
||||
@@ -49,18 +50,19 @@
|
||||
"utopia-php/cache": "0.9.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.45.*",
|
||||
"utopia-php/domains": "0.3.*",
|
||||
"utopia-php/database": "0.45.14",
|
||||
"utopia-php/domains": "0.5.*",
|
||||
"utopia-php/dsn": "0.1.*",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/image": "0.5.*",
|
||||
"utopia-php/fetch": "0.2.*",
|
||||
"utopia-php/image": "0.6.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/logger": "0.3.*",
|
||||
"utopia-php/messaging": "0.2.*",
|
||||
"utopia-php/messaging": "0.3.*",
|
||||
"utopia-php/migration": "0.3.*",
|
||||
"utopia-php/orchestration": "0.9.*",
|
||||
"utopia-php/platform": "0.5.*",
|
||||
"utopia-php/pools": "0.4.*",
|
||||
"utopia-php/pools": "0.5.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/queue": "0.7.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
|
||||
Generated
+342
-225
File diff suppressed because it is too large
Load Diff
+55
-27
@@ -5,12 +5,12 @@
|
||||
|
||||
x-logging: &x-logging
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-file: '5'
|
||||
max-size: '10m'
|
||||
max-file: "5"
|
||||
max-size: "10m"
|
||||
|
||||
version: '3'
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
@@ -53,7 +53,7 @@ services:
|
||||
DEBUG: false
|
||||
TESTING: true
|
||||
VERSION: dev
|
||||
ports:
|
||||
ports:
|
||||
- 9501:80
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -185,6 +185,8 @@ services:
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
|
||||
- _APP_ASSISTANT_OPENAI_API_KEY
|
||||
- _APP_EXPERIMENT_LOGGING_PROVIDER
|
||||
- _APP_EXPERIMENT_LOGGING_CONFIG
|
||||
|
||||
appwrite-realtime:
|
||||
entrypoint: realtime
|
||||
@@ -645,6 +647,7 @@ services:
|
||||
- _APP_MAINTENANCE_RETENTION_AUDIT
|
||||
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
|
||||
- _APP_MAINTENANCE_RETENTION_SCHEDULES
|
||||
- _APP_MAINTENANCE_DELAY
|
||||
|
||||
appwrite-worker-usage:
|
||||
entrypoint: worker-usage
|
||||
@@ -677,6 +680,37 @@ services:
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
|
||||
appwrite-worker-usage-dump:
|
||||
entrypoint: worker-usage-dump
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-usage-dump
|
||||
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_USAGE_STATS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
|
||||
appwrite-schedule:
|
||||
entrypoint: schedule
|
||||
<<: *x-logging
|
||||
@@ -717,7 +751,7 @@ services:
|
||||
hostname: appwrite-executor
|
||||
<<: *x-logging
|
||||
stop_signal: SIGINT
|
||||
image: openruntimes/executor:0.4.5
|
||||
image: openruntimes/executor:0.5.1
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -800,7 +834,7 @@ services:
|
||||
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
|
||||
- MYSQL_USER=${_APP_DB_USER}
|
||||
- MYSQL_PASSWORD=${_APP_DB_PASS}
|
||||
command: 'mysqld --innodb-flush-method=fsync' # add ' --query_cache_size=0' for DB tests
|
||||
command: "mysqld --innodb-flush-method=fsync" # add ' --query_cache_size=0' for DB tests
|
||||
# command: mv /var/lib/mysql/ib_logfile0 /var/lib/mysql/ib_logfile0.bu && mv /var/lib/mysql/ib_logfile1 /var/lib/mysql/ib_logfile1.bu
|
||||
|
||||
# smtp:
|
||||
@@ -846,15 +880,16 @@ services:
|
||||
#
|
||||
# MailCatcher - An SMTP server. Catches all system emails and displays them in a nice UI.
|
||||
# RequestCatcher - An HTTP server. Catches all system https calls and displays them using a simple HTTP API. Used to debug & tests webhooks and HTTP tasks
|
||||
# RedisCommander - A nice UI for exploring Redis data
|
||||
# Webgrind - A nice UI for exploring and debugging code-level stuff
|
||||
# Redis Insight - A nice UI for exploring Redis data
|
||||
# Adminer - A nice UI for exploring MariaDB data
|
||||
# GraphQl Explorer - A nice UI for exploring GraphQL API
|
||||
|
||||
maildev: # used mainly for dev tests
|
||||
image: appwrite/mailcatcher:1.0.0
|
||||
container_name: appwrite-mailcatcher
|
||||
<<: *x-logging
|
||||
ports:
|
||||
- '9503:1080'
|
||||
- "9503:1080"
|
||||
networks:
|
||||
- appwrite
|
||||
|
||||
@@ -863,7 +898,7 @@ services:
|
||||
container_name: appwrite-requestcatcher
|
||||
<<: *x-logging
|
||||
ports:
|
||||
- '9504:5000'
|
||||
- "9504:5000"
|
||||
networks:
|
||||
- appwrite
|
||||
|
||||
@@ -877,21 +912,15 @@ services:
|
||||
networks:
|
||||
- appwrite
|
||||
|
||||
# redis-commander:
|
||||
# image: rediscommander/redis-commander:latest
|
||||
# restart: unless-stopped
|
||||
# networks:
|
||||
# - appwrite
|
||||
# environment:
|
||||
# - REDIS_HOSTS=redis
|
||||
# ports:
|
||||
# - "8081:8081"
|
||||
# webgrind:
|
||||
# image: 'jokkedk/webgrind:latest'
|
||||
# volumes:
|
||||
# - './debug:/tmp'
|
||||
# ports:
|
||||
# - '3001:80'
|
||||
redis-insight:
|
||||
image: redis/redisinsight:latest
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
environment:
|
||||
- REDIS_HOSTS=redis
|
||||
ports:
|
||||
- "8081:5540"
|
||||
|
||||
graphql-explorer:
|
||||
container_name: appwrite-graphql-explorer
|
||||
@@ -904,7 +933,6 @@ services:
|
||||
environment:
|
||||
- SERVER_URL=http://localhost/v1/graphql
|
||||
|
||||
|
||||
# Dev Tools End ------------------------------------------------------------------------------------------
|
||||
|
||||
networks:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.create_phone_verification()
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.create_recovery(email: 'email@example.com', url: 'https://example.com')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.create_verification(url: 'https://example.com')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.delete_identity(identity_id: '[IDENTITY_ID]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.delete_session(session_id: '[SESSION_ID]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.delete_sessions()
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.get_prefs()
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.get_session(session_id: '[SESSION_ID]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.get()
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.list_identities()
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.list_logs()
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.list_sessions()
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.update_email(email: 'email@example.com', password: 'password')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.update_name(name: '[NAME]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.update_password(password: '')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.update_phone_verification(user_id: '[USER_ID]', secret: '[SECRET]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.update_phone(phone: '+12065550100', password: 'password')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.update_prefs(prefs: {})
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.update_recovery(user_id: '[USER_ID]', secret: '[SECRET]', password: 'password', password_again: 'password')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.update_session(session_id: '[SESSION_ID]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.update_status()
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ account = Account.new(client)
|
||||
|
||||
response = account.update_verification(user_id: '[USER_ID]', secret: '[SECRET]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
|
||||
|
||||
response = avatars.get_browser(code: 'aa')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
|
||||
|
||||
response = avatars.get_credit_card(code: 'amex')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
|
||||
|
||||
response = avatars.get_favicon(url: 'https://example.com')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
|
||||
|
||||
response = avatars.get_flag(code: 'af')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
|
||||
|
||||
response = avatars.get_image(url: 'https://example.com')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
|
||||
|
||||
response = avatars.get_initials()
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
|
||||
|
||||
response = avatars.get_qr(text: '[TEXT]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_boolean_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_collection(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', name: '[NAME]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_datetime_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_document(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', document_id: '[DOCUMENT_ID]', data: {})
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_email_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_enum_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', elements: [], required: false)
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_float_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_index(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', type: 'key', attributes: [])
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_integer_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_ip_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_relationship_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', related_collection_id: '[RELATED_COLLECTION_ID]', type: 'oneToOne')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_string_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', size: 1, required: false)
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create_url_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.create(database_id: '[DATABASE_ID]', name: '[NAME]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.delete_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.delete_collection(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.delete_document(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', document_id: '[DOCUMENT_ID]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.delete_index(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.delete(database_id: '[DATABASE_ID]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.get_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.get_collection(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.get_document(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', document_id: '[DOCUMENT_ID]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.get_index(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.get(database_id: '[DATABASE_ID]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.list_attributes(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'Appwrite'
|
||||
require 'appwrite'
|
||||
|
||||
include Appwrite
|
||||
|
||||
@@ -11,4 +11,4 @@ databases = Databases.new(client)
|
||||
|
||||
response = databases.list_collections(database_id: '[DATABASE_ID]')
|
||||
|
||||
puts response.inspect
|
||||
puts response.inspect
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user