mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge remote-tracking branch 'origin/1.9.x' into revert-11585-revert-11402-sync-mongodb
This commit is contained in:
+119
-86
@@ -1,100 +1,133 @@
|
||||
# Version 1.8.1
|
||||
# Version 1.9.0
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Notable changes
|
||||
|
||||
* Add branch deployments support in [#10486](https://github.com/appwrite/appwrite/pull/10486)
|
||||
* Add TanStack Start sites support in [#10681](https://github.com/appwrite/appwrite/pull/10681)
|
||||
* Add Next.js standalone support in [#10747](https://github.com/appwrite/appwrite/pull/10747)
|
||||
* Add Resend integration in [#10690](https://github.com/appwrite/appwrite/pull/10690)
|
||||
* Add option to enable/disable image transformations per-bucket in [#10722](https://github.com/appwrite/appwrite/pull/10722)
|
||||
* Add operators support in [#10735](https://github.com/appwrite/appwrite/pull/10735) and [#10800](https://github.com/appwrite/appwrite/pull/10800)
|
||||
* Add function and sites stats in [#10786](https://github.com/appwrite/appwrite/pull/10786)
|
||||
* Add disable count feature in [#10668](https://github.com/appwrite/appwrite/pull/10668)
|
||||
* Add ElevenLabs site template in [#10782](https://github.com/appwrite/appwrite/pull/10782)
|
||||
* Add suggested environment variables in [#10795](https://github.com/appwrite/appwrite/pull/10795)
|
||||
* Update GeoDB database in [#10890](https://github.com/appwrite/appwrite/pull/10890)
|
||||
* Update Flutter default build runtime in [#10807](https://github.com/appwrite/appwrite/pull/10807)
|
||||
* Upgrade runtimes in [#10804](https://github.com/appwrite/appwrite/pull/10804)
|
||||
* Add PostgreSQL database adapter in [#9772](https://github.com/appwrite/appwrite/pull/9772) and [#11293](https://github.com/appwrite/appwrite/pull/11293)
|
||||
* Add MongoDB support in [#11312](https://github.com/appwrite/appwrite/pull/11312)
|
||||
* Add new webhooks API in [#11033](https://github.com/appwrite/appwrite/pull/11033) and [#11566](https://github.com/appwrite/appwrite/pull/11566)
|
||||
* Add schedules API endpoints in [#11331](https://github.com/appwrite/appwrite/pull/11331)
|
||||
* Add project labels in [#11056](https://github.com/appwrite/appwrite/pull/11056) and project status attribute in [#11291](https://github.com/appwrite/appwrite/pull/11291)
|
||||
* Add resource-based API key structure in [#11003](https://github.com/appwrite/appwrite/pull/11003) with custom ID support in [#11277](https://github.com/appwrite/appwrite/pull/11277) and list queries in [#11278](https://github.com/appwrite/appwrite/pull/11278)
|
||||
* Add string types (varchar, text, mediumtext, longtext) for attributes in [#11174](https://github.com/appwrite/appwrite/pull/11174)
|
||||
* Add encrypt parameter to string attribute types in [#11334](https://github.com/appwrite/appwrite/pull/11334)
|
||||
* Add int64 format support for integer attributes in [#11123](https://github.com/appwrite/appwrite/pull/11123)
|
||||
* Add collection and row storage size in [#11254](https://github.com/appwrite/appwrite/pull/11254) and [#11069](https://github.com/appwrite/appwrite/pull/11069)
|
||||
* Add totalSize on list responses in [#11102](https://github.com/appwrite/appwrite/pull/11102)
|
||||
* Add custom start command for sites and functions in [#10842](https://github.com/appwrite/appwrite/pull/10842)
|
||||
* Add separate build/runtime specifications in [#10849](https://github.com/appwrite/appwrite/pull/10849)
|
||||
* Add deployment retention for sites and functions in [#10959](https://github.com/appwrite/appwrite/pull/10959)
|
||||
* Add auto-delete old deployments in [#10959](https://github.com/appwrite/appwrite/pull/10959)
|
||||
* Add custom JWT duration in [#11009](https://github.com/appwrite/appwrite/pull/11009)
|
||||
* Add multiple application domains support in [#10911](https://github.com/appwrite/appwrite/pull/10911)
|
||||
* Add GraphQL introspection in [#11159](https://github.com/appwrite/appwrite/pull/11159)
|
||||
* Add realtime query subscriptions in [#11202](https://github.com/appwrite/appwrite/pull/11202) and [#11237](https://github.com/appwrite/appwrite/pull/11237)
|
||||
* Add realtime metrics for connections, messages, and bandwidth in [#11438](https://github.com/appwrite/appwrite/pull/11438) and [#11488](https://github.com/appwrite/appwrite/pull/11488)
|
||||
* Add messaging resource migration support in [#11495](https://github.com/appwrite/appwrite/pull/11495)
|
||||
* Add cached documents list in [#10832](https://github.com/appwrite/appwrite/pull/10832)
|
||||
* Add project queries support in [#10990](https://github.com/appwrite/appwrite/pull/10990)
|
||||
* Add batch document creation in [#10894](https://github.com/appwrite/appwrite/pull/10894)
|
||||
* Add async screenshots in [#11110](https://github.com/appwrite/appwrite/pull/11110)
|
||||
* Add new file parameters (encryption, compression) in [#11135](https://github.com/appwrite/appwrite/pull/11135)
|
||||
* Add new site templates in [#10031](https://github.com/appwrite/appwrite/pull/10031)
|
||||
* Add VCS repository authorized field in [#11421](https://github.com/appwrite/appwrite/pull/11421)
|
||||
* Add trusted console projects in [#11248](https://github.com/appwrite/appwrite/pull/11248)
|
||||
|
||||
### Refactoring
|
||||
|
||||
* Refactor to Utopia Platform modules architecture in [#11035](https://github.com/appwrite/appwrite/pull/11035), [#11049](https://github.com/appwrite/appwrite/pull/11049), [#11057](https://github.com/appwrite/appwrite/pull/11057), [#11103](https://github.com/appwrite/appwrite/pull/11103), [#11208](https://github.com/appwrite/appwrite/pull/11208), and [#11398](https://github.com/appwrite/appwrite/pull/11398)
|
||||
* Refactor auth to single instance in [#10872](https://github.com/appwrite/appwrite/pull/10872) and [#11130](https://github.com/appwrite/appwrite/pull/11130)
|
||||
* Refactor usage metrics to stateless publisher pattern in [#11449](https://github.com/appwrite/appwrite/pull/11449)
|
||||
* Refactor messaging and queue in [#10961](https://github.com/appwrite/appwrite/pull/10961)
|
||||
* Refactor functions schedule in [#10913](https://github.com/appwrite/appwrite/pull/10913)
|
||||
* Refactor make Bus dispatch synchronous in [#11449](https://github.com/appwrite/appwrite/pull/11449)
|
||||
* Remove proxy container in [#11039](https://github.com/appwrite/appwrite/pull/11039)
|
||||
|
||||
### Performance
|
||||
|
||||
* Optimize updateDocument() calls to use sparse documents in [#11465](https://github.com/appwrite/appwrite/pull/11465)
|
||||
* Optimize Dockerfile in [#10947](https://github.com/appwrite/appwrite/pull/10947)
|
||||
* Improve domain caching in [#11346](https://github.com/appwrite/appwrite/pull/11346)
|
||||
* Improve memory usage in [#11345](https://github.com/appwrite/appwrite/pull/11345)
|
||||
* Fix memory leak in [#11067](https://github.com/appwrite/appwrite/pull/11067) and [#11241](https://github.com/appwrite/appwrite/pull/11241)
|
||||
* Improve realtime performance in [#11251](https://github.com/appwrite/appwrite/pull/11251)
|
||||
* Enable SMTP keep-alive to reuse connections across mail jobs in [#11496](https://github.com/appwrite/appwrite/pull/11496)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix duplicate document error while creating file in [#10891](https://github.com/appwrite/appwrite/pull/10891)
|
||||
* Fix "Update external deployment (authorize)" throwing 500 error due to invalid query in [#10888](https://github.com/appwrite/appwrite/pull/10888)
|
||||
* Fix error setting user password in [#10889](https://github.com/appwrite/appwrite/pull/10889)
|
||||
* Fix error generating email MFA challenges in [#10884](https://github.com/appwrite/appwrite/pull/10884)
|
||||
* Fix file token expiry in [#10877](https://github.com/appwrite/appwrite/pull/10877)
|
||||
* Fix TanStack Nitro default in [#10860](https://github.com/appwrite/appwrite/pull/10860)
|
||||
* Fix TanStack builds in [#10767](https://github.com/appwrite/appwrite/pull/10767)
|
||||
* Fix nullable validation in [#10819](https://github.com/appwrite/appwrite/pull/10819) and [#10778](https://github.com/appwrite/appwrite/pull/10778)
|
||||
* Fix WebP library in [#10738](https://github.com/appwrite/appwrite/pull/10738)
|
||||
* Fix batch writes in [#10812](https://github.com/appwrite/appwrite/pull/10812)
|
||||
* Fix error handler error in [#10719](https://github.com/appwrite/appwrite/pull/10719)
|
||||
* Fix Next 16 compatibility in [#10713](https://github.com/appwrite/appwrite/pull/10713)
|
||||
* Fix stats usage memory leak in [#10683](https://github.com/appwrite/appwrite/pull/10683)
|
||||
* Fix author URL in template deployments in [#10535](https://github.com/appwrite/appwrite/pull/10535)
|
||||
* Fix VCS lock deletion in [#10691](https://github.com/appwrite/appwrite/pull/10691)
|
||||
* Fix blocked user/resource errors from 401 to 403 in [#11469](https://github.com/appwrite/appwrite/pull/11469)
|
||||
* Fix OAuth for custom domains in [#10967](https://github.com/appwrite/appwrite/pull/10967) and [#11269](https://github.com/appwrite/appwrite/pull/11269)
|
||||
* Fix OAuth redirect custom scheme in [#11292](https://github.com/appwrite/appwrite/pull/11292)
|
||||
* Fix OAuth verified emails in [#10986](https://github.com/appwrite/appwrite/pull/10986)
|
||||
* Fix MFA recovery code validation in [#10925](https://github.com/appwrite/appwrite/pull/10925)
|
||||
* Fix users allow updating phone number to empty in [#11521](https://github.com/appwrite/appwrite/pull/11521)
|
||||
* Fix users optional name error in [#11413](https://github.com/appwrite/appwrite/pull/11413)
|
||||
* Fix file permissions in [#11026](https://github.com/appwrite/appwrite/pull/11026)
|
||||
* Fix bulk insert webhook validation in [#11022](https://github.com/appwrite/appwrite/pull/11022)
|
||||
* Fix execution status update in [#11134](https://github.com/appwrite/appwrite/pull/11134)
|
||||
* Fix execution timeout status in [#11400](https://github.com/appwrite/appwrite/pull/11400)
|
||||
* Fix CORS wildcard in [#10956](https://github.com/appwrite/appwrite/pull/10956)
|
||||
* Fix preflight requests in [#10943](https://github.com/appwrite/appwrite/pull/10943)
|
||||
* Fix SMTP auth check in [#10939](https://github.com/appwrite/appwrite/pull/10939)
|
||||
* Fix scheduled executions trigger in [#10922](https://github.com/appwrite/appwrite/pull/10922)
|
||||
* Fix schedule executions bug in [#10916](https://github.com/appwrite/appwrite/pull/10916)
|
||||
* Fix deployment enum missing canceled value in [#11179](https://github.com/appwrite/appwrite/pull/11179)
|
||||
* Fix invalid chunk total in [#11270](https://github.com/appwrite/appwrite/pull/11270)
|
||||
* Fix sites domains in [#11240](https://github.com/appwrite/appwrite/pull/11240) and [#11355](https://github.com/appwrite/appwrite/pull/11355)
|
||||
* Fix rule domains in [#11355](https://github.com/appwrite/appwrite/pull/11355) and [#11276](https://github.com/appwrite/appwrite/pull/11276)
|
||||
* Fix rules deletion in [#11575](https://github.com/appwrite/appwrite/pull/11575)
|
||||
* Fix VCS template flow in [#11275](https://github.com/appwrite/appwrite/pull/11275)
|
||||
* Fix VCS comment empty in [#11490](https://github.com/appwrite/appwrite/pull/11490)
|
||||
* Fix DSN VCS error in [#11364](https://github.com/appwrite/appwrite/pull/11364)
|
||||
* Fix email URL params encoding in [#11369](https://github.com/appwrite/appwrite/pull/11369)
|
||||
* Fix missing email warning in [#11378](https://github.com/appwrite/appwrite/pull/11378)
|
||||
* Fix race condition in builds worker in [#11336](https://github.com/appwrite/appwrite/pull/11336)
|
||||
* Fix realtime regions in [#11414](https://github.com/appwrite/appwrite/pull/11414)
|
||||
* Fix realtime errors in [#11573](https://github.com/appwrite/appwrite/pull/11573)
|
||||
* Fix realtime TablesDB channels in [#11404](https://github.com/appwrite/appwrite/pull/11404) and [#11430](https://github.com/appwrite/appwrite/pull/11430)
|
||||
* Fix database shared table reconciliation in [#11578](https://github.com/appwrite/appwrite/pull/11578)
|
||||
* Fix PostgreSQL race condition in shared mode project creation in [#11536](https://github.com/appwrite/appwrite/pull/11536)
|
||||
* Fix compression enabled env in [#11171](https://github.com/appwrite/appwrite/pull/11171)
|
||||
* Fix deletes bug in [#10965](https://github.com/appwrite/appwrite/pull/10965)
|
||||
* Fix devkey scopes in [#10984](https://github.com/appwrite/appwrite/pull/10984)
|
||||
* Fix phone auth limit in [#11143](https://github.com/appwrite/appwrite/pull/11143)
|
||||
* Fix relationship document ID validation in [#11193](https://github.com/appwrite/appwrite/pull/11193)
|
||||
* Fix stale project overwrites OAuth in [#11461](https://github.com/appwrite/appwrite/pull/11461)
|
||||
* Fix storage health error swallowing in [#11492](https://github.com/appwrite/appwrite/pull/11492)
|
||||
* Fix Origin validator type error in [#11297](https://github.com/appwrite/appwrite/pull/11297)
|
||||
* Fix getScreenshot image format in [#11017](https://github.com/appwrite/appwrite/pull/11017)
|
||||
* Fix migration error handling in [#11457](https://github.com/appwrite/appwrite/pull/11457)
|
||||
* Fix deprecation warnings in [#11227](https://github.com/appwrite/appwrite/pull/11227)
|
||||
|
||||
### Installer
|
||||
|
||||
* New installer UI in [#11175](https://github.com/appwrite/appwrite/pull/11175) and [#11247](https://github.com/appwrite/appwrite/pull/11247)
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Add CSV export functionality in [#10546](https://github.com/appwrite/appwrite/pull/10546), [#10750](https://github.com/appwrite/appwrite/pull/10750), [#10813](https://github.com/appwrite/appwrite/pull/10813), and [#10847](https://github.com/appwrite/appwrite/pull/10847)
|
||||
* Add JWT disposition in [#10867](https://github.com/appwrite/appwrite/pull/10867)
|
||||
* Add screenshots endpoint in [#10675](https://github.com/appwrite/appwrite/pull/10675)
|
||||
* Add screenshot endpoint stats in [#10706](https://github.com/appwrite/appwrite/pull/10706)
|
||||
* Add users attributes in [#10688](https://github.com/appwrite/appwrite/pull/10688)
|
||||
* Add max build duration environment variable in [#10674](https://github.com/appwrite/appwrite/pull/10674)
|
||||
* Add custom realtime logger in [#10871](https://github.com/appwrite/appwrite/pull/10871)
|
||||
* Add logs in [#10869](https://github.com/appwrite/appwrite/pull/10869)
|
||||
* Improve MFA docs endpoint order in [#10793](https://github.com/appwrite/appwrite/pull/10793)
|
||||
* Auth refactor in [#10758](https://github.com/appwrite/appwrite/pull/10758), [#10837](https://github.com/appwrite/appwrite/pull/10837), [#10682](https://github.com/appwrite/appwrite/pull/10682), and [#10667](https://github.com/appwrite/appwrite/pull/10667)
|
||||
* Bump assistant to 0.8.4 in [#10887](https://github.com/appwrite/appwrite/pull/10887)
|
||||
* Bump database to 3.1.5 in [#10766](https://github.com/appwrite/appwrite/pull/10766)
|
||||
* Bump Utopia DNS in [#10761](https://github.com/appwrite/appwrite/pull/10761)
|
||||
* Update domains to 0.8.3 in [#10658](https://github.com/appwrite/appwrite/pull/10658)
|
||||
* Update domains to 0.9.1 in [#10678](https://github.com/appwrite/appwrite/pull/10678)
|
||||
* Update Apple Swift to 13.3.0 in [#10679](https://github.com/appwrite/appwrite/pull/10679)
|
||||
* Update Apple Swift in [#10663](https://github.com/appwrite/appwrite/pull/10663)
|
||||
* Update CLI to 10.2.2 in [#10672](https://github.com/appwrite/appwrite/pull/10672)
|
||||
* Update to CLI 12.0.0 in [#10853](https://github.com/appwrite/appwrite/pull/10853)
|
||||
* Update docs examples to use Permission class in [#10707](https://github.com/appwrite/appwrite/pull/10707)
|
||||
* Update SDK examples docs in [#10855](https://github.com/appwrite/appwrite/pull/10855)
|
||||
* Release Python SDK in [#10762](https://github.com/appwrite/appwrite/pull/10762)
|
||||
* Release Flutter 20.3.2 in [#10838](https://github.com/appwrite/appwrite/pull/10838)
|
||||
* Release Flutter/Dart add screenshot examples in [#10811](https://github.com/appwrite/appwrite/pull/10811)
|
||||
* Release PHP CLI in [#10791](https://github.com/appwrite/appwrite/pull/10791)
|
||||
* Release SDKs in [#10817](https://github.com/appwrite/appwrite/pull/10817)
|
||||
* Update SDKs in [#10694](https://github.com/appwrite/appwrite/pull/10694), [#10729](https://github.com/appwrite/appwrite/pull/10729), and [#10744](https://github.com/appwrite/appwrite/pull/10744)
|
||||
* Update SDK generator in [#10743](https://github.com/appwrite/appwrite/pull/10743)
|
||||
* Update database in [#10664](https://github.com/appwrite/appwrite/pull/10664)
|
||||
* Update README file in [#10763](https://github.com/appwrite/appwrite/pull/10763)
|
||||
* SDK release documentation in [#10745](https://github.com/appwrite/appwrite/pull/10745)
|
||||
* SDK release runtime config in [#10765](https://github.com/appwrite/appwrite/pull/10765)
|
||||
* Sync specs in [#10789](https://github.com/appwrite/appwrite/pull/10789)
|
||||
* Sync 1.8.0 in [#10677](https://github.com/appwrite/appwrite/pull/10677)
|
||||
* Add workflow for issue triage in [#10718](https://github.com/appwrite/appwrite/pull/10718)
|
||||
* Add issue auto-labeler in [#10700](https://github.com/appwrite/appwrite/pull/10700)
|
||||
* Add AI moderator repo in [#10717](https://github.com/appwrite/appwrite/pull/10717)
|
||||
* Browser bump in [#10850](https://github.com/appwrite/appwrite/pull/10850)
|
||||
* Template type enum override in [#10848](https://github.com/appwrite/appwrite/pull/10848)
|
||||
* VCS reference type in [#10852](https://github.com/appwrite/appwrite/pull/10852)
|
||||
* Index scope description in [#10851](https://github.com/appwrite/appwrite/pull/10851)
|
||||
* Config for environment in [#10833](https://github.com/appwrite/appwrite/pull/10833)
|
||||
* Format instance in [#10830](https://github.com/appwrite/appwrite/pull/10830)
|
||||
* Replace sleep in webhooks service in [#10656](https://github.com/appwrite/appwrite/pull/10656)
|
||||
* Update email composer in [#10720](https://github.com/appwrite/appwrite/pull/10720)
|
||||
* Update facts on GitHub sites and functions in [#10593](https://github.com/appwrite/appwrite/pull/10593) and [#10771](https://github.com/appwrite/appwrite/pull/10771)
|
||||
* Fix wrong user type in [#10875](https://github.com/appwrite/appwrite/pull/10875)
|
||||
* Fix limit and offset computation in [#10880](https://github.com/appwrite/appwrite/pull/10880)
|
||||
* Fix enum examples in [#10828](https://github.com/appwrite/appwrite/pull/10828)
|
||||
* Fix response models multi-methods in [#10815](https://github.com/appwrite/appwrite/pull/10815)
|
||||
* Fix undefined variable in [#10654](https://github.com/appwrite/appwrite/pull/10654)
|
||||
* Fix undefined sequence in [#10652](https://github.com/appwrite/appwrite/pull/10652)
|
||||
* Fix description in [#10702](https://github.com/appwrite/appwrite/pull/10702)
|
||||
* Fix warning in builds worker in [#10705](https://github.com/appwrite/appwrite/pull/10705)
|
||||
* Fix sites create deployment docs in [#10566](https://github.com/appwrite/appwrite/pull/10566)
|
||||
* Fix test dependencies projects in [#10655](https://github.com/appwrite/appwrite/pull/10655)
|
||||
* Fix list sites test in [#10726](https://github.com/appwrite/appwrite/pull/10726)
|
||||
* Add audits upgrade in [#10953](https://github.com/appwrite/appwrite/pull/10953)
|
||||
* Add graceful workers shutdown in [#11104](https://github.com/appwrite/appwrite/pull/11104)
|
||||
* Add pool resilience in [#11139](https://github.com/appwrite/appwrite/pull/11139)
|
||||
* Add function queue job TTL in [#11226](https://github.com/appwrite/appwrite/pull/11226)
|
||||
* Add cleanup stale executions in [#11146](https://github.com/appwrite/appwrite/pull/11146)
|
||||
* Add success abuse reset in [#11085](https://github.com/appwrite/appwrite/pull/11085)
|
||||
* Add SMTP connection validation in [#11079](https://github.com/appwrite/appwrite/pull/11079)
|
||||
* Add allow custom email sender in [#10945](https://github.com/appwrite/appwrite/pull/10945)
|
||||
* Add array domains env support in [#11213](https://github.com/appwrite/appwrite/pull/11213)
|
||||
* Add file create after success hook in [#11054](https://github.com/appwrite/appwrite/pull/11054)
|
||||
* Add delete subscribers in [#11115](https://github.com/appwrite/appwrite/pull/11115)
|
||||
* Add cursor plugin in [#11371](https://github.com/appwrite/appwrite/pull/11371)
|
||||
* Add observability spans in [#11320](https://github.com/appwrite/appwrite/pull/11320), [#11306](https://github.com/appwrite/appwrite/pull/11306), and [#11228](https://github.com/appwrite/appwrite/pull/11228)
|
||||
* Upgrade PHPStan to v2 with full codebase coverage in [#11550](https://github.com/appwrite/appwrite/pull/11550)
|
||||
* Upgrade Traefik in [#11265](https://github.com/appwrite/appwrite/pull/11265)
|
||||
* Upgrade utopia-php/queue in [#11239](https://github.com/appwrite/appwrite/pull/11239)
|
||||
* Upgrade spomky-labs/otphp in [#11263](https://github.com/appwrite/appwrite/pull/11263)
|
||||
* Bump utopia-php/database to stable 5.3.15 in [#11573](https://github.com/appwrite/appwrite/pull/11573)
|
||||
* Bump utopia-php/migration to 1.6.3 in [#11443](https://github.com/appwrite/appwrite/pull/11443)
|
||||
* Consolidate CI workflows in [#11531](https://github.com/appwrite/appwrite/pull/11531) and [#11551](https://github.com/appwrite/appwrite/pull/11551)
|
||||
* Hide deprecated methods from docs in [#10933](https://github.com/appwrite/appwrite/pull/10933)
|
||||
* Deprecate project-level attributes in [#11203](https://github.com/appwrite/appwrite/pull/11203)
|
||||
|
||||
# Version 1.8.0
|
||||
|
||||
|
||||
+3
-3
@@ -72,7 +72,7 @@ docker run -it --rm \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.8.1
|
||||
appwrite/appwrite:1.9.0
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -84,7 +84,7 @@ docker run -it --rm ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.8.1
|
||||
appwrite/appwrite:1.9.0
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -94,7 +94,7 @@ docker run -it --rm `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.8.1
|
||||
appwrite/appwrite:1.9.0
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
|
||||
@@ -75,7 +75,7 @@ docker run -it --rm \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.8.1
|
||||
appwrite/appwrite:1.9.0
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -87,7 +87,7 @@ docker run -it --rm ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.8.1
|
||||
appwrite/appwrite:1.9.0
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -97,7 +97,7 @@ docker run -it --rm `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.8.1
|
||||
appwrite/appwrite:1.9.0
|
||||
```
|
||||
|
||||
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
|
||||
|
||||
@@ -888,7 +888,7 @@ Http::init()
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$request->addFilter(new RequestV20($dbForProject, $route->getPathValues($request)));
|
||||
}
|
||||
if (version_compare($requestFormat, '1.8.2', '<')) {
|
||||
if (version_compare($requestFormat, '1.9.0', '<')) {
|
||||
$request->addFilter(new RequestV21());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ const APP_RESOURCE_TOKEN_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 4321;
|
||||
const APP_VERSION_STABLE = '1.8.2';
|
||||
const APP_VERSION_STABLE = '1.9.0';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
|
||||
@@ -108,6 +108,12 @@
|
||||
"czproject/git-php": "4.*",
|
||||
"laravel/pint": "1.*"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/utopia-php/database"
|
||||
}
|
||||
],
|
||||
"provide": {
|
||||
"ext-phpiredis": "*"
|
||||
},
|
||||
|
||||
Generated
+13
-13
@@ -3850,16 +3850,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "5.3.16",
|
||||
"version": "5.3.17",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "f121418c4dc7169576735620fd8c17093c3b851d"
|
||||
"reference": "cff2b6ed63d3291b74110d086e16ff089fe05993"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/f121418c4dc7169576735620fd8c17093c3b851d",
|
||||
"reference": "f121418c4dc7169576735620fd8c17093c3b851d",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/cff2b6ed63d3291b74110d086e16ff089fe05993",
|
||||
"reference": "cff2b6ed63d3291b74110d086e16ff089fe05993",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3902,10 +3902,10 @@
|
||||
"utopia"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/5.3.16"
|
||||
"source": "https://github.com/utopia-php/database/tree/5.3.17",
|
||||
"issues": "https://github.com/utopia-php/database/issues"
|
||||
},
|
||||
"time": "2026-03-19T10:10:02+00:00"
|
||||
"time": "2026-03-20T01:18:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/detector",
|
||||
@@ -5438,16 +5438,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "1.11.10",
|
||||
"version": "1.11.11",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "96e6b79a241fc615627a820107ca64bbd1b550a6"
|
||||
"reference": "cfc37c85161a5515af4cd2f9885a811f51a2483a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/96e6b79a241fc615627a820107ca64bbd1b550a6",
|
||||
"reference": "96e6b79a241fc615627a820107ca64bbd1b550a6",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/cfc37c85161a5515af4cd2f9885a811f51a2483a",
|
||||
"reference": "cfc37c85161a5515af4cd2f9885a811f51a2483a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5483,9 +5483,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/1.11.10"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.11.11"
|
||||
},
|
||||
"time": "2026-03-19T05:27:36+00:00"
|
||||
"time": "2026-03-19T16:21:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brianium/paratest",
|
||||
|
||||
@@ -918,18 +918,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Appwrite/Platform/Tasks/StatsResources.php
|
||||
|
||||
-
|
||||
message: '#^Call to static method error\(\) on an unknown class Utopia\\CLI\\Console\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: src/Appwrite/Platform/Tasks/Upgrade.php
|
||||
|
||||
-
|
||||
message: '#^Call to static method log\(\) on an unknown class Utopia\\CLI\\Console\.$#'
|
||||
identifier: class.notFound
|
||||
count: 5
|
||||
path: src/Appwrite/Platform/Tasks/Upgrade.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @param references unknown parameter\: \$getProjectDB$#'
|
||||
identifier: parameter.notFound
|
||||
|
||||
@@ -91,7 +91,7 @@ abstract class Migration
|
||||
'1.7.4' => 'V22',
|
||||
'1.8.0' => 'V23',
|
||||
'1.8.1' => 'V23',
|
||||
'1.8.2' => 'V23',
|
||||
'1.9.0' => 'V24',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Conflict;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Exception\Timeout;
|
||||
|
||||
class V24 extends Migration
|
||||
{
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function execute(): void
|
||||
{
|
||||
/**
|
||||
* Disable SubQueries for Performance.
|
||||
*/
|
||||
$subQueries = [
|
||||
'subQueryAccountKeys',
|
||||
'subQueryAttributes',
|
||||
'subQueryAuthenticators',
|
||||
'subQueryChallenges',
|
||||
'subQueryDevKeys',
|
||||
'subQueryIndexes',
|
||||
'subQueryKeys',
|
||||
'subQueryMemberships',
|
||||
'subQueryOrganizationKeys',
|
||||
'subQueryPlatforms',
|
||||
'subQueryProjectVariables',
|
||||
'subQuerySessions',
|
||||
'subQueryTargets',
|
||||
'subQueryTokens',
|
||||
'subQueryTopicTargets',
|
||||
'subQueryVariables',
|
||||
'subQueryWebhooks',
|
||||
];
|
||||
foreach ($subQueries as $name) {
|
||||
Database::addFilter(
|
||||
$name,
|
||||
fn () => null,
|
||||
fn () => []
|
||||
);
|
||||
}
|
||||
|
||||
Console::info('Migrating collections');
|
||||
$this->migrateCollections();
|
||||
|
||||
if ($this->project->getSequence() != 'console') {
|
||||
Console::info('Migrating Databases');
|
||||
$this->migrateDatabases();
|
||||
}
|
||||
|
||||
Console::info('Migrating Buckets');
|
||||
$this->migrateBuckets();
|
||||
|
||||
Console::info('Migrating documents');
|
||||
$this->forEachDocument($this->migrateDocument(...));
|
||||
|
||||
Console::info('Cleaning up old attributes');
|
||||
$this->cleanupOldAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate Collections.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|Throwable
|
||||
*/
|
||||
private function migrateCollections(): void
|
||||
{
|
||||
$projectInternalId = $this->project->getSequence();
|
||||
|
||||
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}\"");
|
||||
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
$this->dbForProject->purgeCachedDocument(Database::METADATA, $id);
|
||||
|
||||
switch ($id) {
|
||||
case 'projects':
|
||||
if ($collectionType === 'console') {
|
||||
$attributes = [
|
||||
'labels',
|
||||
'status',
|
||||
];
|
||||
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 'keys':
|
||||
if ($collectionType === 'console') {
|
||||
$attributes = [
|
||||
'resourceType',
|
||||
'resourceId',
|
||||
'resourceInternalId',
|
||||
];
|
||||
try {
|
||||
$this->createAttributesFromCollection($this->dbForProject, $id, $attributes);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning('Failed to create attributes "' . \implode(', ', $attributes) . "\" in collection {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->dbForProject->deleteIndex($id, '_key_project');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to delete index \"_key_project\" from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->createIndexFromCollection($this->dbForProject, $id, '_key_resource');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to create index \"_key_resource\" from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
break;
|
||||
|
||||
case 'rules':
|
||||
if ($collectionType === 'console') {
|
||||
try {
|
||||
$this->createAttributeFromCollection($this->dbForProject, $id, 'logs');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to create attribute \"logs\" in collection {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
$indexesToDelete = [
|
||||
'_key_type',
|
||||
'_key_trigger',
|
||||
'_key_deploymentResourceType',
|
||||
'_key_owner',
|
||||
'_key_region',
|
||||
'_key_piid_riid_rt',
|
||||
];
|
||||
foreach ($indexesToDelete as $index) {
|
||||
try {
|
||||
$this->dbForProject->deleteIndex($id, $index);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to delete index \"$index\" from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
}
|
||||
|
||||
$indexesToCreate = [
|
||||
'_key_type',
|
||||
'_key_trigger',
|
||||
'_key_deploymentResourceType',
|
||||
'_key_owner',
|
||||
'_key_piid_diid_drt',
|
||||
'_key_region_status_createdAt',
|
||||
];
|
||||
foreach ($indexesToCreate 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 'teams':
|
||||
try {
|
||||
$this->createAttributeFromCollection($this->dbForProject, $id, 'labels');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to create attribute \"labels\" in collection {$id}: {$th->getMessage()}");
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
break;
|
||||
|
||||
case 'databases':
|
||||
if ($collectionType === 'projects') {
|
||||
try {
|
||||
$this->createAttributeFromCollection($this->dbForProject, $id, 'database');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to create attribute \"database\" in collection {$id}: {$th->getMessage()}");
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'functions':
|
||||
$attributes = [
|
||||
'deploymentRetention',
|
||||
'startCommand',
|
||||
'buildSpecification',
|
||||
'runtimeSpecification',
|
||||
];
|
||||
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 'sites':
|
||||
$attributes = [
|
||||
'startCommand',
|
||||
'deploymentRetention',
|
||||
'buildSpecification',
|
||||
'runtimeSpecification',
|
||||
];
|
||||
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 'deployments':
|
||||
try {
|
||||
$this->createAttributeFromCollection($this->dbForProject, $id, 'startCommand');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to create attribute \"startCommand\" in collection {$id}: {$th->getMessage()}");
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
break;
|
||||
|
||||
case 'executions':
|
||||
try {
|
||||
$this->dbForProject->deleteIndex($id, '_key_function_internal_id');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to delete index \"_key_function_internal_id\" from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
$this->createIndexFromCollection($this->dbForProject, $id, '_key_resourceType');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to create index \"_key_resourceType\" from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
break;
|
||||
|
||||
case 'buckets':
|
||||
try {
|
||||
$this->dbForProject->deleteIndex($id, '_fulltext_name');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to delete index \"_fulltext_name\" from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
break;
|
||||
|
||||
case 'providers':
|
||||
try {
|
||||
$this->dbForProject->deleteIndex($id, '_key_name');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to delete index \"_key_name\" from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
break;
|
||||
|
||||
case 'topics':
|
||||
try {
|
||||
$this->dbForProject->deleteIndex($id, '_key_name');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to delete index \"_key_name\" from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate all Database tables
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function migrateDatabases(): void
|
||||
{
|
||||
$this->dbForProject->foreach('databases', function (Document $database) {
|
||||
Console::log("Migrating Collections of {$database->getId()} ({$database->getAttribute('name')})");
|
||||
|
||||
$databaseTable = "database_{$database->getSequence()}";
|
||||
$this->dbForProject->purgeCachedCollection($databaseTable);
|
||||
|
||||
$this->dbForProject->foreach($databaseTable, function (Document $collection) use ($databaseTable) {
|
||||
Console::log("Migrating Collection of {$collection->getId()} ({$collection->getAttribute('name')})");
|
||||
|
||||
$collectionTable = "{$databaseTable}_collection_{$collection->getSequence()}";
|
||||
$this->dbForProject->purgeCachedCollection($collectionTable);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate all Bucket tables
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @throws \PDOException
|
||||
*/
|
||||
protected function migrateBuckets(): void
|
||||
{
|
||||
$this->dbForProject->foreach('buckets', function (Document $bucket) {
|
||||
Console::log("Migrating Bucket {$bucket->getId()} ({$bucket->getAttribute('name')})");
|
||||
|
||||
$bucketTable = "bucket_{$bucket->getSequence()}";
|
||||
$this->dbForProject->purgeCachedCollection($bucketTable);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'keys':
|
||||
$projectInternalId = $document->getAttribute('projectInternalId');
|
||||
$projectId = $document->getAttribute('projectId');
|
||||
|
||||
if (!empty($projectInternalId) && empty($document->getAttribute('resourceInternalId'))) {
|
||||
$document->setAttribute('resourceType', 'projects');
|
||||
$document->setAttribute('resourceId', $projectId);
|
||||
$document->setAttribute('resourceInternalId', $projectInternalId);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old attributes after document migration is complete.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cleanupOldAttributes(): void
|
||||
{
|
||||
$collectionType = match ($this->project->getSequence()) {
|
||||
'console' => 'console',
|
||||
default => 'projects',
|
||||
};
|
||||
|
||||
if ($collectionType === 'console') {
|
||||
$attributesToDelete = [
|
||||
'keys' => ['projectInternalId', 'projectId'],
|
||||
];
|
||||
|
||||
foreach ($attributesToDelete as $collectionId => $attributes) {
|
||||
foreach ($attributes as $attribute) {
|
||||
try {
|
||||
$this->dbForProject->deleteAttribute($collectionId, $attribute);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to delete attribute \"{$attribute}\" from {$collectionId}: {$th->getMessage()}");
|
||||
}
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($collectionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ class Platform
|
||||
public const SCHEME_ANDROID = 'appwrite-android';
|
||||
public const SCHEME_WINDOWS = 'appwrite-windows';
|
||||
public const SCHEME_LINUX = 'appwrite-linux';
|
||||
public const SCHEME_TAURI = 'tauri';
|
||||
|
||||
/**
|
||||
* @var array<string, string> Map scheme types to user-friendly platform names.
|
||||
@@ -53,6 +54,7 @@ class Platform
|
||||
self::SCHEME_FIREFOX_EXTENSION => 'Web (Firefox Extension)',
|
||||
self::SCHEME_SAFARI_EXTENSION => 'Web (Safari Extension)',
|
||||
self::SCHEME_EDGE_EXTENSION => 'Web (Edge Extension)',
|
||||
self::SCHEME_TAURI => 'Web (Tauri)',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,6 +69,7 @@ class Origin extends Validator
|
||||
Platform::SCHEME_FIREFOX_EXTENSION,
|
||||
Platform::SCHEME_SAFARI_EXTENSION,
|
||||
Platform::SCHEME_EDGE_EXTENSION,
|
||||
Platform::SCHEME_TAURI,
|
||||
];
|
||||
if (in_array($this->scheme, $webPlatforms, true)) {
|
||||
$validator = new Hostname($this->allowedHostnames);
|
||||
|
||||
@@ -168,8 +168,27 @@ class Install extends Action
|
||||
}
|
||||
}
|
||||
|
||||
// Block database type changes on existing installations
|
||||
$existingDatabase = $vars['_APP_DB_ADAPTER']['default'] ?? null;
|
||||
// Block database type changes on existing installations.
|
||||
// Only enforce if the existing config explicitly set _APP_DB_ADAPTER
|
||||
// (pre-1.9.0 installs never had this variable).
|
||||
$existingDatabase = null;
|
||||
foreach ($compose->getServices() as $service) {
|
||||
if (!$service) {
|
||||
continue;
|
||||
}
|
||||
$svcEnv = $service->getEnvironment()->list();
|
||||
if (isset($svcEnv['_APP_DB_ADAPTER'])) {
|
||||
$existingDatabase = $svcEnv['_APP_DB_ADAPTER'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($existingDatabase === null) {
|
||||
$envFilePath = $this->path . '/' . $this->getEnvFileName();
|
||||
$rawEnv = @file_get_contents($envFilePath);
|
||||
if ($rawEnv !== false) {
|
||||
$existingDatabase = (new Env($rawEnv))->list()['_APP_DB_ADAPTER'] ?? null;
|
||||
}
|
||||
}
|
||||
if ($existingDatabase !== null && $existingDatabase !== $database) {
|
||||
Console::error("Cannot change database type from '{$existingDatabase}' to '{$database}'.");
|
||||
Console::error('Changing database types on an existing installation is not supported.');
|
||||
@@ -279,7 +298,8 @@ class Install extends Action
|
||||
Console::warning("\nUse 'AAAA' if you're using an IPv6 address and 'A' if you're using an IPv4 address.\n");
|
||||
}
|
||||
}
|
||||
$database = $userInput['_APP_DB_ADAPTER'] ?? $database;
|
||||
$userInput['_APP_DB_ADAPTER'] = $userInput['_APP_DB_ADAPTER'] ?? $database;
|
||||
$database = $userInput['_APP_DB_ADAPTER'];
|
||||
if ($database === 'postgresql') {
|
||||
$userInput['_APP_DB_HOST'] = 'postgresql';
|
||||
$userInput['_APP_DB_PORT'] = 5432;
|
||||
|
||||
@@ -31,7 +31,7 @@ class Migrate extends Action
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('register')
|
||||
->inject('authorisation')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -64,8 +64,43 @@ class Migrate extends Action
|
||||
/** @var Migration $migration */
|
||||
$migration = new $class();
|
||||
|
||||
// Disable subquery filters that reference new schema columns not yet migrated
|
||||
$subQueries = [
|
||||
'subQueryAccountKeys',
|
||||
'subQueryAttributes',
|
||||
'subQueryAuthenticators',
|
||||
'subQueryChallenges',
|
||||
'subQueryDevKeys',
|
||||
'subQueryIndexes',
|
||||
'subQueryKeys',
|
||||
'subQueryMemberships',
|
||||
'subQueryOrganizationKeys',
|
||||
'subQueryPlatforms',
|
||||
'subQueryProjectVariables',
|
||||
'subQuerySessions',
|
||||
'subQueryTargets',
|
||||
'subQueryTokens',
|
||||
'subQueryTopicTargets',
|
||||
'subQueryVariables',
|
||||
'subQueryWebhooks',
|
||||
];
|
||||
foreach ($subQueries as $name) {
|
||||
Database::addFilter(
|
||||
$name,
|
||||
fn () => null,
|
||||
fn () => []
|
||||
);
|
||||
}
|
||||
|
||||
$dbForPlatform->disableValidation();
|
||||
$dbForPlatform->purgeCachedCollection('projects');
|
||||
|
||||
$count = 0;
|
||||
$total = $dbForPlatform->count('projects') + 1;
|
||||
try {
|
||||
$total = $dbForPlatform->count('projects') + 1;
|
||||
} catch (\Throwable) {
|
||||
$total = 0;
|
||||
}
|
||||
|
||||
$dbForPlatform->foreach('projects', function (Document $project) use ($dbForPlatform, $getProjectDB, $register, $migration, &$count, $total, $authorization) {
|
||||
/** @var Database $dbForProject */
|
||||
@@ -74,9 +109,14 @@ class Migrate extends Action
|
||||
|
||||
try {
|
||||
$migration
|
||||
->setProject($project, $dbForProject, $dbForPlatform, $authorization, $getProjectDB)
|
||||
->setPDO($register->get('db', true))
|
||||
->execute();
|
||||
->setProject($project, $dbForProject, $dbForPlatform, $authorization, $getProjectDB);
|
||||
|
||||
$db = $register->get('db', true);
|
||||
if ($db instanceof \Utopia\Database\PDO) {
|
||||
$migration->setPDO($db);
|
||||
}
|
||||
|
||||
$migration->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed to migrate project "' . $project->getId() . '" with error: ' . $th->getMessage());
|
||||
throw $th;
|
||||
@@ -89,9 +129,14 @@ class Migrate extends Action
|
||||
|
||||
try {
|
||||
$migration
|
||||
->setProject($console, $getProjectDB($console), $dbForPlatform, $authorization, $getProjectDB)
|
||||
->setPDO($register->get('db', true))
|
||||
->execute();
|
||||
->setProject($console, $getProjectDB($console), $dbForPlatform, $authorization, $getProjectDB);
|
||||
|
||||
$db = $register->get('db', true);
|
||||
if ($db instanceof \Utopia\Database\PDO) {
|
||||
$migration->setPDO($db);
|
||||
}
|
||||
|
||||
$migration->execute();
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed to migrate project "console" with error: ' . $th->getMessage());
|
||||
throw $th;
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Docker\Compose;
|
||||
use Appwrite\Docker\Env;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Console;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
@@ -80,7 +80,9 @@ class Upgrade extends Install
|
||||
}
|
||||
|
||||
if ($database === null) {
|
||||
throw new \Exception('Database type not found, can not upgrade. Ensure `_APP_DB_ADAPTER` is set in your environment.');
|
||||
// Pre-1.9.0 installations only supported MariaDB
|
||||
$database = 'mariadb';
|
||||
Console::info('No _APP_DB_ADAPTER found in existing configuration, defaulting to mariadb.');
|
||||
}
|
||||
|
||||
$this->lockedDatabase = $database;
|
||||
|
||||
@@ -843,8 +843,7 @@ class OpenAPI3 extends Format
|
||||
break;
|
||||
|
||||
case 'id':
|
||||
$type = 'integer';
|
||||
$format = $rule['format'] ?? 'int32';
|
||||
$type = 'string';
|
||||
break;
|
||||
|
||||
case 'enum':
|
||||
|
||||
@@ -823,8 +823,7 @@ class Swagger2 extends Format
|
||||
break;
|
||||
|
||||
case 'id':
|
||||
$type = 'integer';
|
||||
$format = $rule['format'] ?? 'int32';
|
||||
$type = 'string';
|
||||
break;
|
||||
|
||||
case 'enum':
|
||||
|
||||
@@ -6,7 +6,7 @@ use Appwrite\Utopia\Request\Filter;
|
||||
|
||||
class V21 extends Filter
|
||||
{
|
||||
// Convert 1.8.0 params to 1.8.2
|
||||
// Convert 1.8.0 params to 1.9.0
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
switch ($model) {
|
||||
|
||||
@@ -23,6 +23,18 @@ class V21 extends Filter
|
||||
"functions",
|
||||
fn ($item) => $this->parseFunction($item),
|
||||
),
|
||||
Response::MODEL_DOCUMENT => $this->parseDocument($content),
|
||||
Response::MODEL_DOCUMENT_LIST => $this->handleList(
|
||||
$content,
|
||||
"documents",
|
||||
fn ($item) => $this->parseDocument($item),
|
||||
),
|
||||
Response::MODEL_ROW => $this->parseRow($content),
|
||||
Response::MODEL_ROW_LIST => $this->handleList(
|
||||
$content,
|
||||
"rows",
|
||||
fn ($item) => $this->parseRow($item),
|
||||
),
|
||||
default => $content,
|
||||
};
|
||||
}
|
||||
@@ -46,4 +58,39 @@ class V21 extends Filter
|
||||
unset($content['runtimeSpecification']);
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseDocument(array $content): array
|
||||
{
|
||||
return $this->castSequence($content);
|
||||
}
|
||||
|
||||
protected function parseRow(array $content): array
|
||||
{
|
||||
return $this->castSequence($content);
|
||||
}
|
||||
|
||||
protected function castSequence(array $content): array
|
||||
{
|
||||
if (isset($content['$sequence'])) {
|
||||
$content['$sequence'] = \is_numeric($content['$sequence'])
|
||||
? (int)$content['$sequence']
|
||||
: 0;
|
||||
}
|
||||
|
||||
foreach ($content as $key => $value) {
|
||||
if (\is_array($value)) {
|
||||
if (isset($value['$id'])) {
|
||||
$content[$key] = $this->castSequence($value);
|
||||
} else {
|
||||
foreach ($value as $i => $item) {
|
||||
if (\is_array($item) && isset($item['$id'])) {
|
||||
$content[$key][$i] = $this->castSequence($item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +83,8 @@ class Document extends Any
|
||||
$document->removeAttribute('$collection');
|
||||
$document->removeAttribute('$tenant');
|
||||
|
||||
if (!$document->isEmpty() && \is_numeric($document->getAttribute('$sequence', 0))) {
|
||||
$document->setAttribute('$sequence', (int)$document->getAttribute('$sequence', 0));
|
||||
if (!$document->isEmpty()) {
|
||||
$document->setAttribute('$sequence', (string)$document->getAttribute('$sequence', ''));
|
||||
}
|
||||
|
||||
foreach ($document->getAttributes() as $attribute) {
|
||||
|
||||
@@ -83,8 +83,8 @@ class Row extends Any
|
||||
$document->removeAttribute('$collection');
|
||||
$document->removeAttribute('$tenant');
|
||||
|
||||
if (!$document->isEmpty() && \is_numeric($document->getAttribute('$sequence', 0))) {
|
||||
$document->setAttribute('$sequence', (int)$document->getAttribute('$sequence', 0));
|
||||
if (!$document->isEmpty()) {
|
||||
$document->setAttribute('$sequence', (string)$document->getAttribute('$sequence', ''));
|
||||
}
|
||||
|
||||
foreach ($document->getAttributes() as $column) {
|
||||
|
||||
@@ -2612,9 +2612,7 @@ trait DatabasesBase
|
||||
}
|
||||
$this->assertTrue(array_key_exists('$sequence', $document1['body']));
|
||||
|
||||
$this->getSupportForIntegerIds()
|
||||
? $this->assertIsInt($document1['body']['$sequence'])
|
||||
: $this->assertIsString($document1['body']['$sequence']);
|
||||
$this->assertIsString($document1['body']['$sequence']);
|
||||
|
||||
$this->assertEquals(201, $document2['headers']['status-code']);
|
||||
$this->assertEquals($data['moviesId'], $document2['body'][$this->getContainerIdResponseKey()]);
|
||||
@@ -2694,9 +2692,7 @@ trait DatabasesBase
|
||||
/**
|
||||
* Resubmit same document, nothing to update
|
||||
*/
|
||||
$this->getSupportForIntegerIds()
|
||||
? $this->assertIsInt($document['body']['$sequence'])
|
||||
: $this->assertIsString($document['body']['$sequence']);
|
||||
$this->assertIsString($document['body']['$sequence']);
|
||||
|
||||
$upsertData = [
|
||||
'title' => 'Thor: Ragnarok',
|
||||
|
||||
@@ -73,6 +73,10 @@ class OriginTest extends TestCase
|
||||
$this->assertEquals(false, $validator->isValid('ms-browser-extension://com.company.appname'));
|
||||
$this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Edge Extension) platform on your project console dashboard', $validator->getDescription());
|
||||
|
||||
$this->assertEquals(true, $validator->isValid('tauri://localhost'));
|
||||
$this->assertEquals(false, $validator->isValid('tauri://example.com'));
|
||||
$this->assertEquals('Invalid Origin. Register your new client (example.com) as a new Web (Tauri) platform on your project console dashboard', $validator->getDescription());
|
||||
|
||||
$this->assertEquals(false, $validator->isValid('random-scheme://localhost'));
|
||||
$this->assertEquals('Invalid Scheme. The scheme used (random-scheme) in the Origin (random-scheme://localhost) is not supported. If you are using a custom scheme, please change it to `appwrite-callback-<PROJECT_ID>`', $validator->getDescription());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia\Request\Filters;
|
||||
|
||||
use Appwrite\Utopia\Request\Filter;
|
||||
use Appwrite\Utopia\Request\Filters\V21;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class V21Test extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Filter
|
||||
*/
|
||||
protected $filter;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->filter = new V21();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public static function functionsCreateTemplateDeploymentProvider(): array
|
||||
{
|
||||
return [
|
||||
'convert version to type and reference' => [
|
||||
[
|
||||
'templateId' => 'template123',
|
||||
'version' => '1.0.0',
|
||||
],
|
||||
[
|
||||
'templateId' => 'template123',
|
||||
'type' => 'tag',
|
||||
'reference' => '1.0.0',
|
||||
]
|
||||
],
|
||||
'handle version with semver string' => [
|
||||
[
|
||||
'version' => 'v2.3.1',
|
||||
'templateId' => 'template456',
|
||||
],
|
||||
[
|
||||
'type' => 'tag',
|
||||
'reference' => 'v2.3.1',
|
||||
'templateId' => 'template456',
|
||||
]
|
||||
],
|
||||
'skip conversion when version is empty string' => [
|
||||
[
|
||||
'templateId' => 'template123',
|
||||
'version' => '',
|
||||
],
|
||||
[
|
||||
'templateId' => 'template123',
|
||||
'version' => '',
|
||||
]
|
||||
],
|
||||
'skip conversion when version is missing' => [
|
||||
[
|
||||
'templateId' => 'template123',
|
||||
],
|
||||
[
|
||||
'templateId' => 'template123',
|
||||
]
|
||||
],
|
||||
'preserve other fields when converting version' => [
|
||||
[
|
||||
'templateId' => 'template123',
|
||||
'version' => '3.0.0',
|
||||
'activate' => true,
|
||||
'buildCommand' => 'npm run build',
|
||||
],
|
||||
[
|
||||
'templateId' => 'template123',
|
||||
'type' => 'tag',
|
||||
'reference' => '3.0.0',
|
||||
'activate' => true,
|
||||
'buildCommand' => 'npm run build',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('functionsCreateTemplateDeploymentProvider')]
|
||||
public function testFunctionsCreateTemplateDeployment(array $content, array $expected): void
|
||||
{
|
||||
$model = 'functions.createTemplateDeployment';
|
||||
|
||||
$result = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public static function sitesCreateTemplateDeploymentProvider(): array
|
||||
{
|
||||
return [
|
||||
'convert version to type and reference' => [
|
||||
[
|
||||
'templateId' => 'site-template123',
|
||||
'version' => '2.0.0',
|
||||
],
|
||||
[
|
||||
'templateId' => 'site-template123',
|
||||
'type' => 'tag',
|
||||
'reference' => '2.0.0',
|
||||
]
|
||||
],
|
||||
'skip conversion when version is empty' => [
|
||||
[
|
||||
'templateId' => 'site-template123',
|
||||
'version' => '',
|
||||
],
|
||||
[
|
||||
'templateId' => 'site-template123',
|
||||
'version' => '',
|
||||
]
|
||||
],
|
||||
'skip conversion when version is missing' => [
|
||||
[
|
||||
'templateId' => 'site-template123',
|
||||
],
|
||||
[
|
||||
'templateId' => 'site-template123',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('sitesCreateTemplateDeploymentProvider')]
|
||||
public function testSitesCreateTemplateDeployment(array $content, array $expected): void
|
||||
{
|
||||
$model = 'sites.createTemplateDeployment';
|
||||
|
||||
$result = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public static function functionsCreateProvider(): array
|
||||
{
|
||||
return [
|
||||
'convert specification to buildSpecification and runtimeSpecification' => [
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'runtime' => 'node-18.0',
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
],
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'runtime' => 'node-18.0',
|
||||
'buildSpecification' => 's-1vcpu-512mb',
|
||||
'runtimeSpecification' => 's-1vcpu-512mb',
|
||||
]
|
||||
],
|
||||
'skip conversion when specification is empty' => [
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'runtime' => 'node-18.0',
|
||||
'specification' => '',
|
||||
],
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'runtime' => 'node-18.0',
|
||||
'specification' => '',
|
||||
]
|
||||
],
|
||||
'skip conversion when specification is missing' => [
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'runtime' => 'node-18.0',
|
||||
],
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'runtime' => 'node-18.0',
|
||||
]
|
||||
],
|
||||
'preserve other fields when converting specification' => [
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'specification' => 's-2vcpu-1gb',
|
||||
'timeout' => 30,
|
||||
'enabled' => true,
|
||||
],
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'buildSpecification' => 's-2vcpu-1gb',
|
||||
'runtimeSpecification' => 's-2vcpu-1gb',
|
||||
'timeout' => 30,
|
||||
'enabled' => true,
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('functionsCreateProvider')]
|
||||
public function testFunctionsCreate(array $content, array $expected): void
|
||||
{
|
||||
$model = 'functions.create';
|
||||
|
||||
$result = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public static function sitesCreateProvider(): array
|
||||
{
|
||||
return [
|
||||
'convert specification to buildSpecification and runtimeSpecification' => [
|
||||
[
|
||||
'name' => 'test-site',
|
||||
'framework' => 'nextjs',
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
],
|
||||
[
|
||||
'name' => 'test-site',
|
||||
'framework' => 'nextjs',
|
||||
'buildSpecification' => 's-1vcpu-512mb',
|
||||
'runtimeSpecification' => 's-1vcpu-512mb',
|
||||
]
|
||||
],
|
||||
'skip conversion when specification is empty' => [
|
||||
[
|
||||
'name' => 'test-site',
|
||||
'specification' => '',
|
||||
],
|
||||
[
|
||||
'name' => 'test-site',
|
||||
'specification' => '',
|
||||
]
|
||||
],
|
||||
'skip conversion when specification is missing' => [
|
||||
[
|
||||
'name' => 'test-site',
|
||||
],
|
||||
[
|
||||
'name' => 'test-site',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('sitesCreateProvider')]
|
||||
public function testSitesCreate(array $content, array $expected): void
|
||||
{
|
||||
$model = 'sites.create';
|
||||
|
||||
$result = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public static function functionsUpdateProvider(): array
|
||||
{
|
||||
return [
|
||||
'convert specification to buildSpecification and runtimeSpecification' => [
|
||||
[
|
||||
'name' => 'updated-function',
|
||||
'specification' => 's-2vcpu-1gb',
|
||||
],
|
||||
[
|
||||
'name' => 'updated-function',
|
||||
'buildSpecification' => 's-2vcpu-1gb',
|
||||
'runtimeSpecification' => 's-2vcpu-1gb',
|
||||
]
|
||||
],
|
||||
'skip conversion when specification is missing' => [
|
||||
[
|
||||
'name' => 'updated-function',
|
||||
],
|
||||
[
|
||||
'name' => 'updated-function',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('functionsUpdateProvider')]
|
||||
public function testFunctionsUpdate(array $content, array $expected): void
|
||||
{
|
||||
$model = 'functions.update';
|
||||
|
||||
$result = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public static function sitesUpdateProvider(): array
|
||||
{
|
||||
return [
|
||||
'convert specification to buildSpecification and runtimeSpecification' => [
|
||||
[
|
||||
'name' => 'updated-site',
|
||||
'specification' => 's-2vcpu-1gb',
|
||||
],
|
||||
[
|
||||
'name' => 'updated-site',
|
||||
'buildSpecification' => 's-2vcpu-1gb',
|
||||
'runtimeSpecification' => 's-2vcpu-1gb',
|
||||
]
|
||||
],
|
||||
'skip conversion when specification is missing' => [
|
||||
[
|
||||
'name' => 'updated-site',
|
||||
],
|
||||
[
|
||||
'name' => 'updated-site',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('sitesUpdateProvider')]
|
||||
public function testSitesUpdate(array $content, array $expected): void
|
||||
{
|
||||
$model = 'sites.update';
|
||||
|
||||
$result = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public static function unmatchedModelProvider(): array
|
||||
{
|
||||
return [
|
||||
'unmatched model passes through unchanged' => [
|
||||
'databases.create',
|
||||
[
|
||||
'name' => 'test-database',
|
||||
'databaseId' => 'db123',
|
||||
],
|
||||
[
|
||||
'name' => 'test-database',
|
||||
'databaseId' => 'db123',
|
||||
]
|
||||
],
|
||||
'empty content for unmatched model' => [
|
||||
'users.list',
|
||||
[],
|
||||
[]
|
||||
],
|
||||
'content with specification for unmatched model is not converted' => [
|
||||
'deployments.create',
|
||||
[
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
'name' => 'test',
|
||||
],
|
||||
[
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
'name' => 'test',
|
||||
]
|
||||
],
|
||||
'content with version for unmatched model is not converted' => [
|
||||
'databases.createDocument',
|
||||
[
|
||||
'version' => '1.0.0',
|
||||
'data' => 'test',
|
||||
],
|
||||
[
|
||||
'version' => '1.0.0',
|
||||
'data' => 'test',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('unmatchedModelProvider')]
|
||||
public function testUnmatchedModel(string $model, array $content, array $expected): void
|
||||
{
|
||||
$result = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia\Response\Filters;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filters\V21;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class V21Test extends TestCase
|
||||
{
|
||||
protected ?V21 $filter = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->filter = new V21();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public static function functionProvider(): array
|
||||
{
|
||||
return [
|
||||
'merge buildSpecification and runtimeSpecification into specification' => [
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'buildSpecification' => 's-1vcpu-512mb',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
'runtime' => 'node-18.0',
|
||||
],
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
'runtime' => 'node-18.0',
|
||||
]
|
||||
],
|
||||
'use buildSpecification when present' => [
|
||||
[
|
||||
'buildSpecification' => 's-2vcpu-1gb',
|
||||
'runtimeSpecification' => 's-1vcpu-512mb',
|
||||
],
|
||||
[
|
||||
'specification' => 's-2vcpu-1gb',
|
||||
]
|
||||
],
|
||||
'fallback to specification when buildSpecification is missing' => [
|
||||
[
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
],
|
||||
[
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
]
|
||||
],
|
||||
'handle missing both buildSpecification and specification' => [
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
],
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'specification' => null,
|
||||
]
|
||||
],
|
||||
'handle no spec fields at all' => [
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'runtime' => 'node-18.0',
|
||||
],
|
||||
[
|
||||
'name' => 'test-function',
|
||||
'specification' => null,
|
||||
'runtime' => 'node-18.0',
|
||||
]
|
||||
],
|
||||
'handle empty buildSpecification string' => [
|
||||
[
|
||||
'buildSpecification' => '',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
],
|
||||
[
|
||||
'specification' => '',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('functionProvider')]
|
||||
public function testFunction(array $content, array $expected): void
|
||||
{
|
||||
$model = Response::MODEL_FUNCTION;
|
||||
|
||||
$result = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public static function functionListProvider(): array
|
||||
{
|
||||
return [
|
||||
'convert list of functions' => [
|
||||
[
|
||||
'total' => 2,
|
||||
'functions' => [
|
||||
[
|
||||
'name' => 'function-1',
|
||||
'buildSpecification' => 's-1vcpu-512mb',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
],
|
||||
[
|
||||
'name' => 'function-2',
|
||||
'buildSpecification' => 's-2vcpu-1gb',
|
||||
'runtimeSpecification' => 's-1vcpu-512mb',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'total' => 2,
|
||||
'functions' => [
|
||||
[
|
||||
'name' => 'function-1',
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
],
|
||||
[
|
||||
'name' => 'function-2',
|
||||
'specification' => 's-2vcpu-1gb',
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
'handle empty function list' => [
|
||||
[
|
||||
'total' => 0,
|
||||
'functions' => [],
|
||||
],
|
||||
[
|
||||
'total' => 0,
|
||||
'functions' => [],
|
||||
]
|
||||
],
|
||||
'handle single function in list' => [
|
||||
[
|
||||
'total' => 1,
|
||||
'functions' => [
|
||||
[
|
||||
'buildSpecification' => 's-1vcpu-512mb',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'total' => 1,
|
||||
'functions' => [
|
||||
[
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('functionListProvider')]
|
||||
public function testFunctionList(array $content, array $expected): void
|
||||
{
|
||||
$model = Response::MODEL_FUNCTION_LIST;
|
||||
|
||||
$result = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public static function siteProvider(): array
|
||||
{
|
||||
return [
|
||||
'merge buildSpecification and runtimeSpecification into specification' => [
|
||||
[
|
||||
'name' => 'test-site',
|
||||
'buildSpecification' => 's-1vcpu-512mb',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
'framework' => 'nextjs',
|
||||
],
|
||||
[
|
||||
'name' => 'test-site',
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
'framework' => 'nextjs',
|
||||
]
|
||||
],
|
||||
'use buildSpecification when present' => [
|
||||
[
|
||||
'buildSpecification' => 's-2vcpu-1gb',
|
||||
'runtimeSpecification' => 's-1vcpu-512mb',
|
||||
],
|
||||
[
|
||||
'specification' => 's-2vcpu-1gb',
|
||||
]
|
||||
],
|
||||
'fallback to specification when buildSpecification is missing' => [
|
||||
[
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
],
|
||||
[
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
]
|
||||
],
|
||||
'handle missing both buildSpecification and specification' => [
|
||||
[
|
||||
'name' => 'test-site',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
],
|
||||
[
|
||||
'name' => 'test-site',
|
||||
'specification' => null,
|
||||
]
|
||||
],
|
||||
'handle no spec fields at all' => [
|
||||
[
|
||||
'name' => 'test-site',
|
||||
'framework' => 'react',
|
||||
],
|
||||
[
|
||||
'name' => 'test-site',
|
||||
'specification' => null,
|
||||
'framework' => 'react',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('siteProvider')]
|
||||
public function testSite(array $content, array $expected): void
|
||||
{
|
||||
$model = Response::MODEL_SITE;
|
||||
|
||||
$result = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public static function siteListProvider(): array
|
||||
{
|
||||
return [
|
||||
'convert list of sites' => [
|
||||
[
|
||||
'total' => 2,
|
||||
'sites' => [
|
||||
[
|
||||
'name' => 'site-1',
|
||||
'buildSpecification' => 's-1vcpu-512mb',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
],
|
||||
[
|
||||
'name' => 'site-2',
|
||||
'buildSpecification' => 's-2vcpu-1gb',
|
||||
'runtimeSpecification' => 's-1vcpu-512mb',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'total' => 2,
|
||||
'sites' => [
|
||||
[
|
||||
'name' => 'site-1',
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
],
|
||||
[
|
||||
'name' => 'site-2',
|
||||
'specification' => 's-2vcpu-1gb',
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
'handle empty site list' => [
|
||||
[
|
||||
'total' => 0,
|
||||
'sites' => [],
|
||||
],
|
||||
[
|
||||
'total' => 0,
|
||||
'sites' => [],
|
||||
]
|
||||
],
|
||||
'handle single site in list' => [
|
||||
[
|
||||
'total' => 1,
|
||||
'sites' => [
|
||||
[
|
||||
'name' => 'my-site',
|
||||
'buildSpecification' => 's-1vcpu-512mb',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'total' => 1,
|
||||
'sites' => [
|
||||
[
|
||||
'name' => 'my-site',
|
||||
'specification' => 's-1vcpu-512mb',
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('siteListProvider')]
|
||||
public function testSiteList(array $content, array $expected): void
|
||||
{
|
||||
$model = Response::MODEL_SITE_LIST;
|
||||
|
||||
$result = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public static function documentProvider(): array
|
||||
{
|
||||
return [
|
||||
'cast $sequence to int' => [
|
||||
[
|
||||
'$id' => 'doc1',
|
||||
'$sequence' => '123',
|
||||
'name' => 'test',
|
||||
],
|
||||
[
|
||||
'$id' => 'doc1',
|
||||
'$sequence' => 123,
|
||||
'name' => 'test',
|
||||
]
|
||||
],
|
||||
'non-numeric $sequence becomes 0' => [
|
||||
[
|
||||
'$id' => 'doc1',
|
||||
'$sequence' => 'abc',
|
||||
],
|
||||
[
|
||||
'$id' => 'doc1',
|
||||
'$sequence' => 0,
|
||||
]
|
||||
],
|
||||
'nested relationship document' => [
|
||||
[
|
||||
'$id' => 'doc1',
|
||||
'$sequence' => '1',
|
||||
'author' => [
|
||||
'$id' => 'doc2',
|
||||
'$sequence' => '2',
|
||||
'name' => 'John',
|
||||
],
|
||||
],
|
||||
[
|
||||
'$id' => 'doc1',
|
||||
'$sequence' => 1,
|
||||
'author' => [
|
||||
'$id' => 'doc2',
|
||||
'$sequence' => 2,
|
||||
'name' => 'John',
|
||||
],
|
||||
]
|
||||
],
|
||||
'nested array of relationship documents' => [
|
||||
[
|
||||
'$id' => 'doc1',
|
||||
'$sequence' => '1',
|
||||
'comments' => [
|
||||
[
|
||||
'$id' => 'doc2',
|
||||
'$sequence' => '2',
|
||||
'text' => 'hello',
|
||||
],
|
||||
[
|
||||
'$id' => 'doc3',
|
||||
'$sequence' => '3',
|
||||
'text' => 'world',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'$id' => 'doc1',
|
||||
'$sequence' => 1,
|
||||
'comments' => [
|
||||
[
|
||||
'$id' => 'doc2',
|
||||
'$sequence' => 2,
|
||||
'text' => 'hello',
|
||||
],
|
||||
[
|
||||
'$id' => 'doc3',
|
||||
'$sequence' => 3,
|
||||
'text' => 'world',
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
'deeply nested relationships' => [
|
||||
[
|
||||
'$id' => 'doc1',
|
||||
'$sequence' => '1',
|
||||
'author' => [
|
||||
'$id' => 'doc2',
|
||||
'$sequence' => '2',
|
||||
'profile' => [
|
||||
'$id' => 'doc3',
|
||||
'$sequence' => '3',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'$id' => 'doc1',
|
||||
'$sequence' => 1,
|
||||
'author' => [
|
||||
'$id' => 'doc2',
|
||||
'$sequence' => 2,
|
||||
'profile' => [
|
||||
'$id' => 'doc3',
|
||||
'$sequence' => 3,
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('documentProvider')]
|
||||
public function testDocument(array $content, array $expected): void
|
||||
{
|
||||
$result = $this->filter->parse($content, Response::MODEL_DOCUMENT);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
#[DataProvider('documentProvider')]
|
||||
public function testRow(array $content, array $expected): void
|
||||
{
|
||||
$result = $this->filter->parse($content, Response::MODEL_ROW);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public static function defaultPassthroughProvider(): array
|
||||
{
|
||||
return [
|
||||
'unmatched model passes through unchanged' => [
|
||||
Response::MODEL_DOCUMENT,
|
||||
[
|
||||
'name' => 'test-doc',
|
||||
'$id' => 'doc123',
|
||||
'data' => 'some-value',
|
||||
],
|
||||
[
|
||||
'name' => 'test-doc',
|
||||
'$id' => 'doc123',
|
||||
'data' => 'some-value',
|
||||
]
|
||||
],
|
||||
'empty content passes through unchanged' => [
|
||||
Response::MODEL_DOCUMENT,
|
||||
[],
|
||||
[]
|
||||
],
|
||||
'deployment model passes through unchanged' => [
|
||||
Response::MODEL_DEPLOYMENT,
|
||||
[
|
||||
'id' => 'deployment123',
|
||||
'buildSpecification' => 's-1vcpu-512mb',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
],
|
||||
[
|
||||
'id' => 'deployment123',
|
||||
'buildSpecification' => 's-1vcpu-512mb',
|
||||
'runtimeSpecification' => 's-1vcpu-256mb',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('defaultPassthroughProvider')]
|
||||
public function testDefaultPassthrough(string $model, array $content, array $expected): void
|
||||
{
|
||||
$result = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user