Merge branch '1.6.x' into chore-sync-1.6.x

This commit is contained in:
Steven Nguyen 2025-05-17 14:01:39 -07:00
commit 5db1cff69f
No known key found for this signature in database
5 changed files with 889 additions and 649 deletions

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

View file

@ -82,9 +82,9 @@ abstract class Migration
'1.5.11' => 'V20',
'1.6.0' => 'V21',
'1.6.1' => 'V21',
'1.6.2' => 'V22',
'1.7.0-RC1' => 'V23',
'1.7.0' => 'V23',
'1.6.2' => 'V21',
'1.7.0-RC1' => 'V22',
'1.7.0' => 'V22',
];
/**
@ -385,6 +385,10 @@ abstract class Migration
default => 'projects',
};
if ($from === 'files') {
$collectionType = 'buckets';
}
$collection = $this->collections[$collectionType][$from] ?? null;
if ($collection === null) {

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->dbForProject, $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->dbForProject, $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->dbForProject, $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_COMPUTE_SPECIFICATION_DEFAULT);
// Set specification attribute
if (empty($document->getAttribute('specification'))) {
$document->setAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT);
}
}
return $document;
@ -214,15 +250,34 @@ class V21 extends Migration
*/
private function migrateBuckets(): void
{
foreach ($this->documentsIterator('buckets') as $bucket) {
$this->dbForProject->forEach('buckets', function (Document $bucket) {
$bucketId = 'bucket_' . $bucket['$internalId'];
Console::log("Migrating Bucket {$bucketId} {$bucket->getId()} ({$bucket->getAttribute('name')})");
try {
$this->dbForProject->updateAttribute($bucketId, 'metadata', size: 65534);
} catch (\Throwable $th) {
Console::warning("'metadata' from {$bucketId}: {$th->getMessage()}");
}
try {
$this->createAttributeFromCollection($this->dbForProject, $bucketId, 'transformedAt', 'files');
} catch (\Throwable $th) {
Console::warning("'transformedAt' from {$bucketId}: {$th->getMessage()}");
}
try {
$this->createIndexFromCollection($this->dbForProject, $bucketId, '_key_transformedAt', 'files');
} catch (\Throwable $th) {
Console::warning("'_key_transformedAt' from {$bucketId}: {$th->getMessage()}");
}
try {
$this->dbForProject->purgeCachedCollection($bucketId);
} catch (\Throwable $th) {
Console::warning("'bucketId' from {$bucketId}: {$th->getMessage()}");
Console::warning("purging {$bucketId}: {$th->getMessage()}");
}
}
});
}
}

View file

