Merge branch '1.6.x' into fix-update-messaging-library

This commit is contained in:
Damodar Lohani
2025-05-22 09:57:19 +05:45
committed by GitHub
38 changed files with 1610 additions and 897 deletions
+264
View File
@@ -1,3 +1,267 @@
# 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
+14 -27
View File
@@ -10,12 +10,15 @@ 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;
@@ -23,6 +26,7 @@ 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;
@@ -45,10 +49,7 @@ CLI::setResource('cache', function ($pools) {
$adapters = [];
foreach ($list as $value) {
$adapters[] = $pools
->get($value)
->pop()
->getResource();
$adapters[] = new CachePool($pools->get($value));
}
return new Cache(new Sharding($adapters));
@@ -68,12 +69,8 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
$attempts++;
try {
// Prepare database connection
$dbAdapter = $pools
->get('console')
->pop()
->getResource();
$dbForPlatform = new Database($dbAdapter, $cache);
$adapter = new DatabasePool($pools->get('console'));
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform
->setNamespace('_console')
@@ -91,7 +88,6 @@ 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);
@@ -141,12 +137,8 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
return $database;
}
$dbAdapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$databases[$dsn->getHost()] = $database;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
@@ -172,21 +164,15 @@ 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($project->getInternalId());
return $database;
}
$dbAdapter = $pools
->get('logs')
->pop()
->getResource();
$database = new Database(
$dbAdapter,
$cache
);
$adapter = new DatabasePool($pools->get('logs'));
$database = new Database($adapter, $cache);
$database
->setSharedTables(true)
@@ -210,7 +196,7 @@ CLI::setResource('queueForStatsResources', function (Publisher $publisher) {
return new StatsResources($publisher);
}, ['publisher']);
CLI::setResource('publisher', function (Group $pools) {
return $pools->get('publisher')->pop()->getResource();
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
CLI::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
@@ -298,4 +284,5 @@ $cli
$cli->shutdown()->action(fn () => Timer::clearAll());
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
run($cli->run(...));
-1
View File
@@ -4,7 +4,6 @@ return [
// Accepted inputs files
"jpg" => "image/jpeg",
"jpeg" => "image/jpeg",
"gif" => "image/gif",
"png" => "image/png",
"heic" => "image/heic",
"webp" => "image/webp",
-1
View File
@@ -4,7 +4,6 @@ return [
// Accepted outputs files
"jpg" => "image/jpeg",
"jpeg" => "image/jpeg",
"gif" => "image/gif",
"png" => "image/png",
"webp" => "image/webp",
"heic" => "image/heic",
+3 -3
View File
@@ -187,7 +187,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', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. 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)
->inject('response')
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response));
@@ -215,7 +215,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', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. 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)
->inject('response')
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response));
@@ -243,7 +243,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', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. 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)
->inject('response')
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response));
File diff suppressed because it is too large Load Diff
+65 -72
View File
@@ -3,13 +3,16 @@
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;
@@ -34,8 +37,8 @@ App::get('/v1/health')
namespace: 'health',
group: 'health',
name: 'get',
auth: [AuthType::KEY],
description: '/docs/references/health/get.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -70,11 +73,11 @@ 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,
@@ -86,8 +89,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'),
@@ -97,7 +100,7 @@ App::get('/v1/health/db')
foreach ($configs as $key => $config) {
foreach ($config as $database) {
try {
$adapter = $pools->get($database)->pop()->getResource();
$adapter = new DatabasePool($pools->get($database));
$checkStart = \microtime(true);
@@ -108,16 +111,16 @@ App::get('/v1/health/db')
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
} else {
$failure[] = $database;
$failures[] = $database;
}
} catch (\Throwable $th) {
$failure[] = $database;
} catch (\Throwable) {
$failures[] = $database;
}
}
}
if (!empty($failure)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'DB failure on: ' . implode(", ", $failure));
if (!empty($failures)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'DB failure on: ' . implode(", ", $failures));
}
$response->dynamic(new Document([
@@ -131,11 +134,11 @@ 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,
@@ -147,44 +150,39 @@ 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 $database) {
foreach ($config as $cache) {
try {
/** @var \Utopia\Cache\Adapter $adapter */
$adapter = $pools->get($database)->pop()->getResource();
$adapter = new CachePool($pools->get($cache));
$checkStart = \microtime(true);
if ($adapter->ping()) {
$output[] = new Document([
'name' => $key . " ($database)",
'name' => $key . " ($cache)",
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
} else {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
$failures[] = $cache;
}
} catch (\Throwable $th) {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
} catch (\Throwable) {
$failures[] = $cache;
}
}
}
if (!empty($failures)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Cache failure on: ' . implode(", ", $failures));
}
$response->dynamic(new Document([
'statuses' => $output,
'total' => count($output),
@@ -196,11 +194,11 @@ 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,
@@ -212,44 +210,39 @@ 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 $database) {
foreach ($config as $pubsub) {
try {
/** @var \Appwrite\PubSub\Adapter $adapter */
$adapter = $pools->get($database)->pop()->getResource();
$adapter = new PubSubPool($pools->get($pubsub));
$checkStart = \microtime(true);
if ($adapter->ping()) {
$output[] = new Document([
'name' => $key . " ($database)",
'name' => $key . " ($pubsub)",
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
} else {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
$failures[] = $pubsub;
}
} catch (\Throwable $th) {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
} catch (\Throwable) {
$failures[] = $pubsub;
}
}
}
if (!empty($failures)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Pubsub failure on: ' . implode(", ", $failures));
}
$response->dynamic(new Document([
'statuses' => $output,
'total' => count($output),
@@ -261,11 +254,11 @@ 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,
@@ -325,11 +318,11 @@ 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,
@@ -351,18 +344,18 @@ 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,
@@ -384,18 +377,18 @@ 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,
@@ -441,18 +434,18 @@ 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,
@@ -474,18 +467,18 @@ 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,
@@ -507,18 +500,18 @@ 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,
@@ -541,18 +534,18 @@ 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,
@@ -574,18 +567,18 @@ 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,
@@ -607,18 +600,18 @@ 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,
@@ -640,18 +633,18 @@ 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,
@@ -673,18 +666,18 @@ 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,
@@ -706,18 +699,18 @@ 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,
@@ -746,11 +739,11 @@ 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,
@@ -779,11 +772,11 @@ App::get('/v1/health/storage/local')
->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,
@@ -829,11 +822,11 @@ 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,
@@ -877,11 +870,11 @@ 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,
@@ -923,11 +916,11 @@ 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,
+2 -1
View File
@@ -24,6 +24,7 @@ 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;
@@ -223,7 +224,7 @@ App::post('/v1/projects')
$sharedTables = $sharedTablesV1 || $sharedTablesV2;
if (!$sharedTablesV2) {
$adapter = $pools->get($dsn->getHost())->pop()->getResource();
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$dbForProject = new Database($adapter, $cache);
if ($sharedTables) {
+36 -31
View File
@@ -943,7 +943,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', 100, new Range(0, 100), 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', 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('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)
@@ -996,7 +996,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$algorithm = $file->getAttribute('algorithm', Compression::NONE);
$cipher = $file->getAttribute('openSSLCipher');
$mime = $file->getAttribute('mimeType');
if (!\in_array($mime, $inputs) || $file->getAttribute('sizeActual') > (int) System::getEnv('_APP_STORAGE_PREVIEW_LIMIT', 20000000)) {
if (!\in_array($mime, $inputs) || $file->getAttribute('sizeActual') > (int) System::getEnv('_APP_STORAGE_PREVIEW_LIMIT', APP_STORAGE_READ_BUFFER)) {
if (!\in_array($mime, $inputs)) {
$path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default'];
} else {
@@ -1162,13 +1162,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
}
$response
->setContentType($file->getAttribute('mimeType'))
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
->addHeader('X-Peak', \memory_get_peak_usage())
->addHeader('Content-Disposition', 'attachment; filename="' . $file->getAttribute('name', '') . '"')
;
$size = $file->getAttribute('sizeOriginal', 0);
$rangeHeader = $request->getHeader('range');
@@ -1177,7 +1170,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
$end = $request->getRangeEnd();
$unit = $request->getRangeUnit();
if ($end === null) {
if ($end === null || $end - $start > APP_STORAGE_READ_BUFFER) {
$end = min(($start + MAX_OUTPUT_CHUNK_SIZE - 1), ($size - 1));
}
@@ -1192,6 +1185,13 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
}
$response
->setContentType($file->getAttribute('mimeType'))
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
->addHeader('X-Peak', \memory_get_peak_usage())
->addHeader('Content-Disposition', 'attachment; filename="' . $file->getAttribute('name', '') . '"')
;
$source = '';
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
$source = $deviceForFiles->read($path);
@@ -1225,12 +1225,15 @@ 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) {
@@ -1318,15 +1321,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
$contentType = $file->getAttribute('mimeType');
}
$response
->setContentType($contentType)
->addHeader('Content-Security-Policy', 'script-src none;')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"')
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
->addHeader('X-Peak', \memory_get_peak_usage())
;
$size = $file->getAttribute('sizeOriginal', 0);
$rangeHeader = $request->getHeader('range');
@@ -1335,8 +1329,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
$end = $request->getRangeEnd();
$unit = $request->getRangeUnit();
if ($end === null) {
$end = min(($start + 2000000 - 1), ($size - 1));
if ($end === null || $end - $start > APP_STORAGE_READ_BUFFER) {
$end = min(($start + APP_STORAGE_READ_BUFFER - 1), ($size - 1));
}
if ($unit != 'bytes' || $start >= $end || $end >= $size) {
@@ -1350,6 +1344,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
}
$response
->setContentType($contentType)
->addHeader('Content-Security-Policy', 'script-src none;')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"')
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
->addHeader('X-Peak', \memory_get_peak_usage())
;
$source = '';
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
$source = $deviceForFiles->read($path);
@@ -1383,6 +1386,7 @@ 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;
@@ -1470,14 +1474,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
$contentType = $file->getAttribute('mimeType');
}
$response
->setContentType($contentType)
->addHeader('Content-Security-Policy', 'script-src none;')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"')
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
->addHeader('X-Peak', \memory_get_peak_usage());
$size = $file->getAttribute('sizeOriginal', 0);
$rangeHeader = $request->getHeader('range');
@@ -1486,8 +1482,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
$end = $request->getRangeEnd();
$unit = $request->getRangeUnit();
if ($end === null) {
$end = min(($start + 2000000 - 1), ($size - 1));
if ($end === null || $end - $start > APP_STORAGE_READ_BUFFER) {
$end = min(($start + APP_STORAGE_READ_BUFFER - 1), ($size - 1));
}
if ($unit != 'bytes' || $start >= $end || $end >= $size) {
@@ -1501,6 +1497,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
}
$response
->setContentType($contentType)
->addHeader('Content-Security-Policy', 'script-src none;')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"')
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
->addHeader('X-Peak', \memory_get_peak_usage());
$source = '';
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
$source = $deviceForFiles->read($path);
@@ -1534,6 +1538,7 @@ 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;
+4 -1
View File
@@ -1089,7 +1089,10 @@ 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'])],
queries: [
Query::contains('roles', ['owner']),
Query::equal('teamInternalId', [$team->getInternalId()])
],
max: 2
);
+2 -26
View File
@@ -835,35 +835,11 @@ App::error()
break;
}
break;
case 'Utopia\Database\Exception\Conflict':
$error = new AppwriteException(AppwriteException::DOCUMENT_UPDATE_CONFLICT, previous: $error);
break;
case 'Utopia\Database\Exception\Timeout':
$error = new AppwriteException(AppwriteException::DATABASE_TIMEOUT, previous: $error);
break;
case 'Utopia\Database\Exception\Query':
$error = new AppwriteException(AppwriteException::GENERAL_QUERY_INVALID, $error->getMessage(), previous: $error);
break;
case 'Utopia\Database\Exception\Structure':
$error = new AppwriteException(AppwriteException::DOCUMENT_INVALID_STRUCTURE, $error->getMessage(), previous: $error);
break;
case 'Utopia\Database\Exception\Duplicate':
$error = new AppwriteException(AppwriteException::DOCUMENT_ALREADY_EXISTS);
break;
case 'Utopia\Database\Exception\Restricted':
$error = new AppwriteException(AppwriteException::DOCUMENT_DELETE_RESTRICTED);
break;
case 'Utopia\Database\Exception\Authorization':
$error = new AppwriteException(AppwriteException::USER_UNAUTHORIZED);
break;
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);
case 'Utopia\Database\Exception\Timeout':
$error = new AppwriteException(AppwriteException::DATABASE_TIMEOUT, previous: $error);
break;
}
+2 -2
View File
@@ -544,7 +544,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 = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
$key = $request->cacheIdentifier();
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
@@ -803,7 +803,7 @@ App::shutdown()
$resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user);
}
$key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
$key = $request->cacheIdentifier();
$signature = md5($data['payload']);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
+7 -12
View File
@@ -14,9 +14,11 @@ use Utopia\App;
use Utopia\Audit\Audit;
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;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@@ -167,7 +169,7 @@ function createDatabase(App $app, string $resourceKey, string $dbName, array $co
$sleep = 1;
$attempts = 0;
do {
while (true) {
try {
$attempts++;
$resource = $app->getResource($resourceKey);
@@ -176,13 +178,12 @@ 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...");
@@ -318,11 +319,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
$cache = $app->getResource('cache');
foreach ($sharedTablesV2 as $hostname) {
$adapter = $pools
->get($hostname)
->pop()
->getResource();
$adapter = new DatabasePool($pools->get($hostname));
$dbForProject = (new Database($adapter, $cache))
->setDatabase('appwrite')
->setSharedTables(true)
@@ -332,7 +329,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
try {
Console::success('[Setup] - Creating project database: ' . $hostname . '...');
$dbForProject->create();
} catch (Duplicate) {
} catch (DuplicateException) {
Console::success('[Setup] - Skip: metadata table already exists');
}
@@ -358,7 +355,6 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
}
}
$pools->reclaim();
Console::success('[Setup] - Server database init completed...');
});
@@ -473,6 +469,7 @@ $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);
@@ -490,8 +487,6 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
];
$swooleResponse->end(\json_encode($output));
} finally {
$pools->reclaim();
}
});
+2 -2
View File
@@ -216,13 +216,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, array(
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
));
]);
});
},
'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
+17 -35
View File
@@ -24,10 +24,12 @@ 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\Document;
use Utopia\Database\Helpers\ID;
@@ -37,6 +39,7 @@ 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;
@@ -72,10 +75,10 @@ App::setResource('localeCodes', function () {
// Queues
App::setResource('publisher', function (Group $pools) {
return $pools->get('publisher')->pop()->getResource();
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
App::setResource('consumer', function (Group $pools) {
return $pools->get('consumer')->pop()->getResource();
return new BrokerPool(consumer: $pools->get('consumer'));
}, ['pools']);
App::setResource('queueForMessaging', function (Publisher $publisher) {
return new Messaging($publisher);
@@ -329,12 +332,8 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$dbAdapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$database
->setMetadata('host', \gethostname())
@@ -360,12 +359,8 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
}, ['pools', 'dbForPlatform', 'cache', 'project']);
App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
$dbAdapter = $pools
->get('console')
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$adapter = new DatabasePool($pools->get('console'));
$database = new Database($adapter, $cache);
$database
->setNamespace('_console')
@@ -378,7 +373,7 @@ App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
}, ['pools', 'cache']);
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
$databases = [];
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') {
@@ -420,12 +415,8 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
return $database;
}
$dbAdapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$databases[$dsn->getHost()] = $database;
$configure($database);
@@ -435,21 +426,15 @@ 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($project->getInternalId());
return $database;
}
$dbAdapter = $pools
->get('logs')
->pop()
->getResource();
$database = new Database(
$dbAdapter,
$cache
);
$adapter = new DatabasePool($pools->get('logs'));
$database = new Database($adapter, $cache);
$database
->setSharedTables(true)
@@ -473,10 +458,7 @@ App::setResource('cache', function (Group $pools, Telemetry $telemetry) {
$adapters = [];
foreach ($list as $value) {
$adapters[] = $pools
->get($value)
->pop()
->getResource();
$adapters[] = new CachePool($pools->get($value));
}
$cache = new Cache(new Sharding($adapters));
+65 -45
View File
@@ -5,6 +5,7 @@ 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;
@@ -15,10 +16,12 @@ 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;
@@ -28,13 +31,15 @@ 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 \Utopia\Registry\Registry $register
* @var Registry $register
*/
require_once __DIR__ . '/init.php';
@@ -46,17 +51,17 @@ if (!function_exists('getConsoleDB')) {
{
global $register;
/** @var \Utopia\Pools\Group $pools */
static $database = null;
if ($database !== null) {
return $database;
}
/** @var Group $pools */
$pools = $register->get('pools');
$dbAdapter = $pools
->get('console')
->pop()
->getResource()
;
$database = new Database($dbAdapter, getCache());
$adapter = new DatabasePool($pools->get('console'));
$database = new Database($adapter, getCache());
$database
->setNamespace('_console')
->setMetadata('host', \gethostname())
@@ -72,7 +77,13 @@ if (!function_exists('getProjectDB')) {
{
global $register;
/** @var \Utopia\Pools\Group $pools */
static $databases = [];
if (isset($databases[$project->getInternalId()])) {
return $databases[$project->getInternalId()];
}
/** @var Group $pools */
$pools = $register->get('pools');
if ($project->isEmpty() || $project->getId() === 'console') {
@@ -86,11 +97,7 @@ if (!function_exists('getProjectDB')) {
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$adapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, getCache());
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
@@ -111,7 +118,7 @@ if (!function_exists('getProjectDB')) {
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
return $database;
return $databases[$project->getInternalId()] = $database;
}
}
@@ -121,20 +128,22 @@ if (!function_exists('getCache')) {
{
global $register;
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
static $cache = null;
if ($cache !== null) {
return $cache;
}
$pools = $register->get('pools'); /** @var Group $pools */
$list = Config::getParam('pools-cache', []);
$adapters = [];
foreach ($list as $value) {
$adapters[] = $pools
->get($value)
->pop()
->getResource()
;
$adapters[] = new CachePool($pools->get($value));
}
return new Cache(new Sharding($adapters));
return $cache = new Cache(new Sharding($adapters));
}
}
@@ -142,6 +151,12 @@ 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', '');
@@ -160,21 +175,39 @@ if (!function_exists('getRedis')) {
if (!function_exists('getTimelimit')) {
function getTimelimit(): TimeLimitRedis
{
return new TimeLimitRedis("", 0, 1, getRedis());
static $timelimit = null;
if ($timelimit !== null) {
return $timelimit;
}
return $timelimit = new TimeLimitRedis("", 0, 1, getRedis());
}
}
if (!function_exists('getRealtime')) {
function getRealtime(): Realtime
{
return new Realtime();
static $realtime = null;
if ($realtime !== null) {
return $realtime;
}
return $realtime = new Realtime();
}
}
if (!function_exists('getTelemetry')) {
function getTelemetry(int $workerId): Utopia\Telemetry\Adapter
{
return new NoTelemetry();
static $telemetry = null;
if ($telemetry !== null) {
return $telemetry;
}
return $telemetry = new NoTelemetry();
}
}
@@ -273,7 +306,6 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
sleep(DATABASE_RECONNECT_SLEEP);
}
} while (true);
$register->get('pools')->reclaim();
});
/**
@@ -299,9 +331,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
} catch (Throwable $th) {
call_user_func($logError, $th, "updateWorkerDocument");
} finally {
$register->get('pools')->reclaim();
$logError($th, "updateWorkerDocument");
}
});
}
@@ -370,8 +400,6 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
'data' => $event['data']
]));
}
$register->get('pools')->reclaim();
}
}
/**
@@ -407,8 +435,8 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
}
$start = time();
/** @var \Appwrite\PubSub\Adapter $pubsub */
$pubsub = $register->get('pools')->get('pubsub')->pop()->getResource();
$pubsub = new PubSubPool($register->get('pools')->get('pubsub'));
if ($pubsub->ping(true)) {
$attempts = 0;
Console::success('Pub/sub connection established (worker: ' . $workerId . ')');
@@ -436,8 +464,6 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$realtime->unsubscribe($connection);
$realtime->subscribe($projectId, $connection, $roles, $channels);
$register->get('pools')->reclaim();
}
}
@@ -463,14 +489,12 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
}
});
} catch (Throwable $th) {
call_user_func($logError, $th, "pubSubConnection");
$logError($th, "pubSubConnection");
Console::error('Pub/sub error: ' . $th->getMessage());
$attempts++;
sleep(DATABASE_RECONNECT_SLEEP);
continue;
} finally {
$register->get('pools')->reclaim();
}
}
@@ -572,7 +596,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$stats->incr($project->getId(), 'connections');
$stats->incr($project->getId(), 'connectionsTotal');
} catch (Throwable $th) {
call_user_func($logError, $th, "initServer");
$logError($th, "initServer");
// Handle SQL error code is 'HY000'
$code = $th->getCode();
@@ -596,8 +620,6 @@ $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();
}
});
@@ -696,8 +718,6 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
if ($th->getCode() === 1008) {
$server->close($connection, $th->getCode());
}
} finally {
$register->get('pools')->reclaim();
}
});
+17 -52
View File
@@ -20,10 +20,12 @@ use Appwrite\Platform\Appwrite;
use Executor\Executor;
use Swoole\Runtime;
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;
@@ -33,6 +35,7 @@ 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;
@@ -40,21 +43,17 @@ use Utopia\Registry\Registry;
use Utopia\System\System;
Authorization::disable();
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
Runtime::enableCoroutine();
Server::setResource('register', fn () => $register);
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register) {
$pools = $register->get('pools');
$database = $pools
->get('console')
->pop()
->getResource();
$adapter = new DatabasePool($pools->get('console'));
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform->setNamespace('_console');
$adapter = new Database($database, $cache);
$adapter->setNamespace('_console');
return $adapter;
return $dbForPlatform;
}, ['cache', 'register']);
Server::setResource('project', function (Message $message, Database $dbForPlatform) {
@@ -82,20 +81,9 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$adapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$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)) {
@@ -150,12 +138,8 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
return $database;
}
$dbAdapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$databases[$dsn->getHost()] = $database;
@@ -187,15 +171,8 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
return $database;
}
$dbAdapter = $pools
->get('logs')
->pop()
->getResource();
$database = new Database(
$dbAdapter,
$cache
);
$adapter = new DatabasePool($pools->get('logs'));
$database = new Database($adapter, $cache);
$database
->setSharedTables(true)
@@ -233,11 +210,7 @@ Server::setResource('cache', function (Registry $register) {
$adapters = [];
foreach ($list as $value) {
$adapters[] = $pools
->get($value)
->pop()
->getResource()
;
$adapters[] = new CachePool($pools->get($value));
}
return new Cache(new Sharding($adapters));
@@ -267,11 +240,11 @@ Server::setResource('timelimit', function (\Redis $redis) {
Server::setResource('log', fn () => new Log());
Server::setResource('publisher', function (Group $pools) {
return $pools->get('publisher')->pop()->getResource();
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
Server::setResource('consumer', function (Group $pools) {
return $pools->get('consumer')->pop()->getResource();
return new BrokerPool(consumer: $pools->get('consumer'));
}, ['pools']);
Server::setResource('queueForStatsUsage', function (Publisher $publisher) {
@@ -448,13 +421,6 @@ try {
$worker = $platform->getWorker();
$worker
->shutdown()
->inject('pools')
->action(function (Group $pools) {
$pools->reclaim();
});
$worker
->error()
->inject('error')
@@ -462,8 +428,7 @@ $worker
->inject('log')
->inject('pools')
->inject('project')
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($queueName) {
$pools->reclaim();
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($worker, $queueName) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
if ($logger) {
+1 -1
View File
@@ -65,7 +65,7 @@
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.8.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.9.*",
"utopia-php/queue": "0.10.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "0.8.*",
Generated
+47 -46
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e6b10826abe8ee1a98acd0645366d550",
"content-hash": "63feb1a7cf4cfa2cc7fa0870236e61ea",
"packages": [
{
"name": "adhocore/jwt",
@@ -3499,16 +3499,16 @@
},
{
"name": "utopia-php/database",
"version": "0.69.2",
"version": "0.69.5",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "60591ab073bb80bb9843338754b679bb8169e4ed"
"reference": "4abe53609dfc23b2ea82884d12b149df6a8af2f5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/60591ab073bb80bb9843338754b679bb8169e4ed",
"reference": "60591ab073bb80bb9843338754b679bb8169e4ed",
"url": "https://api.github.com/repos/utopia-php/database/zipball/4abe53609dfc23b2ea82884d12b149df6a8af2f5",
"reference": "4abe53609dfc23b2ea82884d12b149df6a8af2f5",
"shasum": ""
},
"require": {
@@ -3549,9 +3549,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.69.2"
"source": "https://github.com/utopia-php/database/tree/0.69.5"
},
"time": "2025-05-14T07:51:44+00:00"
"time": "2025-05-17T08:01:51+00:00"
},
{
"name": "utopia-php/domains",
@@ -3701,16 +3701,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.33.19",
"version": "0.33.20",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
"reference": "64c7b7bb8a8595ffe875fa8d4b7705684dbf46c0"
"reference": "e1c7ab4e0b5b0a9a70256b1e00912e101e76a131"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/http/zipball/64c7b7bb8a8595ffe875fa8d4b7705684dbf46c0",
"reference": "64c7b7bb8a8595ffe875fa8d4b7705684dbf46c0",
"url": "https://api.github.com/repos/utopia-php/http/zipball/e1c7ab4e0b5b0a9a70256b1e00912e101e76a131",
"reference": "e1c7ab4e0b5b0a9a70256b1e00912e101e76a131",
"shasum": ""
},
"require": {
@@ -3742,22 +3742,22 @@
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
"source": "https://github.com/utopia-php/http/tree/0.33.19"
"source": "https://github.com/utopia-php/http/tree/0.33.20"
},
"time": "2025-03-06T11:37:49+00:00"
"time": "2025-05-18T23:51:21+00:00"
},
{
"name": "utopia-php/image",
"version": "0.8.2",
"version": "0.8.3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/image.git",
"reference": "6c736965177f9a9e71311e22b80cfa88511768e9"
"reference": "8820b0e53b3636b7bdf815e92394d333fef06f26"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/image/zipball/6c736965177f9a9e71311e22b80cfa88511768e9",
"reference": "6c736965177f9a9e71311e22b80cfa88511768e9",
"url": "https://api.github.com/repos/utopia-php/image/zipball/8820b0e53b3636b7bdf815e92394d333fef06f26",
"reference": "8820b0e53b3636b7bdf815e92394d333fef06f26",
"shasum": ""
},
"require": {
@@ -3791,9 +3791,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/image/issues",
"source": "https://github.com/utopia-php/image/tree/0.8.2"
"source": "https://github.com/utopia-php/image/tree/0.8.3"
},
"time": "2025-04-08T11:31:45+00:00"
"time": "2025-05-15T10:39:28+00:00"
},
{
"name": "utopia-php/locale",
@@ -4059,16 +4059,16 @@
},
{
"name": "utopia-php/platform",
"version": "0.7.4",
"version": "0.7.7",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/platform.git",
"reference": "a5b93d8177702ec458c3af9137663133c012b71b"
"reference": "8c43cd866148a7c4c495e3401268429e338004b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/a5b93d8177702ec458c3af9137663133c012b71b",
"reference": "a5b93d8177702ec458c3af9137663133c012b71b",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/8c43cd866148a7c4c495e3401268429e338004b3",
"reference": "8c43cd866148a7c4c495e3401268429e338004b3",
"shasum": ""
},
"require": {
@@ -4077,11 +4077,11 @@
"php": ">=8.0",
"utopia-php/cli": "0.15.*",
"utopia-php/framework": "0.33.*",
"utopia-php/queue": "0.9.*"
"utopia-php/queue": "0.10.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
"phpunit/phpunit": "^9.3"
"laravel/pint": "1.*",
"phpunit/phpunit": "9.*"
},
"type": "library",
"autoload": {
@@ -4103,9 +4103,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/platform/issues",
"source": "https://github.com/utopia-php/platform/tree/0.7.4"
"source": "https://github.com/utopia-php/platform/tree/0.7.7"
},
"time": "2025-03-13T13:00:12+00:00"
"time": "2025-05-20T09:23:44+00:00"
},
{
"name": "utopia-php/pools",
@@ -4214,16 +4214,16 @@
},
{
"name": "utopia-php/queue",
"version": "0.9.1",
"version": "0.10.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/queue.git",
"reference": "32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32"
"reference": "0eccc559168ea72241c39a4c482d868314666be1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32",
"reference": "32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/0eccc559168ea72241c39a4c482d868314666be1",
"reference": "0eccc559168ea72241c39a4c482d868314666be1",
"shasum": ""
},
"require": {
@@ -4232,6 +4232,7 @@
"utopia-php/cli": "0.15.*",
"utopia-php/fetch": "0.4.*",
"utopia-php/framework": "0.33.*",
"utopia-php/pools": "0.8.*",
"utopia-php/telemetry": "0.1.*"
},
"require-dev": {
@@ -4273,9 +4274,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/queue/issues",
"source": "https://github.com/utopia-php/queue/tree/0.9.1"
"source": "https://github.com/utopia-php/queue/tree/0.10.0"
},
"time": "2025-03-28T19:49:36+00:00"
"time": "2025-04-17T12:15:52+00:00"
},
{
"name": "utopia-php/registry",
@@ -4331,16 +4332,16 @@
},
{
"name": "utopia-php/storage",
"version": "0.18.11",
"version": "0.18.12",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
"reference": "43bb91814e0ae9c70d2e4224fe0c9e85aa915c52"
"reference": "9a2556c39b5f4d9f8e79111fd34ec889b7bb1e97"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/43bb91814e0ae9c70d2e4224fe0c9e85aa915c52",
"reference": "43bb91814e0ae9c70d2e4224fe0c9e85aa915c52",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/9a2556c39b5f4d9f8e79111fd34ec889b7bb1e97",
"reference": "9a2556c39b5f4d9f8e79111fd34ec889b7bb1e97",
"shasum": ""
},
"require": {
@@ -4383,9 +4384,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
"source": "https://github.com/utopia-php/storage/tree/0.18.11"
"source": "https://github.com/utopia-php/storage/tree/0.18.12"
},
"time": "2025-05-14T16:00:30+00:00"
"time": "2025-05-15T07:55:58+00:00"
},
{
"name": "utopia-php/swoole",
@@ -4770,16 +4771,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.40.16",
"version": "0.40.18",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "f1f506da74033f0cb5a11e3dffcfd1ee8daf237d"
"reference": "38de4b9c58112d7e83eb75955994c8412a401093"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/f1f506da74033f0cb5a11e3dffcfd1ee8daf237d",
"reference": "f1f506da74033f0cb5a11e3dffcfd1ee8daf237d",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/38de4b9c58112d7e83eb75955994c8412a401093",
"reference": "38de4b9c58112d7e83eb75955994c8412a401093",
"shasum": ""
},
"require": {
@@ -4815,9 +4816,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.40.16"
"source": "https://github.com/appwrite/sdk-generator/tree/0.40.18"
},
"time": "2025-05-09T12:06:09+00:00"
"time": "2025-05-21T14:14:47+00:00"
},
{
"name": "doctrine/annotations",
@@ -0,0 +1 @@
Create or update a Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection) API or directly from your database console.
-7
View File
@@ -286,13 +286,6 @@ class Event
return $this;
}
public function setParamSensitive(string $key): self
{
$this->sensitive[$key] = true;
return $this;
}
/**
* Get param of event.
*
+13 -18
View File
@@ -2,14 +2,14 @@
namespace Appwrite\Messaging\Adapter;
use Appwrite\Messaging\Adapter;
use Appwrite\Messaging\Adapter as MessagingAdapter;
use Appwrite\PubSub\Adapter\Pool as PubSubPool;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Pools\Pool;
class Realtime extends Adapter
class Realtime extends MessagingAdapter
{
/**
* Connection Tree
@@ -36,12 +36,12 @@ class Realtime extends Adapter
*/
public array $subscriptions = [];
private Pool $pubsubPool;
private PubSubPool $pubSubPool;
public function __construct()
{
global $register;
$this->pubsubPool = $register->get('pools')->get('pubsub');
$this->pubSubPool = new PubSubPool($register->get('pools')->get('pubsub'));
}
/**
@@ -132,11 +132,12 @@ class Realtime extends Adapter
* Sends an event to the Realtime Server
* @param string $projectId
* @param array $payload
* @param string $event
* @param array $events
* @param array $channels
* @param array $roles
* @param array $options
* @return void
* @throws \Exception
*/
public function send(string $projectId, array $payload, array $events, array $channels, array $roles, array $options = []): void
{
@@ -147,7 +148,7 @@ class Realtime extends Adapter
$permissionsChanged = array_key_exists('permissionsChanged', $options) && $options['permissionsChanged'];
$userId = array_key_exists('userId', $options) ? $options['userId'] : null;
$message = [
$this->pubSubPool->publish('realtime', json_encode([
'project' => $projectId,
'roles' => $roles,
'permissionsChanged' => $permissionsChanged,
@@ -158,9 +159,7 @@ class Realtime extends Adapter
'timestamp' => DateTime::formatTz(DateTime::now()),
'payload' => $payload
]
];
$this->pubsubPool->use(fn (\Appwrite\PubSub\Adapter $pubsub) => $pubsub->publish('realtime', json_encode($message)));
]));
}
/**
@@ -175,8 +174,9 @@ class Realtime extends Adapter
* - 1,121.328 ms (±0.84%) | 1,000,000 Connections / 10,000,000 Subscriptions
*
* @param array $event
* @return int[]|string[]
*/
public function getSubscribers(array $event)
public function getSubscribers(array $event): array
{
$receivers = [];
@@ -230,7 +230,7 @@ class Realtime extends Adapter
foreach ($channels as $key => $value) {
switch (true) {
case strpos($key, 'account.') === 0:
case \str_starts_with($key, 'account.'):
unset($channels[$key]);
break;
@@ -272,6 +272,7 @@ class Realtime extends Adapter
$channels[] = 'account.' . $parts[1];
$roles = [Role::user(ID::custom($parts[1]))->toString()];
break;
case 'migrations':
case 'rules':
$channels[] = 'console';
$channels[] = 'projects.' . $project->getId();
@@ -352,12 +353,6 @@ class Realtime extends Adapter
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
}
break;
case 'migrations':
$channels[] = 'console';
$channels[] = 'projects.' . $project->getId();
$projectId = 'console';
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
break;
}
+5 -1
View File
@@ -93,7 +93,7 @@ abstract class Migration
'1.5.11' => 'V20',
'1.6.0' => 'V21',
'1.6.1' => 'V21',
'1.6.2' => 'V22',
'1.6.2' => 'V21',
];
/**
@@ -375,6 +375,10 @@ abstract class Migration
default => 'projects',
};
if ($from === 'files') {
$collectionType = 'buckets';
}
$collection = $this->collections[$collectionType][$from] ?? null;
if (is_null($collection)) {
+60 -5
View File
@@ -74,6 +74,27 @@ class V21 extends Migration
Console::warning("'accessedAt' from {$id}: {$th->getMessage()}");
}
break;
case 'rules':
$attributesToCreate = ['owner', 'region'];
foreach ($attributesToCreate as $attribute) {
// Create attribute
try {
$this->createAttributeFromCollection($this->projectDB, $id, $attribute);
} catch (Throwable $th) {
Console::warning("'$attribute' from {$id}: {$th->getMessage()}");
}
}
$indexesToCreate = ['_key_owner', '_key_region'];
foreach ($indexesToCreate as $index) {
// Create index
try {
$this->createIndexFromCollection($this->projectDB, $id, $index);
} catch (Throwable $th) {
Console::warning("'$index' from {$id}: {$th->getMessage()}");
}
}
break;
case 'platforms':
// Increase 'type' length to 255
try {
@@ -82,6 +103,17 @@ class V21 extends Migration
Console::warning("'type' from {$id}: {$th->getMessage()}");
}
break;
case 'installations':
$attributesToCreate = ['personalAccessToken', 'personalAccessTokenExpiry', 'personalRefreshToken'];
foreach ($attributesToCreate as $attribute) {
// Create attribute
try {
$this->createAttributeFromCollection($this->projectDB, $id, $attribute);
} catch (Throwable $th) {
Console::warning("'$attribute' from {$id}: {$th->getMessage()}");
}
}
break;
case 'migrations':
// Create destination attribute
try {
@@ -197,11 +229,15 @@ class V21 extends Migration
$document->setAttribute('accessedAt', DateTime::now());
break;
case 'functions':
// Add scopes attribute
$document->setAttribute('scopes', []);
// Set scopes attribute
if (empty($document->getAttribute('scopes', []))) {
$document->setAttribute('scopes', []);
}
// Add size attribute
$document->setAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT);
// Set specification attribute
if (empty($document->getAttribute('specification'))) {
$document->setAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT);
}
}
return $document;
@@ -217,11 +253,30 @@ class V21 extends Migration
foreach ($this->documentsIterator('buckets') as $bucket) {
$bucketId = 'bucket_' . $bucket['$internalId'];
Console::log("Migrating Bucket {$bucketId} {$bucket->getId()} ({$bucket->getAttribute('name')})");
try {
$this->projectDB->updateAttribute($bucketId, 'metadata', size: 65534);
} catch (\Throwable $th) {
Console::warning("'metadata' from {$bucketId}: {$th->getMessage()}");
}
try {
$this->createAttributeFromCollection($this->projectDB, $bucketId, 'transformedAt', 'files');
} catch (\Throwable $th) {
Console::warning("'transformedAt' from {$bucketId}: {$th->getMessage()}");
}
try {
$this->createIndexFromCollection($this->projectDB, $bucketId, '_key_transformedAt', 'files');
} catch (\Throwable $th) {
Console::warning("'_key_transformedAt' from {$bucketId}: {$th->getMessage()}");
}
try {
$this->projectDB->purgeCachedCollection($bucketId);
} catch (\Throwable $th) {
Console::warning("'bucketId' from {$bucketId}: {$th->getMessage()}");
Console::warning("purging {$bucketId}: {$th->getMessage()}");
}
}
}
-83
View File
@@ -1,83 +0,0 @@
<?php
namespace Appwrite\Migration\Version;
use Appwrite\Migration\Migration;
use Exception;
use Throwable;
use Utopia\CLI\Console;
use Utopia\Database\Database;
class V22 extends Migration
{
/**
* @throws Throwable
*/
public function execute(): void
{
/**
* Disable SubQueries for Performance.
*/
foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables', 'subQueryChallenges', 'subQueryProjectVariables', 'subQueryTargets', 'subQueryTopicTargets'] as $name) {
Database::addFilter(
$name,
fn () => null,
fn () => []
);
}
Console::info('Migrating Collections');
$this->migrateCollections();
}
/**
* Migrate Collections.
*
* @return void
* @throws Exception|Throwable
*/
private function migrateCollections(): void
{
$internalProjectId = $this->project->getInternalId();
$collectionType = match ($internalProjectId) {
'console' => 'console',
default => 'projects',
};
$collections = $this->collections[$collectionType];
foreach ($collections as $collection) {
$id = $collection['$id'];
Console::log("Migrating Collection \"{$id}\"");
$this->projectDB->setNamespace("_$internalProjectId");
switch ($id) {
case 'installations':
// Create personalAccessToken attribute
try {
$this->createAttributeFromCollection($this->projectDB, $id, 'personalAccessToken');
} catch (Throwable $th) {
Console::warning("'personalAccessToken' from {$id}: {$th->getMessage()}");
}
// Create personalAccessTokenExpiry attribute
try {
$this->createAttributeFromCollection($this->projectDB, $id, 'personalAccessTokenExpiry');
} catch (Throwable $th) {
Console::warning("'personalAccessTokenExpiry' from {$id}: {$th->getMessage()}");
}
// Create personalRefreshToken attribute
try {
$this->createAttributeFromCollection($this->projectDB, $id, 'personalRefreshToken');
} catch (Throwable $th) {
Console::warning("'personalRefreshToken' from {$id}: {$th->getMessage()}");
}
break;
}
usleep(50000);
}
}
}
+29 -21
View File
@@ -3,14 +3,19 @@
namespace Appwrite\Platform\Tasks;
use Appwrite\ClamAV\Network;
use Appwrite\PubSub\Adapter;
use Appwrite\PubSub\Adapter\Pool as PubSubPool;
use PHPMailer\PHPMailer\PHPMailer;
use Utopia\App;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Domains\Domain;
use Utopia\DSN\DSN;
use Utopia\Logger\Logger;
use Utopia\Platform\Action;
use Utopia\Pools\Group;
use Utopia\Queue\Broker\Pool as BrokerPool;
use Utopia\Registry\Registry;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
@@ -76,9 +81,9 @@ class Doctor extends Action
Console::log('🟢 Abuse protection is enabled');
}
$authWhitelistRoot = System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', null);
$authWhitelistEmails = System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null);
$authWhitelistIPs = System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null);
$authWhitelistRoot = System::getEnv('_APP_CONSOLE_WHITELIST_ROOT');
$authWhitelistEmails = System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS');
$authWhitelistIPs = System::getEnv('_APP_CONSOLE_WHITELIST_IPS');
if (
empty($authWhitelistRoot)
@@ -114,19 +119,16 @@ class Doctor extends Action
} else {
Console::log('🟢 Logging adapter is enabled (' . $providerName . ')');
}
} catch (\Throwable $th) {
} catch (\Throwable) {
Console::log('🔴 Logging adapter is misconfigured');
}
\usleep(200 * 1000); // Sleep for 0.2 seconds
try {
Console::log("\n" . '[Connectivity]');
} catch (\Throwable $th) {
//throw $th;
}
Console::log("\n" . '[Connectivity]');
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
/** @var Group $pools */
$pools = $register->get('pools');
$configs = [
'Console.DB' => Config::getParam('pools-console'),
@@ -136,20 +138,22 @@ class Doctor extends Action
foreach ($configs as $key => $config) {
foreach ($config as $database) {
try {
$adapter = $pools->get($database)->pop()->getResource();
$adapter = new DatabasePool($pools->get($database));
if ($adapter->ping()) {
Console::success('🟢 ' . str_pad("{$key}({$database})", 50, '.') . 'connected');
} else {
Console::error('🔴 ' . str_pad("{$key}({$database})", 47, '.') . 'disconnected');
}
} catch (\Throwable $th) {
} catch (\Throwable) {
Console::error('🔴 ' . str_pad("{$key}.({$database})", 47, '.') . 'disconnected');
}
}
}
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
/** @var Group $pools */
$pools = $register->get('pools');
$configs = [
'Cache' => Config::getParam('pools-cache'),
'Queue' => Config::getParam('pools-queue'),
@@ -159,15 +163,18 @@ class Doctor extends Action
foreach ($configs as $key => $config) {
foreach ($config as $pool) {
try {
/** @var Adapter $adapter */
$adapter = $pools->get($pool)->pop()->getResource();
$adapter = match($key) {
'Cache' => new CachePool($pools->get($pool)),
'Queue' => new BrokerPool($pools->get($pool)),
'PubSub' => new PubSubPool($pools->get($pool)),
};
if ($adapter->ping()) {
Console::success('🟢 ' . str_pad("{$key}({$pool})", 50, '.') . 'connected');
} else {
Console::error('🔴 ' . str_pad("{$key}({$pool})", 47, '.') . 'disconnected');
}
} catch (\Throwable $th) {
} catch (\Throwable) {
Console::error('🔴 ' . str_pad("{$key}({$pool})", 47, '.') . 'disconnected');
}
}
@@ -185,13 +192,14 @@ class Doctor extends Action
} else {
Console::error('🔴 ' . str_pad("Antivirus", 47, '.') . 'disconnected');
}
} catch (\Throwable $th) {
} catch (\Throwable) {
Console::error('🔴 ' . str_pad("Antivirus", 47, '.') . 'disconnected');
}
}
try {
$mail = $register->get('smtp'); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */
/* @var PHPMailer $mail */
$mail = $register->get('smtp');
$mail->addAddress('demo@example.com', 'Example.com');
$mail->Subject = 'Test SMTP Connection';
@@ -200,7 +208,7 @@ class Doctor extends Action
$mail->send();
Console::success('🟢 ' . str_pad("SMTP", 50, '.') . 'connected');
} catch (\Throwable $th) {
} catch (\Throwable) {
Console::error('🔴 ' . str_pad("SMTP", 47, '.') . 'disconnected');
}
@@ -274,7 +282,7 @@ class Doctor extends Action
Console::error('Failed to check for a newer version' . "\n");
}
}
} catch (\Throwable $th) {
} catch (\Throwable) {
Console::error('Failed to check for a newer version' . "\n");
}
}
+9 -7
View File
@@ -12,6 +12,7 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Pools\Group;
use Utopia\Queue\Broker\Pool as BrokerPool;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Telemetry\Gauge;
@@ -24,6 +25,8 @@ abstract class ScheduleBase extends Action
protected array $schedules = [];
protected BrokerPool $publisher;
private ?Histogram $collectSchedulesTelemetryDuration = null;
private ?Gauge $collectSchedulesTelemetryCount = null;
private ?Gauge $scheduleTelemetryCount = null;
@@ -68,6 +71,7 @@ abstract class ScheduleBase extends Action
Console::title(\ucfirst(static::getSupportedResource()) . ' scheduler V1');
Console::success(APP_NAME . ' ' . \ucfirst(static::getSupportedResource()) . ' scheduler v1 has started');
$this->publisher = new BrokerPool($pools->get('publisher'));
$this->scheduleTelemetryCount = $telemetry->createGauge('task.schedule.count');
$this->collectSchedulesTelemetryDuration = $telemetry->createHistogram('task.schedule.collect_schedules.duration', 's');
$this->collectSchedulesTelemetryCount = $telemetry->createGauge('task.schedule.collect_schedules.count');
@@ -75,16 +79,16 @@ abstract class ScheduleBase extends Action
// start with "0" to load all active documents.
$lastSyncUpdate = "0";
$this->collectSchedules($pools, $dbForPlatform, $getProjectDB, $lastSyncUpdate);
$this->collectSchedules($dbForPlatform, $getProjectDB, $lastSyncUpdate);
Console::success("Starting timers at " . DateTime::now());
/**
* The timer synchronize $schedules copy with database collection.
*/
Timer::tick(static::UPDATE_TIMER * 1000, function () use ($pools, $dbForPlatform, $getProjectDB, &$lastSyncUpdate) {
Timer::tick(static::UPDATE_TIMER * 1000, function () use ($dbForPlatform, $getProjectDB, &$lastSyncUpdate) {
$time = DateTime::now();
Console::log("Sync tick: Running at $time");
$this->collectSchedules($pools, $dbForPlatform, $getProjectDB, $lastSyncUpdate);
$this->collectSchedules($dbForPlatform, $getProjectDB, $lastSyncUpdate);
});
while (true) {
@@ -99,7 +103,7 @@ abstract class ScheduleBase extends Action
}
}
private function collectSchedules(Group $pools, Database $dbForPlatform, callable $getProjectDB, string &$lastSyncUpdate): void
private function collectSchedules(Database $dbForPlatform, callable $getProjectDB, string &$lastSyncUpdate): void
{
// If we haven't synced yet, load all active schedules
$initialLoad = $lastSyncUpdate === "0";
@@ -111,7 +115,7 @@ abstract class ScheduleBase extends Action
* @throws Exception
* @var Document $schedule
*/
$getSchedule = function (Document $schedule) use ($pools, $dbForPlatform, $getProjectDB): array {
$getSchedule = function (Document $schedule) use ($dbForPlatform, $getProjectDB): array {
$project = $dbForPlatform->getDocument('projects', $schedule->getAttribute('projectId'));
$resource = $getProjectDB($project)->getDocument(
@@ -119,8 +123,6 @@ abstract class ScheduleBase extends Action
$schedule->getAttribute('resourceId')
);
$pools->reclaim();
return [
'$internalId' => $schedule->getInternalId(),
'$id' => $schedule->getId(),
@@ -29,9 +29,7 @@ class ScheduleExecutions extends ScheduleBase
protected function enqueueResources(Group $pools, Database $dbForPlatform, callable $getProjectDB): void
{
$queue = $pools->get('publisher')->pop();
$connection = $queue->getResource();
$queueForFunctions = new Func($connection);
$queueForFunctions = new Func($this->publisher);
$intervalEnd = (new \DateTime())->modify('+' . self::ENQUEUE_TIMER . ' seconds');
foreach ($this->schedules as $schedule) {
@@ -85,7 +83,5 @@ class ScheduleExecutions extends ScheduleBase
unset($this->schedules[$schedule['$internalId']]);
}
$queue->reclaim();
}
}
@@ -76,12 +76,9 @@ class ScheduleFunctions extends ScheduleBase
}
foreach ($delayedExecutions as $delay => $schedules) {
\go(function () use ($delay, $schedules, $pools, $dbForPlatform) {
\go(function () use ($delay, $schedules, $dbForPlatform) {
\sleep($delay); // in seconds
$queue = $pools->get('publisher')->pop();
$connection = $queue->getResource();
foreach ($schedules as $delayConfig) {
$scheduleKey = $delayConfig['key'];
// Ensure schedule was not deleted
@@ -93,7 +90,7 @@ class ScheduleFunctions extends ScheduleBase
$this->updateProjectAccess($schedule['project'], $dbForPlatform);
$queueForFunctions = new Func($connection);
$queueForFunctions = new Func($this->publisher);
$queueForFunctions
->setType('schedule')
@@ -105,8 +102,6 @@ class ScheduleFunctions extends ScheduleBase
$this->recordEnqueueDelay($delayConfig['nextDate']);
}
$queue->reclaim();
});
}
@@ -40,10 +40,8 @@ class ScheduleMessages extends ScheduleBase
continue;
}
\go(function () use ($schedule, $scheduledAt, $pools, $dbForPlatform) {
$queue = $pools->get('publisher')->pop();
$connection = $queue->getResource();
$queueForMessaging = new Messaging($connection);
\go(function () use ($schedule, $scheduledAt, $dbForPlatform) {
$queueForMessaging = new Messaging($this->publisher);
$this->updateProjectAccess($schedule['project'], $dbForPlatform);
@@ -58,7 +56,6 @@ class ScheduleMessages extends ScheduleBase
$schedule['$id'],
);
$queue->reclaim();
$this->recordEnqueueDelay($scheduledAt);
unset($this->schedules[$schedule['$internalId']]);
});
+19 -36
View File
@@ -61,10 +61,7 @@ class Deletes extends Action
->inject('executionRetention')
->inject('auditRetention')
->inject('log')
->callback(
fn ($message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, Executor $executor, string $executionRetention, string $auditRetention, Log $log) =>
$this->action($message, $project, $dbForPlatform, $getProjectDB, $getLogsDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $executor, $executionRetention, $auditRetention, $log)
);
->callback($this->action(...));
}
/**
@@ -497,47 +494,33 @@ class Deletes extends Action
AbuseDatabase::COLLECTION,
];
$limit = \count($projectCollectionIds) + 25;
$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;
while (true) {
$collections = $dbForProject->listCollections($limit);
foreach ($collections as $collection) {
try {
if ($projectTables || !\in_array($collection->getId(), $projectCollectionIds)) {
$dbForProject->deleteCollection($collection->getId());
} else {
$this->deleteByGroup(
$collection->getId(),
[
Query::orderAsc()
],
database: $dbForProject
);
}
} catch (Throwable $e) {
Console::error('Error deleting '.$collection->getId().' '.$e->getMessage());
/**
* @var $dbForProject Database
*/
$dbForProject->foreach(Database::METADATA, function (Document $collection) use ($dbForProject, $projectTables, $projectCollectionIds) {
try {
if ($projectTables || !\in_array($collection->getId(), $projectCollectionIds)) {
$dbForProject->deleteCollection($collection->getId());
} else {
$this->deleteByGroup(
$collection->getId(),
[
Query::orderAsc()
],
database: $dbForProject
);
}
} catch (Throwable $e) {
Console::error('Error deleting '.$collection->getId().' '.$e->getMessage());
}
if ($sharedTables) {
$collectionsIds = \array_map(fn ($collection) => $collection->getId(), $collections);
if (empty(\array_diff($collectionsIds, $projectCollectionIds))) {
break;
}
} elseif (empty($collections)) {
break;
}
}
});
// Delete Platforms
$this->deleteByGroup('platforms', [
+5 -7
View File
@@ -325,7 +325,7 @@ class StatsUsage extends Action
break;
}
} catch (Throwable $e) {
console::error("[reducer] " . " {DateTime::now()} " . " {$project->getInternalId()} " . " {$e->getMessage()}");
Console::error("[reducer] " . " {DateTime::now()} " . " {$project->getInternalId()} " . " {$e->getMessage()}");
}
}
@@ -344,7 +344,7 @@ class StatsUsage extends Action
continue;
}
console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys);
Console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys);
try {
foreach ($stats['keys'] ?? [] as $key => $value) {
@@ -381,7 +381,7 @@ class StatsUsage extends Action
}
}
} catch (Exception $e) {
console::error('[' . DateTime::now() . '] project [' . $project->getInternalId() . '] database [' . $project['database'] . '] ' . ' ' . $e->getMessage());
Console::error('[' . DateTime::now() . '] project [' . $project->getInternalId() . '] database [' . $project['database'] . '] ' . ' ' . $e->getMessage());
}
}
@@ -405,7 +405,7 @@ class StatsUsage extends Action
}
protected function prepareForLogsDB(Document $project, Document $stat)
protected function prepareForLogsDB(Document $project, Document $stat): void
{
if (System::getEnv('_APP_STATS_USAGE_DUAL_WRITING', 'disabled') === 'disabled') {
return;
@@ -430,8 +430,7 @@ class StatsUsage extends Action
return;
}
$dbForLogs = call_user_func($this->getLogsDB);
$dbForLogs
$dbForLogs = ($this->getLogsDB)()
->setTenant(null)
->setTenantPerDocument(true);
@@ -446,6 +445,5 @@ class StatsUsage extends Action
} catch (Throwable $th) {
Console::error($th->getMessage());
}
$this->register->get('pools')->get('logs')->reclaim();
}
}
@@ -70,9 +70,9 @@ class StatsUsageDump extends Action
];
/**
* @var callable
* @var callable(Document): Database
*/
protected mixed $getLogsDB;
protected $getLogsDB;
protected array $periods = [
'1h' => 'Y-m-d H:00',
@@ -126,10 +126,10 @@ class StatsUsageDump extends Action
continue;
}
console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys);
Console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys);
try {
/** @var \Utopia\Database\Database $dbForProject */
/** @var Database $dbForProject */
$dbForProject = $getProjectDB($project);
foreach ($stats['keys'] ?? [] as $key => $value) {
if ($value == 0) {
@@ -169,7 +169,7 @@ class StatsUsageDump extends Action
}
}
} catch (\Exception $e) {
console::error('[' . DateTime::now() . '] project [' . $project->getInternalId() . '] database [' . $project['database'] . '] ' . ' ' . $e->getMessage());
Console::error('[' . DateTime::now() . '] project [' . $project->getInternalId() . '] database [' . $project['database'] . '] ' . ' ' . $e->getMessage());
}
}
}
@@ -190,8 +190,7 @@ class StatsUsageDump extends Action
}
}
/** @var \Utopia\Database\Database $dbForLogs*/
$dbForLogs = call_user_func($this->getLogsDB, $project);
$dbForLogs = ($this->getLogsDB)($project);
try {
$dbForLogs->createOrUpdateDocumentsWithIncrease(
@@ -203,7 +202,5 @@ class StatsUsageDump extends Action
} catch (\Throwable $th) {
Console::error($th->getMessage());
}
$this->register->get('pools')->get('logs')->reclaim();
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace Appwrite\PubSub\Adapter;
use Appwrite\PubSub\Adapter;
use Utopia\Database\Exception as DatabaseException;
use Utopia\Pools\Pool as UtopiaPool;
class Pool implements Adapter
{
public function __construct(private UtopiaPool $pool)
{
}
public function ping($message = null): bool
{
return $this->delegate(__FUNCTION__, \func_get_args());
}
public function subscribe($channels, $callback): void
{
$this->delegate(__FUNCTION__, \func_get_args());
}
public function publish($channel, $message): void
{
$this->delegate(__FUNCTION__, \func_get_args());
}
/**
* Forward method calls to the internal adapter instance via the pool.
*
* Required because __call() can't be used to implement abstract methods.
*
* @param string $method
* @param array<mixed> $args
* @return mixed
* @throws DatabaseException
*/
public function delegate(string $method, array $args): mixed
{
return $this->pool->use(function (Adapter $adapter) use ($method, $args) {
return $adapter->{$method}(...$args);
});
}
}
+13
View File
@@ -220,4 +220,17 @@ class Request extends UtopiaRequest
return UtopiaRequest::getUserAgent($default);
}
/**
* Creates a unique stable cache identifier for this GET request.
* Stable-sorts query params, use `serialize` to ensure key&value are part of cache keys.
*
* @return string
*/
public function cacheIdentifier(): string
{
$params = $this->getParams();
ksort($params);
return md5($this->getURI() . '*' . serialize($params) . '*' . APP_CACHE_BUSTER);
}
}
+302 -1
View File
@@ -12,7 +12,6 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Validator\JSON;
trait DatabasesBase
{
@@ -1683,6 +1682,308 @@ trait DatabasesBase
return $data;
}
/**
* @depends testCreateIndexes
*/
public function testUpsertDocument(array $data): void
{
$databaseId = $data['databaseId'];
$documentId = ID::unique();
$document = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'title' => 'Thor: Ragnarok',
'releaseYear' => 2000
],
'permissions' => [
Permission::read(Role::users()),
Permission::update(Role::users()),
Permission::delete(Role::users()),
],
]);
$this->assertEquals(200, $document['headers']['status-code']);
$this->assertCount(3, $document['body']['$permissions']);
$document = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals('Thor: Ragnarok', $document['body']['title']);
$document = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'title' => 'Thor: Love and Thunder',
'releaseYear' => 2000
],
'permissions' => [
Permission::read(Role::users()),
Permission::update(Role::users()),
Permission::delete(Role::users()),
],
]);
$this->assertEquals(200, $document['headers']['status-code']);
$this->assertEquals('Thor: Love and Thunder', $document['body']['title']);
$document = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals('Thor: Love and Thunder', $document['body']['title']);
// removing permission to read and delete
$document = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'title' => 'Thor: Love and Thunder',
'releaseYear' => 2000
],
'permissions' => [
Permission::update(Role::users())
],
]);
// shouldn't be able to read as no read permission
$document = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
switch ($this->getSide()) {
case 'client':
$this->assertEquals(404, $document['headers']['status-code']);
break;
case 'server':
$this->assertEquals(200, $document['headers']['status-code']);
break;
}
// shouldn't be able to delete as no delete permission
$document = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
// simulating for the client
// the document should not be allowed to be deleted as needed downward
if ($this->getSide() === 'client') {
$this->assertEquals(401, $document['headers']['status-code']);
}
// giving the delete permission
$document = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'title' => 'Thor: Love and Thunder',
'releaseYear' => 2000
],
'permissions' => [
Permission::read(Role::users()),
Permission::update(Role::users()),
Permission::delete(Role::users())
],
]);
$document = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(204, $document['headers']['status-code']);
// relationship behaviour
$person = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => 'person-upsert',
'name' => 'person',
'permissions' => [
Permission::read(Role::users()),
Permission::update(Role::users()),
Permission::delete(Role::users()),
Permission::create(Role::users()),
],
'documentSecurity' => true,
]);
$this->assertEquals(201, $person['headers']['status-code']);
$library = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => 'library-upsert',
'name' => 'library',
'permissions' => [
Permission::read(Role::users()),
Permission::update(Role::users()),
Permission::create(Role::users()),
Permission::delete(Role::users()),
],
'documentSecurity' => true,
]);
$this->assertEquals(201, $library['headers']['status-code']);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'fullName',
'size' => 255,
'required' => false,
]);
sleep(1); // Wait for worker
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/relationship', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'relatedCollectionId' => 'library-upsert',
'type' => Database::RELATION_ONE_TO_ONE,
'key' => 'library',
'twoWay' => true,
'onDelete' => Database::RELATION_MUTATE_CASCADE,
]);
sleep(1); // Wait for worker
$libraryName = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $library['body']['$id'] . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'libraryName',
'size' => 255,
'required' => true,
]);
sleep(1); // Wait for worker
$this->assertEquals(202, $libraryName['headers']['status-code']);
// upserting values
$documentId = ID::unique();
$person1 = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/'.$documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'library' => [
'$id' => 'library1',
'$permissions' => [
Permission::read(Role::users()),
Permission::update(Role::users()),
Permission::delete(Role::users()),
],
'libraryName' => 'Library 1',
],
],
'permissions' => [
Permission::read(Role::users()),
Permission::update(Role::users()),
Permission::delete(Role::users()),
]
]);
$this->assertEquals('Library 1', $person1['body']['library']['libraryName']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::select(['fullName', 'library.*'])->toString(),
Query::equal('library', ['library1'])->toString(),
],
]);
$this->assertEquals(1, $documents['body']['total']);
$this->assertEquals('Library 1', $documents['body']['documents'][0]['library']['libraryName']);
$person1 = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/'.$documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'library' => [
'$id' => 'library1',
'$permissions' => [
Permission::read(Role::users()),
Permission::update(Role::users()),
Permission::delete(Role::users()),
],
'libraryName' => 'Library 2',
],
],
'permissions' => [
Permission::read(Role::users()),
Permission::update(Role::users()),
Permission::delete(Role::users()),
]
]);
// data should get updated
$this->assertEquals('Library 2', $person1['body']['library']['libraryName']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::select(['fullName', 'library.*'])->toString(),
Query::equal('library', ['library1'])->toString(),
],
]);
$this->assertEquals(1, $documents['body']['total']);
$this->assertEquals('Library 2', $documents['body']['documents'][0]['library']['libraryName']);
// data should get added
$person1 = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/'.ID::unique(), array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'library' => [
'$id' => 'library2',
'$permissions' => [
Permission::read(Role::users()),
Permission::update(Role::users()),
Permission::delete(Role::users()),
],
'libraryName' => 'Library 2',
],
],
'permissions' => [
Permission::read(Role::users()),
Permission::update(Role::users()),
Permission::delete(Role::users()),
]
]);
$this->assertEquals('Library 2', $person1['body']['library']['libraryName']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::select(['fullName', 'library.*'])->toString()
],
]);
$this->assertEquals(2, $documents['body']['total']);
}
/**
* @depends testCreateDocument
*/
@@ -86,7 +86,7 @@ class TeamsConsoleClientTest extends Scope
$session = $data['session'] ?? '';
/**
* Test for SUCCESS
* Test for FAILURE
*/
$roles = ['developer'];
$response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipUid, array_merge([
@@ -97,12 +97,8 @@ class TeamsConsoleClientTest extends Scope
'roles' => $roles
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertNotEmpty($response['body']['userId']);
$this->assertNotEmpty($response['body']['teamId']);
$this->assertCount(count($roles), $response['body']['roles']);
$this->assertEquals($roles[0], $response['body']['roles'][0]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertEquals('There must be at least one owner in the organization.', $response['body']['message']);
/**
* Test for unknown team
+1 -31
View File
@@ -35,7 +35,7 @@ services:
- VERSION=dev
restart: unless-stopped
ports:
- 9501:80
- "9501:80"
networks:
- appwrite
labels:
@@ -52,15 +52,12 @@ services:
- ./phpunit.xml:/usr/src/code/phpunit.xml
- ./tests:/usr/src/code/tests
- ./app:/usr/src/code/app
# - ./vendor:/usr/src/code/vendor
- ./docs:/usr/src/code/docs
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
- ./debug:/tmp
depends_on:
- mariadb
- redis
# - clamav
environment:
- _APP_COMPRESSION_MIN_SIZE_BYTES
- _APP_ENV
@@ -355,33 +352,6 @@ services:
volumes:
- appwrite-redis:/data:rw
# clamav:
# image: appwrite/clamav:1.2.0
# container_name: appwrite-clamav
# restart: unless-stopped
# networks:
# - appwrite
# volumes:
# - appwrite-uploads:/storage/uploads
# redis-commander:
# image: rediscommander/redis-commander:latest
# restart: unless-stopped
# networks:
# - appwrite
# environment:
# - REDIS_HOSTS=redis
# ports:
# - "8081:8081"
# webgrind:
# image: 'jokkedk/webgrind:latest'
# volumes:
# - './debug:/tmp'
# ports:
# - '3001:80'
networks:
gateway:
appwrite: