Compare commits

..
Author SHA1 Message Date
Chirag Aggarwal e3a093c960 chore: update version to 66 2025-04-24 13:15:16 +00:00
Chirag Aggarwal 20c3663736 chore: update database library to 0.65.x 2025-04-24 09:05:16 +00:00
22922 changed files with 39211 additions and 314376 deletions
+1 -7
View File
@@ -8,10 +8,4 @@ reviews:
base_branches:
- main
- 1.6.x
- 1.7.x
- 1.8.x
high_level_summary: false
poem: false
sequence_diagrams: false
collapse_walkthrough: true
changed_files_summary: false
- 1.7.x
+3 -4
View File
@@ -15,7 +15,6 @@ _APP_SYSTEM_TEAM_EMAIL=team@appwrite.io
_APP_EMAIL_SECURITY=security@appwrite.io
_APP_EMAIL_CERTIFICATES=certificates@appwrite.io
_APP_SYSTEM_RESPONSE_FORMAT=
_APP_CUSTOM_DOMAIN_DENY_LIST=
_APP_OPTIONS_ABUSE=disabled
_APP_OPTIONS_ROUTER_PROTECTION=disabled
_APP_OPTIONS_FORCE_HTTPS=disabled
@@ -24,7 +23,7 @@ _APP_OPENSSL_KEY_V1=your-secret-key
_APP_DOMAIN=traefik
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_SITES=sites.localhost
_APP_DOMAIN_TARGET_CNAME=test.localhost
_APP_DOMAIN_TARGET_CNAME=test.appwrite.io
_APP_DOMAIN_TARGET_A=127.0.0.1
_APP_DOMAIN_TARGET_AAAA=::1
_APP_RULES_FORMAT=md5
@@ -85,9 +84,9 @@ _APP_COMPUTE_RUNTIMES_NETWORK=runtimes
_APP_EXECUTOR_SECRET=your-secret-key
_APP_EXECUTOR_HOST=http://exc1/v1
_APP_FUNCTIONS_RUNTIMES=php-8.0,node-18.0,python-3.9,ruby-3.1
_APP_SITES_RUNTIMES=static-1,node-22,flutter-3.32
_APP_SITES_RUNTIMES=static-1,node-22,flutter-3.29
_APP_MAINTENANCE_INTERVAL=86400
_APP_MAINTENANCE_START_TIME=12:00
_APP_MAINTENANCE_DELAY=
_APP_MAINTENANCE_RETENTION_CACHE=2592000
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
_APP_MAINTENANCE_RETENTION_ABUSE=86400
+5 -26
View File
@@ -1,46 +1,25 @@
name: "SDK Preview"
name: "Console SDK Preview"
on:
pull_request:
paths:
- 'app/config/specs/*-latest-console.json'
workflow_dispatch:
inputs:
platform:
type: choice
description: "Platform to build"
options:
- client
- server
jobs:
setup:
name: Setup & Build SDK
name: Setup & Build Console SDK
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set SDK type
id: set-sdk
run: |
PLATFORM="${{ github.event.inputs.platform }}"
if [ -z "$PLATFORM" ]; then
PLATFORM="console"
fi
if [ "$PLATFORM" = "server" ]; then
echo "sdk_type=nodejs" >> $GITHUB_OUTPUT
else
echo "sdk_type=web" >> $GITHUB_OUTPUT
fi
echo "platform=$PLATFORM" >> $GITHUB_OUTPUT
- name: Load and Start Appwrite
run: |
docker compose build
docker compose up -d
docker compose exec appwrite sdks --platform=${{ steps.set-sdk.outputs.platform }} --sdk=${{ steps.set-sdk.outputs.sdk_type }} --version=latest --git=no
sudo chown -R $USER:$USER ./app/sdks/${{ steps.set-sdk.outputs.platform }}-${{ steps.set-sdk.outputs.sdk_type }}
docker compose exec appwrite sdks --platform=console --sdk=web --version=latest --git=no
sudo chown -R $USER:$USER ./app/sdks/console-web
- uses: actions/setup-node@v4
with:
+3 -4
View File
@@ -156,7 +156,6 @@ jobs:
Sites,
Proxy,
Storage,
Tokens,
Teams,
Users,
Webhooks,
@@ -198,7 +197,7 @@ jobs:
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude=devKeys
e2e_shared_mode_test:
name: E2E Shared Mode Service Test
@@ -277,7 +276,7 @@ jobs:
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude=devKeys
e2e_dev_keys:
name: E2E Service Test (Dev Keys)
@@ -303,7 +302,7 @@ jobs:
docker compose up -d
sleep 30
- name: Run Projects tests with dev keys in dedicated table mode
- name: Run Projects tests with dev keys in ${{ matrix.tables-mode }} table mode
run: |
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
-264
View File
@@ -1,267 +1,3 @@
# Version 1.6.2
## What's Changed
### Notable changes
* Delete git folder to reduce build size in [9076](https://github.com/appwrite/appwrite/pull/9076)
* Upgrade assistant in [9100](https://github.com/appwrite/appwrite/pull/9100)
* Use redis adapter for abuse in [9121](https://github.com/appwrite/appwrite/pull/9121)
* Set base specification CPUs to 0.5 again in [9146](https://github.com/appwrite/appwrite/pull/9146)
* Add new push message parameters in [9060](https://github.com/appwrite/appwrite/pull/9060)
* Update audits to include user type in [9211](https://github.com/appwrite/appwrite/pull/9211)
* Enable HEIC in [9251](https://github.com/appwrite/appwrite/pull/9251)
* Added teamName to membership redirect url in [9269](https://github.com/appwrite/appwrite/pull/9269)
* Add support endpoint url for S3 in [9303](https://github.com/appwrite/appwrite/pull/9303)
* Added RuPay Credit Card Icon in Avatars Service in [5046](https://github.com/appwrite/appwrite/pull/5046)
* Add figma oauth provider in [9623](https://github.com/appwrite/appwrite/pull/9623)
* Update console to version 5.2.58 in [9637](https://github.com/appwrite/appwrite/pull/9637)
### Fixes
* Remove failed attribute in [9032](https://github.com/appwrite/appwrite/pull/9032)
* Fix delete notFound attribute in [9038](https://github.com/appwrite/appwrite/pull/9038)
* 🇮🇸 Added missing Icelandic translations for email strings. in [4848](https://github.com/appwrite/appwrite/pull/4848)
* fix doc comment for filter method in [5769](https://github.com/appwrite/appwrite/pull/5769)
* Delete attribute No throwing Exception on not found in [9157](https://github.com/appwrite/appwrite/pull/9157)
* Fix VCS identity collision in [9138](https://github.com/appwrite/appwrite/pull/9138)
* Fix disabling of email-otp when user wants to in [9200](https://github.com/appwrite/appwrite/pull/9200)
* Ensure user can delete session in [9209](https://github.com/appwrite/appwrite/pull/9209)
* Fix resend invitation in [9218](https://github.com/appwrite/appwrite/pull/9218)
* Fix phone number parsing exception handling in [9246](https://github.com/appwrite/appwrite/pull/9246)
* Fix amazon oauth in [9253](https://github.com/appwrite/appwrite/pull/9253)
* Fix slack oauth scopes, and updated to v2 in [9228](https://github.com/appwrite/appwrite/pull/9228)
* Fix forwarded user agent in [9271](https://github.com/appwrite/appwrite/pull/9271)
* Fix WEBP File Preview Rendering Issue in [9321](https://github.com/appwrite/appwrite/pull/9321)
* Fix build memory specifications in [9360](https://github.com/appwrite/appwrite/pull/9360)
* Fix Self Hosting functions by adding missed config in [9373](https://github.com/appwrite/appwrite/pull/9373)
* Fix resend team invite if already accepted in [9348](https://github.com/appwrite/appwrite/pull/9348)
* Fix null errors on team invite in [9391](https://github.com/appwrite/appwrite/pull/9391)
* Fix email (smtp) to multiple recipients in [9243](https://github.com/appwrite/appwrite/pull/9243)
* Fix stats timing by using receivedAt date when available in [9428](https://github.com/appwrite/appwrite/pull/9428)
* Make min/max params optional for attribute update in [9387](https://github.com/appwrite/appwrite/pull/9387)
* Fix blocking of phone sessions when disabled on console in [9447](https://github.com/appwrite/appwrite/pull/9447)
* Fix logging config in [9467](https://github.com/appwrite/appwrite/pull/9467)
* Update audit timestamp origin in [9481](https://github.com/appwrite/appwrite/pull/9481)
* Fix certificates in deletes worker in [9466](https://github.com/appwrite/appwrite/pull/9466)
* Fix console audits delete in [9547](https://github.com/appwrite/appwrite/pull/9547)
* Fix migrations in [9633](https://github.com/appwrite/appwrite/pull/9633)
* Ensure all 4xx errors in OAuth redirect lead to the failure URL in [9679](https://github.com/appwrite/appwrite/pull/9679)
* Treat 0 as unlimited for CPUs and memory in [9638](https://github.com/appwrite/appwrite/pull/9638)
* Add contextual dispatch logic to fix high CPU usage in [9687](https://github.com/appwrite/appwrite/pull/9687)
### Miscellaneous
* Merge 1.6.x into feat-custom-cf-hostnames in [8904](https://github.com/appwrite/appwrite/pull/8904)
* Improve compression param checks in [8922](https://github.com/appwrite/appwrite/pull/8922)
* upgrade utopia storage in [8930](https://github.com/appwrite/appwrite/pull/8930)
* Feat migration in [8797](https://github.com/appwrite/appwrite/pull/8797)
* feat fix web routes in [8962](https://github.com/appwrite/appwrite/pull/8962)
* Fix no pool access in [9027](https://github.com/appwrite/appwrite/pull/9027)
* feat: use environment variable to check rules format in [9039](https://github.com/appwrite/appwrite/pull/9039)
* Update storage.php in [9037](https://github.com/appwrite/appwrite/pull/9037)
* Upgrade db 0.53.200 in [9050](https://github.com/appwrite/appwrite/pull/9050)
* Chore: upgrade utopia storage in [9066](https://github.com/appwrite/appwrite/pull/9066)
* Update usage-dump payload in [9085](https://github.com/appwrite/appwrite/pull/9085)
* GitHub Workflows security hardening in [3728](https://github.com/appwrite/appwrite/pull/3728)
* Update add-oauth2-provider.md in [4313](https://github.com/appwrite/appwrite/pull/4313)
* update readme-cn some doc in [5278](https://github.com/appwrite/appwrite/pull/5278)
* Add accessibility features in [7042](https://github.com/appwrite/appwrite/pull/7042)
* Add Appwrite Cloud to read me. in [5445](https://github.com/appwrite/appwrite/pull/5445)
* Migration throw error in [9092](https://github.com/appwrite/appwrite/pull/9092)
* Fix usage payload bug in [9097](https://github.com/appwrite/appwrite/pull/9097)
* chore: replace occurrences of dbForConsole to dbForPlatform in [9096](https://github.com/appwrite/appwrite/pull/9096)
* fix(realtime): decrement connectionCounter only if connection is known in [9055](https://github.com/appwrite/appwrite/pull/9055)
* payload bug fix in [9098](https://github.com/appwrite/appwrite/pull/9098)
* Fix usage payload bug in [9099](https://github.com/appwrite/appwrite/pull/9099)
* Usage payload debug in [9101](https://github.com/appwrite/appwrite/pull/9101)
* Usage payload debug in [9103](https://github.com/appwrite/appwrite/pull/9103)
* Usage payload debug in [9104](https://github.com/appwrite/appwrite/pull/9104)
* Feat: createFunction abuse labels in [9102](https://github.com/appwrite/appwrite/pull/9102)
* Docs-create-document in [9105](https://github.com/appwrite/appwrite/pull/9105)
* Docs: Create document and unknown attribute error messages. in [5427](https://github.com/appwrite/appwrite/pull/5427)
* Fix: update project accessed at from router and schedulers in [9109](https://github.com/appwrite/appwrite/pull/9109)
* chore: initial commit in [9111](https://github.com/appwrite/appwrite/pull/9111)
* chore: optimise webhooks payload in [9115](https://github.com/appwrite/appwrite/pull/9115)
* Revert "chore: initial commit" in [9117](https://github.com/appwrite/appwrite/pull/9117)
* chore: fix attribute name in [9118](https://github.com/appwrite/appwrite/pull/9118)
* Migrate to redis abuse in [9124](https://github.com/appwrite/appwrite/pull/9124)
* Added webhooks usage stats in [9125](https://github.com/appwrite/appwrite/pull/9125)
* chore remove abuse cleanup in [9137](https://github.com/appwrite/appwrite/pull/9137)
* fix: remove abuse delete trigger in [9139](https://github.com/appwrite/appwrite/pull/9139)
* Remove firebase OAuth API endpoints in [9144](https://github.com/appwrite/appwrite/pull/9144)
* chore: release client sdks in [9112](https://github.com/appwrite/appwrite/pull/9112)
* Update general.php in [9155](https://github.com/appwrite/appwrite/pull/9155)
* feat(swoole): allow configuration override of available cpus in [9177](https://github.com/appwrite/appwrite/pull/9177)
* Usage databases api read writes addition in [9142](https://github.com/appwrite/appwrite/pull/9142)
* Fix dead connections in [9190](https://github.com/appwrite/appwrite/pull/9190)
* Add hostname to audits in [9165](https://github.com/appwrite/appwrite/pull/9165)
* chore: shifted authphone usage tracking to api calls in [9191](https://github.com/appwrite/appwrite/pull/9191)
* Revert "Fix dead connections" in [9201](https://github.com/appwrite/appwrite/pull/9201)
* Add assertEventually to messaging provider logs test in [9192](https://github.com/appwrite/appwrite/pull/9192)
* feat project sms usage in [9198](https://github.com/appwrite/appwrite/pull/9198)
* chore: add audit labels to project resources in [9056](https://github.com/appwrite/appwrite/pull/9056)
* fix sms usage in [9207](https://github.com/appwrite/appwrite/pull/9207)
* Update database in [9202](https://github.com/appwrite/appwrite/pull/9202)
* Fix dead connections in [9213](https://github.com/appwrite/appwrite/pull/9213)
* Revert "Fix dead connections" in [9214](https://github.com/appwrite/appwrite/pull/9214)
* Add logs db init for consistency in [9163](https://github.com/appwrite/appwrite/pull/9163)
* Split the collection definitions in [9153](https://github.com/appwrite/appwrite/pull/9153)
* Log path with populated parameters in [9220](https://github.com/appwrite/appwrite/pull/9220)
* Add missing scope on function template in [9208](https://github.com/appwrite/appwrite/pull/9208)
* Add relatedCollection default in [9225](https://github.com/appwrite/appwrite/pull/9225)
* fix: function usage in [9235](https://github.com/appwrite/appwrite/pull/9235)
* feat: optimise events payloads in [9232](https://github.com/appwrite/appwrite/pull/9232)
* Optimise webhook events in [9168](https://github.com/appwrite/appwrite/pull/9168)
* fix: maintenance job missing type in [9238](https://github.com/appwrite/appwrite/pull/9238)
* Update Fetch to 0.3.0 in [9245](https://github.com/appwrite/appwrite/pull/9245)
* Fix maintenance job in [9247](https://github.com/appwrite/appwrite/pull/9247)
* chore: add missing case for executions in [9248](https://github.com/appwrite/appwrite/pull/9248)
* Add index dependency exception in [9226](https://github.com/appwrite/appwrite/pull/9226)
* chore: fix benchmarking test when made from fork in [9233](https://github.com/appwrite/appwrite/pull/9233)
* Update SDK Generator versions in [9188](https://github.com/appwrite/appwrite/pull/9188)
* chore: skipped job instead of throwing error in [9250](https://github.com/appwrite/appwrite/pull/9250)
* Implement new SDK Class on 1.6.x in [9237](https://github.com/appwrite/appwrite/pull/9237)
* Delete collection before Appwrite's attributes in [9256](https://github.com/appwrite/appwrite/pull/9256)
* Feat batch usage dump in [9255](https://github.com/appwrite/appwrite/pull/9255)
* Fix cloud tests in [9261](https://github.com/appwrite/appwrite/pull/9261)
* Usage: Databases reads writes in [9260](https://github.com/appwrite/appwrite/pull/9260)
* Update: Latest sdk specs in [9274](https://github.com/appwrite/appwrite/pull/9274)
* Revert "Feat batch usage dump" in [9276](https://github.com/appwrite/appwrite/pull/9276)
* feat: add fast2SMS adapter in [9263](https://github.com/appwrite/appwrite/pull/9263)
* Update Sdk Generator dependency in [9280](https://github.com/appwrite/appwrite/pull/9280)
* Transformed at addition in [9281](https://github.com/appwrite/appwrite/pull/9281)
* Docs: clarify update endpoints only work on draft messages in [9236](https://github.com/appwrite/appwrite/pull/9236)
* Update sdk generator dependency in [9282](https://github.com/appwrite/appwrite/pull/9282)
* Revert "Transformed at addition" in [9284](https://github.com/appwrite/appwrite/pull/9284)
* replaced init for cloud link in [9285](https://github.com/appwrite/appwrite/pull/9285)
* Add transformed at in [9289](https://github.com/appwrite/appwrite/pull/9289)
* Make migrations use Dynamic keys for destination in [9291](https://github.com/appwrite/appwrite/pull/9291)
* Make sessions limit tests assert eventually in [9298](https://github.com/appwrite/appwrite/pull/9298)
* Chore update database in [9306](https://github.com/appwrite/appwrite/pull/9306)
* feat: add AMQP queues in [9287](https://github.com/appwrite/appwrite/pull/9287)
* fix(test): use assertEventually instead of while(true) in [9308](https://github.com/appwrite/appwrite/pull/9308)
* fix(certificate worker): events are published without queue name in [9309](https://github.com/appwrite/appwrite/pull/9309)
* chore: update utopia-php/queue to 0.8.1 in [9311](https://github.com/appwrite/appwrite/pull/9311)
* chore: update utopia-php/queue to 0.8.2 in [9312](https://github.com/appwrite/appwrite/pull/9312)
* fix(schedule-tasks): revert back to direct pool usage in [9313](https://github.com/appwrite/appwrite/pull/9313)
* feat: custom app schemes in [9262](https://github.com/appwrite/appwrite/pull/9262)
* Revert "feat: custom app schemes" in [9319](https://github.com/appwrite/appwrite/pull/9319)
* Restore "feat: custom app schemes"" in [9320](https://github.com/appwrite/appwrite/pull/9320)
* Revert "Restore "feat: custom app schemes""" in [9323](https://github.com/appwrite/appwrite/pull/9323)
* chore: update dependencies in [9330](https://github.com/appwrite/appwrite/pull/9330)
* Feat: logs DB in [9272](https://github.com/appwrite/appwrite/pull/9272)
* Catch invalid index in [9329](https://github.com/appwrite/appwrite/pull/9329)
* Fix: missing call for image transformations counting in [9342](https://github.com/appwrite/appwrite/pull/9342)
* Fix drop abuse on shared table project delete in [9346](https://github.com/appwrite/appwrite/pull/9346)
* Only run all table mode tests on db update in [9338](https://github.com/appwrite/appwrite/pull/9338)
* Fix: missing periodic metric in [9350](https://github.com/appwrite/appwrite/pull/9350)
* feat(builds): check if function is blocked before building in [9332](https://github.com/appwrite/appwrite/pull/9332)
* feat: batch create audit logs in [9347](https://github.com/appwrite/appwrite/pull/9347)
* Chore: Update migrations in [9355](https://github.com/appwrite/appwrite/pull/9355)
* Fix: metric time was not being written to DB in [9354](https://github.com/appwrite/appwrite/pull/9354)
* Fix patch index validation in [9356](https://github.com/appwrite/appwrite/pull/9356)
* Fix image trnasformation metrics in [9370](https://github.com/appwrite/appwrite/pull/9370)
* Use batch delete in worker in [9375](https://github.com/appwrite/appwrite/pull/9375)
* Fix Model Platform is missing response key: store in [9361](https://github.com/appwrite/appwrite/pull/9361)
* Feat key segmented usage in [9336](https://github.com/appwrite/appwrite/pull/9336)
* Feat messaging metrics in [9353](https://github.com/appwrite/appwrite/pull/9353)
* Fix removed audits for shared v2 in [9388](https://github.com/appwrite/appwrite/pull/9388)
* chore: bump utopia-php/image to 0.8.0 in [9390](https://github.com/appwrite/appwrite/pull/9390)
* Fix outdated CLI commands in documentation in [9122](https://github.com/appwrite/appwrite/pull/9122)
* disable logs display in [9398](https://github.com/appwrite/appwrite/pull/9398)
* Log batches per project in [9403](https://github.com/appwrite/appwrite/pull/9403)
* Batch per project in [9410](https://github.com/appwrite/appwrite/pull/9410)
* Fix: stats resources only queue projects accessed in last 3 hours in [9411](https://github.com/appwrite/appwrite/pull/9411)
* Track options requests in [9397](https://github.com/appwrite/appwrite/pull/9397)
* chore: bump docker-base in [9406](https://github.com/appwrite/appwrite/pull/9406)
* refactor: migrate Realtime::send calls to queueForRealtime in [9325](https://github.com/appwrite/appwrite/pull/9325)
* Revert "Fix: stats resources only queue projects accessed in last 3 hours" in [9424](https://github.com/appwrite/appwrite/pull/9424)
* Remove usage and usage dump in favor of stats-usage and stats-usage-dump in [9339](https://github.com/appwrite/appwrite/pull/9339)
* Fix: disable dual writing in [9429](https://github.com/appwrite/appwrite/pull/9429)
* Disable transformedAt update for console users in [9425](https://github.com/appwrite/appwrite/pull/9425)
* chore: add image transformation stats to usage endpoint in [9393](https://github.com/appwrite/appwrite/pull/9393)
* chore: added timeout to deployment builds in tests in [9426](https://github.com/appwrite/appwrite/pull/9426)
* fix: model for image transformations in usage project in [9442](https://github.com/appwrite/appwrite/pull/9442)
* Feat: calculate database storage in stats-resources in [9443](https://github.com/appwrite/appwrite/pull/9443)
* Activities batch writes in [9438](https://github.com/appwrite/appwrite/pull/9438)
* chore: bump cache 0.12.x in [9412](https://github.com/appwrite/appwrite/pull/9412)
* chore: queue console project for maintenance delete in [9479](https://github.com/appwrite/appwrite/pull/9479)
* chore: added logsdb for deletes worker in [9462](https://github.com/appwrite/appwrite/pull/9462)
* Feat: calculate and log time taken for each project in [9491](https://github.com/appwrite/appwrite/pull/9491)
* chore: update initializing dbForLogs in [9494](https://github.com/appwrite/appwrite/pull/9494)
* Feat bulk audit delete in [9487](https://github.com/appwrite/appwrite/pull/9487)
* Prepare 1.6.2 release in [9499](https://github.com/appwrite/appwrite/pull/9499)
* Regenerate specs in [9497](https://github.com/appwrite/appwrite/pull/9497)
* Regenerate examples in [9498](https://github.com/appwrite/appwrite/pull/9498)
* chore: bump sdk in [9414](https://github.com/appwrite/appwrite/pull/9414)
* update queue to 0.9.* in [9505](https://github.com/appwrite/appwrite/pull/9505)
* Feat improve delete queries in [9507](https://github.com/appwrite/appwrite/pull/9507)
* Feat: Add rule attributes in [9508](https://github.com/appwrite/appwrite/pull/9508)
* Sync main into 1.6.x in [9496](https://github.com/appwrite/appwrite/pull/9496)
* Bump console to version 5.2.53 in [9495](https://github.com/appwrite/appwrite/pull/9495)
* Prepare 1.6.1 release in [9294](https://github.com/appwrite/appwrite/pull/9294)
* Improve delete ordering in [9512](https://github.com/appwrite/appwrite/pull/9512)
* Cleanups in [9511](https://github.com/appwrite/appwrite/pull/9511)
* Feat dynamic regions in [9408](https://github.com/appwrite/appwrite/pull/9408)
* Feat env vars to system lib in [9515](https://github.com/appwrite/appwrite/pull/9515)
* Feat: domains count in [9514](https://github.com/appwrite/appwrite/pull/9514)
* Migration read from db in [9529](https://github.com/appwrite/appwrite/pull/9529)
* feat: add pool telemetry in [9530](https://github.com/appwrite/appwrite/pull/9530)
* Disable PDO persistence since we manage our own pool in [9526](https://github.com/appwrite/appwrite/pull/9526)
* chore: set min operations to 1 for reads and writes in [9536](https://github.com/appwrite/appwrite/pull/9536)
* Remove default region in [9430](https://github.com/appwrite/appwrite/pull/9430)
* Use cursor pagination with bigger limit for maintenance project loop in [9546](https://github.com/appwrite/appwrite/pull/9546)
* chore: stop tests on failure in [9525](https://github.com/appwrite/appwrite/pull/9525)
* chore: only update total count for privileged users in [9554](https://github.com/appwrite/appwrite/pull/9554)
* refactor: initialization of audit retention in [9563](https://github.com/appwrite/appwrite/pull/9563)
* Delete worker queries fixes in [9523](https://github.com/appwrite/appwrite/pull/9523)
* Bump database 0.62.x in [9568](https://github.com/appwrite/appwrite/pull/9568)
* Fix: schedules region filtering in [9577](https://github.com/appwrite/appwrite/pull/9577)
* Deletes worker fix selects for pagination in [9578](https://github.com/appwrite/appwrite/pull/9578)
* Add $permissions for delete documents selects in [9579](https://github.com/appwrite/appwrite/pull/9579)
* chore(audits): return queue pre-fetch results in [9533](https://github.com/appwrite/appwrite/pull/9533)
* Revert "chore(audits): return queue pre-fetch results" in [9586](https://github.com/appwrite/appwrite/pull/9586)
* Feat multi tenant insert in [9573](https://github.com/appwrite/appwrite/pull/9573)
* Add order by for cursor in [9588](https://github.com/appwrite/appwrite/pull/9588)
* Feat update fetch in [9592](https://github.com/appwrite/appwrite/pull/9592)
* Fix tenant casting in [9598](https://github.com/appwrite/appwrite/pull/9598)
* Feat update ws in [9602](https://github.com/appwrite/appwrite/pull/9602)
* Update database in [9603](https://github.com/appwrite/appwrite/pull/9603)
* Fix: image transformation cache in [9608](https://github.com/appwrite/appwrite/pull/9608)
* Remove audit payload in [9610](https://github.com/appwrite/appwrite/pull/9610)
* Sample rate from DSN in [9559](https://github.com/appwrite/appwrite/pull/9559)
* Restrict role change for sole org owner in [9615](https://github.com/appwrite/appwrite/pull/9615)
* chore: update php image to 0.8.1 in [9616](https://github.com/appwrite/appwrite/pull/9616)
* feat: refactor executor setup in [9420](https://github.com/appwrite/appwrite/pull/9420)
* chore: update gitpod.yml config in [9561](https://github.com/appwrite/appwrite/pull/9561)
* chore: update dependencies in [9625](https://github.com/appwrite/appwrite/pull/9625)
* Update migrations lib in [9628](https://github.com/appwrite/appwrite/pull/9628)
* feat: cache telemetry in [9624](https://github.com/appwrite/appwrite/pull/9624)
* Bump console to version 5.2.56 in [9631](https://github.com/appwrite/appwrite/pull/9631)
* Multi region support in [8667](https://github.com/appwrite/appwrite/pull/8667)
* Revert "Multi region support" in [9632](https://github.com/appwrite/appwrite/pull/9632)
* Revert "Revert "Multi region support"" in [9636](https://github.com/appwrite/appwrite/pull/9636)
* Fix tasks in [9644](https://github.com/appwrite/appwrite/pull/9644)
* chore: updated the migration version to 8.6 in [9646](https://github.com/appwrite/appwrite/pull/9646)
* Fix: merge the working of StatsUsage and StatsUsageDump in [9585](https://github.com/appwrite/appwrite/pull/9585)
* Update database in [9643](https://github.com/appwrite/appwrite/pull/9643)
* chore: fix error logging for CLI tasks in [9651](https://github.com/appwrite/appwrite/pull/9651)
* fix: usage test assertion in [9653](https://github.com/appwrite/appwrite/pull/9653)
* Fix keys in [9656](https://github.com/appwrite/appwrite/pull/9656)
* Feat: multi tenant dual writing in [9583](https://github.com/appwrite/appwrite/pull/9583)
* Fix/throwing 400 for null order attributes in [9657](https://github.com/appwrite/appwrite/pull/9657)
* feat: sdk group attribute in [9596](https://github.com/appwrite/appwrite/pull/9596)
* Add configurable function and build size in [9648](https://github.com/appwrite/appwrite/pull/9648)
* feat: update API endpoint in the code examples in [8933](https://github.com/appwrite/appwrite/pull/8933)
* chore: abstract token secret hiding to response model in [9574](https://github.com/appwrite/appwrite/pull/9574)
* chore: update sdks in [9655](https://github.com/appwrite/appwrite/pull/9655)
* feat: allow non-critical events to ignore exceptions when enqueuing the event in [9680](https://github.com/appwrite/appwrite/pull/9680)
* Revert "Add configurable function and build size" in [9681](https://github.com/appwrite/appwrite/pull/9681)
* core: introduce endpoint.docs in specs in [9685](https://github.com/appwrite/appwrite/pull/9685)
* fix: remove content-type header from get request specs in [9666](https://github.com/appwrite/appwrite/pull/9666)
* chore: update flutter sdk in [9691](https://github.com/appwrite/appwrite/pull/9691)
# Version 1.6.1
## What's Changed
+5 -5
View File
@@ -415,8 +415,8 @@ In addition, you will also need to add some logic to the `reduce()` method of th
```php
case $document->getCollection() === 'buckets':
$files = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{bucketInternalId}', $document->getSequence(), METRIC_BUCKET_ID_FILES)));
$storage = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{bucketInternalId}', $document->getSequence(), METRIC_BUCKET_ID_FILES_STORAGE)));
$files = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{bucketInternalId}', $document->getInternalId(), METRIC_BUCKET_ID_FILES)));
$storage = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{bucketInternalId}', $document->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE)));
if (!empty($files['value'])) {
$metrics[] = [
@@ -463,9 +463,9 @@ $queueForStatsUsage
->addMetric(METRIC_BUILDS, 1)
->addMetric(METRIC_BUILDS_STORAGE, $build->getAttribute('size', 0))
->addMetric(METRIC_BUILDS_COMPUTE, (int)$build->getAttribute('duration', 0) * 1000)
->addMetric(str_replace('{functionInternalId}', $function->getSequence(), METRIC_FUNCTION_ID_BUILDS), 1)
->addMetric(str_replace('{functionInternalId}', $function->getSequence(), METRIC_FUNCTION_ID_BUILDS_STORAGE), $build->getAttribute('size', 0))
->addMetric(str_replace('{functionInternalId}', $function->getSequence(), METRIC_FUNCTION_ID_BUILDS_COMPUTE), (int)$build->getAttribute('duration', 0) * 1000)
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS), 1)
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_STORAGE), $build->getAttribute('size', 0))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE), (int)$build->getAttribute('duration', 0) * 1000)
->setProject($project)
->trigger();
```
+1
View File
@@ -89,6 +89,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-migrations && \
chmod +x /usr/local/bin/worker-webhooks && \
chmod +x /usr/local/bin/worker-stats-usage && \
chmod +x /usr/local/bin/worker-stats-usage-dump && \
chmod +x /usr/local/bin/stats-resources && \
chmod +x /usr/local/bin/worker-stats-resources
+3 -3
View File
@@ -72,7 +72,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.7.4
appwrite/appwrite:1.6.2
```
### Windows
@@ -84,7 +84,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.7.4
appwrite/appwrite:1.6.2
```
#### PowerShell
@@ -94,7 +94,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.7.4
appwrite/appwrite:1.6.2
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
+3 -4
View File
@@ -1,5 +1,4 @@
> [Get started with Appwrite](https://apwr.dev/appcloud)
> [Join the Init kick off event 19th of May: The future of Appwrite with Founder & CEO Eldad Fux](https://www.youtube.com/watch?v=1g8tuogsp7A)
<br />
<p align="center">
@@ -78,7 +77,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.7.4
appwrite/appwrite:1.6.2
```
### Windows
@@ -90,7 +89,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.7.4
appwrite/appwrite:1.6.2
```
#### PowerShell
@@ -100,7 +99,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.7.4
appwrite/appwrite:1.6.2
```
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
+42 -58
View File
@@ -10,15 +10,11 @@ use Appwrite\Event\StatsUsage;
use Appwrite\Platform\Appwrite;
use Appwrite\Runtimes\Runtimes;
use Executor\Executor;
use Swoole\Runtime;
use Swoole\Timer;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\CLI;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
@@ -26,13 +22,9 @@ use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Platform\Service;
use Utopia\Pools\Group;
use Utopia\Queue\Broker\Pool as BrokerPool;
use Utopia\Queue\Publisher;
use Utopia\Registry\Registry;
use Utopia\System\System;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
use function Swoole\Coroutine\run;
// overwriting runtimes to be architecture agnostic for CLI
Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
@@ -49,7 +41,10 @@ CLI::setResource('cache', function ($pools) {
$adapters = [];
foreach ($list as $value) {
$adapters[] = new CachePool($pools->get($value));
$adapters[] = $pools
->get($value)
->pop()
->getResource();
}
return new Cache(new Sharding($adapters));
@@ -69,8 +64,12 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
$attempts++;
try {
// Prepare database connection
$adapter = new DatabasePool($pools->get('console'));
$dbForPlatform = new Database($adapter, $cache);
$dbAdapter = $pools
->get('console')
->pop()
->getResource();
$dbForPlatform = new Database($dbAdapter, $cache);
$dbForPlatform
->setNamespace('_console')
@@ -88,6 +87,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
$ready = true;
} catch (\Throwable $err) {
Console::warning($err->getMessage());
$pools->get('console')->reclaim();
sleep($sleep);
}
} while ($attempts < $maxAttempts && !$ready);
@@ -125,33 +125,37 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant((int)$project->getSequence())
->setTenant($project->getInternalId())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
->setNamespace('_' . $project->getInternalId());
}
return $database;
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$dbAdapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$databases[$dsn->getHost()] = $database;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant((int)$project->getSequence())
->setTenant($project->getInternalId())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
->setNamespace('_' . $project->getInternalId());
}
$database
@@ -164,15 +168,21 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence());
$database->setTenant($project->getInternalId());
return $database;
}
$adapter = new DatabasePool($pools->get('logs'));
$database = new Database($adapter, $cache);
$dbAdapter = $pools
->get('logs')
->pop()
->getResource();
$database = new Database(
$dbAdapter,
$cache
);
$database
->setSharedTables(true)
@@ -182,21 +192,21 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence());
$database->setTenant($project->getInternalId());
}
return $database;
};
}, ['pools', 'cache']);
CLI::setResource('queueForStatsUsage', function (Publisher $publisher) {
CLI::setResource('queueForStatsUsage', function (Connection $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
CLI::setResource('queueForStatsResources', function (Publisher $publisher) {
return new StatsResources($publisher);
}, ['publisher']);
CLI::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
return $pools->get('publisher')->pop()->getResource();
}, ['pools']);
CLI::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
@@ -209,13 +219,6 @@ CLI::setResource('queueForCertificates', function (Publisher $publisher) {
}, ['publisher']);
CLI::setResource('logError', function (Registry $register) {
return function (Throwable $error, string $namespace, string $action) use ($register) {
Console::error('[Error] Timestamp: ' . date('c', time()));
Console::error('[Error] Type: ' . get_class($error));
Console::error('[Error] Message: ' . $error->getMessage());
Console::error('[Error] File: ' . $error->getFile());
Console::error('[Error] Line: ' . $error->getLine());
Console::error('[Error] Trace: ' . $error->getTraceAsString());
$logger = $register->get('logger');
if ($logger) {
@@ -234,7 +237,6 @@ CLI::setResource('logError', function (Registry $register) {
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->setAction($action);
@@ -248,42 +250,24 @@ CLI::setResource('logError', function (Registry $register) {
Console::error('Error pushing log: ' . $th->getMessage());
}
}
Console::warning("Failed: {$error->getMessage()}");
Console::warning($error->getTraceAsString());
};
}, ['register']);
CLI::setResource('executor', fn () => new Executor());
CLI::setResource('telemetry', fn () => new NoTelemetry());
CLI::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST')));
$platform = new Appwrite();
$args = $platform->getEnv('argv');
if (!isset($args[0])) {
Console::error('Missing task name');
Console::exit(1);
}
\array_shift($args);
$taskName = $args[0];
$platform->init(Service::TYPE_TASK);
$cli = $platform->getCli();
$cli
->error()
->inject('error')
->inject('logError')
->action(function (Throwable $error, callable $logError) use ($taskName) {
call_user_func_array($logError, [
$error,
'Task',
$taskName,
]);
Timer::clearAll();
->action(function (Throwable $error) {
Console::error($error->getMessage());
});
$cli->shutdown()->action(fn () => Timer::clearAll());
// Enable coroutines, but disable TCP hooks. These don't work until we use `\Utopia\Cache\Adapter\Pool` and `\Utopia\Database\Adapter\Pool`.
Runtime::enableCoroutine(SWOOLE_HOOK_ALL ^ SWOOLE_HOOK_TCP);
run($cli->run(...));
$cli->run();
+7 -36
View File
@@ -1101,28 +1101,6 @@ return [
'array' => false,
'filters' => ['json', 'encrypt'],
],
[
'$id' => ID::custom('scopes'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('expire'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'required' => false,
'signed' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
],
'indexes' => [
[
@@ -1439,6 +1417,13 @@ return [
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_roles'),
'type' => Database::INDEX_KEY,
'attributes' => ['roles'],
'lengths' => [128],
'orders' => [],
],
],
],
@@ -2374,20 +2359,6 @@ return [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_expired'),
'type' => Database::INDEX_KEY,
'attributes' => ['expired'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_session_internal_id'),
'type' => Database::INDEX_KEY,
'attributes' => ['sessionInternalId'],
'lengths' => [],
'orders' => [],
],
],
],
+23 -54
View File
@@ -225,7 +225,7 @@ return [
'$id' => ID::custom('templates'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 1_000_000, // TODO make sure size fits
'size' => 1000000, // TODO make sure size fits
'signed' => true,
'required' => false,
'default' => [],
@@ -367,21 +367,7 @@ return [
'attributes' => ['pingedAt'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_database'),
'type' => Database::INDEX_KEY,
'attributes' => ['database'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_region_accessed_at'),
'type' => Database::INDEX_KEY,
'attributes' => ['region', 'accessedAt'],
'lengths' => [],
'orders' => [],
],
]
],
],
@@ -505,20 +491,6 @@ return [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_project_id_region'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectId', 'region'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_region_rt_active'),
'type' => Database::INDEX_KEY,
'attributes' => ['region', 'resourceType', 'active'],
'lengths' => [],
'orders' => [],
],
],
],
@@ -810,6 +782,17 @@ return [
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
@@ -826,6 +809,13 @@ return [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_search'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
],
],
],
@@ -1158,7 +1148,7 @@ return [
'format' => '',
'size' => 32,
'signed' => true,
'required' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
@@ -1417,13 +1407,6 @@ return [
'lengths' => [16],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_piid_riid_rt'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'deploymentInternalId', 'deploymentResourceType'],
'lengths' => [],
'orders' => [],
],
],
],
@@ -1719,14 +1702,7 @@ return [
'attributes' => ['resourceType'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_piid_riid_rt'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'resourceInternalId', 'resourceType'],
'lengths' => [],
'orders' => [],
],
]
],
],
@@ -1874,13 +1850,6 @@ return [
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_piid_prid_rt'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'providerRepositoryId'],
'lengths' => [],
'orders' => [],
],
],
],
@@ -1890,5 +1859,5 @@ return [
'name' => 'vcsCommentLocks',
'attributes' => [],
'indexes' => []
],
]
];
+39 -75
View File
@@ -1029,7 +1029,7 @@ return [
'$id' => ID::custom('fallbackFile'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
@@ -1205,7 +1205,7 @@ return [
'$id' => ID::custom('adapter'), // ssr or static; named this way as it's a term in SSR frameworks
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16,
'size' => 128,
'signed' => true,
'required' => false,
'default' => '',
@@ -1286,7 +1286,6 @@ return [
]
],
],
'deployments' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('deployments'),
@@ -1362,7 +1361,7 @@ return [
'$id' => ID::custom('sourcePath'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
@@ -1373,7 +1372,7 @@ return [
'$id' => ID::custom('type'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'size' => 32,
'signed' => true,
'required' => true,
'default' => null,
@@ -1555,8 +1554,8 @@ return [
'$id' => ID::custom('sourceSize'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 8,
'signed' => false,
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
@@ -1595,6 +1594,17 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('activate'),
'type' => Database::VAR_BOOLEAN,
@@ -1629,7 +1639,7 @@ return [
'filters' => [],
],
[
'$id' => ID::custom('buildStartedAt'),
'$id' => ID::custom('buildStartAt'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
@@ -1640,7 +1650,7 @@ return [
'filters' => ['datetime'],
],
[
'$id' => ID::custom('buildEndedAt'),
'$id' => ID::custom('buildEndAt'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
@@ -1655,7 +1665,7 @@ return [
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
@@ -1665,8 +1675,8 @@ return [
'$id' => ID::custom('buildSize'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 8,
'signed' => false,
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
@@ -1676,8 +1686,8 @@ return [
'$id' => ID::custom('totalSize'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 8,
'signed' => false,
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
@@ -1689,7 +1699,7 @@ return [
'format' => '',
'size' => 16,
'signed' => true,
'required' => false,
'required' => true,
'default' => 'waiting',
'array' => false,
'filters' => [],
@@ -1698,7 +1708,7 @@ return [
'$id' => ID::custom('buildPath'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => '',
@@ -1720,7 +1730,7 @@ return [
'$id' => ID::custom('adapter'), // ssr or static; named this way as it's a term in SSR frameworks
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16,
'size' => 128,
'signed' => true,
'required' => false,
'default' => '',
@@ -1731,7 +1741,7 @@ return [
'$id' => ID::custom('fallbackFile'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
@@ -1754,6 +1764,13 @@ return [
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_search'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_sourceSize'),
'type' => Database::INDEX_KEY,
@@ -1795,28 +1812,13 @@ return [
'attributes' => ['type'],
'lengths' => [32],
'orders' => [Database::ORDER_ASC],
],
[
], [
'$id' => ID::custom('_key_status'),
'type' => Database::INDEX_KEY,
'attributes' => ['status'],
'lengths' => [16],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_resourceId_resourceType'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceId', 'resourceType'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_resource_internal_id'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceInternalId'],
'lengths' => [],
'orders' => [],
],
],
],
@@ -2081,13 +2083,6 @@ return [
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_function_internal_id'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceInternalId'],
'lengths' => [],
'orders' => [],
],
],
],
@@ -2217,13 +2212,6 @@ return [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_resource_internal_id_resource_type'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceInternalId', 'resourceType'],
'lengths' => [],
'orders' => [],
],
],
],
@@ -2473,19 +2461,8 @@ return [
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('accessedAt'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
'filters' => [],
]
],
'indexes' => [
[
@@ -2495,20 +2472,7 @@ return [
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_accessedAt',
'type' => Database::INDEX_KEY,
'attributes' => ['accessedAt'],
'lengths' => [],
'orders' => [],
],
[
'$id' => '_key_resourceInternalId_resourceType',
'type' => Database::INDEX_KEY,
'attributes' => ['resourceInternalId', 'resourceType'],
'lengths' => [],
'orders' => [],
],
],
],
];
+1 -1
View File
@@ -11,7 +11,7 @@ use Utopia\System\System;
$console = [
'$id' => ID::custom('console'),
'$sequence' => ID::custom('console'),
'$internalId' => ID::custom('console'),
'name' => 'Appwrite',
'$collection' => ID::custom('projects'),
'description' => 'Appwrite core engine',
+1 -21
View File
@@ -81,7 +81,7 @@ return [
],
Exception::GENERAL_ROUTE_NOT_FOUND => [
'name' => Exception::GENERAL_ROUTE_NOT_FOUND,
'description' => 'Route not found. Please ensure the endpoint is configured correctly and that the API route is valid for this SDK version. Refer to the API docs for more details.',
'description' => 'The requested route was not found. Please refer to the API docs and try again.',
'code' => 404,
],
Exception::GENERAL_CURSOR_NOT_FOUND => [
@@ -393,16 +393,6 @@ return [
'description' => 'Membership is already confirmed.',
'code' => 409,
],
Exception::MEMBERSHIP_DELETION_PROHIBITED => [
'name' => Exception::MEMBERSHIP_DELETION_PROHIBITED,
'description' => 'Membership deletion is prohibited.',
'code' => 400,
],
Exception::MEMBERSHIP_DOWNGRADE_PROHIBITED => [
'name' => Exception::MEMBERSHIP_DOWNGRADE_PROHIBITED,
'description' => 'Membership role downgrade is prohibited.',
'code' => 400,
],
/** Avatars */
Exception::AVATAR_SET_NOT_FOUND => [
@@ -509,11 +499,6 @@ return [
'description' => 'The requested file token has expired.',
'code' => 401,
],
Exception::TOKEN_RESOURCE_TYPE_INVALID => [
'name' => Exception::TOKEN_RESOURCE_TYPE_INVALID,
'description' => 'The resource type for the token is invalid.',
'code' => 400,
],
/** VCS */
Exception::INSTALLATION_NOT_FOUND => [
@@ -666,11 +651,6 @@ return [
'description' => 'Database timed out. Try adjusting your queries or adding an index.',
'code' => 408
],
Exception::DATABASE_QUERY_ORDER_NULL => [
'name' => Exception::DATABASE_QUERY_ORDER_NULL,
'description' => 'The order attribute had a null value. Cursor pagination requires all documents order attribute values are non-null.',
'code' => 400,
],
/** Collections */
Exception::COLLECTION_NOT_FOUND => [
+2 -19
View File
@@ -253,30 +253,13 @@ return [
'adapters' => [
'static' => [
'key' => 'static',
'buildCommand' => 'flutter build web --release -t lib/main.dart',
'installCommand' => 'flutter pub get',
'buildCommand' => 'flutter build web',
'installCommand' => '',
'outputDirectory' => './build/web',
'startCommand' => 'bash helpers/server.sh',
],
],
],
'react-native' => [
'key' => 'react-native',
'name' => 'React Native',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'adapters' => [
'static' => [
'key' => 'static',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'outputDirectory' => './dist',
'startCommand' => 'bash helpers/server.sh',
'fallbackFile' => 'index.html'
]
]
],
'vite' => [
'key' => 'vite',
'name' => 'Vite',
@@ -79,7 +79,7 @@
<style>
a.button {
display: inline-block;
background: {{accentColor}};
background: #fd366e;
color: #ffffff;
border-radius: 8px;
height: 48px;
@@ -88,7 +88,7 @@
cursor: pointer;
text-align: center;
text-decoration: none;
border-color: {{accentColor}};
border-color: #fd366e;
border-style: solid;
border-width: 1px;
margin-right: 24px;
@@ -126,7 +126,7 @@
<td>
<img
height="32px"
src="{{logoUrl}}"
src="https://cloud.appwrite.io/images/mails/logo.png"
/>
</td>
</tr>
@@ -164,7 +164,7 @@
<tr>
<td style="padding-left: 4px; padding-right: 4px">
<a
href="{{twitterUrl}}"
href="https://twitter.com/appwrite"
class="social-icon"
title="Twitter"
>
@@ -173,7 +173,7 @@
</td>
<td style="padding-left: 4px; padding-right: 4px">
<a
href="{{discordUrl}}"
href="https://appwrite.io/discord"
class="social-icon"
>
<img src="https://cloud.appwrite.io/images/mails/discord.png" height="24" width="24" />
@@ -181,7 +181,7 @@
</td>
<td style="padding-left: 4px; padding-right: 4px">
<a
href="{{githubUrl}}"
href="https://github.com/appwrite/appwrite"
class="social-icon"
>
<img src="https://cloud.appwrite.io/images/mails/github.png" height="24" width="24" />
@@ -191,11 +191,11 @@
</table>
<table style="width: auto; margin: 0 auto; margin-top: 60px">
<tr>
<td><a href="{{termsUrl}}">Terms</a></td>
<td><a href="https://appwrite.io/terms">Terms</a></td>
<td style="color: #e8e9f0">
<div style="margin: 0 8px">|</div>
</td>
<td><a href="{{privacyUrl}}">Privacy</a></td>
<td><a href="https://appwrite.io/privacy">Privacy</a></td>
</tr>
</table>
<p style="text-align: center" align="center">
+7 -59
View File
@@ -11,7 +11,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Amazon',
],
'apple' => [
'name' => 'Apple',
@@ -22,7 +21,6 @@ return [
'form' => 'apple.phtml', // Preparation for adding ability to customized OAuth UI forms, currently handled hardcoded.
'beta' => true,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Apple',
],
'auth0' => [
'name' => 'Auth0',
@@ -33,7 +31,6 @@ return [
'form' => 'auth0.phtml',
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Auth0',
],
'authentik' => [
'name' => 'Authentik',
@@ -44,7 +41,6 @@ return [
'form' => 'authentik.phtml',
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Authentik',
],
'autodesk' => [
'name' => 'Autodesk',
@@ -55,7 +51,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Autodesk',
],
'bitbucket' => [
'name' => 'BitBucket',
@@ -66,7 +61,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Bitbucket',
],
'bitly' => [
'name' => 'Bitly',
@@ -76,8 +70,7 @@ return [
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Bitly',
'mock' => false
],
'box' => [
'name' => 'Box',
@@ -87,8 +80,7 @@ return [
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Box',
'mock' => false
],
'dailymotion' => [
'name' => 'Dailymotion',
@@ -98,8 +90,7 @@ return [
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Dailymotion',
'mock' => false
],
'discord' => [
'name' => 'Discord',
@@ -110,7 +101,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Discord',
],
'disqus' => [
'name' => 'Disqus',
@@ -121,7 +111,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Disqus',
],
'dropbox' => [
'name' => 'Dropbox',
@@ -132,7 +121,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Dropbox',
],
'etsy' => [
'name' => 'Etsy',
@@ -143,7 +131,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Etsy',
],
'facebook' => [
'name' => 'Facebook',
@@ -154,18 +141,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Facebook',
],
'figma' => [
'name' => 'Figma',
'developers' => 'https://www.figma.com/developers/api#oauth2',
'icon' => 'icon-figma',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Figma',
],
'github' => [
'name' => 'GitHub',
@@ -176,7 +151,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Github',
],
'gitlab' => [
'name' => 'GitLab',
@@ -187,7 +161,6 @@ return [
'form' => 'gitlab.phtml',
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Gitlab',
],
'google' => [
'name' => 'Google',
@@ -198,7 +171,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Google',
],
'linkedin' => [
'name' => 'LinkedIn',
@@ -209,7 +181,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Linkedin',
],
'microsoft' => [
'name' => 'Microsoft',
@@ -220,7 +191,6 @@ return [
'form' => 'microsoft.phtml',
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Microsoft',
],
'notion' => [
'name' => 'Notion',
@@ -231,7 +201,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Notion',
],
'oidc' => [
'name' => 'OpenID Connect',
@@ -242,7 +211,6 @@ return [
'form' => 'oidc.phtml',
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Oidc',
],
'okta' => [
'name' => 'Okta',
@@ -253,7 +221,6 @@ return [
'form' => 'okta.phtml',
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Okta',
],
'paypal' => [
'name' => 'PayPal',
@@ -263,8 +230,7 @@ return [
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Paypal',
'mock' => false
],
'paypalSandbox' => [
'name' => 'PayPal Sandbox',
@@ -274,8 +240,7 @@ return [
'sandbox' => true,
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Paypal',
'mock' => false
],
'podio' => [
'name' => 'Podio',
@@ -286,7 +251,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Podio',
],
'salesforce' => [
'name' => 'Salesforce',
@@ -297,7 +261,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Salesforce',
],
'slack' => [
'name' => 'Slack',
@@ -308,7 +271,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Slack',
],
'spotify' => [
'name' => 'Spotify',
@@ -319,7 +281,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Spotify',
],
'stripe' => [
'name' => 'Stripe',
@@ -329,8 +290,7 @@ return [
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Stripe',
'mock' => false
],
'tradeshift' => [
'name' => 'Tradeshift',
@@ -341,7 +301,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Tradeshift',
],
'tradeshiftBox' => [
'name' => 'Tradeshift Sandbox',
@@ -352,7 +311,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Tradeshift',
],
'twitch' => [
'name' => 'Twitch',
@@ -363,7 +321,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Twitch',
],
'wordpress' => [
'name' => 'WordPress',
@@ -373,8 +330,7 @@ return [
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Wordpress',
'mock' => false
],
'yahoo' => [
'name' => 'Yahoo',
@@ -385,7 +341,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Yahoo',
],
'yammer' => [
'name' => 'Yammer',
@@ -396,7 +351,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Yammer',
],
'yandex' => [
'name' => 'Yandex',
@@ -407,7 +361,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Yandex',
],
'zoho' => [
'name' => 'Zoho',
@@ -418,7 +371,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Zoho',
],
'zoom' => [
'name' => 'Zoom',
@@ -429,7 +381,6 @@ return [
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Zoom',
],
// 'instagram' => [
// 'name' => 'Instagram',
@@ -438,7 +389,6 @@ return [
// 'enabled' => false,
// 'beta' => false,
// 'mock' => false,
// 'class' => 'Appwrite\\Auth\\OAuth2\\Instagram',
// ],
// 'twitter' => [
// 'name' => 'twitter',
@@ -447,7 +397,6 @@ return [
// 'enabled' => false,
// 'beta' => false,
// 'mock' => false,
// 'class' => 'Appwrite\\Auth\\OAuth2\\Twitter',
// ],
// Keep Last
@@ -460,6 +409,5 @@ return [
'form' => false,
'beta' => false,
'mock' => true,
'class' => 'Appwrite\\Auth\\OAuth2\\Mock',
],
];
+17 -22
View File
@@ -11,7 +11,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '18.1.0',
'version' => '17.0.1',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -59,7 +59,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '17.0.1',
'version' => '15.0.0',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -77,7 +77,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '10.1.1',
'version' => '9.0.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -112,7 +112,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'version' => '8.1.0',
'version' => '6.1.0',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@@ -134,7 +134,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.9.1',
'version' => '0.7.1',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -199,7 +199,7 @@ return [
[
'key' => 'web',
'name' => 'Console',
'version' => '1.7.0',
'version' => '1.2.1',
'url' => 'https://github.com/appwrite/sdk-for-console',
'package' => '',
'enabled' => true,
@@ -217,7 +217,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '8.0.1',
'version' => '6.2.2',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -231,11 +231,6 @@ return [
'gitRepoName' => 'sdk-for-cli',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'exclude' => [
'services' => [
['name' => 'assistant'],
],
],
],
],
],
@@ -250,7 +245,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '17.0.0',
'version' => '15.0.1',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -268,7 +263,7 @@ return [
[
'key' => 'deno',
'name' => 'Deno',
'version' => '15.0.0',
'version' => '12.2.0',
'url' => 'https://github.com/appwrite/sdk-for-deno',
'package' => 'https://deno.land/x/appwrite',
'enabled' => true,
@@ -286,7 +281,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '15.0.0',
'version' => '12.2.0',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@@ -304,7 +299,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '11.0.0',
'version' => '9.0.2',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -322,7 +317,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '16.0.0',
'version' => '12.2.0',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -340,7 +335,7 @@ return [
[
'key' => 'go',
'name' => 'Go',
'version' => '0.8.0',
'version' => '0.3.0',
'url' => 'https://github.com/appwrite/sdk-for-go',
'package' => 'https://github.com/appwrite/sdk-for-go',
'enabled' => true,
@@ -358,7 +353,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.13.0',
'version' => '0.11.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@@ -376,7 +371,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '16.0.0',
'version' => '14.0.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -394,7 +389,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '9.0.0',
'version' => '6.2.0',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@@ -416,7 +411,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '10.1.0',
'version' => '8.0.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
+3
View File
@@ -65,6 +65,9 @@ return [
'tests' => false,
'optional' => true,
'icon' => '/images/services/databases.png',
'globalAttributes' => [
'databaseId'
]
],
'locale' => [
'key' => 'locale',
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,8 +4,8 @@ return [
// Accepted inputs files
"jpg" => "image/jpeg",
"jpeg" => "image/jpeg",
"gif" => "image/gif",
"png" => "image/png",
"heic" => "image/heic",
"webp" => "image/webp",
"gif" => "image/gif",
];
+1 -1
View File
@@ -4,9 +4,9 @@ return [
// Accepted outputs files
"jpg" => "image/jpeg",
"jpeg" => "image/jpeg",
"gif" => "image/gif",
"png" => "image/png",
"webp" => "image/webp",
"heic" => "image/heic",
"avif" => "image/avif",
"gif" => "image/gif",
];
-6
View File
@@ -1,6 +0,0 @@
<?php
use Utopia\Image\Image;
use Utopia\System\System;
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
+2 -2
View File
@@ -14,7 +14,7 @@ return [
],
'DART' => [
'name' => 'dart',
'versions' => ['3.8', '3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
],
'GO' => [
'name' => 'go',
@@ -38,6 +38,6 @@ return [
],
'FLUTTER' => [
'name' => 'flutter',
'versions' => ['3.32', '3.24']
'versions' => ['3.24']
],
];
+20 -243
View File
@@ -106,16 +106,6 @@ const TEMPLATE_FRAMEWORKS = [
'outputDirectory' => './dist',
'fallbackFile' => 'index.html',
],
'REACT_NATIVE' => [
'key' => 'react-native',
'name' => 'React Native',
'installCommand' => 'npm install',
'buildCommand' => 'npm run build',
'buildRuntime' => 'node-22',
'adapter' => 'static',
'outputDirectory' => './dist',
'fallbackFile' => '+not-found.html',
],
'ANGULAR' => [
'key' => 'angular',
'name' => 'Angular',
@@ -193,15 +183,13 @@ return [
'variables' => []
],
[
'key' => 'playground-for-lynx',
'name' => 'Lynx playground',
'tagline' => 'A basic Lynx website without Appwrite SDK integration.',
// When we add Lynx with Appwrite SDK, use following tagline for it:
// 'tagline' => 'Sample application built with Lynx, a cross-platform framework focused on performance.',
'key' => 'lynx-starter',
'name' => 'Lynx Starter',
'tagline' => 'Sample application built with Lynx, a cross-platform framework focused on performance.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-lynx-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-lynx-light.png',
'screenshotDark' => $url . '/images/sites/templates/lynx-starter-dark.png',
'screenshotLight' => $url . '/images/sites/templates/lynx-starter-light.png',
'frameworks' => [
getFramework('LYNX', [
'providerRootDirectory' => './lynx/starter',
@@ -876,8 +864,9 @@ return [
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-native-dark.png',
'screenshotLight' => $url . '/images/sites/templates/starter-for-react-native-light.png',
'frameworks' => [
getFramework('REACT_NATIVE', [
getFramework('REACT', [
'providerRootDirectory' => './',
'fallbackFile' => '+not-found.html',
]),
],
'vcsProvider' => 'github',
@@ -1119,14 +1108,15 @@ return [
'providerVersion' => '0.1.*',
'variables' => []
],
// TODO: Remove astro starter eventually, or add all frameworks's starters
[
'key' => 'playground-for-astro',
'name' => 'Astro playground',
'tagline' => 'A basic Astro website without Appwrite SDK integration.',
'key' => 'astro-starter',
'name' => 'Astro starter',
'tagline' => 'Sample application built with Astro, a content-driven web framework.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-astro-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-astro-light.png',
'screenshotDark' => $url . '/images/sites/templates/astro-starter-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-starter-light.png',
'frameworks' => [
getFramework('ASTRO', [
'providerRootDirectory' => './astro/starter',
@@ -1135,17 +1125,17 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'playground-for-remix',
'name' => 'Remix playground',
'tagline' => 'A basic Remix website without Appwrite SDK integration.',
'key' => 'remix-starter',
'name' => 'Remix starter',
'tagline' => 'Sample application built with Remix, a React meta-framework.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-remix-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-remix-light.png',
'screenshotDark' => $url . '/images/sites/templates/remix-starter-dark.png',
'screenshotLight' => $url . '/images/sites/templates/remix-starter-light.png',
'frameworks' => [
getFramework('REMIX', [
'providerRootDirectory' => './remix/starter',
@@ -1154,220 +1144,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'playground-for-nextjs',
'name' => 'Next.js playground',
'tagline' => 'A basic Next.js website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-nextjs-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-nextjs-light.png',
'frameworks' => [
getFramework('NEXTJS', [
'providerRootDirectory' => './nextjs/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
'key' => 'playground-for-flutter',
'name' => 'Flutter playground',
'tagline' => 'A basic Flutter website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-flutter-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-flutter-light.png',
'frameworks' => [
getFramework('FLUTTER', [
'providerRootDirectory' => './flutter/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
'key' => 'playground-for-vite',
'name' => 'Vite playground',
'tagline' => 'A basic Vite website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-vite-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-vite-light.png',
'frameworks' => [
getFramework('VITE', [
'providerRootDirectory' => './vite/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
'key' => 'playground-for-angular',
'name' => 'Angular playground',
'tagline' => 'A basic Angular website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-angular-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-angular-light.png',
'frameworks' => [
getFramework('ANGULAR', [
'providerRootDirectory' => './angular/starter',
'outputDirectory' => './dist/starter/browser',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
'key' => 'playground-for-analog',
'name' => 'Analog playground',
'tagline' => 'A basic Analog website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-analog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-analog-light.png',
'frameworks' => [
getFramework('ANALOG', [
'providerRootDirectory' => './analog/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
'key' => 'playground-for-svelte',
'name' => 'Svelte playground',
'tagline' => 'A basic Svelte website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-svelte-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-svelte-light.png',
'frameworks' => [
getFramework('SVELTEKIT', [
'providerRootDirectory' => './sveltekit/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
'key' => 'playground-for-react',
'name' => 'React playground',
'tagline' => 'A basic React website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-react-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-react-light.png',
'frameworks' => [
getFramework('REACT', [
'outputDirectory' => './build',
'providerRootDirectory' => './react/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
'key' => 'playground-for-vue',
'name' => 'Vue playground',
'tagline' => 'A basic Vue website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-vue-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-vue-light.png',
'frameworks' => [
getFramework('VUE', [
'providerRootDirectory' => './vue/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
'key' => 'playground-for-nuxt',
'name' => 'Nuxt playground',
'tagline' => 'A basic Nuxt website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-nuxt-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-nuxt-light.png',
'frameworks' => [
getFramework('NUXT', [
'providerRootDirectory' => './nuxt/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
'key' => 'playground-for-react-native',
'name' => 'React Native playground',
'tagline' => 'A basic React Native website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-react-native-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-react-native-light.png',
'frameworks' => [
getFramework('REACT_NATIVE', [
'providerRootDirectory' => './react-native/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
'key' => 'gallery-for-lynx',
'name' => 'Lynx gallery',
'tagline' => 'A Lynx website showcasing gallery with smooth animations.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/gallery-for-lynx-dark.png',
'screenshotLight' => $url . '/images/sites/templates/gallery-for-lynx-light.png',
'frameworks' => [
getFramework('LYNX', [
'providerRootDirectory' => './lynx/gallery',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => []
],
];
+3 -30
View File
@@ -88,15 +88,6 @@ return [
'question' => 'Enter your Appwrite hostname',
'filter' => ''
],
[
'name' => '_APP_CUSTOM_DOMAIN_DENY_LIST',
'description' => 'List of reserved or prohibited domains when configuring custom domains.',
'introduction' => '',
'default' => 'example.com,test.com,app.example.com',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DOMAIN_FUNCTIONS',
'description' => 'A domain to use for function preview URLs. Setting to empty turns off function preview URLs.',
@@ -106,15 +97,6 @@ return [
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DOMAIN_SITES',
'description' => 'A domain to use for site preview URLs.',
'introduction' => '',
'default' => 'sites.localhost',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DOMAIN_TARGET',
'description' => 'Deprecated since 1.7.0. A DNS A record hostname to serve as a CNAME target for your Appwrite custom domains. You can use the same value as used for the Appwrite \'_APP_DOMAIN\' variable. The default value is \'localhost\'.',
@@ -863,7 +845,7 @@ return [
],
[
'name' => '_APP_FUNCTIONS_CPUS',
'description' => 'Deprecated since 1.7.0. The maximum number of CPU core a single cloud function is allowed to use. Please note that setting a value higher than available cores will result in a function error, which might result in an error. The default value is empty. When it\'s empty or 0, CPU limit will be disabled',
'description' => 'Deprecated since 1.7.0. The maximum number of CPU core a single cloud function is allowed to use. Please note that setting a value higher than available cores will result in a function error, which might result in an error. The default value is empty. When it\'s empty, CPU limit will be disabled.',
'introduction' => '0.7.0',
'default' => '0',
'required' => false,
@@ -881,7 +863,7 @@ return [
],
[
'name' => '_APP_FUNCTIONS_MEMORY',
'description' => 'Deprecated since 1.7.0. The maximum amount of memory a single cloud function is allowed to use in megabytes. The default value is empty. When it\'s empty or 0, memory limit will be disabled.',
'description' => 'Deprecated since 1.7.0. The maximum amount of memory a single cloud function is allowed to use in megabytes. The default value is empty. When it\'s empty, memory limit will be disabled.',
'introduction' => '0.7.0',
'default' => '0',
'required' => false,
@@ -1163,22 +1145,13 @@ return [
],
[
'name' => '_APP_MAINTENANCE_DELAY',
'description' => 'Deprecated with 1.6.2 use _APP_MAINTENANCE_START_TIME instead to run the maintenance at a specific time per day.',
'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_START_TIME',
'description' => 'The time of day (in 24-hour format) when the maintenance process should start. The default value is 00:00.',
'introduction' => '1.6.2',
'default' => '00:00',
'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).',
File diff suppressed because it is too large Load Diff
+9 -16
View File
@@ -171,7 +171,6 @@ App::get('/v1/avatars/credit-cards/:code')
->label('cache.resource', 'avatar/credit-card')
->label('sdk', new Method(
namespace: 'avatars',
group: null,
name: 'getCreditCard',
description: '/docs/references/avatars/get-credit-card.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -187,7 +186,7 @@ App::get('/v1/avatars/credit-cards/:code')
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: ' . \implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))) . '.')
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->inject('response')
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response));
@@ -199,7 +198,6 @@ App::get('/v1/avatars/browsers/:code')
->label('cache.resource', 'avatar/browser')
->label('sdk', new Method(
namespace: 'avatars',
group: null,
name: 'getBrowser',
description: '/docs/references/avatars/get-browser.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -215,7 +213,7 @@ App::get('/v1/avatars/browsers/:code')
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-browsers'))), 'Browser Code.')
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->inject('response')
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response));
@@ -227,7 +225,6 @@ App::get('/v1/avatars/flags/:code')
->label('cache.resource', 'avatar/flag')
->label('sdk', new Method(
namespace: 'avatars',
group: null,
name: 'getFlag',
description: '/docs/references/avatars/get-flag.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -243,7 +240,7 @@ App::get('/v1/avatars/flags/:code')
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-flags'))), 'Country Code. ISO Alpha-2 country code format.')
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->inject('response')
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response));
@@ -255,7 +252,6 @@ App::get('/v1/avatars/image')
->label('cache.resource', 'avatar/image')
->label('sdk', new Method(
namespace: 'avatars',
group: null,
name: 'getImage',
description: '/docs/references/avatars/get-image.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -326,7 +322,6 @@ App::get('/v1/avatars/favicon')
->label('cache.resource', 'avatar/favicon')
->label('sdk', new Method(
namespace: 'avatars',
group: null,
name: 'getFavicon',
description: '/docs/references/avatars/get-favicon.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -477,7 +472,6 @@ App::get('/v1/avatars/qr')
->label('scope', 'avatars.read')
->label('sdk', new Method(
namespace: 'avatars',
group: null,
name: 'getQR',
description: '/docs/references/avatars/get-qr.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -527,7 +521,6 @@ App::get('/v1/avatars/initials')
->label('cache.resource', 'avatar/initials')
->label('sdk', new Method(
namespace: 'avatars',
group: null,
name: 'getInitials',
description: '/docs/references/avatars/get-initials.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -669,7 +662,7 @@ App::get('/v1/cards/cloud')
}
}
$isPlatinum = $user->getSequence() % 100 === 0;
$isPlatinum = $user->getInternalId() % 100 === 0;
} else {
$name = $mock === 'normal-long' ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian';
$createdAt = new \DateTime('now');
@@ -859,7 +852,7 @@ App::get('/v1/cards/cloud-back')
$isEmployee = \array_key_exists($email, $employees);
$isGolden = $isEmployee || $isHero || $isContributor;
$isPlatinum = $user->getSequence() % 100 === 0;
$isPlatinum = $user->getInternalId() % 100 === 0;
} else {
$userId = '63e0bcf3c3eb803ba530';
@@ -926,9 +919,9 @@ App::get('/v1/cards/cloud-og')
}
if (!$mock) {
$sequence = $user->getSequence();
$bgVariation = $sequence % 3 === 0 ? '1' : ($sequence % 3 === 1 ? '2' : '3');
$cardVariation = $sequence % 3 === 0 ? '1' : ($sequence % 3 === 1 ? '2' : '3');
$internalId = $user->getInternalId();
$bgVariation = $internalId % 3 === 0 ? '1' : ($internalId % 3 === 1 ? '2' : '3');
$cardVariation = $internalId % 3 === 0 ? '1' : ($internalId % 3 === 1 ? '2' : '3');
$name = $user->getAttribute('name', 'Anonymous');
$email = $user->getAttribute('email', '');
@@ -958,7 +951,7 @@ App::get('/v1/cards/cloud-og')
}
}
$isPlatinum = $user->getSequence() % 100 === 0;
$isPlatinum = $user->getInternalId() % 100 === 0;
} else {
$bgVariation = \str_ends_with($mock, '-bg2') ? '2' : (\str_ends_with($mock, '-bg3') ? '3' : '1');
$cardVariation = \str_ends_with($mock, '-right') ? '2' : (\str_ends_with($mock, '-middle') ? '3' : '1');
+1 -3
View File
@@ -29,7 +29,6 @@ App::get('/v1/console/variables')
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'console',
group: 'console',
name: 'variables',
description: '/docs/references/console/variables.md',
auth: [AuthType::ADMIN],
@@ -87,12 +86,11 @@ App::get('/v1/console/variables')
});
App::post('/v1/console/assistant')
->desc('Create assistant query')
->desc('Ask query')
->groups(['api', 'assistant'])
->label('scope', 'assistant.read')
->label('sdk', new Method(
namespace: 'assistant',
group: 'console',
name: 'chat',
description: '/docs/references/assistant/chat.md',
auth: [AuthType::ADMIN],
File diff suppressed because it is too large Load Diff
-3
View File
@@ -44,7 +44,6 @@ App::get('/v1/graphql')
->label('scope', 'graphql')
->label('sdk', new Method(
namespace: 'graphql',
group: 'graphql',
name: 'get',
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
hide: true,
@@ -91,7 +90,6 @@ App::post('/v1/graphql/mutation')
->label('scope', 'graphql')
->label('sdk', new Method(
namespace: 'graphql',
group: 'graphql',
name: 'mutation',
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
description: '/docs/references/graphql/post.md',
@@ -142,7 +140,6 @@ App::post('/v1/graphql')
->label('scope', 'graphql')
->label('sdk', new Method(
namespace: 'graphql',
group: 'graphql',
name: 'query',
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
description: '/docs/references/graphql/post.md',
+111 -93
View File
@@ -3,16 +3,13 @@
use Appwrite\ClamAV\Network;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\PubSub\Adapter\Pool as PubSubPool;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Config\Config;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Document;
use Utopia\Domains\Validator\PublicDomain;
use Utopia\Pools\Group;
@@ -35,10 +32,9 @@ App::get('/v1/health')
->label('scope', 'health.read')
->label('sdk', new Method(
namespace: 'health',
group: 'health',
name: 'get',
description: '/docs/references/health/get.md',
auth: [AuthType::KEY],
description: '/docs/references/health/get.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -73,11 +69,10 @@ App::get('/v1/health/db')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'health',
name: 'getDB',
description: '/docs/references/health/get-db.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -89,8 +84,8 @@ App::get('/v1/health/db')
->inject('response')
->inject('pools')
->action(function (Response $response, Group $pools) {
$output = [];
$failures = [];
$configs = [
'Console.DB' => Config::getParam('pools-console'),
@@ -100,7 +95,7 @@ App::get('/v1/health/db')
foreach ($configs as $key => $config) {
foreach ($config as $database) {
try {
$adapter = new DatabasePool($pools->get($database));
$adapter = $pools->get($database)->pop()->getResource();
$checkStart = \microtime(true);
@@ -111,16 +106,16 @@ App::get('/v1/health/db')
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
} else {
$failures[] = $database;
$failure[] = $database;
}
} catch (\Throwable) {
$failures[] = $database;
} catch (\Throwable $th) {
$failure[] = $database;
}
}
}
if (!empty($failures)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'DB failure on: ' . implode(", ", $failures));
if (!empty($failure)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'DB failure on: ' . implode(", ", $failure));
}
$response->dynamic(new Document([
@@ -134,11 +129,10 @@ App::get('/v1/health/cache')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'health',
name: 'getCache',
description: '/docs/references/health/get-cache.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -150,39 +144,44 @@ App::get('/v1/health/cache')
->inject('response')
->inject('pools')
->action(function (Response $response, Group $pools) {
$output = [];
$failures = [];
$configs = [
'Cache' => Config::getParam('pools-cache'),
];
foreach ($configs as $key => $config) {
foreach ($config as $cache) {
foreach ($config as $database) {
try {
$adapter = new CachePool($pools->get($cache));
/** @var \Utopia\Cache\Adapter $adapter */
$adapter = $pools->get($database)->pop()->getResource();
$checkStart = \microtime(true);
if ($adapter->ping()) {
$output[] = new Document([
'name' => $key . " ($cache)",
'name' => $key . " ($database)",
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
} else {
$failures[] = $cache;
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
}
} catch (\Throwable) {
$failures[] = $cache;
} catch (\Throwable $th) {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
}
}
}
if (!empty($failures)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Cache failure on: ' . implode(", ", $failures));
}
$response->dynamic(new Document([
'statuses' => $output,
'total' => count($output),
@@ -194,11 +193,10 @@ App::get('/v1/health/pubsub')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'health',
name: 'getPubSub',
description: '/docs/references/health/get-pubsub.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -210,39 +208,44 @@ App::get('/v1/health/pubsub')
->inject('response')
->inject('pools')
->action(function (Response $response, Group $pools) {
$output = [];
$failures = [];
$configs = [
'PubSub' => Config::getParam('pools-pubsub'),
];
foreach ($configs as $key => $config) {
foreach ($config as $pubsub) {
foreach ($config as $database) {
try {
$adapter = new PubSubPool($pools->get($pubsub));
/** @var \Appwrite\PubSub\Adapter $adapter */
$adapter = $pools->get($database)->pop()->getResource();
$checkStart = \microtime(true);
if ($adapter->ping()) {
$output[] = new Document([
'name' => $key . " ($pubsub)",
'name' => $key . " ($database)",
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
} else {
$failures[] = $pubsub;
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
}
} catch (\Throwable) {
$failures[] = $pubsub;
} catch (\Throwable $th) {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
}
}
}
if (!empty($failures)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Pubsub failure on: ' . implode(", ", $failures));
}
$response->dynamic(new Document([
'statuses' => $output,
'total' => count($output),
@@ -254,11 +257,10 @@ App::get('/v1/health/time')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'health',
name: 'getTime',
description: '/docs/references/health/get-time.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -318,11 +320,10 @@ App::get('/v1/health/queue/webhooks')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getQueueWebhooks',
description: '/docs/references/health/get-queue-webhooks.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -344,18 +345,17 @@ App::get('/v1/health/queue/webhooks')
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
}, ['response']);
App::get('/v1/health/queue/logs')
->desc('Get logs queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getQueueLogs',
description: '/docs/references/health/get-queue-logs.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -377,18 +377,17 @@ App::get('/v1/health/queue/logs')
}
$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', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'health',
name: 'getCertificate',
description: '/docs/references/health/get-certificate.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -434,18 +433,17 @@ App::get('/v1/health/certificate')
'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'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getQueueCertificates',
description: '/docs/references/health/get-queue-certificates.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -467,18 +465,17 @@ App::get('/v1/health/queue/certificates')
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
}, ['response']);
App::get('/v1/health/queue/builds')
->desc('Get builds queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getQueueBuilds',
description: '/docs/references/health/get-queue-builds.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -500,18 +497,17 @@ App::get('/v1/health/queue/builds')
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
}, ['response']);
App::get('/v1/health/queue/databases')
->desc('Get databases queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getQueueDatabases',
description: '/docs/references/health/get-queue-databases.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -534,18 +530,17 @@ App::get('/v1/health/queue/databases')
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
}, ['response']);
App::get('/v1/health/queue/deletes')
->desc('Get deletes queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getQueueDeletes',
description: '/docs/references/health/get-queue-deletes.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -567,18 +562,17 @@ App::get('/v1/health/queue/deletes')
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
}, ['response']);
App::get('/v1/health/queue/mails')
->desc('Get mails queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getQueueMails',
description: '/docs/references/health/get-queue-mails.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -600,18 +594,17 @@ App::get('/v1/health/queue/mails')
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
}, ['response']);
App::get('/v1/health/queue/messaging')
->desc('Get messaging queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getQueueMessaging',
description: '/docs/references/health/get-queue-messaging.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -633,18 +626,17 @@ App::get('/v1/health/queue/messaging')
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
}, ['response']);
App::get('/v1/health/queue/migrations')
->desc('Get migrations queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getQueueMigrations',
description: '/docs/references/health/get-queue-migrations.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -666,18 +658,17 @@ App::get('/v1/health/queue/migrations')
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
}, ['response']);
App::get('/v1/health/queue/functions')
->desc('Get functions queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getQueueFunctions',
description: '/docs/references/health/get-queue-functions.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -699,18 +690,17 @@ App::get('/v1/health/queue/functions')
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
}, ['response']);
App::get('/v1/health/queue/stats-resources')
->desc('Get stats resources queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getQueueStatsResources',
description: '/docs/references/health/get-queue-stats-resources.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -739,11 +729,10 @@ App::get('/v1/health/queue/stats-usage')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getQueueUsage',
description: '/docs/references/health/get-queue-stats-usage.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -767,16 +756,47 @@ App::get('/v1/health/queue/stats-usage')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
App::get('/v1/health/queue/stats-usage-dump')
->desc('Get usage dump queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueStatsUsageDump',
description: '/docs/references/health/get-queue-stats-usage-dump.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$size = $publisher->getQueueSize(new Queue(Event::STATS_USAGE_DUMP_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
App::get('/v1/health/storage/local')
->desc('Get local storage')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'storage',
name: 'getStorageLocal',
description: '/docs/references/health/get-storage-local.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -822,11 +842,10 @@ App::get('/v1/health/storage')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'storage',
name: 'getStorage',
description: '/docs/references/health/get-storage.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -845,18 +864,15 @@ App::get('/v1/health/storage')
$checkStart = \microtime(true);
foreach ($devices as $device) {
$uniqueFileName = \uniqid('health', true);
$filePath = $device->getPath($uniqueFileName);
if (!$device->write($filePath, 'test', 'text/plain')) {
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($filePath) !== 'test') {
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($filePath)) {
if (!$device->delete($device->getPath('health.txt'))) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed deleting test file from ' . $device->getRoot());
}
}
@@ -874,11 +890,10 @@ App::get('/v1/health/anti-virus')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'health',
name: 'getAntivirus',
description: '/docs/references/health/get-storage-anti-virus.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -920,11 +935,10 @@ App::get('/v1/health/queue/failed/:name')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
group: 'queue',
name: 'getFailedJobs',
description: '/docs/references/health/get-failed-queue-jobs.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -941,6 +955,7 @@ App::get('/v1/health/queue/failed/:name')
Event::FUNCTIONS_QUEUE_NAME,
Event::STATS_RESOURCES_QUEUE_NAME,
Event::STATS_USAGE_QUEUE_NAME,
Event::STATS_USAGE_DUMP_QUEUE_NAME,
Event::WEBHOOK_QUEUE_NAME,
Event::CERTIFICATES_QUEUE_NAME,
Event::BUILDS_QUEUE_NAME,
@@ -966,6 +981,9 @@ App::get('/v1/health/stats') // Currently only used internally
->desc('Get system stats')
->groups(['api', 'health'])
->label('scope', 'root')
// ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
// ->label('sdk.namespace', 'health')
// ->label('sdk.method', 'getStats')
->label('docs', false)
->inject('response')
->inject('register')
-8
View File
@@ -17,7 +17,6 @@ App::get('/v1/locale')
->label('scope', 'locale.read')
->label('sdk', new Method(
namespace: 'locale',
group: null,
name: 'get',
description: '/docs/references/locale/get-locale.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -81,7 +80,6 @@ App::get('/v1/locale/codes')
->label('scope', 'locale.read')
->label('sdk', new Method(
namespace: 'locale',
group: null,
name: 'listCodes',
description: '/docs/references/locale/list-locale-codes.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -107,7 +105,6 @@ App::get('/v1/locale/countries')
->label('scope', 'locale.read')
->label('sdk', new Method(
namespace: 'locale',
group: null,
name: 'listCountries',
description: '/docs/references/locale/list-countries.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -144,7 +141,6 @@ App::get('/v1/locale/countries/eu')
->label('scope', 'locale.read')
->label('sdk', new Method(
namespace: 'locale',
group: null,
name: 'listCountriesEU',
description: '/docs/references/locale/list-countries-eu.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -183,7 +179,6 @@ App::get('/v1/locale/countries/phones')
->label('scope', 'locale.read')
->label('sdk', new Method(
namespace: 'locale',
group: null,
name: 'listCountriesPhones',
description: '/docs/references/locale/list-countries-phones.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -221,7 +216,6 @@ App::get('/v1/locale/continents')
->label('scope', 'locale.read')
->label('sdk', new Method(
namespace: 'locale',
group: null,
name: 'listContinents',
description: '/docs/references/locale/list-continents.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -257,7 +251,6 @@ App::get('/v1/locale/currencies')
->label('scope', 'locale.read')
->label('sdk', new Method(
namespace: 'locale',
group: null,
name: 'listCurrencies',
description: '/docs/references/locale/list-currencies.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -284,7 +277,6 @@ App::get('/v1/locale/languages')
->label('scope', 'locale.read')
->label('sdk', new Method(
namespace: 'locale',
group: null,
name: 'listLanguages',
description: '/docs/references/locale/list-languages.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
+28 -98
View File
@@ -30,7 +30,6 @@ use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
@@ -64,7 +63,6 @@ App::post('/v1/messaging/providers/mailgun')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'createMailgunProvider',
description: '/docs/references/messaging/create-mailgun-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -158,7 +156,6 @@ App::post('/v1/messaging/providers/sendgrid')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'createSendgridProvider',
description: '/docs/references/messaging/create-sendgrid-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -240,7 +237,6 @@ App::post('/v1/messaging/providers/smtp')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'createSmtpProvider',
description: '/docs/references/messaging/create-smtp-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -335,7 +331,6 @@ App::post('/v1/messaging/providers/msg91')
->label('event', 'providers.[providerId].create')
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'createMsg91Provider',
description: '/docs/references/messaging/create-msg91-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -418,7 +413,6 @@ App::post('/v1/messaging/providers/telesign')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'createTelesignProvider',
description: '/docs/references/messaging/create-telesign-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -502,7 +496,6 @@ App::post('/v1/messaging/providers/textmagic')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'createTextmagicProvider',
description: '/docs/references/messaging/create-textmagic-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -586,7 +579,6 @@ App::post('/v1/messaging/providers/twilio')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'createTwilioProvider',
description: '/docs/references/messaging/create-twilio-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -670,7 +662,6 @@ App::post('/v1/messaging/providers/vonage')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'createVonageProvider',
description: '/docs/references/messaging/create-vonage-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -754,7 +745,6 @@ App::post('/v1/messaging/providers/fcm')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'createFcmProvider',
description: '/docs/references/messaging/create-fcm-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -824,7 +814,6 @@ App::post('/v1/messaging/providers/apns')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'createApnsProvider',
description: '/docs/references/messaging/create-apns-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -914,7 +903,6 @@ App::get('/v1/messaging/providers')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'listProviders',
description: '/docs/references/messaging/list-providers.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -963,15 +951,10 @@ App::get('/v1/messaging/providers')
$cursor->setValue($cursorDocument);
}
try {
$providers = $dbForProject->find('providers', $queries);
$total = $dbForProject->count('providers', $queries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'providers' => $providers,
'total' => $total,
'providers' => $dbForProject->find('providers', $queries),
'total' => $dbForProject->count('providers', $queries, APP_LIMIT_COUNT),
]), Response::MODEL_PROVIDER_LIST);
});
@@ -982,7 +965,6 @@ App::get('/v1/messaging/providers/:providerId/logs')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'listProviderLogs',
description: '/docs/references/messaging/list-provider-logs.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -1079,7 +1061,6 @@ App::get('/v1/messaging/providers/:providerId')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'getProvider',
description: '/docs/references/messaging/get-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -1113,7 +1094,6 @@ App::patch('/v1/messaging/providers/mailgun/:providerId')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'updateMailgunProvider',
description: '/docs/references/messaging/update-mailgun-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -1226,7 +1206,6 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'updateSendgridProvider',
description: '/docs/references/messaging/update-sendgrid-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -1324,7 +1303,6 @@ App::patch('/v1/messaging/providers/smtp/:providerId')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'updateSmtpProvider',
description: '/docs/references/messaging/update-smtp-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -1453,7 +1431,6 @@ App::patch('/v1/messaging/providers/msg91/:providerId')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'updateMsg91Provider',
description: '/docs/references/messaging/update-msg91-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -1540,7 +1517,6 @@ App::patch('/v1/messaging/providers/telesign/:providerId')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'updateTelesignProvider',
description: '/docs/references/messaging/update-telesign-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -1629,7 +1605,6 @@ App::patch('/v1/messaging/providers/textmagic/:providerId')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'updateTextmagicProvider',
description: '/docs/references/messaging/update-textmagic-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -1718,7 +1693,6 @@ App::patch('/v1/messaging/providers/twilio/:providerId')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'updateTwilioProvider',
description: '/docs/references/messaging/update-twilio-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -1807,7 +1781,6 @@ App::patch('/v1/messaging/providers/vonage/:providerId')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'updateVonageProvider',
description: '/docs/references/messaging/update-vonage-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -1896,7 +1869,6 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'updateFcmProvider',
description: '/docs/references/messaging/update-fcm-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -1972,7 +1944,6 @@ App::patch('/v1/messaging/providers/apns/:providerId')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'updateApnsProvider',
description: '/docs/references/messaging/update-apns-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -2074,7 +2045,6 @@ App::delete('/v1/messaging/providers/:providerId')
->label('resourceType', RESOURCE_TYPE_PROVIDERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'providers',
name: 'deleteProvider',
description: '/docs/references/messaging/delete-provider.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -2117,7 +2087,6 @@ App::post('/v1/messaging/topics')
->label('resourceType', RESOURCE_TYPE_TOPICS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'topics',
name: 'createTopic',
description: '/docs/references/messaging/create-topic.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -2164,7 +2133,6 @@ App::get('/v1/messaging/topics')
->label('resourceType', RESOURCE_TYPE_TOPICS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'topics',
name: 'listTopics',
description: '/docs/references/messaging/list-topics.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -2213,15 +2181,10 @@ App::get('/v1/messaging/topics')
$cursor->setValue($cursorDocument[0]);
}
try {
$topics = $dbForProject->find('topics', $queries);
$total = $dbForProject->count('topics', $queries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'topics' => $topics,
'total' => $total,
'topics' => $dbForProject->find('topics', $queries),
'total' => $dbForProject->count('topics', $queries, APP_LIMIT_COUNT),
]), Response::MODEL_TOPIC_LIST);
});
@@ -2232,7 +2195,6 @@ App::get('/v1/messaging/topics/:topicId/logs')
->label('resourceType', RESOURCE_TYPE_TOPICS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'topics',
name: 'listTopicLogs',
description: '/docs/references/messaging/list-topic-logs.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -2330,7 +2292,6 @@ App::get('/v1/messaging/topics/:topicId')
->label('resourceType', RESOURCE_TYPE_TOPICS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'topics',
name: 'getTopic',
description: '/docs/references/messaging/get-topic.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -2365,7 +2326,6 @@ App::patch('/v1/messaging/topics/:topicId')
->label('resourceType', RESOURCE_TYPE_TOPICS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'topics',
name: 'updateTopic',
description: '/docs/references/messaging/update-topic.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -2416,7 +2376,6 @@ App::delete('/v1/messaging/topics/:topicId')
->label('resourceType', RESOURCE_TYPE_TOPICS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'topics',
name: 'deleteTopic',
description: '/docs/references/messaging/delete-topic.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -2464,7 +2423,6 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'subscribers',
name: 'createSubscriber',
description: '/docs/references/messaging/create-subscriber.md',
auth: [AuthType::JWT, AuthType::SESSION, AuthType::ADMIN, AuthType::KEY],
@@ -2511,11 +2469,11 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
Permission::delete(Role::user($user->getId())),
],
'topicId' => $topicId,
'topicInternalId' => $topic->getSequence(),
'topicInternalId' => $topic->getInternalId(),
'targetId' => $targetId,
'targetInternalId' => $target->getSequence(),
'targetInternalId' => $target->getInternalId(),
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'userInternalId' => $user->getInternalId(),
'providerType' => $target->getAttribute('providerType'),
'search' => implode(' ', [
$subscriberId,
@@ -2564,7 +2522,6 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'subscribers',
name: 'listSubscribers',
description: '/docs/references/messaging/list-subscribers.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -2597,7 +2554,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
throw new Exception(Exception::TOPIC_NOT_FOUND);
}
$queries[] = Query::equal('topicInternalId', [$topic->getSequence()]);
$queries[] = Query::equal('topicInternalId', [$topic->getInternalId()]);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
@@ -2622,11 +2579,8 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
$cursor->setValue($cursorDocument);
}
try {
$subscribers = $dbForProject->find('subscribers', $queries);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$subscribers = $dbForProject->find('subscribers', $queries);
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) {
return function () use ($subscriber, $dbForProject) {
@@ -2653,7 +2607,6 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'subscribers',
name: 'listSubscriberLogs',
description: '/docs/references/messaging/list-subscriber-logs.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -2751,7 +2704,6 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'subscribers',
name: 'getSubscriber',
description: '/docs/references/messaging/get-subscriber.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -2800,7 +2752,6 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS)
->label('sdk', new Method(
namespace: 'messaging',
group: 'subscribers',
name: 'deleteSubscriber',
description: '/docs/references/messaging/delete-subscriber.md',
auth: [AuthType::JWT, AuthType::SESSION, AuthType::ADMIN, AuthType::KEY],
@@ -2867,7 +2818,6 @@ App::post('/v1/messaging/messages/email')
->label('resourceType', RESOURCE_TYPE_MESSAGES)
->label('sdk', new Method(
namespace: 'messaging',
group: 'messages',
name: 'createEmail',
description: '/docs/references/messaging/create-email.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -2947,7 +2897,7 @@ App::post('/v1/messaging/messages/email')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
@@ -2989,7 +2939,7 @@ App::post('/v1/messaging/messages/email')
'region' => $project->getAttribute('region'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getSequence(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $scheduledAt,
@@ -3026,7 +2976,6 @@ App::post('/v1/messaging/messages/sms')
->label('resourceType', RESOURCE_TYPE_MESSAGES)
->label('sdk', new Method(
namespace: 'messaging',
group: 'messages',
name: 'createSms',
description: '/docs/references/messaging/create-sms.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -3112,7 +3061,7 @@ App::post('/v1/messaging/messages/sms')
'region' => $project->getAttribute('region'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getSequence(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $scheduledAt,
@@ -3149,7 +3098,6 @@ App::post('/v1/messaging/messages/push')
->label('resourceType', RESOURCE_TYPE_MESSAGES)
->label('sdk', new Method(
namespace: 'messaging',
group: 'messages',
name: 'createPush',
description: '/docs/references/messaging/create-push.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -3232,7 +3180,7 @@ App::post('/v1/messaging/messages/push')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
@@ -3330,7 +3278,7 @@ App::post('/v1/messaging/messages/push')
'region' => $project->getAttribute('region'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getSequence(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $scheduledAt,
@@ -3364,7 +3312,6 @@ App::get('/v1/messaging/messages')
->label('resourceType', RESOURCE_TYPE_MESSAGES)
->label('sdk', new Method(
namespace: 'messaging',
group: 'messages',
name: 'listMessages',
description: '/docs/references/messaging/list-messages.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -3413,15 +3360,10 @@ App::get('/v1/messaging/messages')
$cursor->setValue($cursorDocument);
}
try {
$messages = $dbForProject->find('messages', $queries);
$total = $dbForProject->count('messages', $queries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'messages' => $messages,
'total' => $total,
'messages' => $dbForProject->find('messages', $queries),
'total' => $dbForProject->count('messages', $queries, APP_LIMIT_COUNT),
]), Response::MODEL_MESSAGE_LIST);
});
@@ -3432,7 +3374,6 @@ App::get('/v1/messaging/messages/:messageId/logs')
->label('resourceType', RESOURCE_TYPE_MESSAGES)
->label('sdk', new Method(
namespace: 'messaging',
group: 'logs',
name: 'listMessageLogs',
description: '/docs/references/messaging/list-message-logs.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -3530,7 +3471,6 @@ App::get('/v1/messaging/messages/:messageId/targets')
->label('resourceType', RESOURCE_TYPE_MESSAGES)
->label('sdk', new Method(
namespace: 'messaging',
group: 'messages',
name: 'listTargets',
description: '/docs/references/messaging/list-message-targets.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -3593,15 +3533,10 @@ App::get('/v1/messaging/messages/:messageId/targets')
$cursor->setValue($cursorDocument);
}
try {
$targets = $dbForProject->find('targets', $queries);
$total = $dbForProject->count('targets', $queries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'targets' => $targets,
'total' => $total,
'targets' => $dbForProject->find('targets', $queries),
'total' => $dbForProject->count('targets', $queries, APP_LIMIT_COUNT),
]), Response::MODEL_TARGET_LIST);
});
@@ -3612,7 +3547,6 @@ App::get('/v1/messaging/messages/:messageId')
->label('resourceType', RESOURCE_TYPE_MESSAGES)
->label('sdk', new Method(
namespace: 'messaging',
group: 'messages',
name: 'getMessage',
description: '/docs/references/messaging/get-message.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -3646,7 +3580,6 @@ App::patch('/v1/messaging/messages/email/:messageId')
->label('resourceType', RESOURCE_TYPE_MESSAGES)
->label('sdk', new Method(
namespace: 'messaging',
group: 'messages',
name: 'updateEmail',
description: '/docs/references/messaging/update-email.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -3731,7 +3664,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
'region' => $project->getAttribute('region'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getSequence(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $scheduledAt,
@@ -3796,7 +3729,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
@@ -3853,7 +3786,6 @@ App::patch('/v1/messaging/messages/sms/:messageId')
->label('resourceType', RESOURCE_TYPE_MESSAGES)
->label('sdk', new Method(
namespace: 'messaging',
group: 'messages',
name: 'updateSms',
description: '/docs/references/messaging/update-sms.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -3933,7 +3865,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
'region' => $project->getAttribute('region'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getSequence(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $scheduledAt,
@@ -4015,7 +3947,6 @@ App::patch('/v1/messaging/messages/push/:messageId')
->label('resourceType', RESOURCE_TYPE_MESSAGES)
->label('sdk', new Method(
namespace: 'messaging',
group: 'messages',
name: 'updatePush',
description: '/docs/references/messaging/update-push.md',
auth: [AuthType::ADMIN, AuthType::KEY],
@@ -4107,7 +4038,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
'region' => $project->getAttribute('region'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getSequence(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $scheduledAt,
@@ -4210,7 +4141,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
@@ -4275,7 +4206,6 @@ App::delete('/v1/messaging/messages/:messageId')
->label('resourceType', RESOURCE_TYPE_MESSAGES)
->label('sdk', new Method(
namespace: 'messaging',
group: 'messages',
name: 'delete',
description: '/docs/references/messaging/delete-message.md',
auth: [AuthType::ADMIN, AuthType::KEY],
+13 -33
View File
@@ -15,7 +15,6 @@ use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
@@ -44,13 +43,12 @@ include_once __DIR__ . '/../shared/api.php';
App::post('/v1/migrations/appwrite')
->groups(['api', 'migrations'])
->desc('Create Appwrite migration')
->desc('Migrate Appwrite data')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createAppwriteMigration',
description: '/docs/references/migrations/migration-appwrite.md',
auth: [AuthType::ADMIN],
@@ -105,13 +103,12 @@ App::post('/v1/migrations/appwrite')
App::post('/v1/migrations/firebase')
->groups(['api', 'migrations'])
->desc('Create Firebase migration')
->desc('Migrate Firebase data')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createFirebaseMigration',
description: '/docs/references/migrations/migration-firebase.md',
auth: [AuthType::ADMIN],
@@ -172,13 +169,12 @@ App::post('/v1/migrations/firebase')
App::post('/v1/migrations/supabase')
->groups(['api', 'migrations'])
->desc('Create Supabase migration')
->desc('Migrate Supabase data')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createSupabaseMigration',
description: '/docs/references/migrations/migration-supabase.md',
auth: [AuthType::ADMIN],
@@ -239,13 +235,12 @@ App::post('/v1/migrations/supabase')
App::post('/v1/migrations/nhost')
->groups(['api', 'migrations'])
->desc('Create NHost migration')
->desc('Migrate NHost data')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createNHostMigration',
description: '/docs/references/migrations/migration-nhost.md',
auth: [AuthType::ADMIN],
@@ -314,7 +309,6 @@ App::post('/v1/migrations/csv')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createCsvMigration',
description: '/docs/references/migrations/migration-csv.md',
auth: [AuthType::ADMIN],
@@ -345,7 +339,7 @@ App::post('/v1/migrations/csv')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
@@ -437,7 +431,6 @@ App::get('/v1/migrations')
->label('scope', 'migrations.read')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'list',
description: '/docs/references/migrations/list-migrations.md',
auth: [AuthType::ADMIN],
@@ -489,15 +482,10 @@ App::get('/v1/migrations')
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$migrations = $dbForProject->find('migrations', $queries);
$total = $dbForProject->count('migrations', $filterQueries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'migrations' => $migrations,
'total' => $total,
'migrations' => $dbForProject->find('migrations', $queries),
'total' => $dbForProject->count('migrations', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_MIGRATION_LIST);
});
@@ -507,7 +495,6 @@ App::get('/v1/migrations/:migrationId')
->label('scope', 'migrations.read')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'get',
description: '/docs/references/migrations/get-migration.md',
auth: [AuthType::ADMIN],
@@ -533,11 +520,10 @@ App::get('/v1/migrations/:migrationId')
App::get('/v1/migrations/appwrite/report')
->groups(['api', 'migrations'])
->desc('Get Appwrite migration report')
->desc('Generate a report on Appwrite data')
->label('scope', 'migrations.write')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'getAppwriteReport',
description: '/docs/references/migrations/migration-appwrite-report.md',
auth: [AuthType::ADMIN],
@@ -557,7 +543,6 @@ App::get('/v1/migrations/appwrite/report')
->inject('project')
->inject('user')
->action(function (array $resources, string $endpoint, string $projectID, string $key, Response $response) {
$appwrite = new Appwrite($projectID, $endpoint, $key);
try {
@@ -582,11 +567,10 @@ App::get('/v1/migrations/appwrite/report')
App::get('/v1/migrations/firebase/report')
->groups(['api', 'migrations'])
->desc('Get Firebase migration report')
->desc('Generate a report on Firebase data')
->label('scope', 'migrations.write')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'getFirebaseReport',
description: '/docs/references/migrations/migration-firebase-report.md',
auth: [AuthType::ADMIN],
@@ -635,11 +619,10 @@ App::get('/v1/migrations/firebase/report')
App::get('/v1/migrations/supabase/report')
->groups(['api', 'migrations'])
->desc('Get Supabase migration report')
->desc('Generate a report on Supabase Data')
->label('scope', 'migrations.write')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'getSupabaseReport',
description: '/docs/references/migrations/migration-supabase-report.md',
auth: [AuthType::ADMIN],
@@ -684,11 +667,10 @@ App::get('/v1/migrations/supabase/report')
App::get('/v1/migrations/nhost/report')
->groups(['api', 'migrations'])
->desc('Get NHost migration report')
->desc('Generate a report on NHost Data')
->label('scope', 'migrations.write')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'getNHostReport',
description: '/docs/references/migrations/migration-nhost-report.md',
auth: [AuthType::ADMIN],
@@ -733,14 +715,13 @@ App::get('/v1/migrations/nhost/report')
App::patch('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Update retry migration')
->desc('Retry migration')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].retry')
->label('audits.event', 'migration.retry')
->label('audits.resource', 'migrations/{request.migrationId}')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'retry',
description: '/docs/references/migrations/retry-migration.md',
auth: [AuthType::ADMIN],
@@ -791,7 +772,6 @@ App::delete('/v1/migrations/:migrationId')
->label('audits.resource', 'migrations/{request.migrationId}')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'delete',
description: '/docs/references/migrations/delete-migration.md',
auth: [AuthType::ADMIN],
+9 -15
View File
@@ -27,7 +27,6 @@ App::get('/v1/project/usage')
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'project',
group: null,
name: 'getUsage',
description: '/docs/references/project/get-usage.md',
auth: [AuthType::ADMIN],
@@ -150,7 +149,7 @@ App::get('/v1/project/usage')
$executionsBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS);
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
@@ -166,7 +165,7 @@ App::get('/v1/project/usage')
$executionsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS);
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
@@ -182,7 +181,7 @@ App::get('/v1/project/usage')
$buildsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getSequence()], METRIC_RESOURCE_TYPE_ID_BUILDS_MB_SECONDS);
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_BUILDS_MB_SECONDS);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
@@ -198,7 +197,7 @@ App::get('/v1/project/usage')
$bucketsBreakdown = array_map(function ($bucket) use ($dbForProject) {
$id = $bucket->getId();
$name = $bucket->getAttribute('name');
$metric = str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES_STORAGE);
$metric = str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
@@ -214,7 +213,7 @@ App::get('/v1/project/usage')
$databasesStorageBreakdown = array_map(function ($database) use ($dbForProject) {
$id = $database->getId();
$name = $database->getAttribute('name');
$metric = str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_STORAGE);
$metric = str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_STORAGE);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
@@ -231,13 +230,13 @@ App::get('/v1/project/usage')
$functionsStorageBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$deploymentMetric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getSequence()], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE);
$deploymentMetric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE);
$deploymentValue = $dbForProject->findOne('stats', [
Query::equal('metric', [$deploymentMetric]),
Query::equal('period', ['inf'])
]);
$buildMetric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getSequence()], METRIC_RESOURCE_TYPE_ID_BUILDS_STORAGE);
$buildMetric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_BUILDS_STORAGE);
$buildValue = $dbForProject->findOne('stats', [
Query::equal('metric', [$buildMetric]),
Query::equal('period', ['inf'])
@@ -255,7 +254,7 @@ App::get('/v1/project/usage')
$executionsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS);
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
@@ -271,7 +270,7 @@ App::get('/v1/project/usage')
$buildsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getSequence()], METRIC_RESOURCE_TYPE_ID_BUILDS_MB_SECONDS);
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getInternalId()], METRIC_RESOURCE_TYPE_ID_BUILDS_MB_SECONDS);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
@@ -389,7 +388,6 @@ App::post('/v1/project/variables')
->label('audits.event', 'variable.create')
->label('sdk', new Method(
namespace: 'project',
group: null,
name: 'createVariable',
description: '/docs/references/project/create-variable.md',
auth: [AuthType::ADMIN],
@@ -451,7 +449,6 @@ App::get('/v1/project/variables')
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'project',
group: null,
name: 'listVariables',
description: '/docs/references/project/list-variables.md',
auth: [AuthType::ADMIN],
@@ -482,7 +479,6 @@ App::get('/v1/project/variables/:variableId')
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'project',
group: null,
name: 'getVariable',
description: '/docs/references/project/get-variable.md',
auth: [AuthType::ADMIN],
@@ -512,7 +508,6 @@ App::put('/v1/project/variables/:variableId')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'project',
group: null,
name: 'updateVariable',
description: '/docs/references/project/update-variable.md',
auth: [AuthType::ADMIN],
@@ -570,7 +565,6 @@ App::delete('/v1/project/variables/:variableId')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'project',
group: null,
name: 'deleteVariable',
description: '/docs/references/project/delete-variable.md',
auth: [AuthType::ADMIN],
+33 -102
View File
@@ -24,12 +24,10 @@ use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
@@ -70,7 +68,6 @@ App::post('/v1/projects')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'create',
description: '/docs/references/projects/create.md',
auth: [AuthType::ADMIN],
@@ -141,14 +138,6 @@ App::post('/v1/projects')
$databases = Config::getParam('pools-database', []);
if ($region !== 'default') {
$databaseKeys = System::getEnv('_APP_DATABASE_KEYS', '');
$keys = explode(',', $databaseKeys);
$databases = array_filter($keys, function ($value) use ($region) {
return str_contains($value, $region);
});
}
$databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE');
$index = \array_search($databaseOverride, $databases);
if ($index !== false) {
@@ -182,7 +171,7 @@ App::post('/v1/projects')
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'name' => $name,
'teamInternalId' => $team->getSequence(),
'teamInternalId' => $team->getInternalId(),
'teamId' => $team->getId(),
'region' => $region,
'description' => $description,
@@ -216,27 +205,27 @@ App::post('/v1/projects')
$dsn = new DSN('mysql://' . $dsn);
}
$adapter = $pools->get($dsn->getHost())->pop()->getResource();
$dbForProject = new Database($adapter, $cache);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
$sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', ''));
$projectTables = !\in_array($dsn->getHost(), $sharedTables);
$sharedTablesV1 = \in_array($dsn->getHost(), $sharedTablesV1);
$sharedTablesV2 = !$projectTables && !$sharedTablesV1;
$sharedTables = $sharedTablesV1 || $sharedTablesV2;
if (!$sharedTablesV2) {
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$dbForProject = new Database($adapter, $cache);
if ($sharedTables) {
$dbForProject
->setSharedTables(true)
->setTenant($sharedTablesV1 ? (int)$project->getSequence() : null)
->setTenant($sharedTablesV1 ? $project->getInternalId() : null)
->setNamespace($dsn->getParam('namespace'));
} else {
$dbForProject
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
->setNamespace('_' . $project->getInternalId());
}
$create = true;
@@ -308,7 +297,6 @@ App::get('/v1/projects')
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'list',
description: '/docs/references/projects/list.md',
auth: [AuthType::ADMIN],
@@ -361,15 +349,10 @@ App::get('/v1/projects')
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$projects = $dbForPlatform->find('projects', $queries);
$total = $dbForPlatform->count('projects', $filterQueries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'projects' => $projects,
'total' => $total,
'projects' => $dbForPlatform->find('projects', $queries),
'total' => $dbForPlatform->count('projects', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_PROJECT_LIST);
});
@@ -379,7 +362,6 @@ App::get('/v1/projects/:projectId')
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'get',
description: '/docs/references/projects/get.md',
auth: [AuthType::ADMIN],
@@ -412,7 +394,6 @@ App::patch('/v1/projects/:projectId')
->label('audits.resource', 'project/{request.projectId}')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'update',
description: '/docs/references/projects/update.md',
auth: [AuthType::ADMIN],
@@ -466,7 +447,6 @@ App::patch('/v1/projects/:projectId/team')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'updateTeam',
description: '/docs/references/projects/update-team.md',
auth: [AuthType::ADMIN],
@@ -504,12 +484,12 @@ App::patch('/v1/projects/:projectId/team')
$project
->setAttribute('teamId', $teamId)
->setAttribute('teamInternalId', $team->getSequence())
->setAttribute('teamInternalId', $team->getInternalId())
->setAttribute('$permissions', $permissions);
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project);
$installations = $dbForPlatform->find('installations', [
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
foreach ($installations as $installation) {
$installation->getAttribute('$permissions', $permissions);
@@ -517,7 +497,7 @@ App::patch('/v1/projects/:projectId/team')
}
$repositories = $dbForPlatform->find('repositories', [
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
foreach ($repositories as $repository) {
$repository->getAttribute('$permissions', $permissions);
@@ -525,7 +505,7 @@ App::patch('/v1/projects/:projectId/team')
}
$vcsComments = $dbForPlatform->find('vcsComments', [
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
foreach ($vcsComments as $vcsComment) {
$vcsComment->getAttribute('$permissions', $permissions);
@@ -541,7 +521,6 @@ App::patch('/v1/projects/:projectId/service')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'updateServiceStatus',
description: '/docs/references/projects/update-service-status.md',
auth: [AuthType::ADMIN],
@@ -579,7 +558,6 @@ App::patch('/v1/projects/:projectId/service/all')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'updateServiceStatusAll',
description: '/docs/references/projects/update-service-status-all.md',
auth: [AuthType::ADMIN],
@@ -620,7 +598,6 @@ App::patch('/v1/projects/:projectId/api')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'updateApiStatus',
description: '/docs/references/projects/update-api-status.md',
auth: [AuthType::ADMIN],
@@ -658,7 +635,6 @@ App::patch('/v1/projects/:projectId/api/all')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'updateApiStatusAll',
description: '/docs/references/projects/update-api-status-all.md',
auth: [AuthType::ADMIN],
@@ -699,7 +675,6 @@ App::patch('/v1/projects/:projectId/oauth2')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'updateOAuth2',
description: '/docs/references/projects/update-oauth2.md',
auth: [AuthType::ADMIN],
@@ -750,7 +725,6 @@ App::patch('/v1/projects/:projectId/auth/session-alerts')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'updateSessionAlerts',
description: '/docs/references/projects/update-session-alerts.md',
auth: [AuthType::ADMIN],
@@ -788,7 +762,6 @@ App::patch('/v1/projects/:projectId/auth/memberships-privacy')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'updateMembershipsPrivacy',
description: '/docs/references/projects/update-memberships-privacy.md',
auth: [AuthType::ADMIN],
@@ -830,7 +803,6 @@ App::patch('/v1/projects/:projectId/auth/limit')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'updateAuthLimit',
description: '/docs/references/projects/update-auth-limit.md',
auth: [AuthType::ADMIN],
@@ -868,7 +840,6 @@ App::patch('/v1/projects/:projectId/auth/duration')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'updateAuthDuration',
description: '/docs/references/projects/update-auth-duration.md',
auth: [AuthType::ADMIN],
@@ -906,7 +877,6 @@ App::patch('/v1/projects/:projectId/auth/:method')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'updateAuthStatus',
description: '/docs/references/projects/update-auth-status.md',
auth: [AuthType::ADMIN],
@@ -947,7 +917,6 @@ App::patch('/v1/projects/:projectId/auth/password-history')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'updateAuthPasswordHistory',
description: '/docs/references/projects/update-auth-password-history.md',
auth: [AuthType::ADMIN],
@@ -985,7 +954,6 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'updateAuthPasswordDictionary',
description: '/docs/references/projects/update-auth-password-dictionary.md',
auth: [AuthType::ADMIN],
@@ -1018,12 +986,11 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary')
});
App::patch('/v1/projects/:projectId/auth/personal-data')
->desc('Update personal data check')
->desc('Enable or disable checking user passwords for similarity with their personal data.')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'updatePersonalDataCheck',
description: '/docs/references/projects/update-personal-data-check.md',
auth: [AuthType::ADMIN],
@@ -1061,7 +1028,6 @@ App::patch('/v1/projects/:projectId/auth/max-sessions')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'updateAuthSessionsLimit',
description: '/docs/references/projects/update-auth-sessions-limit.md',
auth: [AuthType::ADMIN],
@@ -1099,7 +1065,6 @@ App::patch('/v1/projects/:projectId/auth/mock-numbers')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'updateMockNumbers',
description: '/docs/references/projects/update-mock-numbers.md',
auth: [AuthType::ADMIN],
@@ -1147,7 +1112,6 @@ App::delete('/v1/projects/:projectId')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'delete',
description: '/docs/references/projects/delete.md',
auth: [AuthType::ADMIN],
@@ -1191,7 +1155,6 @@ App::post('/v1/projects/:projectId/webhooks')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'webhooks',
name: 'createWebhook',
description: '/docs/references/projects/create-webhook.md',
auth: [AuthType::ADMIN],
@@ -1229,7 +1192,7 @@ App::post('/v1/projects/:projectId/webhooks')
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'projectInternalId' => $project->getSequence(),
'projectInternalId' => $project->getInternalId(),
'projectId' => $project->getId(),
'name' => $name,
'events' => $events,
@@ -1256,7 +1219,6 @@ App::get('/v1/projects/:projectId/webhooks')
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'projects',
group: 'webhooks',
name: 'listWebhooks',
description: '/docs/references/projects/list-webhooks.md',
auth: [AuthType::ADMIN],
@@ -1279,7 +1241,7 @@ App::get('/v1/projects/:projectId/webhooks')
}
$webhooks = $dbForPlatform->find('webhooks', [
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::limit(5000),
]);
@@ -1295,7 +1257,6 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'projects',
group: 'webhooks',
name: 'getWebhook',
description: '/docs/references/projects/get-webhook.md',
auth: [AuthType::ADMIN],
@@ -1320,7 +1281,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
$webhook = $dbForPlatform->findOne('webhooks', [
Query::equal('$id', [$webhookId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($webhook->isEmpty()) {
@@ -1336,7 +1297,6 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'webhooks',
name: 'updateWebhook',
description: '/docs/references/projects/update-webhook.md',
auth: [AuthType::ADMIN],
@@ -1370,7 +1330,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
$webhook = $dbForPlatform->findOne('webhooks', [
Query::equal('$id', [$webhookId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($webhook->isEmpty()) {
@@ -1402,7 +1362,6 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'webhooks',
name: 'updateWebhookSignature',
description: '/docs/references/projects/update-webhook-signature.md',
auth: [AuthType::ADMIN],
@@ -1427,7 +1386,7 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
$webhook = $dbForPlatform->findOne('webhooks', [
Query::equal('$id', [$webhookId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($webhook->isEmpty()) {
@@ -1448,7 +1407,6 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'webhooks',
name: 'deleteWebhook',
description: '/docs/references/projects/delete-webhook.md',
auth: [AuthType::ADMIN],
@@ -1474,7 +1432,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
$webhook = $dbForPlatform->findOne('webhooks', [
Query::equal('$id', [$webhookId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($webhook->isEmpty()) {
@@ -1496,7 +1454,6 @@ App::post('/v1/projects/:projectId/keys')
->label('scope', 'keys.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'keys',
name: 'createKey',
description: '/docs/references/projects/create-key.md',
auth: [AuthType::ADMIN],
@@ -1528,7 +1485,7 @@ App::post('/v1/projects/:projectId/keys')
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'projectInternalId' => $project->getSequence(),
'projectInternalId' => $project->getInternalId(),
'projectId' => $project->getId(),
'name' => $name,
'scopes' => $scopes,
@@ -1553,7 +1510,6 @@ App::get('/v1/projects/:projectId/keys')
->label('scope', 'keys.read')
->label('sdk', new Method(
namespace: 'projects',
group: 'keys',
name: 'listKeys',
description: '/docs/references/projects/list-keys.md',
auth: [AuthType::ADMIN],
@@ -1576,7 +1532,7 @@ App::get('/v1/projects/:projectId/keys')
}
$keys = $dbForPlatform->find('keys', [
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::limit(5000),
]);
@@ -1592,7 +1548,6 @@ App::get('/v1/projects/:projectId/keys/:keyId')
->label('scope', 'keys.read')
->label('sdk', new Method(
namespace: 'projects',
group: 'keys',
name: 'getKey',
description: '/docs/references/projects/get-key.md',
auth: [AuthType::ADMIN],
@@ -1617,7 +1572,7 @@ App::get('/v1/projects/:projectId/keys/:keyId')
$key = $dbForPlatform->findOne('keys', [
Query::equal('$id', [$keyId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($key->isEmpty()) {
@@ -1633,7 +1588,6 @@ App::put('/v1/projects/:projectId/keys/:keyId')
->label('scope', 'keys.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'keys',
name: 'updateKey',
description: '/docs/references/projects/update-key.md',
auth: [AuthType::ADMIN],
@@ -1661,7 +1615,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
$key = $dbForPlatform->findOne('keys', [
Query::equal('$id', [$keyId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($key->isEmpty()) {
@@ -1686,7 +1640,6 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
->label('scope', 'keys.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'keys',
name: 'deleteKey',
description: '/docs/references/projects/delete-key.md',
auth: [AuthType::ADMIN],
@@ -1712,7 +1665,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
$key = $dbForPlatform->findOne('keys', [
Query::equal('$id', [$keyId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($key->isEmpty()) {
@@ -1734,7 +1687,6 @@ App::post('/v1/projects/:projectId/jwts')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'auth',
name: 'createJWT',
description: '/docs/references/projects/create-jwt.md',
auth: [AuthType::ADMIN],
@@ -1778,7 +1730,6 @@ App::post('/v1/projects/:projectId/platforms')
->label('scope', 'platforms.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'platforms',
name: 'createPlatform',
description: '/docs/references/projects/create-platform.md',
auth: [AuthType::ADMIN],
@@ -1811,7 +1762,7 @@ App::post('/v1/projects/:projectId/platforms')
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'projectInternalId' => $project->getSequence(),
'projectInternalId' => $project->getInternalId(),
'projectId' => $project->getId(),
'type' => $type,
'name' => $name,
@@ -1835,7 +1786,6 @@ App::get('/v1/projects/:projectId/platforms')
->label('scope', 'platforms.read')
->label('sdk', new Method(
namespace: 'projects',
group: 'platforms',
name: 'listPlatforms',
description: '/docs/references/projects/list-platforms.md',
auth: [AuthType::ADMIN],
@@ -1858,7 +1808,7 @@ App::get('/v1/projects/:projectId/platforms')
}
$platforms = $dbForPlatform->find('platforms', [
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::limit(5000),
]);
@@ -1874,7 +1824,6 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
->label('scope', 'platforms.read')
->label('sdk', new Method(
namespace: 'projects',
group: 'platforms',
name: 'getPlatform',
description: '/docs/references/projects/get-platform.md',
auth: [AuthType::ADMIN],
@@ -1899,7 +1848,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
$platform = $dbForPlatform->findOne('platforms', [
Query::equal('$id', [$platformId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($platform->isEmpty()) {
@@ -1915,7 +1864,6 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
->label('scope', 'platforms.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'platforms',
name: 'updatePlatform',
description: '/docs/references/projects/update-platform.md',
auth: [AuthType::ADMIN],
@@ -1943,7 +1891,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
$platform = $dbForPlatform->findOne('platforms', [
Query::equal('$id', [$platformId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($platform->isEmpty()) {
@@ -1971,7 +1919,6 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
->label('scope', 'platforms.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'platforms',
name: 'deletePlatform',
description: '/docs/references/projects/delete-platform.md',
auth: [AuthType::ADMIN],
@@ -1997,7 +1944,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
$platform = $dbForPlatform->findOne('platforms', [
Query::equal('$id', [$platformId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
if ($platform->isEmpty()) {
@@ -2019,7 +1966,6 @@ App::patch('/v1/projects/:projectId/smtp')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'templates',
name: 'updateSmtp',
description: '/docs/references/projects/update-smtp.md',
auth: [AuthType::ADMIN],
@@ -2116,7 +2062,6 @@ App::post('/v1/projects/:projectId/smtp/tests')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'templates',
name: 'createSmtpTest',
description: '/docs/references/projects/create-smtp-test.md',
auth: [AuthType::ADMIN],
@@ -2140,8 +2085,7 @@ App::post('/v1/projects/:projectId/smtp/tests')
->inject('response')
->inject('dbForPlatform')
->inject('queueForMails')
->inject('plan')
->action(function (string $projectId, array $emails, string $senderName, string $senderEmail, string $replyTo, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForPlatform, Mail $queueForMails, array $plan) {
->action(function (string $projectId, array $emails, string $senderName, string $senderEmail, string $replyTo, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForPlatform, Mail $queueForMails) {
$project = $dbForPlatform->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -2154,14 +2098,7 @@ App::post('/v1/projects/:projectId/smtp/tests')
$template = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-smtp-test.tpl');
$template
->setParam('{{from}}', "{$senderName} ({$senderEmail})")
->setParam('{{replyTo}}', "{$senderName} ({$replyToEmail})")
->setParam('{{logoUrl}}', $plan['logoUrl'] ?? APP_EMAIL_LOGO_URL)
->setParam('{{accentColor}}', $plan['accentColor'] ?? APP_EMAIL_ACCENT_COLOR)
->setParam('{{twitterUrl}}', $plan['twitterUrl'] ?? APP_SOCIAL_TWITTER)
->setParam('{{discordUrl}}', $plan['discordUrl'] ?? APP_SOCIAL_DISCORD)
->setParam('{{githubUrl}}', $plan['githubUrl'] ?? APP_SOCIAL_GITHUB_APPWRITE)
->setParam('{{termsUrl}}', $plan['termsUrl'] ?? APP_EMAIL_TERMS_URL)
->setParam('{{privacyUrl}}', $plan['privacyUrl'] ?? APP_EMAIL_PRIVACY_URL);
->setParam('{{replyTo}}', "{$senderName} ({$replyToEmail})");
foreach ($emails as $email) {
$queueForMails
@@ -2191,7 +2128,6 @@ App::get('/v1/projects/:projectId/templates/sms/:type/:locale')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'templates',
name: 'getSmsTemplate',
description: '/docs/references/projects/get-sms-template.md',
auth: [AuthType::ADMIN],
@@ -2239,7 +2175,6 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'templates',
name: 'getEmailTemplate',
description: '/docs/references/projects/get-email-template.md',
auth: [AuthType::ADMIN],
@@ -2298,7 +2233,6 @@ App::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'templates',
name: 'updateSmsTemplate',
description: '/docs/references/projects/update-sms-template.md',
auth: [AuthType::ADMIN],
@@ -2345,7 +2279,6 @@ App::patch('/v1/projects/:projectId/templates/email/:type/:locale')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'templates',
name: 'updateEmailTemplate',
description: '/docs/references/projects/update-email-template.md',
auth: [AuthType::ADMIN],
@@ -2402,7 +2335,6 @@ App::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'templates',
name: 'deleteSmsTemplate',
description: '/docs/references/projects/delete-sms-template.md',
auth: [AuthType::ADMIN],
@@ -2448,12 +2380,11 @@ App::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
});
App::delete('/v1/projects/:projectId/templates/email/:type/:locale')
->desc('Delete custom email template')
->desc('Reset custom email template')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'templates',
name: 'deleteEmailTemplate',
description: '/docs/references/projects/delete-email-template.md',
auth: [AuthType::ADMIN],
+8 -44
View File
@@ -31,7 +31,6 @@ App::get('/v1/proxy/rules')
->label('scope', 'rules.read')
->label('sdk', new Method(
namespace: 'proxy',
group: null,
name: 'listRules',
description: '/docs/references/proxy/list-rules.md',
auth: [AuthType::ADMIN],
@@ -58,7 +57,7 @@ App::get('/v1/proxy/rules')
$queries[] = Query::search('search', $search);
}
$queries[] = Query::equal('projectInternalId', [$project->getSequence()]);
$queries[] = Query::equal('projectInternalId', [$project->getInternalId()]);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
@@ -106,7 +105,6 @@ App::get('/v1/proxy/rules/:ruleId')
->label('scope', 'rules.read')
->label('sdk', new Method(
namespace: 'proxy',
group: null,
name: 'getRule',
description: '/docs/references/proxy/get-rule.md',
auth: [AuthType::ADMIN],
@@ -124,7 +122,7 @@ App::get('/v1/proxy/rules/:ruleId')
->action(function (string $ruleId, Response $response, Document $project, Database $dbForPlatform) {
$rule = $dbForPlatform->getDocument('rules', $ruleId);
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getSequence()) {
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
throw new Exception(Exception::RULE_NOT_FOUND);
}
@@ -144,7 +142,6 @@ App::delete('/v1/proxy/rules/:ruleId')
->label('audits.resource', 'rule/{request.ruleId}')
->label('sdk', new Method(
namespace: 'proxy',
group: null,
name: 'deleteRule',
description: '/docs/references/proxy/delete-rule.md',
auth: [AuthType::ADMIN],
@@ -165,7 +162,7 @@ App::delete('/v1/proxy/rules/:ruleId')
->action(function (string $ruleId, Response $response, Document $project, Database $dbForPlatform, Delete $queueForDeletes, Event $queueForEvents) {
$rule = $dbForPlatform->getDocument('rules', $ruleId);
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getSequence()) {
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
throw new Exception(Exception::RULE_NOT_FOUND);
}
@@ -189,7 +186,6 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
->label('audits.resource', 'rule/{response.$id}')
->label('sdk', new Method(
namespace: 'proxy',
group: null,
name: 'updateRuleVerification',
description: '/docs/references/proxy/update-rule-verification.md',
auth: [AuthType::ADMIN],
@@ -210,46 +206,15 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForPlatform, Log $log) {
$rule = $dbForPlatform->getDocument('rules', $ruleId);
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getSequence()) {
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
throw new Exception(Exception::RULE_NOT_FOUND);
}
$targetCNAME = null;
switch ($rule->getAttribute('type', '')) {
case 'api':
// For example: fra.cloud.appwrite.io
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
break;
case 'redirect':
// For example: appwrite.network
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_SITES', ''));
break;
case 'deployment':
switch ($rule->getAttribute('deploymentResourceType', '')) {
case 'function':
// For example: fra.appwrite.run
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_FUNCTIONS', ''));
break;
case 'site':
// For example: appwrite.network
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_SITES', ''));
break;
default:
break;
}
// no break
default:
break;
}
$validators = [];
if (!is_null($targetCNAME)) {
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
}
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) {
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
}
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
}
@@ -291,8 +256,7 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
// Issue a TLS certificate when domain is verified
$queueForCertificates
->setDomain(new Document([
'domain' => $rule->getAttribute('domain'),
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
'domain' => $rule->getAttribute('domain')
]))
->trigger();
+60 -103
View File
@@ -24,7 +24,6 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Exception\NotFound as NotFoundException;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
@@ -63,7 +62,6 @@ App::post('/v1/storage/buckets')
->label('audits.resource', 'bucket/{response.$id}')
->label('sdk', new Method(
namespace: 'storage',
group: 'buckets',
name: 'createBucket',
description: '/docs/references/storage/create-bucket.md',
auth: [AuthType::KEY],
@@ -145,7 +143,7 @@ App::post('/v1/storage/buckets')
$bucket = $dbForProject->getDocument('buckets', $bucketId);
$dbForProject->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes, permissions: $permissions ?? [], documentSecurity: $fileSecurity);
$dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes, permissions: $permissions ?? [], documentSecurity: $fileSecurity);
} catch (DuplicateException) {
throw new Exception(Exception::STORAGE_BUCKET_ALREADY_EXISTS);
}
@@ -166,7 +164,6 @@ App::get('/v1/storage/buckets')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk', new Method(
namespace: 'storage',
group: 'buckets',
name: 'listBuckets',
description: '/docs/references/storage/list-buckets.md',
auth: [AuthType::KEY],
@@ -219,15 +216,10 @@ App::get('/v1/storage/buckets')
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$buckets = $dbForProject->find('buckets', $queries);
$total = $dbForProject->count('buckets', $filterQueries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'buckets' => $buckets,
'total' => $total,
'buckets' => $dbForProject->find('buckets', $queries),
'total' => $dbForProject->count('buckets', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_BUCKET_LIST);
});
@@ -238,7 +230,6 @@ App::get('/v1/storage/buckets/:bucketId')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk', new Method(
namespace: 'storage',
group: 'buckets',
name: 'getBucket',
description: '/docs/references/storage/get-bucket.md',
auth: [AuthType::KEY],
@@ -273,7 +264,6 @@ App::put('/v1/storage/buckets/:bucketId')
->label('audits.resource', 'bucket/{response.$id}')
->label('sdk', new Method(
namespace: 'storage',
group: 'buckets',
name: 'updateBucket',
description: '/docs/references/storage/update-bucket.md',
auth: [AuthType::KEY],
@@ -316,17 +306,17 @@ App::put('/v1/storage/buckets/:bucketId')
$permissions = Permission::aggregate($permissions);
$bucket = $dbForProject->updateDocument('buckets', $bucket->getId(), $bucket
->setAttribute('name', $name)
->setAttribute('$permissions', $permissions)
->setAttribute('maximumFileSize', $maximumFileSize)
->setAttribute('allowedFileExtensions', $allowedFileExtensions)
->setAttribute('fileSecurity', $fileSecurity)
->setAttribute('enabled', $enabled)
->setAttribute('encryption', $encryption)
->setAttribute('compression', $compression)
->setAttribute('antivirus', $antivirus));
->setAttribute('name', $name)
->setAttribute('$permissions', $permissions)
->setAttribute('maximumFileSize', $maximumFileSize)
->setAttribute('allowedFileExtensions', $allowedFileExtensions)
->setAttribute('fileSecurity', $fileSecurity)
->setAttribute('enabled', $enabled)
->setAttribute('encryption', $encryption)
->setAttribute('compression', $compression)
->setAttribute('antivirus', $antivirus));
$dbForProject->updateCollection('bucket_' . $bucket->getSequence(), $permissions, $fileSecurity);
$dbForProject->updateCollection('bucket_' . $bucket->getInternalId(), $permissions, $fileSecurity);
$queueForEvents
->setParam('bucketId', $bucket->getId());
@@ -344,7 +334,6 @@ App::delete('/v1/storage/buckets/:bucketId')
->label('audits.resource', 'bucket/{request.bucketId}')
->label('sdk', new Method(
namespace: 'storage',
group: 'buckets',
name: 'deleteBucket',
description: '/docs/references/storage/delete-bucket.md',
auth: [AuthType::KEY],
@@ -398,18 +387,17 @@ App::post('/v1/storage/buckets/:bucketId/files')
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk', new Method(
namespace: 'storage',
group: 'files',
name: 'createFile',
description: '/docs/references/storage/create-file.md',
type: MethodType::UPLOAD,
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
requestType: 'multipart/form-data',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_FILE,
)
],
type: MethodType::UPLOAD,
requestType: ContentType::MULTIPART
]
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new CustomId(), 'File ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
@@ -558,7 +546,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
$path = $deviceForFiles->getPath($fileId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
$path = str_ireplace($deviceForFiles->getRoot(), $deviceForFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
$metadata = ['content_type' => $deviceForLocal->getFileMimeType($fileTmpName)];
if (!$file->isEmpty()) {
@@ -652,7 +640,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
'$id' => $fileId,
'$permissions' => $permissions,
'bucketId' => $bucket->getId(),
'bucketInternalId' => $bucket->getSequence(),
'bucketInternalId' => $bucket->getInternalId(),
'name' => $fileName,
'path' => $path,
'signature' => $fileHash,
@@ -671,7 +659,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
'metadata' => $metadata,
]);
$file = $dbForProject->createDocument('bucket_' . $bucket->getSequence(), $doc);
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
} else {
$file = $file
->setAttribute('$permissions', $permissions)
@@ -696,7 +684,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
if (!$validator->isValid($bucket->getCreate())) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
}
} else {
if ($file->isEmpty()) {
@@ -704,7 +692,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
'$id' => ID::custom($fileId),
'$permissions' => $permissions,
'bucketId' => $bucket->getId(),
'bucketInternalId' => $bucket->getSequence(),
'bucketInternalId' => $bucket->getInternalId(),
'name' => $fileName,
'path' => $path,
'signature' => '',
@@ -720,7 +708,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
]);
try {
$file = $dbForProject->createDocument('bucket_' . $bucket->getSequence(), $doc);
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
@@ -741,7 +729,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
}
try {
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
@@ -768,7 +756,6 @@ App::get('/v1/storage/buckets/:bucketId/files')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk', new Method(
namespace: 'storage',
group: 'files',
name: 'listFiles',
description: '/docs/references/storage/list-files.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -826,9 +813,9 @@ App::get('/v1/storage/buckets/:bucketId/files')
$fileId = $cursor->getValue();
if ($fileSecurity && !$valid) {
$cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
$cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($cursorDocument->isEmpty()) {
@@ -842,16 +829,14 @@ App::get('/v1/storage/buckets/:bucketId/files')
try {
if ($fileSecurity && !$valid) {
$files = $dbForProject->find('bucket_' . $bucket->getSequence(), $queries);
$total = $dbForProject->count('bucket_' . $bucket->getSequence(), $filterQueries, APP_LIMIT_COUNT);
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries);
$total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
} else {
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getSequence(), $queries));
$total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getSequence(), $filterQueries, APP_LIMIT_COUNT));
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries));
$total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
}
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
@@ -868,7 +853,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk', new Method(
namespace: 'storage',
group: 'files',
name: 'getFile',
description: '/docs/references/storage/get-file.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -902,9 +886,9 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
}
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
@@ -925,7 +909,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->label('cache.resource', 'file/{request.fileId}')
->label('sdk', new Method(
namespace: 'storage',
group: 'files',
name: 'getFilePreview',
description: '/docs/references/storage/get-file-preview.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -943,7 +926,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true)
->param('height', 0, new Range(0, 4000), 'Resize preview image height, Pass an integer between 0 to 4000.', true)
->param('gravity', Image::GRAVITY_CENTER, new WhiteList(Image::getGravityTypes()), 'Image crop gravity. Can be one of ' . implode(",", Image::getGravityTypes()), true)
->param('quality', -1, new Range(-1, 100), 'Preview image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
->param('quality', 100, new Range(0, 100), 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->param('borderWidth', 0, new Range(0, 100), 'Preview image border in pixels. Pass an integer between 0 to 100. Defaults to 0.', true)
->param('borderColor', '', new HexColor(), 'Preview image border color. Use a valid HEX color, no # is needed for prefix.', true)
->param('borderRadius', 0, new Range(0, 4000), 'Preview image border radius in pixels. Pass an integer between 0 to 4000.', true)
@@ -951,14 +934,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->param('rotation', 0, new Range(-360, 360), 'Preview image rotation in degrees. Pass an integer between -360 and 360.', true)
->param('background', '', new HexColor(), 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true)
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true)
// NOTE: this is only for the sdk generator and is not used in the action below and is utilised in `resources.php` for `resourceToken`.
->param('token', '', new Text(512), 'File token for accessing this file.', true)
->inject('response')
->inject('dbForProject')
->inject('resourceToken')
->inject('deviceForFiles')
->inject('deviceForLocal')
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, ?string $token, Response $response, Database $dbForProject, Document $resourceToken, Device $deviceForFiles, Device $deviceForLocal) {
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Response $response, Database $dbForProject, Document $resourceToken, Device $deviceForFiles, Device $deviceForLocal) {
if (!\extension_loaded('imagick')) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
@@ -973,7 +954,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getInternalId();
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
@@ -982,12 +963,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
}
if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getInternalId()) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -1116,7 +1097,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk', new Method(
namespace: 'storage',
group: 'files',
name: 'getFileDownload',
description: '/docs/references/storage/get-file-download.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -1131,15 +1111,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
))
->param('bucketId', '', new UID(), 'Storage bucket ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
// NOTE: this is only for the sdk generator and is not used in the action below and is utilised in `resources.php` for `resourceToken`.
->param('token', '', new Text(512), 'File token for accessing this file.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('mode')
->inject('resourceToken')
->inject('deviceForFiles')
->action(function (string $bucketId, string $fileId, ?string $token, Request $request, Response $response, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles) {
->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceForFiles) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
@@ -1150,22 +1127,17 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid && !$isToken) {
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
}
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
throw new Exception(Exception::USER_UNAUTHORIZED);
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
@@ -1241,15 +1213,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
if (!empty($source)) {
if (!empty($rangeHeader)) {
$response->send(substr($source, $start, ($end - $start + 1)));
return;
}
$response->send($source);
return;
}
if (!empty($rangeHeader)) {
$response->send($deviceForFiles->read($path, $start, ($end - $start + 1)));
return;
}
if ($size > APP_STORAGE_READ_BUFFER) {
@@ -1276,7 +1245,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk', new Method(
namespace: 'storage',
group: 'files',
name: 'getFileView',
description: '/docs/references/storage/get-file-view.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -1291,15 +1259,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
// NOTE: this is only for the sdk generator and is not used in the action below and is utilised in `resources.php` for `resourceToken`.
->param('token', '', new Text(512), 'File token for accessing this file.', true)
->inject('response')
->inject('request')
->inject('dbForProject')
->inject('mode')
->inject('resourceToken')
->inject('deviceForFiles')
->action(function (string $bucketId, string $fileId, ?string $token, Response $response, Request $request, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles) {
->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceForFiles) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
@@ -1309,22 +1274,17 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid && !$isToken) {
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
}
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
throw new Exception(Exception::USER_UNAUTHORIZED);
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
@@ -1410,7 +1370,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
if (!empty($source)) {
if (!empty($rangeHeader)) {
$response->send(substr($source, $start, ($end - $start + 1)));
return;
}
$response->send($source);
return;
@@ -1443,6 +1402,9 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
->groups(['api', 'storage'])
->label('scope', 'public')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->param('jwt', '', new Text(2048, 0), 'JSON Web Token to validate', true)
@@ -1478,7 +1440,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
@@ -1562,7 +1524,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
if (!empty($source)) {
if (!empty($rangeHeader)) {
$response->send(substr($source, $start, ($end - $start + 1)));
return;
}
$response->send($source);
return;
@@ -1604,7 +1565,6 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk', new Method(
namespace: 'storage',
group: 'files',
name: 'updateFile',
description: '/docs/references/storage/update-file.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -1643,7 +1603,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
}
// Read permission should not be required for update
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
@@ -1689,9 +1649,9 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
try {
if ($fileSecurity && !$valid) {
$file = $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file);
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
} else {
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
}
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -1719,7 +1679,6 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk', new Method(
namespace: 'storage',
group: 'files',
name: 'deleteFile',
description: '/docs/references/storage/delete-file.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -1757,7 +1716,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
}
// Read permission should not be required for delete
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
@@ -1787,9 +1746,9 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
try {
if ($fileSecurity && !$valid) {
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getSequence(), $fileId);
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getSequence(), $fileId));
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -1820,7 +1779,6 @@ App::get('/v1/storage/usage')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk', new Method(
namespace: 'storage',
group: null,
name: 'getUsage',
description: '/docs/references/storage/get-usage.md',
auth: [AuthType::ADMIN],
@@ -1907,7 +1865,6 @@ App::get('/v1/storage/:bucketId/usage')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk', new Method(
namespace: 'storage',
group: null,
name: 'getBucketUsage',
description: '/docs/references/storage/get-bucket-usage.md',
auth: [AuthType::ADMIN],
@@ -1937,14 +1894,14 @@ App::get('/v1/storage/:bucketId/usage')
$stats = $usage = [];
$days = $periods[$range];
$metrics = [
str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES),
str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES_STORAGE),
str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED),
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES),
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE),
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED),
];
Authorization::skip(function () use ($dbForProject, $dbForLogs, $bucket, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$db = ($metric === str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED))
$db = ($metric === str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED))
? $dbForLogs
: $dbForProject;
+36 -86
View File
@@ -33,7 +33,6 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Authorization as AuthorizationException;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
@@ -64,7 +63,6 @@ App::post('/v1/teams')
->label('audits.resource', 'team/{response.$id}')
->label('sdk', new Method(
namespace: 'teams',
group: 'teams',
name: 'create',
description: '/docs/references/teams/create-team.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -123,9 +121,9 @@ App::post('/v1/teams')
Permission::delete(Role::team($team->getId(), 'owner')),
],
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'userInternalId' => $user->getInternalId(),
'teamId' => $team->getId(),
'teamInternalId' => $team->getSequence(),
'teamInternalId' => $team->getInternalId(),
'roles' => $roles,
'invited' => DateTime::now(),
'joined' => DateTime::now(),
@@ -155,7 +153,6 @@ App::get('/v1/teams')
->label('scope', 'teams.read')
->label('sdk', new Method(
namespace: 'teams',
group: 'teams',
name: 'list',
description: '/docs/references/teams/list-teams.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -208,12 +205,9 @@ App::get('/v1/teams')
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$results = $dbForProject->find('teams', $queries);
$total = $dbForProject->count('teams', $filterQueries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$results = $dbForProject->find('teams', $queries);
$total = $dbForProject->count('teams', $filterQueries, APP_LIMIT_COUNT);
$response->dynamic(new Document([
'teams' => $results,
@@ -227,7 +221,6 @@ App::get('/v1/teams/:teamId')
->label('scope', 'teams.read')
->label('sdk', new Method(
namespace: 'teams',
group: 'teams',
name: 'get',
description: '/docs/references/teams/get-team.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -258,7 +251,6 @@ App::get('/v1/teams/:teamId/prefs')
->label('scope', 'teams.read')
->label('sdk', new Method(
namespace: 'teams',
group: 'teams',
name: 'getPrefs',
description: '/docs/references/teams/get-team-prefs.md',
auth: [AuthType::SESSION, AuthType::JWT],
@@ -294,7 +286,6 @@ App::put('/v1/teams/:teamId')
->label('audits.resource', 'team/{response.$id}')
->label('sdk', new Method(
namespace: 'teams',
group: 'teams',
name: 'updateName',
description: '/docs/references/teams/update-team-name.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -323,7 +314,9 @@ App::put('/v1/teams/:teamId')
->setAttribute('name', $name)
->setAttribute('search', implode(' ', [$teamId, $name]));
$team = $dbForProject->updateDocument('teams', $team->getId(), $team);
$team = $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $team) {
return $dbForProject->updateDocument('teams', $team->getId(), $team);
});
$queueForEvents->setParam('teamId', $team->getId());
@@ -340,7 +333,6 @@ App::put('/v1/teams/:teamId/prefs')
->label('audits.userId', '{response.$id}')
->label('sdk', new Method(
namespace: 'teams',
group: 'teams',
name: 'updatePrefs',
description: '/docs/references/teams/update-team-prefs.md',
auth: [AuthType::SESSION, AuthType::JWT],
@@ -380,7 +372,6 @@ App::delete('/v1/teams/:teamId')
->label('audits.resource', 'team/{request.teamId}')
->label('sdk', new Method(
namespace: 'teams',
group: 'teams',
name: 'delete',
description: '/docs/references/teams/delete-team.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -439,7 +430,6 @@ App::post('/v1/teams/:teamId/memberships')
->label('audits.userId', '{request.userId}')
->label('sdk', new Method(
namespace: 'teams',
group: 'memberships',
name: 'createMembership',
description: '/docs/references/teams/create-team-membership.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -595,8 +585,8 @@ App::post('/v1/teams/:teamId/memberships')
}
$membership = $dbForProject->findOne('memberships', [
Query::equal('userInternalId', [$invitee->getSequence()]),
Query::equal('teamInternalId', [$team->getSequence()]),
Query::equal('userInternalId', [$invitee->getInternalId()]),
Query::equal('teamInternalId', [$team->getInternalId()]),
]);
$secret = Auth::tokenGenerator();
@@ -612,9 +602,9 @@ App::post('/v1/teams/:teamId/memberships')
Permission::delete(Role::team($team->getId(), 'owner')),
],
'userId' => $invitee->getId(),
'userInternalId' => $invitee->getSequence(),
'userInternalId' => $invitee->getInternalId(),
'teamId' => $team->getId(),
'teamInternalId' => $team->getSequence(),
'teamInternalId' => $team->getInternalId(),
'roles' => $roles,
'invited' => DateTime::now(),
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
@@ -807,7 +797,6 @@ App::get('/v1/teams/:teamId/memberships')
->label('scope', 'teams.read')
->label('sdk', new Method(
namespace: 'teams',
group: 'memberships',
name: 'listMemberships',
description: '/docs/references/teams/list-team-members.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -842,7 +831,7 @@ App::get('/v1/teams/:teamId/memberships')
}
// Set internal queries
$queries[] = Query::equal('teamInternalId', [$team->getSequence()]);
$queries[] = Query::equal('teamInternalId', [$team->getInternalId()]);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
@@ -871,20 +860,17 @@ App::get('/v1/teams/:teamId/memberships')
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$memberships = $dbForProject->find(
collection: 'memberships',
queries: $queries,
);
$total = $dbForProject->count(
collection: 'memberships',
queries: $filterQueries,
max: APP_LIMIT_COUNT
);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$memberships = $dbForProject->find(
collection: 'memberships',
queries: $queries,
);
$total = $dbForProject->count(
collection: 'memberships',
queries: $filterQueries,
max: APP_LIMIT_COUNT
);
$memberships = array_filter($memberships, fn (Document $membership) => !empty($membership->getAttribute('userId')));
@@ -949,7 +935,6 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
->label('scope', 'teams.read')
->label('sdk', new Method(
namespace: 'teams',
group: 'memberships',
name: 'getMembership',
description: '/docs/references/teams/get-team-member.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -1036,7 +1021,6 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
->label('audits.resource', 'team/{request.teamId}')
->label('sdk', new Method(
namespace: 'teams',
group: 'memberships',
name: 'updateMembership',
description: '/docs/references/teams/update-team-membership.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -1090,20 +1074,14 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
// Quick check: fetch up to 2 owners to determine if only one exists
$ownersCount = $dbForProject->count(
collection: 'memberships',
queries: [
Query::contains('roles', ['owner']),
Query::equal('teamInternalId', [$team->getSequence()])
],
queries: [Query::contains('roles', ['owner'])],
max: 2
);
// Is the role change being requested by the user on their own membership?
$isCurrentUserAnOwner = $user->getSequence() === $membership->getAttribute('userInternalId');
// Prevent role change if there's only one owner left,
// the requester is that owner, and the new `$roles` no longer include 'owner'
if ($ownersCount === 1 && $isOwner && $isCurrentUserAnOwner && !\in_array('owner', $roles)) {
throw new Exception(Exception::MEMBERSHIP_DOWNGRADE_PROHIBITED, 'There must be at least one owner in the organization.');
// the requester is that owner, and the new `$roles` no longer include 'owner'!
if ($ownersCount === 1 && $isOwner && !\in_array('owner', $roles)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'There must be at least one owner in the organization.');
}
}
@@ -1146,7 +1124,6 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->label('audits.userId', '{request.userId}')
->label('sdk', new Method(
namespace: 'teams',
group: 'memberships',
name: 'updateMembershipStatus',
description: '/docs/references/teams/update-team-membership-status.md',
auth: [AuthType::SESSION, AuthType::JWT],
@@ -1183,7 +1160,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
throw new Exception(Exception::TEAM_NOT_FOUND);
}
if ($membership->getAttribute('teamInternalId') !== $team->getSequence()) {
if ($membership->getAttribute('teamInternalId') !== $team->getInternalId()) {
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
}
@@ -1200,7 +1177,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$user->setAttributes($dbForProject->getDocument('users', $userId)->getArrayCopy()); // Get user
}
if ($membership->getAttribute('userInternalId') !== $user->getSequence()) {
if ($membership->getAttribute('userInternalId') !== $user->getInternalId()) {
throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user (' . $user->getAttribute('email') . ')');
}
@@ -1232,7 +1209,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
Permission::delete(Role::user($user->getId())),
],
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'userInternalId' => $user->getInternalId(),
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'providerUid' => $user->getAttribute('email'),
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
@@ -1304,7 +1281,6 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
->label('audits.resource', 'team/{request.teamId}')
->label('sdk', new Method(
namespace: 'teams',
group: 'memberships',
name: 'deleteMembership',
description: '/docs/references/teams/delete-team-membership.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
@@ -1318,12 +1294,10 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->inject('user')
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $queueForEvents) {
$membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1331,9 +1305,9 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception(Exception::TEAM_INVITE_NOT_FOUND);
}
$profile = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
if ($profile->isEmpty()) {
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
@@ -1343,33 +1317,10 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception(Exception::TEAM_NOT_FOUND);
}
if ($membership->getAttribute('teamInternalId') !== $team->getSequence()) {
if ($membership->getAttribute('teamInternalId') !== $team->getInternalId()) {
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
}
if ($project->getId() === 'console') {
// Quick check:
// fetch up to 2 owners to determine if only one exists
$ownersCount = $dbForProject->count(
collection: 'memberships',
queries: [
Query::contains('roles', ['owner']),
Query::equal('teamInternalId', [$team->getSequence()])
],
max: 2
);
// Is the deletion being requested by the user on their own membership and they are also the owner?
$isSelfOwner =
in_array('owner', $membership->getAttribute('roles')) &&
$membership->getAttribute('userInternalId') === $user->getSequence();
if ($ownersCount === 1 && $isSelfOwner) {
/* Prevent removal if the user is the only owner. */
throw new Exception(Exception::MEMBERSHIP_DELETION_PROHIBITED, 'There must be at least one owner in the organization.');
}
}
try {
$dbForProject->deleteDocument('memberships', $membership->getId());
} catch (AuthorizationException $exception) {
@@ -1378,15 +1329,15 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership from DB');
}
$dbForProject->purgeCachedDocument('users', $profile->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
if ($membership->getAttribute('confirm')) { // Count only confirmed members
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
}
$queueForEvents
->setParam('userId', $user->getId())
->setParam('teamId', $team->getId())
->setParam('userId', $profile->getId())
->setParam('membershipId', $membership->getId())
->setPayload($response->output($membership, Response::MODEL_MEMBERSHIP))
;
@@ -1400,7 +1351,6 @@ App::get('/v1/teams/:teamId/logs')
->label('scope', 'teams.read')
->label('sdk', new Method(
namespace: 'teams',
group: 'logs',
name: 'listLogs',
description: '/docs/references/teams/get-team-logs.md',
auth: [AuthType::ADMIN],
+24 -87
View File
@@ -9,8 +9,6 @@ use Appwrite\Auth\Validator\PasswordDictionary;
use Appwrite\Auth\Validator\PasswordHistory;
use Appwrite\Auth\Validator\PersonalData;
use Appwrite\Auth\Validator\Phone;
use Appwrite\Deletes\Identities as DeleteIdentities;
use Appwrite\Deletes\Targets as DeleteTargets;
use Appwrite\Detector\Detector;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
@@ -36,7 +34,6 @@ use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
@@ -140,7 +137,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
Permission::delete(Role::user($user->getId())),
],
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'userInternalId' => $user->getInternalId(),
'providerType' => 'email',
'identifier' => $email,
]));
@@ -164,7 +161,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
Permission::delete(Role::user($user->getId())),
],
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'userInternalId' => $user->getInternalId(),
'providerType' => 'sms',
'identifier' => $phone,
]));
@@ -195,7 +192,6 @@ App::post('/v1/users')
->label('audits.resource', 'user/{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'create',
description: '/docs/references/users/create-user.md',
auth: [AuthType::KEY],
@@ -230,7 +226,6 @@ App::post('/v1/users/bcrypt')
->label('audits.resource', 'user/{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'createBcryptUser',
description: '/docs/references/users/create-bcrypt-user.md',
auth: [AuthType::KEY],
@@ -265,7 +260,6 @@ App::post('/v1/users/md5')
->label('audits.resource', 'user/{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'createMD5User',
description: '/docs/references/users/create-md5-user.md',
auth: [AuthType::KEY],
@@ -300,7 +294,6 @@ App::post('/v1/users/argon2')
->label('audits.resource', 'user/{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'createArgon2User',
description: '/docs/references/users/create-argon2-user.md',
auth: [AuthType::KEY],
@@ -335,7 +328,6 @@ App::post('/v1/users/sha')
->label('audits.resource', 'user/{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'createSHAUser',
description: '/docs/references/users/create-sha-user.md',
auth: [AuthType::KEY],
@@ -377,7 +369,6 @@ App::post('/v1/users/phpass')
->label('audits.resource', 'user/{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'createPHPassUser',
description: '/docs/references/users/create-phpass-user.md',
auth: [AuthType::KEY],
@@ -412,7 +403,6 @@ App::post('/v1/users/scrypt')
->label('audits.resource', 'user/{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'createScryptUser',
description: '/docs/references/users/create-scrypt-user.md',
auth: [AuthType::KEY],
@@ -460,7 +450,6 @@ App::post('/v1/users/scrypt-modified')
->label('audits.resource', 'user/{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'createScryptModifiedUser',
description: '/docs/references/users/create-scrypt-modified-user.md',
auth: [AuthType::KEY],
@@ -499,7 +488,6 @@ App::post('/v1/users/:userId/targets')
->label('scope', 'targets.write')
->label('sdk', new Method(
namespace: 'users',
group: 'targets',
name: 'createTarget',
description: '/docs/references/users/create-target.md',
auth: [AuthType::KEY, AuthType::ADMIN],
@@ -564,10 +552,10 @@ App::post('/v1/users/:userId/targets')
Permission::delete(Role::user($user->getId())),
],
'providerId' => empty($provider->getId()) ? null : $provider->getId(),
'providerInternalId' => $provider->isEmpty() ? null : $provider->getSequence(),
'providerInternalId' => $provider->isEmpty() ? null : $provider->getInternalId(),
'providerType' => $providerType,
'userId' => $userId,
'userInternalId' => $user->getSequence(),
'userInternalId' => $user->getInternalId(),
'identifier' => $identifier,
'name' => ($name !== '') ? $name : null,
]));
@@ -591,7 +579,6 @@ App::get('/v1/users')
->label('scope', 'users.read')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'list',
description: '/docs/references/users/list-users.md',
auth: [AuthType::KEY],
@@ -644,15 +631,10 @@ App::get('/v1/users')
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$users = $dbForProject->find('users', $queries);
$total = $dbForProject->count('users', $filterQueries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'users' => $users,
'total' => $total,
'users' => $dbForProject->find('users', $queries),
'total' => $dbForProject->count('users', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_USER_LIST);
});
@@ -662,7 +644,6 @@ App::get('/v1/users/:userId')
->label('scope', 'users.read')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'get',
description: '/docs/references/users/get-user.md',
auth: [AuthType::KEY],
@@ -693,7 +674,6 @@ App::get('/v1/users/:userId/prefs')
->label('scope', 'users.read')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'getPrefs',
description: '/docs/references/users/get-user-prefs.md',
auth: [AuthType::KEY],
@@ -726,7 +706,6 @@ App::get('/v1/users/:userId/targets/:targetId')
->label('scope', 'targets.read')
->label('sdk', new Method(
namespace: 'users',
group: 'targets',
name: 'getTarget',
description: '/docs/references/users/get-user-target.md',
auth: [AuthType::KEY, AuthType::ADMIN],
@@ -764,7 +743,6 @@ App::get('/v1/users/:userId/sessions')
->label('scope', 'users.read')
->label('sdk', new Method(
namespace: 'users',
group: 'sessions',
name: 'listSessions',
description: '/docs/references/users/list-user-sessions.md',
auth: [AuthType::KEY],
@@ -811,7 +789,6 @@ App::get('/v1/users/:userId/memberships')
->label('scope', 'users.read')
->label('sdk', new Method(
namespace: 'users',
group: 'memberships',
name: 'listMemberships',
description: '/docs/references/users/list-user-memberships.md',
auth: [AuthType::KEY],
@@ -846,7 +823,7 @@ App::get('/v1/users/:userId/memberships')
}
// Set internal queries
$queries[] = Query::equal('userInternalId', [$user->getSequence()]);
$queries[] = Query::equal('userInternalId', [$user->getInternalId()]);
$memberships = array_map(function ($membership) use ($dbForProject, $user) {
$team = $dbForProject->getDocument('teams', $membership->getAttribute('teamId'));
@@ -871,7 +848,6 @@ App::get('/v1/users/:userId/logs')
->label('scope', 'users.read')
->label('sdk', new Method(
namespace: 'users',
group: 'logs',
name: 'listLogs',
description: '/docs/references/users/list-user-logs.md',
auth: [AuthType::KEY],
@@ -910,7 +886,7 @@ App::get('/v1/users/:userId/logs')
$audit = new Audit($dbForProject);
$logs = $audit->getLogsByUser($user->getSequence(), $queries);
$logs = $audit->getLogsByUser($user->getInternalId(), $queries);
$output = [];
@@ -957,7 +933,7 @@ App::get('/v1/users/:userId/logs')
}
$response->dynamic(new Document([
'total' => $audit->countLogsByUser($user->getSequence(), $queries),
'total' => $audit->countLogsByUser($user->getInternalId(), $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -968,7 +944,6 @@ App::get('/v1/users/:userId/targets')
->label('scope', 'targets.read')
->label('sdk', new Method(
namespace: 'users',
group: 'targets',
name: 'listTargets',
description: '/docs/references/users/list-user-targets.md',
auth: [AuthType::KEY, AuthType::ADMIN],
@@ -980,7 +955,7 @@ App::get('/v1/users/:userId/targets')
]
))
->param('userId', '', new UID(), 'User ID.')
->param('queries', [], new Targets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Targets::ALLOWED_ATTRIBUTES), true)
->param('queries', [], new Targets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Users::ALLOWED_ATTRIBUTES), true)
->inject('response')
->inject('dbForProject')
->action(function (string $userId, array $queries, Response $response, Database $dbForProject) {
@@ -1021,15 +996,10 @@ App::get('/v1/users/:userId/targets')
$cursor->setValue($cursorDocument);
}
try {
$targets = $dbForProject->find('targets', $queries);
$total = $dbForProject->count('targets', $queries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'targets' => $targets,
'total' => $total,
'targets' => $dbForProject->find('targets', $queries),
'total' => $dbForProject->count('targets', $queries, APP_LIMIT_COUNT),
]), Response::MODEL_TARGET_LIST);
});
@@ -1039,7 +1009,6 @@ App::get('/v1/users/identities')
->label('scope', 'users.read')
->label('sdk', new Method(
namespace: 'users',
group: 'identities',
name: 'listIdentities',
description: '/docs/references/users/list-identities.md',
auth: [AuthType::KEY],
@@ -1092,15 +1061,10 @@ App::get('/v1/users/identities')
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$identities = $dbForProject->find('identities', $queries);
$total = $dbForProject->count('identities', $filterQueries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'identities' => $identities,
'total' => $total,
'identities' => $dbForProject->find('identities', $queries),
'total' => $dbForProject->count('identities', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_IDENTITY_LIST);
});
@@ -1114,7 +1078,6 @@ App::patch('/v1/users/:userId/status')
->label('audits.userId', '{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'updateStatus',
description: '/docs/references/users/update-user-status.md',
auth: [AuthType::KEY],
@@ -1155,7 +1118,6 @@ App::put('/v1/users/:userId/labels')
->label('audits.resource', 'user/{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'updateLabels',
description: '/docs/references/users/update-user-labels.md',
auth: [AuthType::KEY],
@@ -1198,7 +1160,6 @@ App::patch('/v1/users/:userId/verification/phone')
->label('audits.resource', 'user/{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'updatePhoneVerification',
description: '/docs/references/users/update-user-phone-verification.md',
auth: [AuthType::KEY],
@@ -1240,7 +1201,6 @@ App::patch('/v1/users/:userId/name')
->label('audits.userId', '{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'updateName',
description: '/docs/references/users/update-user-name.md',
auth: [AuthType::KEY],
@@ -1283,7 +1243,6 @@ App::patch('/v1/users/:userId/password')
->label('audits.userId', '{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'updatePassword',
description: '/docs/references/users/update-user-password.md',
auth: [AuthType::KEY],
@@ -1366,7 +1325,6 @@ App::patch('/v1/users/:userId/email')
->label('audits.userId', '{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'updateEmail',
description: '/docs/references/users/update-user-email.md',
auth: [AuthType::KEY],
@@ -1396,7 +1354,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('userInternalId', $user->getSequence()),
Query::notEqual('userInternalId', $user->getInternalId()),
]);
if (!$identityWithMatchingEmail->isEmpty()) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
@@ -1440,7 +1398,7 @@ App::patch('/v1/users/:userId/email')
Permission::delete(Role::user($user->getId())),
],
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'userInternalId' => $user->getInternalId(),
'providerType' => 'email',
'identifier' => $email,
]));
@@ -1466,7 +1424,6 @@ App::patch('/v1/users/:userId/phone')
->label('audits.resource', 'user/{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'updatePhone',
description: '/docs/references/users/update-user-phone.md',
auth: [AuthType::KEY],
@@ -1529,7 +1486,7 @@ App::patch('/v1/users/:userId/phone')
Permission::delete(Role::user($user->getId())),
],
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'userInternalId' => $user->getInternalId(),
'providerType' => 'sms',
'identifier' => $number,
]));
@@ -1556,7 +1513,6 @@ App::patch('/v1/users/:userId/verification')
->label('audits.userId', '{request.userId}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'updateEmailVerification',
description: '/docs/references/users/update-user-email-verification.md',
auth: [AuthType::KEY],
@@ -1594,7 +1550,6 @@ App::patch('/v1/users/:userId/prefs')
->label('scope', 'users.write')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'updatePrefs',
description: '/docs/references/users/update-user-prefs.md',
auth: [AuthType::KEY],
@@ -1635,7 +1590,6 @@ App::patch('/v1/users/:userId/targets/:targetId')
->label('scope', 'targets.write')
->label('sdk', new Method(
namespace: 'users',
group: 'targets',
name: 'updateTarget',
description: '/docs/references/users/update-target.md',
auth: [AuthType::KEY, AuthType::ADMIN],
@@ -1711,7 +1665,7 @@ App::patch('/v1/users/:userId/targets/:targetId')
$target
->setAttribute('providerId', $provider->getId())
->setAttribute('providerInternalId', $provider->getSequence());
->setAttribute('providerInternalId', $provider->getInternalId());
}
if ($name) {
@@ -1740,7 +1694,6 @@ App::patch('/v1/users/:userId/mfa')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'updateMfa',
description: '/docs/references/users/update-user-mfa.md',
auth: [AuthType::KEY],
@@ -1780,7 +1733,6 @@ App::get('/v1/users/:userId/mfa/factors')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk', new Method(
namespace: 'users',
group: 'mfa',
name: 'listMfaFactors',
description: '/docs/references/users/list-mfa-factors.md',
auth: [AuthType::KEY],
@@ -1819,7 +1771,6 @@ App::get('/v1/users/:userId/mfa/recovery-codes')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk', new Method(
namespace: 'users',
group: 'mfa',
name: 'getMfaRecoveryCodes',
description: '/docs/references/users/get-mfa-recovery-codes.md',
auth: [AuthType::KEY],
@@ -1864,7 +1815,6 @@ App::patch('/v1/users/:userId/mfa/recovery-codes')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk', new Method(
namespace: 'users',
group: 'mfa',
name: 'createMfaRecoveryCodes',
description: '/docs/references/users/create-mfa-recovery-codes.md',
auth: [AuthType::KEY],
@@ -1906,7 +1856,7 @@ App::patch('/v1/users/:userId/mfa/recovery-codes')
});
App::put('/v1/users/:userId/mfa/recovery-codes')
->desc('Update MFA recovery codes (regenerate)')
->desc('Regenerate MFA recovery codes')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.mfa.recovery-codes')
->label('scope', 'users.write')
@@ -1916,7 +1866,6 @@ App::put('/v1/users/:userId/mfa/recovery-codes')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk', new Method(
namespace: 'users',
group: 'mfa',
name: 'updateMfaRecoveryCodes',
description: '/docs/references/users/update-mfa-recovery-codes.md',
auth: [AuthType::KEY],
@@ -1967,7 +1916,6 @@ App::delete('/v1/users/:userId/mfa/authenticators/:type')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk', new Method(
namespace: 'users',
group: 'mfa',
name: 'deleteMfaAuthenticator',
description: '/docs/references/users/delete-mfa-authenticator.md',
auth: [AuthType::KEY],
@@ -2015,7 +1963,6 @@ App::post('/v1/users/:userId/sessions')
->label('usage.metric', 'sessions.{scope}.requests.create')
->label('sdk', new Method(
namespace: 'users',
group: 'sessions',
name: 'createSession',
description: '/docs/references/users/create-session.md',
auth: [AuthType::KEY],
@@ -2051,7 +1998,7 @@ App::post('/v1/users/:userId/sessions')
[
'$id' => ID::unique(),
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'userInternalId' => $user->getInternalId(),
'provider' => Auth::SESSION_PROVIDER_SERVER,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'userAgent' => $request->getUserAgent('UNKNOWN'),
@@ -2100,7 +2047,6 @@ App::post('/v1/users/:userId/tokens')
->label('audits.resource', 'user/{request.userId}')
->label('sdk', new Method(
namespace: 'users',
group: 'sessions',
name: 'createToken',
description: '/docs/references/users/create-token.md',
auth: [AuthType::KEY],
@@ -2131,7 +2077,7 @@ App::post('/v1/users/:userId/tokens')
$token = new Document([
'$id' => ID::unique(),
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'userInternalId' => $user->getInternalId(),
'type' => Auth::TOKEN_TYPE_GENERIC,
'secret' => Auth::hash($secret),
'expire' => $expire,
@@ -2163,7 +2109,6 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
->label('audits.resource', 'user/{request.userId}')
->label('sdk', new Method(
namespace: 'users',
group: 'sessions',
name: 'deleteSession',
description: '/docs/references/users/delete-user-session.md',
auth: [AuthType::KEY],
@@ -2214,7 +2159,6 @@ App::delete('/v1/users/:userId/sessions')
->label('audits.resource', 'user/{user.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'sessions',
name: 'deleteSessions',
description: '/docs/references/users/delete-user-sessions.md',
auth: [AuthType::KEY],
@@ -2264,7 +2208,6 @@ App::delete('/v1/users/:userId')
->label('audits.resource', 'user/{request.userId}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'delete',
description: '/docs/references/users/delete.md',
auth: [AuthType::KEY],
@@ -2293,8 +2236,6 @@ App::delete('/v1/users/:userId')
$clone = clone $user;
$dbForProject->deleteDocument('users', $userId);
DeleteIdentities::delete($dbForProject, Query::equal('userInternalId', [$user->getSequence()]));
DeleteTargets::delete($dbForProject, Query::equal('userInternalId', [$user->getSequence()]));
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
@@ -2316,7 +2257,6 @@ App::delete('/v1/users/:userId/targets/:targetId')
->label('scope', 'targets.write')
->label('sdk', new Method(
namespace: 'users',
group: 'targets',
name: 'deleteTarget',
description: '/docs/references/users/delete-target.md',
auth: [AuthType::KEY, AuthType::ADMIN],
@@ -2374,7 +2314,6 @@ App::delete('/v1/users/identities/:identityId')
->label('audits.resource', 'identity/{request.$identityId}')
->label('sdk', new Method(
namespace: 'users',
group: 'identities',
name: 'deleteIdentity',
description: '/docs/references/users/delete-identity.md',
auth: [AuthType::KEY],
@@ -2414,7 +2353,6 @@ App::post('/v1/users/:userId/jwts')
->label('scope', 'users.write')
->label('sdk', new Method(
namespace: 'users',
group: 'sessions',
name: 'createJWT',
description: '/docs/references/users/create-user-jwt.md',
auth: [AuthType::KEY],
@@ -2470,7 +2408,6 @@ App::get('/v1/users/usage')
->label('scope', 'users.read')
->label('sdk', new Method(
namespace: 'users',
group: null,
name: 'getUsage',
description: '/docs/references/users/get-usage.md',
auth: [AuthType::ADMIN],
+40 -65
View File
@@ -19,7 +19,6 @@ use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
@@ -78,11 +77,11 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$resourceCollection = $resourceType === "function" ? 'functions' : 'sites';
$resourceId = $repository->getAttribute('resourceId');
$resource = Authorization::skip(fn () => $dbForProject->getDocument($resourceCollection, $resourceId));
$resourceInternalId = $resource->getSequence();
$resourceInternalId = $resource->getInternalId();
$deploymentId = ID::unique();
$repositoryId = $repository->getId();
$repositoryInternalId = $repository->getSequence();
$repositoryInternalId = $repository->getInternalId();
$providerRepositoryId = $repository->getAttribute('providerRepositoryId');
$installationId = $repository->getAttribute('installationId');
$installationInternalId = $repository->getAttribute('installationInternalId');
@@ -157,7 +156,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
],
'installationInternalId' => $installationInternalId,
'installationId' => $installationId,
'projectInternalId' => $project->getSequence(),
'projectInternalId' => $project->getInternalId(),
'projectId' => $project->getId(),
'providerRepositoryId' => $providerRepositoryId,
'providerBranch' => $providerBranch,
@@ -252,12 +251,13 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
'providerCommitUrl' => $providerCommitUrl,
'providerCommentId' => \strval($latestCommentId),
'providerBranch' => $providerBranch,
'search' => implode(' ', [$deploymentId, $resource->getAttribute('entrypoint', '')]),
'activate' => $activate,
])));
$resource = $resource
->setAttribute('latestDeploymentId', $deployment->getId())
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
->setAttribute('latestDeploymentInternalId', $deployment->getInternalId())
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
Authorization::skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource));
@@ -273,12 +273,12 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'type' => 'deployment',
'trigger' => 'deployment',
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $resourceId,
'deploymentResourceInternalId' => $resourceInternalId,
@@ -293,25 +293,19 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
// VCS branch preview
if (!empty($providerBranch)) {
$branchPrefix = substr($providerBranch, 0, 16);
if (strlen($providerBranch) > 16) {
$remainingChars = substr($providerBranch, 16);
$branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, 7);
}
$resourceProjectHash = substr(hash('sha256', $resource->getId() . $project->getId()), 0, 7);
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
$domain = "branch-{$providerBranch}-{$resource->getId()}-{$project->getId()}.{$sitesDomain}";
$ruleId = md5($domain);
try {
Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'type' => 'deployment',
'trigger' => 'deployment',
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $resourceId,
'deploymentResourceInternalId' => $resourceInternalId,
@@ -330,19 +324,19 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
// VCS commit preview
if (!empty($providerCommitHash)) {
$domain = "commit-" . substr($providerCommitHash, 0, 16) . ".{$sitesDomain}";
$domain = "commit-{$providerCommitHash}-{$resource->getId()}-{$project->getId()}.{$sitesDomain}";
$ruleId = md5($domain);
try {
Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'type' => 'deployment',
'trigger' => 'deployment',
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $resourceId,
'deploymentResourceInternalId' => $resourceInternalId,
@@ -363,7 +357,6 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
if (!empty($providerCommitHash) && $resource->getAttribute('providerSilentMode', false) === false) {
$resourceName = $resource->getAttribute('name');
$projectName = $project->getAttribute('name');
$region = $project->getAttribute('region', 'default');
$name = "{$resourceName} ({$projectName})";
$message = 'Starting...';
@@ -378,7 +371,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$owner = $github->getOwnerName($providerInstallationId);
$providerTargetUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$region-$projectId/$resourceCollection/$resourceType-$resourceId";
$providerTargetUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$projectId/$resourceCollection/$resourceType-$resourceId";
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, 'pending', $message, $providerTargetUrl, $name);
}
@@ -404,13 +397,12 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
};
App::get('/v1/vcs/github/authorize')
->desc('Create GitHub app installation')
->desc('Install GitHub app')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('error', __DIR__ . '/../../views/general/error.phtml')
->label('sdk', new Method(
namespace: 'vcs',
group: 'installations',
name: 'createGitHubInstallation',
description: '/docs/references/vcs/create-github-installation.md',
auth: [AuthType::ADMIN],
@@ -454,7 +446,7 @@ App::get('/v1/vcs/github/authorize')
});
App::get('/v1/vcs/github/callback')
->desc('Get installation and authorization from GitHub app')
->desc('Capture installation and authorization from GitHub app')
->groups(['api', 'vcs'])
->label('scope', 'public')
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -477,6 +469,16 @@ App::get('/v1/vcs/github/callback')
$state = \json_decode($state, true);
$projectId = $state['projectId'] ?? '';
$defaultState = [
'success' => $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$projectId/settings/git-installations",
'failure' => $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$projectId/settings/git-installations",
];
$state = \array_merge($defaultState, $state ?? []);
$redirectSuccess = $state['success'] ?? '';
$redirectFailure = $state['failure'] ?? '';
$project = $dbForPlatform->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -493,18 +495,6 @@ App::get('/v1/vcs/github/callback')
throw new Exception(Exception::PROJECT_NOT_FOUND, $error);
}
$region = $project->getAttribute('region', 'default');
$defaultState = [
'success' => $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$region-$projectId/settings/git-installations",
'failure' => $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$region-$projectId/settings/git-installations",
];
$state = \array_merge($defaultState, $state ?? []);
$redirectSuccess = $state['success'] ?? '';
$redirectFailure = $state['failure'] ?? '';
// Create / Update installation
if (!empty($providerInstallationId)) {
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
@@ -512,7 +502,7 @@ App::get('/v1/vcs/github/callback')
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$projectInternalId = $project->getSequence();
$projectInternalId = $project->getInternalId();
$installation = $dbForPlatform->findOne('installations', [
Query::equal('providerInstallationId', [$providerInstallationId]),
@@ -594,7 +584,6 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
->label('scope', 'vcs.read')
->label('sdk', new Method(
namespace: 'vcs',
group: 'repositories',
name: 'getRepositoryContents',
description: '/docs/references/vcs/get-repository-contents.md',
auth: [AuthType::ADMIN],
@@ -608,12 +597,11 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
->param('installationId', '', new Text(256), 'Installation Id')
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
->param('providerRootDirectory', '', new Text(256, 0), 'Path to get contents of nested directory', true)
->param('providerReference', '', new Text(256, 0), 'Git reference (branch, tag, commit) to get contents from', true)
->inject('gitHub')
->inject('response')
->inject('project')
->inject('dbForPlatform')
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, string $providerReference, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
@@ -635,7 +623,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$contents = $github->listRepositoryContents($owner, $repositoryName, $providerRootDirectory, $providerReference);
$contents = $github->listRepositoryContents($owner, $repositoryName, $providerRootDirectory);
$vcsContents = [];
foreach ($contents as $content) {
@@ -663,7 +651,6 @@ App::post('/v1/vcs/github/installations/:installationId/detections')
->label('scope', 'vcs.write')
->label('sdk', new Method(
namespace: 'vcs',
group: 'repositories',
name: 'createRepositoryDetection',
description: '/docs/references/vcs/create-repository-detection.md',
auth: [AuthType::ADMIN],
@@ -821,7 +808,6 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
->label('scope', 'vcs.read')
->label('sdk', new Method(
namespace: 'vcs',
group: 'repositories',
name: 'listRepositories',
description: '/docs/references/vcs/list-repositories.md',
auth: [AuthType::ADMIN],
@@ -973,7 +959,6 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories')
->label('scope', 'vcs.write')
->label('sdk', new Method(
namespace: 'vcs',
group: 'repositories',
name: 'createRepository',
description: '/docs/references/vcs/create-repository.md',
auth: [AuthType::ADMIN],
@@ -1009,7 +994,7 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories')
if (empty($accessToken) || empty($refreshToken) || empty($accessTokenExpiry)) {
$identity = $dbForPlatform->findOne('identities', [
Query::equal('provider', ['github']),
Query::equal('userInternalId', [$user->getSequence()]),
Query::equal('userInternalId', [$user->getInternalId()]),
]);
if ($identity->isEmpty()) {
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
@@ -1086,7 +1071,6 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
->label('scope', 'vcs.read')
->label('sdk', new Method(
namespace: 'vcs',
group: 'repositories',
name: 'getRepository',
description: '/docs/references/vcs/get-repository.md',
auth: [AuthType::ADMIN],
@@ -1141,7 +1125,6 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
->label('scope', 'vcs.read')
->label('sdk', new Method(
namespace: 'vcs',
group: 'repositories',
name: 'listRepositoryBranches',
description: '/docs/references/vcs/list-repository-branches.md',
auth: [AuthType::ADMIN],
@@ -1219,7 +1202,6 @@ App::post('/v1/vcs/github/events')
if ($event == $github::EVENT_PUSH) {
$providerBranchCreated = $parsedPayload["branchCreated"] ?? false;
$providerBranchDeleted = $parsedPayload["branchDeleted"] ?? false;
$providerBranch = $parsedPayload["branch"] ?? '';
$providerBranchUrl = $parsedPayload["branchUrl"] ?? '';
$providerRepositoryId = $parsedPayload["repositoryId"] ?? '';
@@ -1241,8 +1223,8 @@ App::post('/v1/vcs/github/events')
Query::limit(100),
]));
// create new deployment only on push and not when branch is created or deleted
if (!$providerBranchCreated && !$providerBranchDeleted) {
// create new deployment only on push and not when branch is created
if (!$providerBranchCreated) {
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
}
} elseif ($event == $github::EVENT_INSTALLATION) {
@@ -1257,7 +1239,7 @@ App::post('/v1/vcs/github/events')
foreach ($installations as $installation) {
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('installationInternalId', [$installation->getSequence()]),
Query::equal('installationInternalId', [$installation->getInternalId()]),
Query::limit(1000)
]));
@@ -1336,7 +1318,6 @@ App::get('/v1/vcs/installations')
->label('scope', 'vcs.read')
->label('sdk', new Method(
namespace: 'vcs',
group: 'installations',
name: 'listInstallations',
description: '/docs/references/vcs/list-installations.md',
auth: [AuthType::ADMIN],
@@ -1360,7 +1341,7 @@ App::get('/v1/vcs/installations')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries[] = Query::equal('projectInternalId', [$project->getSequence()]);
$queries[] = Query::equal('projectInternalId', [$project->getInternalId()]);
if (!empty($search)) {
$queries[] = Query::search('search', $search);
@@ -1392,12 +1373,9 @@ App::get('/v1/vcs/installations')
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$results = $dbForPlatform->find('installations', $queries);
$total = $dbForPlatform->count('installations', $filterQueries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$results = $dbForPlatform->find('installations', $queries);
$total = $dbForPlatform->count('installations', $filterQueries, APP_LIMIT_COUNT);
$response->dynamic(new Document([
'installations' => $results,
@@ -1411,7 +1389,6 @@ App::get('/v1/vcs/installations/:installationId')
->label('scope', 'vcs.read')
->label('sdk', new Method(
namespace: 'vcs',
group: 'installations',
name: 'getInstallation',
description: '/docs/references/vcs/get-installation.md',
auth: [AuthType::ADMIN],
@@ -1433,7 +1410,7 @@ App::get('/v1/vcs/installations/:installationId')
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if ($installation->getAttribute('projectInternalId') !== $project->getSequence()) {
if ($installation->getAttribute('projectInternalId') !== $project->getInternalId()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
@@ -1446,7 +1423,6 @@ App::delete('/v1/vcs/installations/:installationId')
->label('scope', 'vcs.write')
->label('sdk', new Method(
namespace: 'vcs',
group: 'installations',
name: 'deleteInstallation',
description: '/docs/references/vcs/delete-installation.md',
auth: [AuthType::ADMIN],
@@ -1482,12 +1458,11 @@ App::delete('/v1/vcs/installations/:installationId')
});
App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositoryId')
->desc('Update external deployment (authorize)')
->desc('Authorize external deployment')
->groups(['api', 'vcs'])
->label('scope', 'vcs.write')
->label('sdk', new Method(
namespace: 'vcs',
group: 'repositories',
name: 'updateExternalDeployments',
description: '/docs/references/vcs/update-external-deployments.md',
auth: [AuthType::ADMIN],
@@ -1516,7 +1491,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
}
$repository = Authorization::skip(fn () => $dbForPlatform->getDocument('repositories', $repositoryId, [
Query::equal('projectInternalId', [$project->getSequence()])
Query::equal('projectInternalId', [$project->getInternalId()])
]));
if ($repository->isEmpty()) {
+73 -101
View File
@@ -79,18 +79,11 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . '://' . System::getEnv('_APP_DOMAIN', '');
if ($rule->isEmpty()) {
$appDomainFunctionsFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
$appDomainFunctions = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$appDomainSites = System::getEnv('_APP_DOMAIN_SITES', '');
if (!empty($appDomainFunctionsFallback) && \str_ends_with($host, $appDomainFunctionsFallback)) {
$appDomainFunctions = $appDomainFunctionsFallback;
}
if ($host === $appDomainFunctions || $host === $appDomainSites) {
if ($host === System::getEnv('_APP_DOMAIN_FUNCTIONS', '') || $host === System::getEnv('_APP_DOMAIN_SITES', '')) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain cannot be used for security reasons. Please use any subdomain instead.', view: $errorView);
}
if (\str_ends_with($host, $appDomainFunctions) || \str_ends_with($host, $appDomainSites)) {
if (\str_ends_with($host, System::getEnv('_APP_DOMAIN_FUNCTIONS', '')) || \str_ends_with($host, System::getEnv('_APP_DOMAIN_SITES', ''))) {
$exception = new AppwriteException(AppwriteException::RULE_NOT_FOUND, 'This domain is not connected to any Appwrite resources. Visit domains tab under function/site settings to configure it.', view: $errorView);
$exception->addCTA('Start with this domain', $url . '/console');
@@ -149,31 +142,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$dbForProject = $getProjectDB($project);
/** @var Document $deployment */
if (!empty($rule->getAttribute('deploymentId', ''))) {
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
} else {
// 1.6.x DB schema compatibility
// TODO: Make sure deploymentId is never empty, and remove this code
// Check if site or function; should never be site, but better safe than sorry
// Attempts to use attribute from both schemas (1.6 and 1.7)
$resourceType = $rule->getAttribute('deploymentResourceType', $rule->getAttribute('resourceType', ''));
// ID of site or function
$resourceId = $rule->getAttribute('deploymentResourceId', '');
// Document of site or function
$resource = $resourceType === 'function' ?
Authorization::skip(fn () => $dbForProject->getDocument('functions', $resourceId)) :
Authorization::skip(fn () => $dbForProject->getDocument('sites', $resourceId));
// ID of active deployments
// Attempts to use attribute from both schemas (1.6 and 1.7)
$activeDeploymentId = $resource->getAttribute('deploymentId', $resource->getAttribute('deployment', ''));
// Get deployment document, as intended originally
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
}
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
if ($deployment->getAttribute('resourceType', '') === 'functions') {
$type = 'function';
@@ -186,7 +155,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$resourceId = $rule->getAttribute('deploymentResourceId', '');
$type = ($resourceType === 'site') ? 'sites' : 'functions';
$exception = new AppwriteException(AppwriteException::DEPLOYMENT_NOT_FOUND, view: $errorView);
$exception->addCTA('View deployments', $url . '/console/project-' . $project->getAttribute('region', 'default') . '-' . $projectId . '/' . $type . '/' . $resourceType . '-' . $resourceId);
$exception->addCTA('View deployments', $url . '/console/project-' . $projectId . '/' . $type . '/' . $resourceType . '-' . $resourceId);
throw $exception;
}
@@ -319,22 +288,21 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$allowAnyStatus = !\is_null($apiKey) && $apiKey->isDeploymentStatusIgnored();
if (!$allowAnyStatus && $deployment->getAttribute('status') !== 'ready') {
$status = $deployment->getAttribute('status');
$region = $project->getAttribute('region', 'default');
switch ($status) {
case 'failed':
$exception = new AppwriteException(AppwriteException::BUILD_FAILED, view: $errorView);
$ctaUrl = '/console/project-' . $region . '-' . $project->getId() . '/sites/site-' . $resource->getId() . '/deployments/deployment-' . $deployment->getId();
$ctaUrl = '/console/project-' . $project->getId() . '/sites/site-' . $resource->getId() . '/deployments/deployment-' . $deployment->getId();
$exception->addCTA('View logs', $url . $ctaUrl);
break;
case 'canceled':
$exception = new AppwriteException(AppwriteException::BUILD_CANCELED, view: $errorView);
$ctaUrl = '/console/project-' . $region . '-' . $project->getId() . '/sites/site-' . $resource->getId() . '/deployments';
$ctaUrl = '/console/project-' . $project->getId() . '/sites/site-' . $resource->getId() . '/deployments';
$exception->addCTA('View deployments', $url . $ctaUrl);
break;
default:
$exception = new AppwriteException(AppwriteException::BUILD_NOT_READY, view: $errorView);
$ctaUrl = '/console/project-' . $region . '-' . $project->getId() . '/sites/site-' . $resource->getId() . '/deployments/deployment-' . $deployment->getId();
$ctaUrl = '/console/project-' . $project->getId() . '/sites/site-' . $resource->getId() . '/deployments/deployment-' . $deployment->getId();
$exception->addCTA('Reload', '/');
$exception->addCTA('View logs', $url . $ctaUrl);
break;
@@ -346,7 +314,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$permissions = $resource->getAttribute('execute');
if (!(\in_array('any', $permissions)) && !(\in_array('guests', $permissions))) {
$exception = new AppwriteException(AppwriteException::FUNCTION_EXECUTE_PERMISSION_MISSING, view: $errorView);
$exception->addCTA('View settings', $url . '/console/project-' . $project->getAttribute('region', 'default') . '-' . $project->getId() . '/functions/function-' . $resource->getId() . '/settings');
$exception->addCTA('View settings', $url . '/console/project-' . $project->getId() . '/functions/function-' . $resource->getId() . '/settings');
throw $exception;
}
}
@@ -392,9 +360,9 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$execution = new Document([
'$id' => $executionId,
'$permissions' => [],
'resourceInternalId' => $resource->getSequence(),
'resourceInternalId' => $resource->getInternalId(),
'resourceId' => $resource->getId(),
'deploymentInternalId' => $deployment->getSequence(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
'responseStatusCode' => 0,
'responseHeaders' => [],
@@ -453,33 +421,16 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$endpoint = $protocol . '://' . $hostname . "/v1";
// Appwrite vars
if ($type === 'function') {
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
'APPWRITE_FUNCTION_ID' => $resource->getId(),
'APPWRITE_FUNCTION_NAME' => $resource->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'] ?? '',
'APPWRITE_FUNCTION_CPUS' => $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
'APPWRITE_FUNCTION_MEMORY' => $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
]);
} elseif ($type === 'site') {
$vars = \array_merge($vars, [
'APPWRITE_SITE_API_ENDPOINT' => $endpoint,
'APPWRITE_SITE_ID' => $resource->getId(),
'APPWRITE_SITE_NAME' => $resource->getAttribute('name'),
'APPWRITE_SITE_DEPLOYMENT' => $deployment->getId(),
'APPWRITE_SITE_PROJECT_ID' => $project->getId(),
'APPWRITE_SITE_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_SITE_RUNTIME_VERSION' => $runtime['version'] ?? '',
'APPWRITE_SITE_CPUS' => $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
'APPWRITE_SITE_MEMORY' => $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
]);
}
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
'APPWRITE_FUNCTION_ID' => $resource->getId(),
'APPWRITE_FUNCTION_NAME' => $resource->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'] ?? '',
'APPWRITE_FUNCTION_CPUS' => $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
'APPWRITE_FUNCTION_MEMORY' => $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
'APPWRITE_VERSION' => APP_VERSION_STABLE,
'APPWRITE_REGION' => $project->getAttribute('region'),
'APPWRITE_DEPLOYMENT_TYPE' => $deployment->getAttribute('type', ''),
@@ -512,26 +463,27 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
'function' => $deployment->getAttribute('entrypoint', ''),
'site' => '',
};
$source = $deployment->getAttribute('buildPath', '');
$extension = str_ends_with($source, '.tar') ? 'tar' : 'tar.gz';
$startCommand = $runtime['startCommand'];
if ($type === 'site') {
if ($type === 'function') {
$runtimeEntrypoint = match ($version) {
'v2' => '',
default => 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $runtime['startCommand'] . '"'
};
} elseif ($type === 'site') {
$frameworks = Config::getParam('frameworks', []);
$framework = $frameworks[$resource->getAttribute('framework', '')] ?? null;
$startCommand = $runtime['startCommand'];
if (!is_null($framework)) {
$adapter = ($framework['adapters'] ?? [])[$deployment->getAttribute('adapter', '')] ?? null;
if (!is_null($adapter) && isset($adapter['startCommand'])) {
$startCommand = $adapter['startCommand'];
}
}
}
$runtimeEntrypoint = match ($version) {
'v2' => '',
default => "cp /tmp/code.$extension /mnt/code/code.$extension && nohup helpers/start.sh \"$startCommand\"",
};
$runtimeEntrypoint = 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $startCommand . '"';
}
$entrypoint = match ($type) {
'function' => $deployment->getAttribute('entrypoint', ''),
@@ -545,7 +497,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
variables: $vars,
timeout: $resource->getAttribute('timeout', 30),
image: $runtime['image'],
source: $source,
source: $deployment->getAttribute('buildPath', ''),
entrypoint: $entrypoint,
version: $version,
path: $path,
@@ -692,11 +644,11 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
$metricTypeExecutions = str_replace(['{resourceType}'], [$deployment->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_EXECUTIONS);
$metricTypeIdExecutions = str_replace(['{resourceType}', '{resourceInternalId}'], [$deployment->getAttribute('resourceType'), $resource->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS);
$metricTypeIdExecutions = str_replace(['{resourceType}', '{resourceInternalId}'], [$deployment->getAttribute('resourceType'), $resource->getInternalId()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS);
$metricTypeExecutionsCompute = str_replace(['{resourceType}'], [$deployment->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_EXECUTIONS_COMPUTE);
$metricTypeIdExecutionsCompute = str_replace(['{resourceType}', '{resourceInternalId}'], [$deployment->getAttribute('resourceType'), $resource->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_COMPUTE);
$metricTypeIdExecutionsCompute = str_replace(['{resourceType}', '{resourceInternalId}'], [$deployment->getAttribute('resourceType'), $resource->getInternalId()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_COMPUTE);
$metricTypeExecutionsMbSeconds = str_replace(['{resourceType}'], [$deployment->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_EXECUTIONS_MB_SECONDS);
$metricTypeIdExecutionsMBSeconds = str_replace(['{resourceType}', '{resourceInternalId}'], [$deployment->getAttribute('resourceType'), $resource->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS);
$metricTypeIdExecutionsMBSeconds = str_replace(['{resourceType}', '{resourceInternalId}'], [$deployment->getAttribute('resourceType'), $resource->getInternalId()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS);
if ($deployment->getAttribute('resourceType') === 'sites') {
$queueForStatsUsage
->disableMetric(METRIC_NETWORK_REQUESTS)
@@ -719,9 +671,9 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
->addMetric(METRIC_SITES_REQUESTS, 1)
->addMetric(METRIC_SITES_INBOUND, $request->getSize() + $fileSize)
->addMetric(METRIC_SITES_OUTBOUND, $response->getSize())
->addMetric(str_replace('{siteInternalId}', $resource->getSequence(), METRIC_SITES_ID_REQUESTS), 1)
->addMetric(str_replace('{siteInternalId}', $resource->getSequence(), METRIC_SITES_ID_INBOUND), $request->getSize() + $fileSize)
->addMetric(str_replace('{siteInternalId}', $resource->getSequence(), METRIC_SITES_ID_OUTBOUND), $response->getSize())
->addMetric(str_replace('{siteInternalId}', $resource->getInternalId(), METRIC_SITES_ID_REQUESTS), 1)
->addMetric(str_replace('{siteInternalId}', $resource->getInternalId(), METRIC_SITES_ID_INBOUND), $request->getSize() + $fileSize)
->addMetric(str_replace('{siteInternalId}', $resource->getInternalId(), METRIC_SITES_ID_OUTBOUND), $response->getSize())
;
}
@@ -749,6 +701,12 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
return false;
} elseif ($type === 'redirect') {
$url = $rule->getAttribute('redirectUrl', '');
$query = ($swooleRequest->server['query_string'] ?? '');
if (!empty($query)) {
$url .= '?' . $query;
}
$response->redirect($url, \intval($rule->getAttribute('redirectStatusCode', 301)));
return true;
} else {
@@ -885,17 +843,8 @@ App::init()
}
$owner = '';
$functionsDomainFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$siteDomain = System::getEnv('_APP_DOMAIN_SITES', '');
if (!empty($functionsDomainFallback) && \str_ends_with($host, $functionsDomainFallback)) {
$functionsDomain = $functionsDomainFallback;
}
if (
(!empty($functionsDomain) && \str_ends_with($domain->get(), $functionsDomain)) ||
(!empty($siteDomain) && \str_ends_with($domain->get(), $siteDomain))
) {
if (!empty($functionsDomain) && \str_ends_with($domain->get(), $functionsDomain)) {
$owner = 'Appwrite';
}
@@ -908,7 +857,7 @@ App::init()
'type' => 'api',
'status' => 'verifying',
'projectId' => $console->getId(),
'projectInternalId' => $console->getSequence(),
'projectInternalId' => $console->getInternalId(),
'search' => implode(' ', [$ruleId, $domain->get()]),
'owner' => $owner,
'region' => $console->getAttribute('region')
@@ -958,7 +907,7 @@ App::init()
)[0] ?? new Document();
}
if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) {
if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getInternalId()) {
$refDomainOrigin = $origin;
}
}
@@ -1041,7 +990,7 @@ App::init()
->addHeader('Server', 'Appwrite')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent')
->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $refDomain)
->addHeader('Access-Control-Allow-Credentials', 'true');
@@ -1104,7 +1053,7 @@ App::options()
$response
->addHeader('Server', 'Appwrite')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent')
->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $origin)
->addHeader('Access-Control-Allow-Credentials', 'true')
@@ -1171,12 +1120,36 @@ App::error()
break;
}
break;
case 'Utopia\Database\Exception\Authorization':
$error = new AppwriteException(AppwriteException::USER_UNAUTHORIZED);
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;
case 'Utopia\Database\Exception\Relationship':
$error = new AppwriteException(AppwriteException::RELATIONSHIP_VALUE_INVALID, $error->getMessage(), previous: $error);
break;
case 'Utopia\Database\Exception\NotFound':
$error = new AppwriteException(AppwriteException::COLLECTION_NOT_FOUND, $error->getMessage(), previous: $error);
break;
case 'Utopia\Database\Exception\Dependency':
$error = new AppwriteException(AppwriteException::INDEX_DEPENDENCY, null, previous: $error);
break;
}
$code = $error->getCode();
@@ -1501,7 +1474,6 @@ App::get('/v1/ping')
->label('event', 'projects.[projectId].ping')
->label('sdk', new Method(
namespace: 'ping',
group: null,
name: 'get',
hide: true,
description: <<<EOT
+2 -2
View File
@@ -185,7 +185,7 @@ App::post('/v1/mock/api-key-unprefixed')
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'projectInternalId' => $project->getSequence(),
'projectInternalId' => $project->getInternalId(),
'projectId' => $project->getId(),
'name' => 'Outdated key',
'scopes' => $scopes,
@@ -235,7 +235,7 @@ App::get('/v1/mock/github/callback')
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$projectInternalId = $project->getSequence();
$projectInternalId = $project->getInternalId();
$teamId = $project->getAttribute('teamId', '');
+32 -51
View File
@@ -92,20 +92,8 @@ $eventDatabaseListener = function (Document $project, Document $document, Respon
$usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) {
$value = 1;
switch ($event) {
case Database::EVENT_DOCUMENT_DELETE:
$value = -1;
break;
case Database::EVENT_DOCUMENTS_DELETE:
$value = -1 * $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_CREATE:
$value = $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_UPSERT:
$value = $document->getAttribute('created', 0);
break;
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$value = -1;
}
switch (true) {
@@ -261,36 +249,34 @@ App::init()
subject: 'keys'
);
if (!$dbKey) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($dbKey) {
$accessedAt = $dbKey->getAttribute('accessedAt', 0);
$accessedAt = $dbKey->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
$dbKey->setAttribute('accessedAt', DateTime::now());
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
$sdkValidator = new WhiteList($servers, true);
$sdk = $request->getHeader('x-sdk-name', 'UNKNOWN');
if ($sdk !== 'UNKNOWN' && $sdkValidator->isValid($sdk)) {
$sdks = $dbKey->getAttribute('sdks', []);
if (!in_array($sdk, $sdks)) {
$sdks[] = $sdk;
$dbKey->setAttribute('sdks', $sdks);
/** Update access time as well */
$dbKey->setAttribute('accessedAt', Datetime::now());
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
$dbKey->setAttribute('accessedAt', DateTime::now());
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
}
$queueForAudits->setUser($user);
$sdkValidator = new WhiteList($servers, true);
$sdk = $request->getHeader('x-sdk-name', 'UNKNOWN');
if ($sdk !== 'UNKNOWN' && $sdkValidator->isValid($sdk)) {
$sdks = $dbKey->getAttribute('sdks', []);
if (!in_array($sdk, $sdks)) {
$sdks[] = $sdk;
$dbKey->setAttribute('sdks', $sdks);
/** Update access time as well */
$dbKey->setAttribute('accessedAt', Datetime::now());
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
}
$queueForAudits->setUser($user);
}
}
} // Admin User Authentication
elseif (($project->getId() === 'console' && !$team->isEmpty() && !$user->isEmpty()) || ($project->getId() !== 'console' && !$user->isEmpty() && $mode === APP_MODE_ADMIN)) {
@@ -351,8 +337,6 @@ App::init()
*/
$method = $route->getLabel('sdk', false);
// Take the first method if there's more than one,
// namespace can not differ between methods on the same route
if (\is_array($method)) {
$method = $method[0];
}
@@ -539,9 +523,6 @@ App::init()
$dbForProject
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
$project,
$document,
@@ -558,7 +539,7 @@ App::init()
$isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview';
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !Auth::isPrivilegedUser(Authorization::getRoles());
$key = $request->cacheIdentifier();
$key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
@@ -567,14 +548,14 @@ App::init()
$data = $cache->load($key, $timestamp);
if (!empty($data) && !$cacheLog->isEmpty()) {
$parts = explode('/', $cacheLog->getAttribute('resourceType', ''));
$parts = explode('/', $cacheLog->getAttribute('resourceType'));
$type = $parts[0] ?? null;
if ($type === 'bucket' && (!$isImageTransformation || !$isDisabled)) {
$bucketId = $parts[1] ?? null;
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getInternalId();
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
@@ -593,12 +574,12 @@ App::init()
$fileId = $parts[1] ?? null;
if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getInternalId()) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -824,7 +805,7 @@ App::shutdown()
$resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user);
}
$key = $request->cacheIdentifier();
$key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
$signature = md5($data['payload']);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$accessedAt = $cacheLog->getAttribute('accessedAt', 0);
+99 -100
View File
@@ -15,11 +15,9 @@ use Utopia\Audit\Audit;
use Utopia\CLI\Console;
use Utopia\Compression\Compression;
use Utopia\Config\Config;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@@ -48,6 +46,29 @@ $http = new Server(
$payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
$totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
$http
->set([
'worker_num' => $totalWorkers,
'dispatch_func' => 'dispatch',
'open_http2_protocol' => true,
'http_compression' => false,
'package_max_length' => $payloadSize,
'buffer_output_size' => $payloadSize,
'task_worker_num' => 1, // required for the task to fetch domains background
]);
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
Console::success('Worker ' . ++$workerId . ' started successfully');
});
$http->on(Constant::EVENT_BEFORE_RELOAD, function ($server, $workerId) {
Console::success('Starting reload...');
});
$http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
Console::success('Reload completed...');
});
/**
* Assigns HTTP requests to worker threads by analyzing its payload/content.
*
@@ -65,105 +86,76 @@ $totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intva
*/
function dispatch(Server $server, int $fd, int $type, $data = null): int
{
$resolveWorkerId = function (Server $server, $data = null) {
global $totalWorkers, $domains;
global $totalWorkers, $domains;
// If data is not set we can send request to any worker
// first we try to pick idle worker, if not we randomly pick a worker
if ($data === null) {
for ($i = 0; $i < $totalWorkers; $i++) {
if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) {
return $i;
}
}
return rand(0, $totalWorkers - 1);
}
$riskyWorkersPercent = intval(System::getEnv('_APP_RISKY_WORKERS_PERCENT', 80)) / 100; // Decimal form 0 to 1
// Each worker has numeric ID, starting from 0 and incrementing
// From 0 to riskyWorkers, we consider safe workers
// From riskyWorkers to totalWorkers, we consider risky workers
$riskyWorkers = (int)floor($totalWorkers * $riskyWorkersPercent); // Absolute amount of risky workers
$domain = '';
// max up to 3 as first line has request details and second line has host
$lines = explode("\n", $data, 3);
$request = $lines[0];
if (count($lines) > 1) {
$domain = trim(explode('Host: ', $lines[1])[1]);
}
// Sync executions are considered risky
$risky = false;
if (str_starts_with($request, 'POST') && str_contains($request, '/executions')) {
$risky = true;
} elseif (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) {
$risky = true;
} elseif ($domains->get(md5($domain), 'value') === 1) {
// executions request coming from custom domain
$risky = true;
}
if ($risky) {
// If risky request, only consider risky workers
for ($j = $riskyWorkers; $j < $totalWorkers; $j++) {
/** Reference https://openswoole.com/docs/modules/swoole-server-getWorkerStatus#description */
if ($server->getWorkerStatus($j) === SWOOLE_WORKER_IDLE) {
// If idle worker found, give to him
return $j;
}
}
// If no idle workers, give to random risky worker
$worker = rand($riskyWorkers, $totalWorkers - 1);
Console::warning("swoole_dispatch: Risky branch: did not find a idle worker, picking random worker {$worker}");
return $worker;
}
// If safe request, give to any idle worker
// Its fine to pick risky worker here, because it's idle. Idle is never actually risky
// If data is not set we can send request to any worker
// first we try to pick idle worker, if not we randomly pick a worker
if ($data === null) {
for ($i = 0; $i < $totalWorkers; $i++) {
if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) {
return $i;
}
}
return rand(0, $totalWorkers - 1);
}
// If no idle worker found, give to random safe worker
// We avoid risky workers here, as it could be in work - not idle. Thats exactly when they are risky.
$worker = rand(0, $riskyWorkers - 1);
Console::warning("swoole_dispatch: Non-risky branch: did not find a idle worker, picking random worker {$worker}");
$riskyWorkersPercent = intval(System::getEnv('_APP_RISKY_WORKERS_PERCENT', 80)) / 100; // Decimal form 0 to 1
// Each worker has numeric ID, starting from 0 and incrementing
// From 0 to riskyWorkers, we consider safe workers
// From riskyWorkers to totalWorkers, we consider risky workers
$riskyWorkers = (int) floor($totalWorkers * $riskyWorkersPercent); // Absolute amount of risky workers
$domain = '';
// max up to 3 as first line has request details and second line has host
$lines = explode("\n", $data, 3);
$request = $lines[0];
if (count($lines) > 1) {
$domain = trim(explode('Host: ', $lines[1])[1]);
}
// Sync executions are considered risky
$risky = false;
if (str_starts_with($request, 'POST') && str_contains($request, '/executions')) {
$risky = true;
} elseif (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) {
$risky = true;
} elseif ($domains->get(md5($domain), 'value') === 1) {
// executions request coming from custom domain
$risky = true;
}
if ($risky) {
// If risky request, only consider risky workers
for ($j = $riskyWorkers; $j < $totalWorkers; $j++) {
/** Reference https://openswoole.com/docs/modules/swoole-server-getWorkerStatus#description */
if ($server->getWorkerStatus($j) === SWOOLE_WORKER_IDLE) {
// If idle worker found, give to him
return $j;
}
}
// If no idle workers, give to random risky worker
$worker = rand($riskyWorkers, $totalWorkers - 1);
Console::warning("swoole_dispatch: Risky branch: did not find a idle worker, picking random worker {$worker}");
return $worker;
};
$workerId = $resolveWorkerId($server, $data);
$server->bind($fd, $workerId);
return $workerId;
}
// If safe request, give to any idle worker
// Its fine to pick risky worker here, because it's idle. Idle is never actually risky
for ($i = 0; $i < $totalWorkers; $i++) {
if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) {
return $i;
}
}
// If no idle worker found, give to random safe worker
// We avoid risky workers here, as it could be in work - not idle. Thats exactly when they are risky.
$worker = rand(0, $riskyWorkers - 1);
Console::warning("swoole_dispatch: Non-risky branch: did not find a idle worker, picking random worker {$worker}");
return $worker;
}
$http
->set([
Constant::OPTION_WORKER_NUM => $totalWorkers,
Constant::OPTION_DISPATCH_FUNC => dispatch(...),
Constant::OPTION_DISPATCH_MODE => SWOOLE_DISPATCH_UIDMOD,
Constant::OPTION_HTTP_COMPRESSION => false,
Constant::OPTION_PACKAGE_MAX_LENGTH => $payloadSize,
Constant::OPTION_OUTPUT_BUFFER_SIZE => $payloadSize,
Constant::OPTION_TASK_WORKER_NUM => 1, // required for the task to fetch domains background
]);
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
Console::success('Worker ' . ++$workerId . ' started successfully');
});
$http->on(Constant::EVENT_BEFORE_RELOAD, function ($server, $workerId) {
Console::success('Starting reload...');
});
$http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
Console::success('Reload completed...');
});
include __DIR__ . '/controllers/general.php';
function createDatabase(App $app, string $resourceKey, string $dbName, array $collections, mixed $pools, callable $extraSetup = null): void
@@ -172,7 +164,7 @@ function createDatabase(App $app, string $resourceKey, string $dbName, array $co
$sleep = 1;
$attempts = 0;
while (true) {
do {
try {
$attempts++;
$resource = $app->getResource($resourceKey);
@@ -181,12 +173,13 @@ function createDatabase(App $app, string $resourceKey, string $dbName, array $co
break; // exit loop on success
} catch (\Exception $e) {
Console::warning(" └── Database not ready. Retrying connection ({$attempts})...");
$pools->reclaim();
if ($attempts >= $max) {
throw new \Exception(' └── Failed to connect to database: ' . $e->getMessage());
}
sleep($sleep);
}
}
} while ($attempts < $max);
Console::success("[Setup] - $dbName database init started...");
@@ -309,7 +302,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
'orders' => $index['orders'],
]), $files['indexes']);
$dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes);
$dbForPlatform->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
}
if (Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
@@ -318,7 +311,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
'$id' => ID::custom('screenshots'),
'$collection' => ID::custom('buckets'),
'name' => 'Screenshots',
'maximumFileSize' => 20000000, // ~20MB
'maximumFileSize' => 5000000, // ~5MB
'allowedFileExtensions' => [ 'png' ],
'enabled' => true,
'compression' => Compression::GZIP,
@@ -357,7 +350,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
'orders' => $index['orders'],
]), $files['indexes']);
Authorization::skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes));
Authorization::skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes));
}
});
@@ -369,7 +362,11 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
$cache = $app->getResource('cache');
foreach ($sharedTablesV2 as $hostname) {
$adapter = new DatabasePool($pools->get($hostname));
$adapter = $pools
->get($hostname)
->pop()
->getResource();
$dbForProject = (new Database($adapter, $cache))
->setDatabase('appwrite')
->setSharedTables(true)
@@ -379,7 +376,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
try {
Console::success('[Setup] - Creating project database: ' . $hostname . '...');
$dbForProject->create();
} catch (DuplicateException) {
} catch (Duplicate) {
Console::success('[Setup] - Skip: metadata table already exists');
}
@@ -405,6 +402,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
}
}
$pools->reclaim();
Console::success('[Setup] - Server database init completed...');
});
@@ -519,7 +517,6 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
Console::error('[Error] Trace: ' . $th->getTraceAsString());
$swooleResponse->setStatusCode(500);
@@ -537,11 +534,13 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
];
$swooleResponse->end(\json_encode($output));
} finally {
$pools->reclaim();
}
});
// Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory
$http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
$http->on('Task', function () use ($register, $domains) {
$lastSyncUpdate = null;
$pools = $register->get('pools');
App::setResource('pools', fn () => $pools);
-2
View File
@@ -2,8 +2,6 @@
use Utopia\Config\Config;
require_once __DIR__ . '/../config/storage/resource_limits.php';
Config::load('template-runtimes', __DIR__ . '/../config/template-runtimes.php');
Config::load('events', __DIR__ . '/../config/events.php');
Config::load('auth', __DIR__ . '/../config/auth.php');
+2 -17
View File
@@ -6,10 +6,6 @@ const APP_NAME = 'Appwrite';
const APP_DOMAIN = 'appwrite.io';
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
const APP_EMAIL_SECURITY = ''; // Default security email address
const APP_EMAIL_LOGO_URL = 'https://cloud.appwrite.io/images/mails/logo.png';
const APP_EMAIL_ACCENT_COLOR = '#fd366e';
const APP_EMAIL_TERMS_URL = 'https://appwrite.io/terms';
const APP_EMAIL_PRIVACY_URL = 'https://appwrite.io/privacy';
const APP_USERAGENT = APP_NAME . '-Server v%s. Please report abuse at %s';
const APP_MODE_DEFAULT = 'default';
const APP_MODE_ADMIN = 'admin';
@@ -30,15 +26,13 @@ const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1_000_000;
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
const APP_LIMIT_DATABASE_BATCH = 100; // Default maximum batch size for database operations
const APP_KEY_ACCESS = 24 * 60 * 60; // 24 hours
const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
const APP_RESOURCE_TOKEN_ACCESS = 24 * 60 * 60; // 24 hours
const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 4320;
const APP_VERSION_STABLE = '1.7.4';
const APP_CACHE_BUSTER = 4318;
const APP_VERSION_STABLE = '1.7.0';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@@ -51,7 +45,6 @@ const APP_DATABASE_TIMEOUT_MILLISECONDS_API = 15 * 1000; // 15 seconds
const APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER = 300 * 1000; // 5 minutes
const APP_DATABASE_TIMEOUT_MILLISECONDS_TASK = 300 * 1000; // 5 minutes
const APP_DATABASE_QUERY_MAX_VALUES = 500;
const APP_DATABASE_ENCRYPT_SIZE_MIN = 150;
const APP_STORAGE_UPLOADS = '/storage/uploads';
const APP_STORAGE_SITES = '/storage/sites';
const APP_STORAGE_FUNCTIONS = '/storage/functions';
@@ -67,7 +60,6 @@ const APP_SOCIAL_FACEBOOK = 'https://www.facebook.com/appwrite.io';
const APP_SOCIAL_LINKEDIN = 'https://www.linkedin.com/company/appwrite';
const APP_SOCIAL_INSTAGRAM = 'https://www.instagram.com/appwrite.io';
const APP_SOCIAL_GITHUB = 'https://github.com/appwrite';
const APP_SOCIAL_GITHUB_APPWRITE = 'https://github.com/appwrite/appwrite';
const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord';
const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
@@ -265,10 +257,3 @@ const RESOURCE_TYPE_PROVIDERS = 'providers';
const RESOURCE_TYPE_TOPICS = 'topics';
const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers';
const RESOURCE_TYPE_MESSAGES = 'messages';
// Resource types for Tokens
const TOKENS_RESOURCE_TYPE_FILES = 'files';
const TOKENS_RESOURCE_TYPE_SITES = 'sites';
const TOKENS_RESOURCE_TYPE_FUNCTIONS = 'functions';
const TOKENS_RESOURCE_TYPE_DATABASES = 'databases';
+21 -30
View File
@@ -71,27 +71,18 @@ Database::addFilter(
},
function (mixed $value, Document $document, Database $database) {
$attributes = $database->find('attributes', [
Query::equal('collectionInternalId', [$document->getSequence()]),
Query::equal('collectionInternalId', [$document->getInternalId()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForAttributes()),
]);
foreach ($attributes as $attribute) {
$attributeType = $attribute->getAttribute('type');
switch ($attributeType) {
case Database::VAR_RELATIONSHIP:
$options = $attribute->getAttribute('options');
foreach ($options as $key => $value) {
$attribute->setAttribute($key, $value);
}
$attribute->removeAttribute('options');
break;
case Database::VAR_STRING:
$filters = $attribute->getAttribute('filters', []);
$attribute->setAttribute('encrypt', in_array('encrypt', $filters));
break;
if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
$options = $attribute->getAttribute('options');
foreach ($options as $key => $value) {
$attribute->setAttribute($key, $value);
}
$attribute->removeAttribute('options');
}
}
@@ -107,7 +98,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('indexes', [
Query::equal('collectionInternalId', [$document->getSequence()]),
Query::equal('collectionInternalId', [$document->getInternalId()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForIndexes()),
]);
@@ -122,7 +113,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('platforms', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::equal('projectInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
}
@@ -136,7 +127,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('keys', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::equal('projectInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
}
@@ -150,7 +141,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('devKeys', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::equal('projectInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
}
@@ -164,7 +155,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('webhooks', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::equal('projectInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
}
@@ -177,7 +168,7 @@ Database::addFilter(
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database->find('sessions', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
@@ -191,7 +182,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
->find('tokens', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
@@ -205,7 +196,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
->find('challenges', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
@@ -219,7 +210,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
->find('authenticators', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
@@ -233,7 +224,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
->find('memberships', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
@@ -247,7 +238,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('variables', [
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::equal('resourceInternalId', [$document->getInternalId()]),
Query::equal('resourceType', ['function', 'site']),
Query::limit(APP_LIMIT_SUBQUERY),
]);
@@ -325,7 +316,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
->find('targets', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY)
]));
}
@@ -340,13 +331,13 @@ Database::addFilter(
$targetIds = Authorization::skip(fn () => \array_map(
fn ($document) => $document->getAttribute('targetInternalId'),
$database->find('subscribers', [
Query::equal('topicInternalId', [$document->getSequence()]),
Query::equal('topicInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY)
])
));
if (\count($targetIds) > 0) {
return $database->skipValidation(fn () => $database->find('targets', [
Query::equal('$sequence', $targetIds)
Query::equal('$internalId', $targetIds)
]));
}
return [];
+7 -8
View File
@@ -15,7 +15,6 @@ use Utopia\Config\Config;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Adapter\MySQL;
use Utopia\Database\Adapter\SQL;
use Utopia\Database\PDO;
use Utopia\Domains\Validator\PublicDomain;
use Utopia\DSN\DSN;
use Utopia\Logger\Adapter\AppSignal;
@@ -216,13 +215,13 @@ $register->set('pools', function () {
'mysql',
'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, [
\PDO::ATTR_TIMEOUT => 3, // Seconds
\PDO::ATTR_PERSISTENT => false,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
\PDO::ATTR_EMULATE_PREPARES => true,
\PDO::ATTR_STRINGIFY_FETCHES => true
]);
return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array(
PDO::ATTR_TIMEOUT => 3, // Seconds
PDO::ATTR_PERSISTENT => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_STRINGIFY_FETCHES => true
));
});
},
'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
+88 -92
View File
@@ -24,12 +24,10 @@ use Appwrite\Utopia\Request;
use Executor\Executor;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime as DatabaseDateTime;
use Utopia\Database\Document;
@@ -40,7 +38,6 @@ use Utopia\DSN\DSN;
use Utopia\Locale\Locale;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
use Utopia\Queue\Broker\Pool as BrokerPool;
use Utopia\Queue\Publisher;
use Utopia\Storage\Device;
use Utopia\Storage\Device\AWS;
@@ -52,8 +49,6 @@ use Utopia\Storage\Device\S3;
use Utopia\Storage\Device\Wasabi;
use Utopia\Storage\Storage;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
use Utopia\Validator\Hostname;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub as VcsGitHub;
@@ -77,10 +72,10 @@ App::setResource('localeCodes', function () {
// Queues
App::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
return $pools->get('publisher')->pop()->getResource();
}, ['pools']);
App::setResource('consumer', function (Group $pools) {
return new BrokerPool(consumer: $pools->get('consumer'));
return $pools->get('consumer')->pop()->getResource();
}, ['pools']);
App::setResource('queueForMessaging', function (Publisher $publisher) {
return new Messaging($publisher);
@@ -334,8 +329,12 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$dbAdapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$database
->setMetadata('host', \gethostname())
@@ -348,21 +347,25 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant((int)$project->getSequence())
->setTenant($project->getInternalId())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
->setNamespace('_' . $project->getInternalId());
}
return $database;
}, ['pools', 'dbForPlatform', 'cache', 'project']);
App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
$adapter = new DatabasePool($pools->get('console'));
$database = new Database($adapter, $cache);
$dbAdapter = $pools
->get('console')
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$database
->setNamespace('_console')
@@ -375,7 +378,7 @@ App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
}, ['pools', 'cache']);
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
$databases = [];
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') {
@@ -401,13 +404,13 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant((int)$project->getSequence())
->setTenant($project->getInternalId())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
->setNamespace('_' . $project->getInternalId());
}
});
@@ -417,8 +420,12 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
return $database;
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$dbAdapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$databases[$dsn->getHost()] = $database;
$configure($database);
@@ -428,15 +435,21 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, &$database) {
return function (?Document $project = null) use ($pools, $cache, $database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence());
$database->setTenant($project->getInternalId());
return $database;
}
$adapter = new DatabasePool($pools->get('logs'));
$database = new Database($adapter, $cache);
$dbAdapter = $pools
->get('logs')
->pop()
->getResource();
$database = new Database(
$dbAdapter,
$cache
);
$database
->setSharedTables(true)
@@ -446,27 +459,27 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence());
$database->setTenant($project->getInternalId());
}
return $database;
};
}, ['pools', 'cache']);
App::setResource('telemetry', fn () => new NoTelemetry());
App::setResource('cache', function (Group $pools, Telemetry $telemetry) {
App::setResource('cache', function (Group $pools) {
$list = Config::getParam('pools-cache', []);
$adapters = [];
foreach ($list as $value) {
$adapters[] = new CachePool($pools->get($value));
$adapters[] = $pools
->get($value)
->pop()
->getResource()
;
}
$cache = new Cache(new Sharding($adapters));
$cache->setTelemetry($telemetry);
return $cache;
}, ['pools', 'telemetry']);
return new Cache(new Sharding($adapters));
}, ['pools']);
App::setResource('redis', function () {
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
@@ -489,24 +502,29 @@ App::setResource('timelimit', function (\Redis $redis) {
};
}, ['redis']);
App::setResource('deviceForLocal', function (Telemetry $telemetry) {
return new Device\Telemetry($telemetry, new Local());
}, ['telemetry']);
App::setResource('deviceForFiles', function ($project, Telemetry $telemetry) {
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
App::setResource('deviceForSites', function ($project, Telemetry $telemetry) {
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
App::setResource('deviceForImports', function ($project, Telemetry $telemetry) {
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
App::setResource('deviceForFunctions', function ($project, Telemetry $telemetry) {
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
App::setResource('deviceForBuilds', function ($project, Telemetry $telemetry) {
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
App::setResource('deviceForLocal', function () {
return new Local();
});
App::setResource('deviceForFiles', function ($project) {
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
}, ['project']);
App::setResource('deviceForSites', function ($project) {
return getDevice(APP_STORAGE_SITES . '/app-' . $project->getId());
}, ['project']);
App::setResource('deviceForImports', function (Document $project) {
return getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId());
}, ['project']);
App::setResource('deviceForFunctions', function ($project) {
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
}, ['project']);
App::setResource('deviceForBuilds', function ($project) {
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
}, ['project']);
function getDevice(string $root, string $connection = ''): Device
{
@@ -535,8 +553,7 @@ function getDevice(string $root, string $connection = ''): Device
switch ($device) {
case Storage::DEVICE_S3:
if (!empty($url)) {
$bucketRoot = (!empty($bucket) ? $bucket . '/' : '') . \ltrim($root, '/');
return new S3($bucketRoot, $accessKey, $accessSecret, $url, $region, $acl);
return new S3($root, $accessKey, $accessSecret, $url, $region, $acl);
} else {
return new AWS($root, $accessKey, $accessSecret, $bucket, $region, $acl);
}
@@ -568,8 +585,7 @@ function getDevice(string $root, string $connection = ''): Device
$s3Acl = 'private';
$s3EndpointUrl = System::getEnv('_APP_STORAGE_S3_ENDPOINT', '');
if (!empty($s3EndpointUrl)) {
$bucketRoot = (!empty($s3Bucket) ? $s3Bucket . '/' : '') . \ltrim($root, '/');
return new S3($bucketRoot, $s3AccessKey, $s3SecretKey, $s3EndpointUrl, $s3Region, $s3Acl);
return new S3($root, $s3AccessKey, $s3SecretKey, $s3EndpointUrl, $s3Region, $s3Acl);
} else {
return new AWS($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl);
}
@@ -800,7 +816,7 @@ App::setResource('devKey', function (Request $request, Document $project, array
// add sdk to key
$sdkValidator = new WhiteList($servers, true);
$sdk = \strtolower($request->getHeader('x-sdk-name', 'UNKNOWN'));
$sdk = $request->getHeader('x-sdk-name', 'UNKNOWN');
if ($sdk !== 'UNKNOWN' && $sdkValidator->isValid($sdk)) {
$sdks = $key->getAttribute('sdks', []);
@@ -839,7 +855,7 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
$team = Authorization::skip(function () use ($dbForPlatform, $teamInternalId) {
return $dbForPlatform->findOne('teams', [
Query::equal('$sequence', [$teamInternalId]),
Query::equal('$internalId', [$teamInternalId]),
]);
});
@@ -880,13 +896,13 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key
return Key::decode($project, $key);
}, ['request', 'project']);
App::setResource('executor', fn () => new Executor());
App::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST')));
App::setResource('resourceToken', function ($project, $dbForProject, $request) {
$tokenJWT = $request->getParam('token');
if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
$jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
try {
$payload = $jwt->decode($tokenJWT);
@@ -895,52 +911,32 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
}
$tokenId = $payload['tokenId'] ?? '';
if (empty($tokenId)) {
$secret = $payload['secret'] ?? '';
if (empty($tokenId) || empty($secret)) {
return new Document([]);
}
$token = Authorization::skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId));
if ($token->isEmpty()) {
if ($token->isEmpty() || $token->getAttribute('secret') !== $secret) {
return new Document([]);
}
$expiry = $token->getAttribute('expire');
if ($token->getAttribute('resourceType') === 'file') {
$internalIds = explode(':', $token->getAttribute('resourceInternalId'));
$ids = explode(':', $token->getAttribute('resourceId'));
if ($expiry !== null) {
$now = new \DateTime();
$expiryDate = new \DateTime($expiry);
if ($expiryDate < $now) {
if (count($internalIds) !== 2 || count($ids) !== 2) {
return new Document([]);
}
return new Document([
'bucketId' => $ids[0],
'fileId' => $ids[1],
'bucketInternalId' => $internalIds[0],
'fileInternalId' => $internalIds[1],
]);
}
return match ($token->getAttribute('resourceType')) {
TOKENS_RESOURCE_TYPE_FILES => (function () use ($token, $dbForProject) {
$sequences = explode(':', $token->getAttribute('resourceInternalId'));
$ids = explode(':', $token->getAttribute('resourceId'));
if (count($sequences) !== 2 || count($ids) !== 2) {
return new Document([]);
}
$accessedAt = $token->getAttribute('accessedAt', 0);
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), - APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) {
$token->setAttribute('accessedAt', DatabaseDateTime::now());
Authorization::skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
}
return new Document([
'bucketId' => $ids[0],
'fileId' => $ids[1],
'bucketInternalId' => $sequences[0],
'fileInternalId' => $sequences[1],
]);
})(),
default => throw new Exception(Exception::TOKEN_RESOURCE_TYPE_INVALID),
};
}
return new Document([]);
}, ['project', 'dbForProject', 'request']);
+47 -67
View File
@@ -5,7 +5,6 @@ use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Network\Validator\Origin;
use Appwrite\PubSub\Adapter\Pool as PubSubPool;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Swoole\Http\Request as SwooleRequest;
@@ -16,12 +15,10 @@ use Swoole\Timer;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
@@ -31,15 +28,13 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
use Utopia\Registry\Registry;
use Utopia\System\System;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
use Utopia\WebSocket\Adapter;
use Utopia\WebSocket\Server;
/**
* @var Registry $register
* @var \Utopia\Registry\Registry $register
*/
require_once __DIR__ . '/init.php';
@@ -51,17 +46,17 @@ if (!function_exists('getConsoleDB')) {
{
global $register;
static $database = null;
if ($database !== null) {
return $database;
}
/** @var Group $pools */
/** @var \Utopia\Pools\Group $pools */
$pools = $register->get('pools');
$adapter = new DatabasePool($pools->get('console'));
$database = new Database($adapter, getCache());
$dbAdapter = $pools
->get('console')
->pop()
->getResource()
;
$database = new Database($dbAdapter, getCache());
$database
->setNamespace('_console')
->setMetadata('host', \gethostname())
@@ -77,13 +72,7 @@ if (!function_exists('getProjectDB')) {
{
global $register;
static $databases = [];
if (isset($databases[$project->getSequence()])) {
return $databases[$project->getSequence()];
}
/** @var Group $pools */
/** @var \Utopia\Pools\Group $pools */
$pools = $register->get('pools');
if ($project->isEmpty() || $project->getId() === 'console') {
@@ -97,7 +86,11 @@ if (!function_exists('getProjectDB')) {
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$adapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($adapter, getCache());
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
@@ -105,20 +98,20 @@ if (!function_exists('getProjectDB')) {
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant((int)$project->getSequence())
->setTenant($project->getInternalId())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
->setNamespace('_' . $project->getInternalId());
}
$database
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
return $databases[$project->getSequence()] = $database;
return $database;
}
}
@@ -128,22 +121,20 @@ if (!function_exists('getCache')) {
{
global $register;
static $cache = null;
if ($cache !== null) {
return $cache;
}
$pools = $register->get('pools'); /** @var Group $pools */
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
$list = Config::getParam('pools-cache', []);
$adapters = [];
foreach ($list as $value) {
$adapters[] = new CachePool($pools->get($value));
$adapters[] = $pools
->get($value)
->pop()
->getResource()
;
}
return $cache = new Cache(new Sharding($adapters));
return new Cache(new Sharding($adapters));
}
}
@@ -151,12 +142,6 @@ if (!function_exists('getCache')) {
if (!function_exists('getRedis')) {
function getRedis(): \Redis
{
static $redis = null;
if ($redis !== null) {
return $redis;
}
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
$port = System::getEnv('_APP_REDIS_PORT', 6379);
$pass = System::getEnv('_APP_REDIS_PASS', '');
@@ -175,39 +160,21 @@ if (!function_exists('getRedis')) {
if (!function_exists('getTimelimit')) {
function getTimelimit(): TimeLimitRedis
{
static $timelimit = null;
if ($timelimit !== null) {
return $timelimit;
}
return $timelimit = new TimeLimitRedis("", 0, 1, getRedis());
return new TimeLimitRedis("", 0, 1, getRedis());
}
}
if (!function_exists('getRealtime')) {
function getRealtime(): Realtime
{
static $realtime = null;
if ($realtime !== null) {
return $realtime;
}
return $realtime = new Realtime();
return new Realtime();
}
}
if (!function_exists('getTelemetry')) {
function getTelemetry(int $workerId): Utopia\Telemetry\Adapter
{
static $telemetry = null;
if ($telemetry !== null) {
return $telemetry;
}
return $telemetry = new NoTelemetry();
return new NoTelemetry();
}
}
@@ -306,6 +273,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
sleep(DATABASE_RECONNECT_SLEEP);
}
} while (true);
$register->get('pools')->reclaim();
});
/**
@@ -331,7 +299,9 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
} catch (Throwable $th) {
$logError($th, "updateWorkerDocument");
call_user_func($logError, $th, "updateWorkerDocument");
} finally {
$register->get('pools')->reclaim();
}
});
}
@@ -400,6 +370,8 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
'data' => $event['data']
]));
}
$register->get('pools')->reclaim();
}
}
/**
@@ -435,8 +407,8 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
}
$start = time();
$pubsub = new PubSubPool($register->get('pools')->get('pubsub'));
/** @var \Appwrite\PubSub\Adapter $pubsub */
$pubsub = $register->get('pools')->get('pubsub')->pop()->getResource();
if ($pubsub->ping(true)) {
$attempts = 0;
Console::success('Pub/sub connection established (worker: ' . $workerId . ')');
@@ -464,6 +436,8 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$realtime->unsubscribe($connection);
$realtime->subscribe($projectId, $connection, $roles, $channels);
$register->get('pools')->reclaim();
}
}
@@ -489,12 +463,14 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
}
});
} catch (Throwable $th) {
$logError($th, "pubSubConnection");
call_user_func($logError, $th, "pubSubConnection");
Console::error('Pub/sub error: ' . $th->getMessage());
$attempts++;
sleep(DATABASE_RECONNECT_SLEEP);
continue;
} finally {
$register->get('pools')->reclaim();
}
}
@@ -596,7 +572,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$stats->incr($project->getId(), 'connections');
$stats->incr($project->getId(), 'connectionsTotal');
} catch (Throwable $th) {
$logError($th, "initServer");
call_user_func($logError, $th, "initServer");
// Handle SQL error code is 'HY000'
$code = $th->getCode();
@@ -620,6 +596,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
Console::error('[Error] Code: ' . $response['data']['code']);
Console::error('[Error] Message: ' . $response['data']['message']);
}
} finally {
$register->get('pools')->reclaim();
}
});
@@ -718,6 +696,8 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
if ($th->getCode() === 1008) {
$server->close($connection, $th->getCode());
}
} finally {
$register->get('pools')->reclaim();
}
});
+31
View File
@@ -151,6 +151,37 @@
<div class="center"><a href="/"><button>Go to homepage</button></a></div>
</div>
</div>
<div class="brand">
<p>Powered by</p>
<svg class="logo-dark" width="110" height="20" viewBox="0 0 110 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M31.8649 16.2461C33.6492 16.2461 34.5511 15.3184 34.9433 14.6867H35.1197C35.1981 15.3578 35.6687 15.9895 36.5903 15.9895H38.3353V14.0156H37.8843C37.5706 14.0156 37.4138 13.838 37.4138 13.5617V5.64661H35.1001V6.90986H34.9236C34.4727 6.27823 33.5315 5.39001 31.8061 5.39001C29.0611 5.39001 27.022 7.67965 27.022 10.818C27.022 13.9564 29.1003 16.2461 31.8649 16.2461ZM32.2767 13.9959C30.6493 13.9959 29.3748 12.7919 29.3748 10.8378C29.3748 8.92316 30.6101 7.62044 32.2571 7.62044C33.8256 7.62044 35.1393 8.78499 35.1393 10.8378C35.1393 12.5945 34.0217 13.9959 32.2767 13.9959Z" fill="#EDEDF0" />
<path d="M39.7013 20H42.0149V14.6867H42.1914C42.6227 15.3184 43.5443 16.2461 45.3677 16.2461C48.1127 16.2461 50.1127 13.9169 50.1127 10.818C50.1127 7.69939 47.9755 5.39001 45.2109 5.39001C43.4462 5.39001 42.5835 6.35719 42.1718 6.89012H41.9953V5.64661H39.7013V20ZM44.8776 14.0551C43.2894 14.0551 41.9757 12.8708 41.9757 10.818C41.9757 9.06133 43.0933 7.58096 44.8383 7.58096C46.4657 7.58096 47.7402 8.86395 47.7402 10.818C47.7402 12.7326 46.5049 14.0551 44.8776 14.0551Z" fill="#EDEDF0" />
<path d="M51.3065 20H53.6202V14.6867H53.7966C54.228 15.3184 55.1495 16.2461 56.973 16.2461C59.718 16.2461 61.5273 13.9169 61.5273 10.818C61.5273 7.69939 59.5807 5.39001 56.8161 5.39001C55.0515 5.39001 54.1888 6.35719 53.777 6.89012H53.6005V5.64661H51.3065V20ZM56.4828 14.0551C54.8946 14.0551 53.5809 12.8708 53.5809 10.818C53.5809 9.06133 54.6985 7.58096 56.4436 7.58096C58.071 7.58096 59.3454 8.86395 59.3454 10.818C59.3454 12.7326 58.1102 14.0551 56.4828 14.0551Z" fill="#EDEDF0" />
<path d="M64.5857 16.2296H67.8601L69.7227 8.11721H69.8404L71.7031 16.2296H74.9579L77.5642 5.88678H75.2323L73.3697 14.0189H73.1932L71.3305 5.88678H68.2522L66.3699 14.0189H66.1935L64.3504 5.88678H61.8799L64.5857 16.2296Z" fill="#EDEDF0" />
<path d="M78.7363 16.2296H81.0499V11.1174C81.0499 9.16334 81.9519 7.9593 83.6381 7.9593H84.6576V5.63019H83.893C82.5793 5.63019 81.5793 6.53815 81.1872 7.40663H81.0303V5.88678H78.7363V16.2296Z" fill="#EDEDF0" />
<path d="M96.1391 16.2296H97.943V14.1571H96.1587C95.4529 14.1571 95.1588 13.8413 95.1588 13.111V7.93956H98.0606V5.88678H95.1588V2.98526H92.9628V5.88678H91.0413V7.93956H92.8255V13.1307C92.8255 15.3217 94.1392 16.2296 96.1391 16.2296Z" fill="#EDEDF0" />
<path d="M104.15 16.2461C106.287 16.2461 108.17 15.1802 108.836 13.0287L106.719 12.5155C106.346 13.6603 105.268 14.2525 104.13 14.2525C102.444 14.2525 101.327 13.1472 101.307 11.4102H109.091V10.7588C109.091 7.67965 107.189 5.39001 104.052 5.39001C101.287 5.39001 98.915 7.58096 98.915 10.8378C98.915 13.9959 101.013 16.2461 104.15 16.2461ZM101.327 9.71269C101.464 8.46918 102.581 7.42305 104.052 7.42305C105.464 7.42305 106.621 8.31128 106.738 9.71269H101.327Z" fill="#EDEDF0" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M90.0125 16.2296H87.6989V7.93956H85.895V5.88678H90.0125V16.2296Z" fill="#EDEDF0" />
<path d="M88.6834 4.45145C89.5265 4.45145 90.154 3.81983 90.154 2.99082C90.154 2.18155 89.5265 1.54993 88.6834 1.54993C87.8403 1.54993 87.2129 2.18155 87.2129 2.99082C87.2129 3.81983 87.8403 4.45145 88.6834 4.45145Z" fill="#EDEDF0" />
<path d="M20.2007 13.6935V18.258H8.88588C5.5894 18.258 2.71111 16.4222 1.17116 13.6935C0.947288 13.2968 0.751353 12.8806 0.586995 12.4486C0.26435 11.6021 0.0615332 10.6938 0 9.74603V8.51195C0.0133592 8.30074 0.03441 8.09119 0.0619381 7.88413C0.118209 7.45921 0.203222 7.04343 0.314953 6.63926C1.37195 2.80758 4.8089 0 8.88588 0C12.9629 0 16.3994 2.80758 17.4564 6.63926H12.6184C11.8241 5.39025 10.4493 4.5645 8.88588 4.5645C7.32245 4.5645 5.94767 5.39025 5.15341 6.63926C4.91132 7.01895 4.72349 7.43764 4.60042 7.88413C4.49112 8.27999 4.43282 8.69744 4.43282 9.12899C4.43282 10.4373 4.96962 11.6166 5.83027 12.4486C6.62778 13.2209 7.70299 13.6935 8.88588 13.6935H20.2007Z" fill="#FD366E" />
<path d="M20.2006 7.88412V12.4486H11.9414C12.8021 11.6166 13.3389 10.4373 13.3389 9.12899C13.3389 8.69744 13.2806 8.27999 13.1713 7.88412H20.2006Z" fill="#FD366E" />
</svg>
<svg class="logo-light" width="110" height="20" viewBox="0 0 110 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M31.8648 16.2461C33.649 16.2461 34.5509 15.3184 34.9431 14.6867H35.1195C35.198 15.3578 35.6685 15.9895 36.5901 15.9895H38.3351V14.0156H37.8841C37.5704 14.0156 37.4136 13.838 37.4136 13.5617V5.64661H35.0999V6.90986H34.9235C34.4725 6.27823 33.5314 5.39001 31.8059 5.39001C29.0609 5.39001 27.0218 7.67965 27.0218 10.818C27.0218 13.9564 29.1001 16.2461 31.8648 16.2461ZM32.2765 13.9959C30.6491 13.9959 29.3746 12.7919 29.3746 10.8378C29.3746 8.92316 30.6099 7.62044 32.2569 7.62044C33.8255 7.62044 35.1391 8.78499 35.1391 10.8378C35.1391 12.5945 34.0215 13.9959 32.2765 13.9959Z" fill="#2D2D31" />
<path d="M39.7011 20H42.0147V14.6867H42.1912C42.6226 15.3184 43.5441 16.2461 45.3676 16.2461C48.1126 16.2461 50.1125 13.9169 50.1125 10.818C50.1125 7.69939 47.9753 5.39001 45.2107 5.39001C43.4461 5.39001 42.5833 6.35719 42.1716 6.89012H41.9951V5.64661H39.7011V20ZM44.8774 14.0551C43.2892 14.0551 41.9755 12.8708 41.9755 10.818C41.9755 9.06133 43.0931 7.58096 44.8382 7.58096C46.4656 7.58096 47.74 8.86395 47.74 10.818C47.74 12.7326 46.5048 14.0551 44.8774 14.0551Z" fill="#2D2D31" />
<path d="M51.3063 20H53.62V14.6867H53.7964C54.2278 15.3184 55.1493 16.2461 56.9728 16.2461C59.7178 16.2461 61.5271 13.9169 61.5271 10.818C61.5271 7.69939 59.5805 5.39001 56.8159 5.39001C55.0513 5.39001 54.1886 6.35719 53.7768 6.89012H53.6004V5.64661H51.3063V20ZM56.4826 14.0551C54.8944 14.0551 53.5808 12.8708 53.5808 10.818C53.5808 9.06133 54.6984 7.58096 56.4434 7.58096C58.0708 7.58096 59.3453 8.86395 59.3453 10.818C59.3453 12.7326 58.11 14.0551 56.4826 14.0551Z" fill="#2D2D31" />
<path d="M64.5855 16.2296H67.8599L69.7226 8.11721H69.8402L71.7029 16.2296H74.9577L77.564 5.88678H75.2322L73.3695 14.0189H73.193L71.3303 5.88678H68.252L66.3697 14.0189H66.1933L64.3502 5.88678H61.8797L64.5855 16.2296Z" fill="#2D2D31" />
<path d="M78.7361 16.2296H81.0498V11.1174C81.0498 9.16334 81.9517 7.9593 83.6379 7.9593H84.6575V5.63019H83.8928C82.5791 5.63019 81.5791 6.53815 81.187 7.40663H81.0301V5.88678H78.7361V16.2296Z" fill="#2D2D31" />
<path d="M96.1389 16.2296H97.9428V14.1571H96.1585C95.4527 14.1571 95.1586 13.8413 95.1586 13.111V7.93956H98.0604V5.88678H95.1586V2.98526H92.9626V5.88678H91.0411V7.93956H92.8253V13.1307C92.8253 15.3217 94.139 16.2296 96.1389 16.2296Z" fill="#2D2D31" />
<path d="M104.15 16.2461C106.287 16.2461 108.169 15.1802 108.836 13.0287L106.718 12.5155C106.346 13.6603 105.268 14.2525 104.13 14.2525C102.444 14.2525 101.326 13.1472 101.307 11.4102H109.091V10.7588C109.091 7.67965 107.189 5.39001 104.052 5.39001C101.287 5.39001 98.9148 7.58096 98.9148 10.8378C98.9148 13.9959 101.013 16.2461 104.15 16.2461ZM101.326 9.71269C101.464 8.46918 102.581 7.42305 104.052 7.42305C105.464 7.42305 106.62 8.31128 106.738 9.71269H101.326Z" fill="#2D2D31" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M90.0123 16.2296H87.6987V7.93956H85.8948V5.88678H90.0123V16.2296Z" fill="#2D2D31" />
<path d="M88.6835 4.45145C89.5266 4.45145 90.154 3.81983 90.154 2.99082C90.154 2.18155 89.5266 1.54993 88.6835 1.54993C87.8404 1.54993 87.213 2.18155 87.213 2.99082C87.213 3.81983 87.8404 4.45145 88.6835 4.45145Z" fill="#2D2D31" />
<path d="M20.2007 13.6935V18.258H8.88588C5.5894 18.258 2.71111 16.4222 1.17116 13.6935C0.947288 13.2968 0.751353 12.8806 0.586995 12.4486C0.26435 11.6021 0.0615332 10.6938 0 9.74603V8.51195C0.0133592 8.30074 0.03441 8.09119 0.0619381 7.88413C0.118209 7.45921 0.203222 7.04343 0.314953 6.63926C1.37195 2.80758 4.8089 0 8.88588 0C12.9629 0 16.3994 2.80758 17.4564 6.63926H12.6184C11.8241 5.39025 10.4493 4.5645 8.88588 4.5645C7.32245 4.5645 5.94767 5.39025 5.15341 6.63926C4.91132 7.01895 4.72349 7.43764 4.60042 7.88413C4.49112 8.27999 4.43282 8.69744 4.43282 9.12899C4.43282 10.4373 4.96962 11.6166 5.83027 12.4486C6.62778 13.2209 7.70299 13.6935 8.88588 13.6935H20.2007Z" fill="#FD366E" />
<path d="M20.2007 7.88412V12.4486H11.9415C12.8022 11.6166 13.339 10.4373 13.339 9.12899C13.339 8.69744 13.2807 8.27999 13.1714 7.88412H20.2007Z" fill="#FD366E" />
</svg>
</div>
</body>
</html>
+56 -28
View File
@@ -479,39 +479,67 @@ switch ($type) {
</div>
</div>
<?php if ($development) : ?>
<div x-show="page === 'trace'" class="error-trace">
<button class="back-button" x-on:click="page = 'error'">
Back
</button>
<div class="trace-grid-header">Error trace</div>
<?php foreach ($trace as $index => $traceItem): ?>
<div class="trace-grid">
<?php if (isset($traceItem['file'])): ?>
<div class="trace-label">file</div>
<div class="trace-value"><?php echo $this->print($traceItem['file']); ?></div>
<?php endif; ?>
<div x-show="page === 'trace'" class="error-trace">
<button class="back-button" x-on:click="page = 'error'">
Back
</button>
<div class="trace-grid-header">Error trace</div>
<?php foreach ($trace as $index => $traceItem): ?>
<div class="trace-grid">
<?php if (isset($traceItem['file'])): ?>
<div class="trace-label">file</div>
<div class="trace-value"><?php echo $this->print($traceItem['file']); ?></div>
<?php endif; ?>
<?php if (isset($traceItem['line'])): ?>
<div class="trace-label">line</div>
<div class="trace-value"><?php echo $this->print($traceItem['line']); ?></div>
<?php endif; ?>
<?php if (isset($traceItem['line'])): ?>
<div class="trace-label">line</div>
<div class="trace-value"><?php echo $this->print($traceItem['line']); ?></div>
<?php endif; ?>
<?php if (isset($traceItem['function'])): ?>
<div class="trace-label">function</div>
<div class="trace-value"><?php echo $this->print($traceItem['function']); ?></div>
<?php endif; ?>
<?php if (isset($traceItem['function'])): ?>
<div class="trace-label">function</div>
<div class="trace-value"><?php echo $this->print($traceItem['function']); ?></div>
<?php endif; ?>
<?php if (isset($traceItem['args'])): ?>
<div class="trace-label">args</div>
<div class="trace-args"><pre><?php echo $this->print(\var_export($traceItem['args'], true)); ?></pre></div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if (isset($traceItem['args'])): ?>
<div class="trace-label">args</div>
<div class="trace-args"><pre><?php echo $this->print(\var_export($traceItem['args'], true)); ?></pre></div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<div x-show="page === 'error'" class="brand">
<p>Powered by</p>
<svg class="logo-dark" width="110" height="20" viewBox="0 0 110 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M31.8649 16.2461C33.6492 16.2461 34.5511 15.3184 34.9433 14.6867H35.1197C35.1981 15.3578 35.6687 15.9895 36.5903 15.9895H38.3353V14.0156H37.8843C37.5706 14.0156 37.4138 13.838 37.4138 13.5617V5.64661H35.1001V6.90986H34.9236C34.4727 6.27823 33.5315 5.39001 31.8061 5.39001C29.0611 5.39001 27.022 7.67965 27.022 10.818C27.022 13.9564 29.1003 16.2461 31.8649 16.2461ZM32.2767 13.9959C30.6493 13.9959 29.3748 12.7919 29.3748 10.8378C29.3748 8.92316 30.6101 7.62044 32.2571 7.62044C33.8256 7.62044 35.1393 8.86395 35.1393 10.8378C35.1393 12.5945 34.0217 13.9959 32.2767 13.9959Z" fill="#EDEDF0" />
<path d="M39.7013 20H42.0149V14.6867H42.1914C42.6227 15.3184 43.5443 16.2461 45.3677 16.2461C48.1127 16.2461 50.1127 13.9169 50.1127 10.818C50.1127 7.69939 47.9755 5.39001 45.2109 5.39001C43.4462 5.39001 42.5835 6.35719 42.1718 6.89012H41.9953V5.63019H39.7013V20ZM44.8776 14.0551C43.2894 14.0551 41.9757 12.8708 41.9757 10.818C41.9757 9.06133 43.0933 7.58096 44.8383 7.58096C46.4657 7.58096 47.7402 8.86395 47.7402 10.818C47.7402 12.7326 46.5049 14.0551 44.8776 14.0551Z" fill="#EDEDF0" />
<path d="M51.3065 20H53.6202V14.6867H53.7966C54.228 15.3184 55.1495 16.2461 56.973 16.2461C59.718 16.2461 61.5273 13.9169 61.5273 10.818C61.5273 7.69939 59.5807 5.39001 56.8161 5.39001C55.0515 5.39001 54.1888 6.35719 53.777 6.89012H53.6005V5.64661H51.3065V20ZM56.4828 14.0551C54.8946 14.0551 53.5809 12.8708 53.5809 10.818C53.5809 9.06133 54.6985 7.58096 56.4436 7.58096C58.071 7.58096 59.3454 8.86395 59.3454 10.818C59.3454 12.7326 58.1102 14.0551 56.4828 14.0551Z" fill="#EDEDF0" />
<path d="M64.5857 16.2296H67.8601L69.7227 8.11721H69.8404L71.7031 16.2296H74.9579L77.5642 5.88678H75.2323L73.3697 14.0189H73.1932L71.3305 5.88678H68.2522L66.3699 14.0189H66.1935L64.3504 5.88678H61.8799L64.5857 16.2296Z" fill="#EDEDF0" />
<path d="M78.7363 16.2296H81.0499V11.1174C81.0499 9.16334 81.9519 7.9593 83.6381 7.9593H84.6576V5.63019H83.893C82.5793 5.63019 81.5793 6.53815 81.1872 7.40663H81.0303V5.88678H78.7363V16.2296Z" fill="#EDEDF0" />
<path d="M96.1391 16.2296H97.943V14.1571H96.1587C95.4529 14.1571 95.1588 13.8413 95.1588 13.111V7.93956H98.0606V5.88678H95.1588V2.98526H92.9628V5.88678H91.0413V7.93956H92.8255V13.1307C92.8255 15.3217 94.1392 16.2296 96.1391 16.2296Z" fill="#EDEDF0" />
<path d="M104.15 16.2461C106.287 16.2461 108.17 15.1802 108.836 13.0287L106.719 12.5155C106.346 13.6603 105.268 14.2525 104.13 14.2525C102.444 14.2525 101.327 13.1472 101.307 11.4102H109.091V10.7588C109.091 7.67965 107.189 5.39001 104.052 5.39001C101.287 5.39001 98.915 7.58096 98.915 10.8378C98.915 13.9959 101.013 16.2461 104.15 16.2461ZM101.327 9.71269C101.464 8.46918 102.581 7.42305 104.052 7.42305C105.464 7.42305 106.621 8.31128 106.738 9.71269H101.327Z" fill="#EDEDF0" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M90.0125 16.2296H87.6989V7.93956H85.895V5.88678H90.0125V16.2296Z" fill="#EDEDF0" />
<path d="M88.6834 4.45145C89.5265 4.45145 90.154 3.81983 90.154 2.99082C90.154 2.18155 89.5265 1.54993 88.6834 1.54993C87.8403 1.54993 87.2129 2.18155 87.2129 2.99082C87.2129 3.81983 87.8403 4.45145 88.6834 4.45145Z" fill="#EDEDF0" />
<path d="M20.2007 13.6935V18.258H8.88588C5.5894 18.258 2.71111 16.4222 1.17116 13.6935C0.947288 13.2968 0.751353 12.8806 0.586995 12.4486C0.26435 11.6021 0.0615332 10.6938 0 9.74603V8.51195C0.0133592 8.30074 0.03441 8.09119 0.0619381 7.88413C0.118209 7.45921 0.203222 7.04343 0.314953 6.63926C1.37195 2.80758 4.8089 0 8.88588 0C12.9629 0 16.3994 2.80758 17.4564 6.63926H12.6184C11.8241 5.39025 10.4493 4.5645 8.88588 4.5645C7.32245 4.5645 5.94767 5.39025 5.15341 6.63926C4.91132 7.01895 4.72349 7.43764 4.60042 7.88413C4.49112 8.27999 4.43282 8.69744 4.43282 9.12899C4.43282 10.4373 4.96962 11.6166 5.83027 12.4486C6.62778 13.2209 7.70299 13.6935 8.88588 13.6935H20.2007Z" fill="#FD366E" />
<path d="M20.2006 7.88412V12.4486H11.9414C12.8021 11.6166 13.3389 10.4373 13.3389 9.12899C13.3389 8.69744 13.2806 8.27999 13.1713 7.88412H20.2006Z" fill="#FD366E" />
</svg>
<svg class="logo-light" width="110" height="20" viewBox="0 0 110 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M31.8648 16.2461C33.649 16.2461 34.5509 15.3184 34.9431 14.6867H35.1195C35.198 15.3578 35.6685 15.9895 36.5901 15.9895H38.3351V14.0156H37.8841C37.5704 14.0156 37.4136 13.838 37.4136 13.5617V5.64661H35.0999V6.90986H34.9235C34.4725 6.27823 33.5314 5.39001 31.8059 5.39001C29.0609 5.39001 27.0218 7.67965 27.0218 10.818C27.0218 13.9564 29.1001 16.2461 31.8648 16.2461ZM32.2765 13.9959C30.6491 13.9959 29.3746 12.7919 29.3746 10.8378C29.3746 8.92316 30.6099 7.62044 32.2569 7.62044C33.8255 7.62044 35.1391 8.78499 35.1391 10.8378C35.1391 12.5945 34.0215 13.9959 32.2765 13.9959Z" fill="#2D2D31" />
<path d="M39.7011 20H42.0147V14.6867H42.1912C42.6226 15.3184 43.5441 16.2461 45.3676 16.2461C48.1126 16.2461 50.1125 13.9169 50.1125 10.818C50.1125 7.69939 47.9753 5.39001 45.2107 5.39001C43.4461 5.39001 42.5833 6.35719 42.1716 6.89012H41.9951V5.64661H39.7011V20ZM44.8774 14.0551C43.2892 14.0551 41.9755 12.8708 41.9755 10.818C41.9755 9.06133 43.0931 7.58096 44.8382 7.58096C46.4656 7.58096 47.74 8.86395 47.74 10.818C47.74 12.7326 46.5048 14.0551 44.8774 14.0551Z" fill="#2D2D31" />
<path d="M51.3063 20H53.62V14.6867H53.7964C54.2278 15.3184 55.1493 16.2461 56.9728 16.2461C59.7178 16.2461 61.5271 13.9169 61.5271 10.818C61.5271 7.69939 59.5805 5.39001 56.8159 5.39001C55.0513 5.39001 54.1886 6.35719 53.7768 6.89012H53.6004V5.64661H51.3063V20ZM56.4826 14.0551C54.8944 14.0551 53.5808 12.8708 53.5808 10.818C53.5808 9.06133 54.6984 7.58096 56.4434 7.58096C58.0708 7.58096 59.3453 8.86395 59.3453 10.818C59.3453 12.7326 58.11 14.0551 56.4826 14.0551Z" fill="#2D2D31" />
<path d="M64.5855 16.2296H67.8599L69.7226 8.11721H69.8402L71.7029 16.2296H74.9577L77.564 5.88678H75.2322L73.3695 14.0189H73.193L71.3303 5.88678H68.252L66.3697 14.0189H66.1933L64.3502 5.88678H61.8797L64.5855 16.2296Z" fill="#2D2D31" />
<path d="M78.7361 16.2296H81.0498V11.1174C81.0498 9.16334 81.9517 7.9593 83.6379 7.9593H84.6575V5.63019H83.8928C82.5791 5.63019 81.5791 6.53815 81.187 7.40663H81.0301V5.88678H78.7361V16.2296Z" fill="#2D2D31" />
<path d="M96.1389 16.2296H97.9428V14.1571H96.1585C95.4527 14.1571 95.1586 13.8413 95.1586 13.111V7.93956H98.0604V5.88678H95.1586V2.98526H92.9626V5.88678H91.0411V7.93956H92.8253V13.1307C92.8253 15.3217 94.139 16.2296 96.1389 16.2296Z" fill="#2D2D31" />
<path d="M104.15 16.2461C106.287 16.2461 108.169 15.1802 108.836 13.0287L106.718 12.5155C106.346 13.6603 105.268 14.2525 104.13 14.2525C102.444 14.2525 101.326 13.1472 101.307 11.4102H109.091V10.7588C109.091 7.67965 107.189 5.39001 104.052 5.39001C101.287 5.39001 98.9148 7.58096 98.9148 10.8378C98.9148 13.9959 101.013 16.2461 104.15 16.2461ZM101.326 9.71269C101.464 8.46918 102.581 7.42305 104.052 7.42305C105.464 7.42305 106.62 8.31128 106.738 9.71269H101.326Z" fill="#2D2D31" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M90.0123 16.2296H87.6987V7.93956H85.8948V5.88678H90.0123V16.2296Z" fill="#2D2D31" />
<path d="M88.6835 4.45145C89.5266 4.45145 90.154 3.81983 90.154 2.99082C90.154 2.18155 89.5266 1.54993 88.6835 1.54993C87.8404 1.54993 87.213 2.18155 87.213 2.99082C87.213 3.81983 87.8404 4.45145 88.6835 4.45145Z" fill="#2D2D31" />
<path d="M20.2007 13.6935V18.258H8.88588C5.5894 18.258 2.71111 16.4222 1.17116 13.6935C0.947288 13.2968 0.751353 12.8806 0.586995 12.4486C0.26435 11.6021 0.0615332 10.6938 0 9.74603V8.51195C0.0133592 8.30074 0.03441 8.09119 0.0619381 7.88413C0.118209 7.45921 0.203222 7.04343 0.314953 6.63926C1.37195 2.80758 4.8089 0 8.88588 0C12.9629 0 16.3994 2.80758 17.4564 6.63926H12.6184C11.8241 5.39025 10.4493 4.5645 8.88588 4.5645C7.32245 4.5645 5.94767 5.39025 5.15341 6.63926C4.91132 7.01895 4.72349 7.43764 4.60042 7.88413C4.49112 8.27999 4.43282 8.69744 4.43282 9.12899C4.43282 10.4373 4.96962 11.6166 5.83027 12.4486C6.62778 13.2209 7.70299 13.6935 8.88588 13.6935H20.2007Z" fill="#FD366E" />
<path d="M20.2007 7.88412V12.4486H11.9415C12.8022 11.6166 13.339 10.4373 13.339 9.12899C13.339 8.69744 13.2807 8.27999 13.1714 7.88412H20.2007Z" fill="#FD366E" />
</svg>
</div>
</body>
</html>
+35 -10
View File
@@ -61,7 +61,6 @@ $image = $this->getParam('image', '');
- traefik.http.routers.appwrite_api_https.tls=true
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-imports:/storage/imports:rw
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
@@ -152,7 +151,6 @@ $image = $this->getParam('image', '');
- _APP_LOGGING_CONFIG
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_DELAY
- _APP_MAINTENANCE_START_TIME
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
@@ -177,7 +175,7 @@ $image = $this->getParam('image', '');
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: <?php echo $organization; ?>/console:6.0.13
image: <?php echo $organization; ?>/console:5.2.53
restart: unless-stopped
networks:
- appwrite
@@ -617,8 +615,6 @@ $image = $this->getParam('image', '');
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-imports:/storage/imports:rw
depends_on:
- mariadb
environment:
@@ -685,9 +681,11 @@ $image = $this->getParam('image', '');
container_name: appwrite-task-stats-resources
entrypoint: stats-resources
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
@@ -765,6 +763,34 @@ $image = $this->getParam('image', '');
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-worker-stats-usage-dump:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: worker-stats-usage-dump
<<: *x-logging
container_name: appwrite-worker-stats-usage-dump
restart: unless-stopped
networks:
- appwrite
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_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-task-scheduler-functions:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: schedule-functions
@@ -841,7 +867,7 @@ $image = $this->getParam('image', '');
- _APP_DB_PASS
appwrite-assistant:
image: appwrite/assistant:0.8.3
image: appwrite/assistant:0.4.0
container_name: appwrite-assistant
<<: *x-logging
restart: unless-stopped
@@ -851,7 +877,7 @@ $image = $this->getParam('image', '');
- _APP_ASSISTANT_OPENAI_API_KEY
appwrite-browser:
image: appwrite/browser:0.2.4
image: appwrite/browser:0.2.2
container_name: appwrite-browser
<<: *x-logging
restart: unless-stopped
@@ -864,7 +890,7 @@ $image = $this->getParam('image', '');
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
image: openruntimes/executor:0.7.20
image: openruntimes/executor:0.7.13
networks:
- appwrite
- runtimes
@@ -964,7 +990,6 @@ volumes:
appwrite-redis:
appwrite-cache:
appwrite-uploads:
appwrite-imports:
appwrite-certificates:
appwrite-functions:
appwrite-sites:
+86 -65
View File
@@ -15,19 +15,16 @@ use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\StatsUsageDump;
use Appwrite\Event\Webhook;
use Appwrite\Platform\Appwrite;
use Executor\Executor;
use Swoole\Process;
use Swoole\Runtime;
use Swoole\Timer;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
@@ -37,28 +34,28 @@ use Utopia\Logger\Log;
use Utopia\Logger\Logger;
use Utopia\Platform\Service;
use Utopia\Pools\Group;
use Utopia\Queue\Broker\Pool as BrokerPool;
use Utopia\Queue\Message;
use Utopia\Queue\Publisher;
use Utopia\Queue\Server;
use Utopia\Registry\Registry;
use Utopia\Storage\Device\Telemetry as TelemetryDevice;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
Authorization::disable();
Runtime::enableCoroutine();
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
Server::setResource('register', fn () => $register);
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register) {
$pools = $register->get('pools');
$adapter = new DatabasePool($pools->get('console'));
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform->setNamespace('_console');
$database = $pools
->get('console')
->pop()
->getResource();
return $dbForPlatform;
$adapter = new Database($database, $cache);
$adapter->setNamespace('_console');
return $adapter;
}, ['cache', 'register']);
Server::setResource('project', function (Message $message, Database $dbForPlatform) {
@@ -86,21 +83,32 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$adapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($adapter, $cache);
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant((int)$project->getSequence())
->setTenant($project->getInternalId())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
->setNamespace('_' . $project->getInternalId());
}
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
@@ -131,20 +139,24 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant((int)$project->getSequence())
->setTenant($project->getInternalId())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
->setNamespace('_' . $project->getInternalId());
}
return $database;
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$dbAdapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$databases[$dsn->getHost()] = $database;
@@ -153,13 +165,13 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant((int)$project->getSequence())
->setTenant($project->getInternalId())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
->setNamespace('_' . $project->getInternalId());
}
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
@@ -172,12 +184,19 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence());
$database->setTenant($project->getInternalId());
return $database;
}
$adapter = new DatabasePool($pools->get('logs'));
$database = new Database($adapter, $cache);
$dbAdapter = $pools
->get('logs')
->pop()
->getResource();
$database = new Database(
$dbAdapter,
$cache
);
$database
->setSharedTables(true)
@@ -187,7 +206,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence());
$database->setTenant($project->getInternalId());
}
return $database;
@@ -215,7 +234,11 @@ Server::setResource('cache', function (Registry $register) {
$adapters = [];
foreach ($list as $value) {
$adapters[] = new CachePool($pools->get($value));
$adapters[] = $pools
->get($value)
->pop()
->getResource()
;
}
return new Cache(new Sharding($adapters));
@@ -244,19 +267,22 @@ Server::setResource('timelimit', function (\Redis $redis) {
Server::setResource('log', fn () => new Log());
Server::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
return $pools->get('publisher')->pop()->getResource();
}, ['pools']);
Server::setResource('consumer', function (Group $pools) {
return new BrokerPool(consumer: $pools->get('consumer'));
return $pools->get('consumer')->pop()->getResource();
}, ['pools']);
Server::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
Server::setResource('queueForStatsUsageDump', function (Publisher $publisher) {
return new StatsUsageDump($publisher);
}, ['publisher']);
Server::setResource('queueForDatabase', function (Publisher $publisher) {
return new EventDatabase($publisher);
}, ['publisher']);
@@ -313,41 +339,35 @@ Server::setResource('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
Server::setResource('telemetry', fn () => new NoTelemetry());
Server::setResource('deviceForSites', function (Document $project) {
return getDevice(APP_STORAGE_SITES . '/app-' . $project->getId());
}, ['project']);
Server::setResource('deviceForSites', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
Server::setResource('deviceForImports', function (Document $project) {
return getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId());
}, ['project']);
Server::setResource('deviceForImports', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
Server::setResource('deviceForFunctions', function (Document $project) {
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
}, ['project']);
Server::setResource('deviceForFunctions', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
Server::setResource('deviceForFiles', function (Document $project) {
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
}, ['project']);
Server::setResource('deviceForFiles', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
Server::setResource('deviceForBuilds', function (Document $project) {
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
}, ['project']);
Server::setResource('deviceForBuilds', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
Server::setResource('deviceForCache', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
Server::setResource('deviceForCache', function (Document $project) {
return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId());
}, ['project']);
Server::setResource(
'isResourceBlocked',
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
);
Server::setResource('plan', function (array $plan = []) {
return [];
});
Server::setResource('certificates', function () {
$email = System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'));
if (empty($email)) {
@@ -402,7 +422,7 @@ Server::setResource('logError', function (Registry $register, Document $project)
};
}, ['register', 'project']);
Server::setResource('executor', fn () => new Executor());
Server::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST')));
$pools = $register->get('pools');
$platform = new Appwrite();
@@ -441,6 +461,13 @@ try {
$worker = $platform->getWorker();
$worker
->shutdown()
->inject('pools')
->action(function (Group $pools) {
$pools->reclaim();
});
$worker
->error()
->inject('error')
@@ -448,7 +475,8 @@ $worker
->inject('log')
->inject('pools')
->inject('project')
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($worker, $queueName) {
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($queueName) {
$pools->reclaim();
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
if ($logger) {
@@ -484,15 +512,8 @@ $worker
});
$worker->workerStart()
->action(function () use ($worker, $workerName) {
Console::info("Worker $workerName started");
Process::signal(SIGTERM, function () use ($worker, $workerName) {
Console::info("Stopping worker $workerName.");
$worker->stop();
Timer::clearAll();
});
->action(function () use ($workerName) {
Console::info("Worker $workerName started");
});
$worker->start();
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php doctor $@
php /usr/src/code/app/cli.php doctor $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php install $@
php /usr/src/code/app/cli.php install $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php maintenance $@
php /usr/src/code/app/cli.php maintenance $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php migrate $@
php /usr/src/code/app/cli.php migrate $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php queue-count --type=failed $@
php /usr/src/code/app/cli.php queue-count --type=failed $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php queue-count --type=processing $@
php /usr/src/code/app/cli.php queue-count --type=processing $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php queue-count --type=success $@
php /usr/src/code/app/cli.php queue-count --type=success $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php queue-retry $@
php /usr/src/code/app/cli.php queue-retry $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/realtime.php $@
php /usr/src/code/app/realtime.php $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php schedule-executions $@
php /usr/src/code/app/cli.php schedule-executions $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php schedule-functions $@
php /usr/src/code/app/cli.php schedule-functions $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php schedule-messages $@
php /usr/src/code/app/cli.php schedule-messages $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php screenshot $@
php /usr/src/code/app/cli.php screenshot $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php sdks $@
php /usr/src/code/app/cli.php sdks $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php specs $@
php /usr/src/code/app/cli.php specs $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php ssl $@
php /usr/src/code/app/cli.php ssl $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php stats-resources $@
php /usr/src/code/app/cli.php stats-resources $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec /usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml $@
/usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php upgrade $@
php /usr/src/code/app/cli.php upgrade $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php vars $@
php /usr/src/code/app/cli.php vars $@

Some files were not shown because too many files have changed in this diff Show More