@ -7,8 +7,13 @@ use Exception;
use Throwable;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Conflict;
use Utopia\Database\Exception\Structure;
use Utopia\Database\Exception\Timeout;
use Utopia\Database\Query;
class V22 extends Migration
class V23 extends Migration
{
/**
* @throws Throwable
@ -16,9 +21,9 @@ class V22 extends Migration
public function execute(): void
{
/**
* Disable SubQueries for Performance.
*/
foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables', 'subQueryChallenges', 'subQueryProjectVariables', 'subQueryTargets', 'subQueryTopicTargets'] as $name) {
* Disable SubQueries for Performance.
*/
foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryDevKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables', 'subQueryChallenges', 'subQueryProjectVariables', 'subQueryTargets', 'subQueryTopicTargets'] as $name) {
Database::addFilter(
$name,
fn () => null,
@ -26,8 +31,14 @@ class V22 extends Migration
);
}
Console::info('Migrating Collections');
Console::info('Migrating collections');
$this->migrateCollections();
Console::info('Migrating documents');
$this->forEachDocument($this->migrateDocument(...));
Console::info('Cleaning up collections');
$this->cleanCollections();
}
/**
@ -38,8 +49,428 @@ class V22 extends Migration
*/
private function migrateCollections(): void
{
$internalProjectId = $this->project->getInternalId();
$collectionType = match ($internalProjectId) {
$projectInternalId = $this->project->getInternalId();
if (empty($projectInternalId)) {
throw new Exception('Project ID is null');
}
$collectionType = match ($projectInternalId) {
'console' => 'console',
default => 'projects',
};
$collections = $this->collections[$collectionType];
foreach ($collections as $collection) {
$id = $collection['$id'];
if (empty($id)) {
continue;
}
Console::log("Migrating collection \"{$id}\"");
switch ($id) {
case '_metadata':
$this->createCollection('sites');
$this->createCollection('resourceTokens');
if ($projectInternalId === 'console') {
$this->createCollection('devKeys');
}
break;
case 'identities':
$attributes = [
'scopes',
'expire',
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'projects':
try {
$attributes = [
'devKeys',
];
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'rules':
$attributes = [
'type',
'trigger',
'redirectUrl',
'redirectStatusCode',
'deploymentResourceType',
'deploymentId',
'deploymentInternalId',
'deploymentResourceId',
'deploymentResourceInternalId',
'deploymentVcsProviderBranch',
'search'
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$indexes = [
'_key_search',
'_key_type',
'_key_trigger',
'_key_deploymentResourceType',
'_key_deploymentResourceId',
'_key_deploymentResourceInternalId',
'_key_deploymentId',
'_key_deploymentInternalId',
'_key_deploymentVcsProviderBranch',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'memberships':
$indexes = [
'_key_roles',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (Throwable $th) {
Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'migrations':
$attributes = [
'options',
'resourceId',
'resourceType'
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$indexes = [
'_key_resource_id',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (Throwable $th) {
Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'functions':
$attributes = [
'deploymentId',
'deploymentCreatedAt',
'latestDeploymentId',
'latestDeploymentInternalId',
'latestDeploymentCreatedAt',
'latestDeploymentStatus',
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$indexes = [
'_key_deploymentId',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (Throwable $th) {
Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'deployments':
$attributes = [
'buildCommands',
'sourcePath',
'buildOutput',
'adapter',
'fallbackFile',
'sourceSize',
'sourceMetadata',
'sourceChunksTotal',
'sourceChunksUploaded',
'screenshotLight',
'screenshotDark',
'buildStartedAt',
'buildEndedAt',
'buildDuration',
'buildSize',
'status',
'buildPath',
'buildLogs',
'totalSize',
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$indexes = [
'_key_sourceSize',
'_key_buildSize',
'_key_totalSize',
'_key_buildDuration',
'_key_type',
'_key_status',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'executions':
$attributes = [
'resourceInternalId',
'resourceId',
'resourceType'
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$indexes = [
'_key_resource',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'variables':
$attributes = [
'secret',
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$this->dbForProject->purgeCachedCollection($id);
break;
default:
break;
}
}
}
/**
* Fix run on each document
*
* @param Document $document
* @return Document
* @throws Conflict
* @throws Structure
* @throws Timeout
* @throws \Utopia\Database\Exception
* @throws \Utopia\Database\Exception\Authorization
* @throws \Utopia\Database\Exception\Query
*/
private function migrateDocument(Document $document): Document
{
switch ($document->getCollection()) {
case 'rules':
/*
1. Convert "resourceType" to "type". Convert "function" to "deployment"
2. Convert "resourceId" to "deploymentResourceId"
3. Convert "resourceInternalId" to "deploymentResourceInternalId"
4. Fill "trigger" with "manual"
5. Fill "deploymentResourceType". If "resourceType" is "function", set "deploymentResourceType" to "function"
6. Fill "search" with "{$id} {domain}"
7. Fill "deploymentId" and "deploymentInternalId". If "deploymentResourceType" is "function", get project DB, and find function with ID "resourceId". Then fill rule's "deploymentId" with function's "deployment", and "deploymentId" as backup
*/
$deploymentResourceType = null;
$type = $document->getAttribute('resourceType', $document->getAttribute('type', ''));
if ($type === 'function') {
$type = 'deployment';
$deploymentResourceType = 'function';
}
$resourceId = $document->getAttribute('resourceId', $document->getAttribute('deploymentResourceId'));
$resourceInternalId = $document->getAttribute('resourceInternalId', $document->getAttribute('deploymentResourceInternalId'));
$document
->setAttribute('type', $type)
->setAttribute('trigger', 'manual')
->setAttribute('deploymentResourceId', $resourceId)
->setAttribute('deploymentResourceInternalId', $resourceInternalId)
->setAttribute('deploymentResourceType', $document->getAttribute('deploymentResourceType', $deploymentResourceType))
->setAttribute('search', \implode(' ', [$document->getId(), $document->getAttribute('domain', '')]));
if ($deploymentResourceType === 'function') {
$project = $this->dbForProject->getDocument('projects', $document->getAttribute('projectId'));
$dbForOwnerProject = ($this->getProjectDB)($project);
$function = $dbForOwnerProject->getDocument('functions', $resourceId);
$deploymentId = $function->getAttribute('deployment', $function->getAttribute('deploymentId', $document->getAttribute('deploymentId')));
$deploymentInternalId = $function->getAttribute('deploymentInternalId', $document->getAttribute('deploymentInternalId', ''));
$document
->setAttribute('deploymentId', $deploymentId)
->setAttribute('deploymentInternalId', $deploymentInternalId);
}
break;
case 'variables':
/*
1. Fill "secret" with "false"
*/
$document->setAttribute('secret', $document->getAttribute('secret', false));
break;
case 'executions':
/*
1. Convert "functionInternalId" to "resourceInternalId"
2. Convert "functionId" to "resourceId"
3. Fill "resourceType" with "functions"
*/
$document
->setAttribute('resourceInternalId', $document->getAttribute('functionInternalId', $document->getAttribute('resourceInternalId')))
->setAttribute('resourceId', $document->getAttribute('functionId', $document->getAttribute('resourceId', '')))
->setAttribute('resourceType', $document->getAttribute('resourceType', 'functions'));
break;
case 'functions':
/*
1. Convert "deployment" to "deploymentId"
--- Fetch activeDeployment from "deploymentId"
2. Fill "deploymentCreatedAt" with deployment's "$createdAt"
--- Fetch latestDeployment using find()
3. Fill latestDeploymentId with latestDeployment's "$id"
4. Fill latestDeploymentInternalId with latestDeployment's "$internalId"
5. Fill latestDeploymentCreatedAt with latestDeployment's "$createdAt"
6. Fill latestDeploymentStatus with latestDeployment's build's "status"
*/
if ($document->getAttribute('deployment')) {
$document->setAttribute('deploymentId', $document->getAttribute('deployment', $document->getAttribute('deploymentId', '')));
}
$deploymentId = $document->getAttribute('deploymentId');
$deployment = $this->dbForProject->getDocument('deployments', $deploymentId);
$document->setAttribute('deploymentCreatedAt', $deployment->getCreatedAt());
$latestDeployment = $this->dbForProject->findOne('deployments', [
Query::orderDesc(),
Query::equal('resourceId', [$document->getId()]),
Query::equal('resourceType', ['functions']),
]);
$latestBuild = $this->dbForProject->getDocument('builds', $latestDeployment->getAttribute('buildId', ''));
$document
->setAttribute('latestDeploymentId', $latestDeployment->getId())
->setAttribute('latestDeploymentInternalId', $latestDeployment->getInternalId())
->setAttribute('latestDeploymentCreatedAt', $latestDeployment->getCreatedAt())
->setAttribute('latestDeploymentStatus', $latestBuild->getAttribute('status', $document->getAttribute('latestDeploymentStatus', '')));
break;
case 'deployments':
/*
6. Convert "commands" to "buildCommands"
7. Convert "path" to "sourcePath"
8. Convert "size" to "sourceSize"
9. Convert "metadata" to "sourceMetadata"
10. Convert "chunksTotal" to "sourceChunksTotal"
11. Convert "chunksUploaded" to "sourceChunksUploaded"
--- Get build of deployment
12. Convert build's "startTime" to "buildStartedAt"
13. Convert build's "endTime" to "buildEndedAt"
14. Convert build's "duration" to "buildDuration"
15. Convert build's "size" to "buildSize"
16. Convert build's "status" to "status"
17. Convert build's "path" to "buildPath"
18. Convert build's "logs" to "buildLogs"
19. Fill "totalSize" with "buildSize" plus "sourceSize"
*/
$document
->setAttribute('buildCommands', $document->getAttribute('commands', $document->getAttribute('buildCommands', '')))
->setAttribute('sourcePath', $document->getAttribute('path', $document->getAttribute('sourcePath', '')))
->setAttribute('sourceSize', $document->getAttribute('size', $document->getAttribute('sourceSize', 0)))
->setAttribute('sourceMetadata', $document->getAttribute('metadata', $document->getAttribute('sourceMetadata', [])))
->setAttribute('sourceChunksTotal', $document->getAttribute('chunksTotal', $document->getAttribute('sourceChunksTotal', 0)))
->setAttribute('sourceChunksUploaded', $document->getAttribute('chunksUploaded', $document->getAttribute('sourceChunksUploaded', 0)));
$build = new Document();
if (!empty($document->getAttribute('buildId'))) {
$build = $this->dbForProject->getDocument('builds', $document->getAttribute('buildId'));
}
$document
->setAttribute('buildStartedAt', $build->getAttribute('startTime', $document->getAttribute('buildStartTime', '')))
->setAttribute('buildEndedAt', $build->getAttribute('endTime', $document->getAttribute('buildEndTime', '')))
->setAttribute('buildDuration', $build->getAttribute('duration', $document->getAttribute('buildDuration', 0)))
->setAttribute('buildSize', $build->getAttribute('size', $document->getAttribute('buildSize', 0)))
->setAttribute('status', $build->getAttribute('status', $document->getAttribute('status', '')))
->setAttribute('buildPath', $build->getAttribute('path', $document->getAttribute('buildPath', '')))
->setAttribute('buildLogs', $build->getAttribute('logs', $document->getAttribute('buildLogs', '')));
$totalSize = $document->getAttribute('buildSize', 0)
+ $document->getAttribute('sourceSize', 0);
$document->setAttribute('totalSize', $totalSize);
break;
case 'migrations':
/*
1. Fill "options" with "[]"
*/
$document->setAttribute('options', $document->getAttribute('options', []));
break;
default:
break;
}
return $document;
}
private function cleanCollections(): void
{
$projectInternalId = $this->project->getInternalId();
$collectionType = match ($projectInternalId) {
'console' => 'console',
default => 'projects',
};
@ -48,36 +479,129 @@ class V22 extends Migration
foreach ($collections as $collection) {
$id = $collection['$id'];
Console::log("Migrating Collection \"{$id}\"");
$this->dbForProject->setNamespace("_$internalProjectId");
Console::log("Cleaning up collection \"{$id}\"");
switch ($id) {
case 'installations':
// Create personalAccessToken attribute
try {
$this->createAttributeFromCollection($this->dbForProject, $id, 'personalAccessToken');
} catch (Throwable $th) {
Console::warning("'personalAccessToken' from {$id}: {$th->getMessage()}");
}
// Create personalAccessTokenExpiry attribute
try {
$this->createAttributeFromCollection($this->dbForProject, $id, 'personalAccessTokenExpiry');
} catch (Throwable $th) {
Console::warning("'personalAccessTokenExpiry' from {$id}: {$th->getMessage()}");
}
// Create personalRefreshToken attribute
try {
$this->createAttributeFromCollection($this->dbForProject, $id, 'personalRefreshToken');
} catch (Throwable $th) {
Console::warning("'personalRefreshToken' from {$id}: {$th->getMessage()}");
case '_metadata':
if (!$this->dbForProject->getCollection('builds')->isEmpty()) {
$this->dbForProject->deleteCollection('builds');
}
break;
}
case 'rules':
$attributes = [
'resourceId',
'resourceInternalId',
'resourceType',
];
foreach ($attributes as $attribute) {
try {
$this->dbForProject->deleteAttribute($id, $attribute);
} catch (\Throwable $th) {
Console::warning("Failed to delete attribute \"$attribute\" from collection {$id}: {$th->getMessage()}");
}
}
usleep(50000);
$indexesToDelete = [
'_key_resourceId',
'_key_resourceInternalId',
'_key_resourceType',
];
foreach ($indexesToDelete as $index) {
try {
$this->dbForProject->deleteIndex($id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to delete index \"$index\" from collection {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'functions':
try {
$this->dbForProject->deleteAttribute($id, 'deployment');
} catch (\Throwable $th) {
Console::warning("Failed to delete attribute \"deployment\" from collection {$id}: {$th->getMessage()}");
}
$indexesToDelete = [
'_key_deployment'
];
foreach ($indexesToDelete as $index) {
try {
$this->dbForProject->deleteIndex($id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to delete index \"$index\" from collection {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'deployments':
$attributes = [
'buildInternalId',
'buildId',
'commands',
'path',
'size',
'metadata',
'chunksTotal',
'chunksUploaded',
'search'
];
foreach ($attributes as $attribute) {
try {
$this->dbForProject->deleteAttribute($id, $attribute);
} catch (\Throwable $th) {
Console::warning("Failed to delete attribute \"$attribute\" from collection {$id}: {$th->getMessage()}");
}
}
$indexesToDelete = [
'_key_buildId',
'_key_size',
'_key_search'
];
foreach ($indexesToDelete as $index) {
try {
$this->dbForProject->deleteIndex($id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to delete index \"$index\" from collection {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'executions':
$attributes = [
'functionId',
'functionInternalId',
'search'
];
foreach ($attributes as $attribute) {
try {
$this->dbForProject->deleteAttribute($id, $attribute);
} catch (\Throwable $th) {
Console::warning("Failed to delete attribute \"$attribute\" from collection {$id}: {$th->getMessage()}");
}
}
$indexesToDelete = [
'_key_function',
'_fulltext_search'
];
foreach ($indexesToDelete as $index) {
try {
$this->dbForProject->deleteIndex($id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to delete index \"$index\" from collection {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
default:
break;
}
}
}
}

View file

@ -1,607 +0,0 @@
<?php
namespace Appwrite\Migration\Version;
use Appwrite\Migration\Migration;
use Exception;
use Throwable;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Conflict;
use Utopia\Database\Exception\Structure;
use Utopia\Database\Exception\Timeout;
use Utopia\Database\Query;
class V23 extends Migration
{
/**
* @throws Throwable
*/
public function execute(): void
{
/**
* Disable SubQueries for Performance.
*/
foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryDevKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables', 'subQueryChallenges', 'subQueryProjectVariables', 'subQueryTargets', 'subQueryTopicTargets'] as $name) {
Database::addFilter(
$name,
fn () => null,
fn () => []
);
}
Console::info('Migrating collections');
$this->migrateCollections();
Console::info('Migrating documents');
$this->forEachDocument($this->migrateDocument(...));
Console::info('Cleaning up collections');
$this->cleanCollections();
}
/**
* Migrate Collections.
*
* @return void
* @throws Exception|Throwable
*/
private function migrateCollections(): void
{
$projectInternalId = $this->project->getInternalId();
if (empty($projectInternalId)) {
throw new Exception('Project ID is null');
}
$collectionType = match ($projectInternalId) {
'console' => 'console',
default => 'projects',
};
$collections = $this->collections[$collectionType];
foreach ($collections as $collection) {
$id = $collection['$id'];
if (empty($id)) {
continue;
}
Console::log("Migrating collection \"{$id}\"");
switch ($id) {
case '_metadata':
$this->createCollection('sites');
$this->createCollection('resourceTokens');
if ($projectInternalId === 'console') {
$this->createCollection('devKeys');
}
break;
case 'identities':
$attributes = [
'scopes',
'expire',
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'projects':
try {
$attributes = [
'devKeys',
];
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'rules':
$attributes = [
'type',
'trigger',
'redirectUrl',
'redirectStatusCode',
'deploymentResourceType',
'deploymentId',
'deploymentInternalId',
'deploymentResourceId',
'deploymentResourceInternalId',
'deploymentVcsProviderBranch',
'search'
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$indexes = [
'_key_search',
'_key_type',
'_key_trigger',
'_key_deploymentResourceType',
'_key_deploymentResourceId',
'_key_deploymentResourceInternalId',
'_key_deploymentId',
'_key_deploymentInternalId',
'_key_deploymentVcsProviderBranch',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'memberships':
$indexes = [
'_key_roles',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (Throwable $th) {
Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'migrations':
$attributes = [
'options',
'resourceId',
'resourceType'
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$indexes = [
'_key_resource_id',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (Throwable $th) {
Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'functions':
$attributes = [
'deploymentId',
'deploymentCreatedAt',
'latestDeploymentId',
'latestDeploymentInternalId',
'latestDeploymentCreatedAt',
'latestDeploymentStatus',
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$indexes = [
'_key_deploymentId',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (Throwable $th) {
Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'deployments':
$attributes = [
'buildCommands',
'sourcePath',
'buildOutput',
'adapter',
'fallbackFile',
'sourceSize',
'sourceMetadata',
'sourceChunksTotal',
'sourceChunksUploaded',
'screenshotLight',
'screenshotDark',
'buildStartedAt',
'buildEndedAt',
'buildDuration',
'buildSize',
'status',
'buildPath',
'buildLogs',
'totalSize',
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$indexes = [
'_key_sourceSize',
'_key_buildSize',
'_key_totalSize',
'_key_buildDuration',
'_key_type',
'_key_status',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'executions':
$attributes = [
'resourceInternalId',
'resourceId',
'resourceType'
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$indexes = [
'_key_resource',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'variables':
$attributes = [
'secret',
];
try {
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
} catch (\Throwable $th) {
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
}
$this->dbForProject->purgeCachedCollection($id);
break;
default:
break;
}
}
}
/**
* Fix run on each document
*
* @param Document $document
* @return Document
* @throws Conflict
* @throws Structure
* @throws Timeout
* @throws \Utopia\Database\Exception
* @throws \Utopia\Database\Exception\Authorization
* @throws \Utopia\Database\Exception\Query
*/
private function migrateDocument(Document $document): Document
{
switch ($document->getCollection()) {
case 'rules':
/*
1. Convert "resourceType" to "type". Convert "function" to "deployment"
2. Convert "resourceId" to "deploymentResourceId"
3. Convert "resourceInternalId" to "deploymentResourceInternalId"
4. Fill "trigger" with "manual"
5. Fill "deploymentResourceType". If "resourceType" is "function", set "deploymentResourceType" to "function"
6. Fill "search" with "{$id} {domain}"
7. Fill "deploymentId" and "deploymentInternalId". If "deploymentResourceType" is "function", get project DB, and find function with ID "resourceId". Then fill rule's "deploymentId" with function's "deployment", and "deploymentId" as backup
*/
$deploymentResourceType = null;
$type = $document->getAttribute('resourceType', $document->getAttribute('type', ''));
if ($type === 'function') {
$type = 'deployment';
$deploymentResourceType = 'function';
}
$resourceId = $document->getAttribute('resourceId', $document->getAttribute('deploymentResourceId'));
$resourceInternalId = $document->getAttribute('resourceInternalId', $document->getAttribute('deploymentResourceInternalId'));
$document
->setAttribute('type', $type)
->setAttribute('trigger', 'manual')
->setAttribute('deploymentResourceId', $resourceId)
->setAttribute('deploymentResourceInternalId', $resourceInternalId)
->setAttribute('deploymentResourceType', $document->getAttribute('deploymentResourceType', $deploymentResourceType))
->setAttribute('search', \implode(' ', [$document->getId(), $document->getAttribute('domain', '')]));
if ($deploymentResourceType === 'function') {
$project = $this->dbForProject->getDocument('projects', $document->getAttribute('projectId'));
$dbForOwnerProject = ($this->getProjectDB)($project);
$function = $dbForOwnerProject->getDocument('functions', $resourceId);
$deploymentId = $function->getAttribute('deployment', $function->getAttribute('deploymentId', $document->getAttribute('deploymentId')));
$deploymentInternalId = $function->getAttribute('deploymentInternalId', $document->getAttribute('deploymentInternalId', ''));
$document
->setAttribute('deploymentId', $deploymentId)
->setAttribute('deploymentInternalId', $deploymentInternalId);
}
break;
case 'variables':
/*
1. Fill "secret" with "false"
*/
$document->setAttribute('secret', $document->getAttribute('secret', false));
break;
case 'executions':
/*
1. Convert "functionInternalId" to "resourceInternalId"
2. Convert "functionId" to "resourceId"
3. Fill "resourceType" with "functions"
*/
$document
->setAttribute('resourceInternalId', $document->getAttribute('functionInternalId', $document->getAttribute('resourceInternalId')))
->setAttribute('resourceId', $document->getAttribute('functionId', $document->getAttribute('resourceId', '')))
->setAttribute('resourceType', $document->getAttribute('resourceType', 'functions'));
break;
case 'functions':
/*
1. Convert "deployment" to "deploymentId"
--- Fetch activeDeployment from "deploymentId"
2. Fill "deploymentCreatedAt" with deployment's "$createdAt"
--- Fetch latestDeployment using find()
3. Fill latestDeploymentId with latestDeployment's "$id"
4. Fill latestDeploymentInternalId with latestDeployment's "$internalId"
5. Fill latestDeploymentCreatedAt with latestDeployment's "$createdAt"
6. Fill latestDeploymentStatus with latestDeployment's build's "status"
*/
if ($document->getAttribute('deployment')) {
$document->setAttribute('deploymentId', $document->getAttribute('deployment', $document->getAttribute('deploymentId', '')));
}
$deploymentId = $document->getAttribute('deploymentId');
$deployment = $this->dbForProject->getDocument('deployments', $deploymentId);
$document->setAttribute('deploymentCreatedAt', $deployment->getCreatedAt());
$latestDeployment = $this->dbForProject->findOne('deployments', [
Query::orderDesc(),
Query::equal('resourceId', [$document->getId()]),
Query::equal('resourceType', ['functions']),
]);
$latestBuild = $this->dbForProject->getDocument('builds', $latestDeployment->getAttribute('buildId', ''));
$document
->setAttribute('latestDeploymentId', $latestDeployment->getId())
->setAttribute('latestDeploymentInternalId', $latestDeployment->getInternalId())
->setAttribute('latestDeploymentCreatedAt', $latestDeployment->getCreatedAt())
->setAttribute('latestDeploymentStatus', $latestBuild->getAttribute('status', $document->getAttribute('latestDeploymentStatus', '')));
break;
case 'deployments':
/*
6. Convert "commands" to "buildCommands"
7. Convert "path" to "sourcePath"
8. Convert "size" to "sourceSize"
9. Convert "metadata" to "sourceMetadata"
10. Convert "chunksTotal" to "sourceChunksTotal"
11. Convert "chunksUploaded" to "sourceChunksUploaded"
--- Get build of deployment
12. Convert build's "startTime" to "buildStartedAt"
13. Convert build's "endTime" to "buildEndedAt"
14. Convert build's "duration" to "buildDuration"
15. Convert build's "size" to "buildSize"
16. Convert build's "status" to "status"
17. Convert build's "path" to "buildPath"
18. Convert build's "logs" to "buildLogs"
19. Fill "totalSize" with "buildSize" plus "sourceSize"
*/
$document
->setAttribute('buildCommands', $document->getAttribute('commands', $document->getAttribute('buildCommands', '')))
->setAttribute('sourcePath', $document->getAttribute('path', $document->getAttribute('sourcePath', '')))
->setAttribute('sourceSize', $document->getAttribute('size', $document->getAttribute('sourceSize', 0)))
->setAttribute('sourceMetadata', $document->getAttribute('metadata', $document->getAttribute('sourceMetadata', [])))
->setAttribute('sourceChunksTotal', $document->getAttribute('chunksTotal', $document->getAttribute('sourceChunksTotal', 0)))
->setAttribute('sourceChunksUploaded', $document->getAttribute('chunksUploaded', $document->getAttribute('sourceChunksUploaded', 0)));
$build = new Document();
if (!empty($document->getAttribute('buildId'))) {
$build = $this->dbForProject->getDocument('builds', $document->getAttribute('buildId'));
}
$document
->setAttribute('buildStartedAt', $build->getAttribute('startTime', $document->getAttribute('buildStartTime', '')))
->setAttribute('buildEndedAt', $build->getAttribute('endTime', $document->getAttribute('buildEndTime', '')))
->setAttribute('buildDuration', $build->getAttribute('duration', $document->getAttribute('buildDuration', 0)))
->setAttribute('buildSize', $build->getAttribute('size', $document->getAttribute('buildSize', 0)))
->setAttribute('status', $build->getAttribute('status', $document->getAttribute('status', '')))
->setAttribute('buildPath', $build->getAttribute('path', $document->getAttribute('buildPath', '')))
->setAttribute('buildLogs', $build->getAttribute('logs', $document->getAttribute('buildLogs', '')));
$totalSize = $document->getAttribute('buildSize', 0)
+ $document->getAttribute('sourceSize', 0);
$document->setAttribute('totalSize', $totalSize);
break;
case 'migrations':
/*
1. Fill "options" with "[]"
*/
$document->setAttribute('options', $document->getAttribute('options', []));
break;
default:
break;
}
return $document;
}
private function cleanCollections(): void
{
$projectInternalId = $this->project->getInternalId();
$collectionType = match ($projectInternalId) {
'console' => 'console',
default => 'projects',
};
$collections = $this->collections[$collectionType];
foreach ($collections as $collection) {
$id = $collection['$id'];
Console::log("Cleaning up collection \"{$id}\"");
switch ($id) {
case '_metadata':
if (!$this->dbForProject->getCollection('builds')->isEmpty()) {
$this->dbForProject->deleteCollection('builds');
}
break;
case 'rules':
$attributes = [
'resourceId',
'resourceInternalId',
'resourceType',
];
foreach ($attributes as $attribute) {
try {
$this->dbForProject->deleteAttribute($id, $attribute);
} catch (\Throwable $th) {
Console::warning("Failed to delete attribute \"$attribute\" from collection {$id}: {$th->getMessage()}");
}
}
$indexesToDelete = [
'_key_resourceId',
'_key_resourceInternalId',
'_key_resourceType',
];
foreach ($indexesToDelete as $index) {
try {
$this->dbForProject->deleteIndex($id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to delete index \"$index\" from collection {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'functions':
try {
$this->dbForProject->deleteAttribute($id, 'deployment');
} catch (\Throwable $th) {
Console::warning("Failed to delete attribute \"deployment\" from collection {$id}: {$th->getMessage()}");
}
$indexesToDelete = [
'_key_deployment'
];
foreach ($indexesToDelete as $index) {
try {
$this->dbForProject->deleteIndex($id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to delete index \"$index\" from collection {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'deployments':
$attributes = [
'buildInternalId',
'buildId',
'commands',
'path',
'size',
'metadata',
'chunksTotal',
'chunksUploaded',
'search'
];
foreach ($attributes as $attribute) {
try {
$this->dbForProject->deleteAttribute($id, $attribute);
} catch (\Throwable $th) {
Console::warning("Failed to delete attribute \"$attribute\" from collection {$id}: {$th->getMessage()}");
}
}
$indexesToDelete = [
'_key_buildId',
'_key_size',
'_key_search'
];
foreach ($indexesToDelete as $index) {
try {
$this->dbForProject->deleteIndex($id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to delete index \"$index\" from collection {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'executions':
$attributes = [
'functionId',
'functionInternalId',
'search'
];
foreach ($attributes as $attribute) {
try {
$this->dbForProject->deleteAttribute($id, $attribute);
} catch (\Throwable $th) {
Console::warning("Failed to delete attribute \"$attribute\" from collection {$id}: {$th->getMessage()}");
}
}
$indexesToDelete = [
'_key_function',
'_fulltext_search'
];
foreach ($indexesToDelete as $index) {
try {
$this->dbForProject->deleteIndex($id, $index);
} catch (\Throwable $th) {
Console::warning("Failed to delete index \"$index\" from collection {$id}: {$th->getMessage()}");
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
default:
break;
}
}
}
}