Compare commits

...
Author SHA1 Message Date
Torsten Dittmann 5091861e6e Merge remote-tracking branch 'origin/1.9.x' into feat/command-execute-migration 2026-05-26 01:09:09 +04:00
Shmuel FogelandGitHub 22be6514fb Merge pull request #12293 from appwrite/users-skip-targets
skip subQueryTargets in users route
2026-05-25 12:11:58 +03:00
Jake BarnbyandGitHub 7e4645ffbd Merge branch '1.9.x' into users-skip-targets 2026-05-25 20:09:58 +12:00
Damodar LohaniandGitHub 4b801273c0 Merge pull request #12384 from appwrite/CLO-4385-fix-event-params-on-chunked-retry
fix(uploads): reset queueForEvents on idempotent chunked-upload retry
2026-05-25 13:04:08 +05:45
Damodar LohaniandClaude Opus 4.7 d6ca57d008 fix(uploads): reset queueForEvents on idempotent chunked-upload retry
The early-return branches added in #12138 for already-complete chunked
uploads bypass the trailing $queueForEvents->setParam('bucketId', ...)
calls. The action returns cleanly, the shutdown hook then calls
Event::generateEvents with empty params, throws \InvalidArgumentException,
and the error handler maps it to HTTP 500.

Reset the queue on each early-return path so the shutdown short-circuits
on the empty event string -- the resource was created on a previous
request and that request already fired the event; replaying it would
double-trigger webhooks/functions.

Applied to Storage Files Create, Functions Deployments Create, and
Sites Deployments Create -- all three endpoints share the same pattern
introduced by #12138.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 06:36:25 +00:00
Chirag AggarwalandGitHub 03555fa365 Merge pull request #12376 from appwrite/codex/builds-worker-span-logs
Replace builds worker logs with span attributes
2026-05-25 09:00:38 +05:30
Damodar LohaniandGitHub 9cbd5be707 Merge pull request #12329 from appwrite/feat/actor-type-constants-CLO-4357
feat!: rename ACTIVITY_TYPE_*, ROLE_APPS, isApp() to ACTOR_TYPE_*, ROLE_KEYS, isKey() + bump utopia-php/audit to ^2.4
2026-05-24 10:02:05 +05:45
Damodar LohaniandGitHub e9aaea19a4 Merge pull request #12378 from appwrite/CLO-4364-bump-php-runtimes-0.20.1
chore: bump appwrite/php-runtimes to 0.20.1
2026-05-24 09:44:45 +05:45
Damodar Lohani 3fc5fd8fcf chore: bump appwrite/php-runtimes to 0.20.1 2026-05-24 03:08:25 +00:00
Damodar Lohani 5f63100f4c fix(presences): rename isApp() callers to isKey() (new files from upstream 1.9.x merge) 2026-05-24 02:20:47 +00:00
Damodar Lohani 39aeecc58c fix(audits): drop removed 'location' field from event payload (utopia-php/audit 2.4 schema) 2026-05-24 02:20:47 +00:00
Damodar Lohani ce31a4f336 feat!: rename User::ROLE_APPS to ROLE_KEYS and isApp() to isKey() 2026-05-24 02:20:47 +00:00
Damodar Lohani 5f1e537e53 chore: bump utopia-php/audit to ^2.4 2026-05-24 02:20:47 +00:00
Damodar LohaniandClaude Opus 4.7 27370b6acb feat!: rename ACTIVITY_TYPE_* constants to ACTOR_TYPE_*
Renames the actor identity constants used by the audit/auth flow:

- ACTIVITY_TYPE_USER             -> ACTOR_TYPE_USER             ('user')
- ACTIVITY_TYPE_ADMIN            -> ACTOR_TYPE_ADMIN            ('admin')
- ACTIVITY_TYPE_GUEST            -> ACTOR_TYPE_GUEST            ('guest')
- ACTIVITY_TYPE_KEY_PROJECT      -> ACTOR_TYPE_KEY_PROJECT      ('keyProject')
- ACTIVITY_TYPE_KEY_ACCOUNT      -> ACTOR_TYPE_KEY_ACCOUNT      ('keyAccount')
- ACTIVITY_TYPE_KEY_ORGANIZATION -> ACTOR_TYPE_KEY_ORGANIZATION ('keyOrganization')

Values are unchanged. Call sites updated in:
- app/controllers/shared/api.php
- src/Appwrite/Platform/Workers/Audits.php

Audit payload key rename (userType -> actorType) and utopia-php/audit
bump will land in a follow-up PR.

BREAKING CHANGE: ACTIVITY_TYPE_* global constants are removed. Any
downstream extension or plugin importing those names must be updated
to the ACTOR_TYPE_* equivalents.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 02:20:17 +00:00
Chirag Aggarwal f1abdde9cf Replace builds worker logs with span attributes 2026-05-23 20:37:34 +05:30
Harsh MahajanandGitHub 8436fb0175 Merge pull request #12375 from appwrite/fix/organization-projects-api
Fix: organization project API headers and queries
2026-05-22 16:46:45 +05:30
harsh mahajan 6a4dcd76a7 Keep organization projects schema unchanged 2026-05-22 15:45:32 +05:30
harsh mahajan e831b3e952 Add project status index 2026-05-22 15:39:16 +05:30
harsh mahajan 4dec4f2ff2 Fix organization project API headers and queries 2026-05-22 15:32:44 +05:30
Harsh MahajanandGitHub aa4c6b6e8f Merge pull request #12374 from appwrite/fix/smtp-test-email-missing-platform-vars
Fix missing platform variables in SMTP test email template
2026-05-22 13:39:59 +05:30
harsh mahajan 2628cc31fc Remove unused $plan injection 2026-05-22 13:12:50 +05:30
harsh mahajanandHarsh Mahajan e3768ce8ee Fix missing platform variables in SMTP test email template
The SMTP test email uses email-base-styled.tpl as its base template,
which contains {{platform}}, {{logoUrl}}, {{accentColor}}, and social/
legal link placeholders. These were never passed as template variables,
causing them to render as literal strings (e.g. "{{platform}} logo").

Inject the platform config and pass the variables to MailMessage,
matching the pattern used by OTP and magic-url email flows.

Co-Authored-By: Harsh Mahajan <harsh@appwrite.io>
2026-05-22 13:03:48 +05:30
Jake BarnbyandGitHub dfc5f1c8dc Merge pull request #12371 from appwrite/fix-project-smtp-port-type
fix(project): cast smtpPort to int in response model
2026-05-22 19:09:07 +12:00
Harsh MahajanandGitHub e375009a49 Merge pull request #12373 from appwrite/feat-ser-401-expose-build-trigger-fields
Expose providerBranches and providerPaths in Function and Site response
2026-05-22 12:36:59 +05:30
harsh mahajanandHarsh Mahajan cf1623bc91 Expose providerBranches and providerPaths in Function and Site response models
These fields were already persisted on update but omitted from the response
model, causing them to disappear after a page refresh in the console.

Co-Authored-By: Harsh Mahajan <harsh@appwrite.io>
2026-05-22 12:27:05 +05:30
Prem Palanisamy e364082577 fix(project): cast smtpPort to int in response model 2026-05-22 07:52:25 +01:00
Luke B. SilverandGitHub 9951ccdca3 Merge pull request #12338 from appwrite/fix/coroutines-correctness
chore: migrate to utopia-php/http feat-safe-wildcards
2026-05-21 21:58:55 +01:00
loks0nandClaude Opus 4.7 035cbacbfb Use 'params' injection in V20 filter
Drops the redundant Route::resolveParams() call. The matched path
params are now provided directly via the new frame-local 'params'
injection from utopia-php/http, avoiding a second URL parse.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 21:09:55 +01:00
loks0nandClaude Opus 4.7 690c72b789 Use 'route' injection in remaining api/auth init hooks
The 'route' injection is frame-local and non-nullable inside a
matched action's hooks. Replaces \$utopia->match() lookups in api.php
and auth.php init hooks, drops the dead \$utopia inject from the
session shutdown hook, and removes the now-redundant null guards.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 21:09:41 +01:00
loks0nandClaude Opus 4.7 dd0c8af160 Use 'route' injection in shutdown hooks
The 'route' injection introduced in utopia-php/http feat-safe-wildcards
is frame-local and non-nullable inside a matched action's hooks, so
the shutdown handlers in api.php and mock.php no longer need to call
match() and dereference a nullable result.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 21:09:41 +01:00
loks0nandClaude Opus 4.7 933fffd763 Use route template instead of request URI for documentsdb path check
The documentsdb/vectorsdb routes are registered with
setHttpPath('/v1/documentsdb/...') with no aliases, so getPath()
returns a template containing the substring we're matching against
— and matches the prior getMatchedPath() semantics without depending
on the raw request URI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 21:09:41 +01:00
loks0nandClaude Opus 4.7 1538927486 chore: migrate to utopia-php/http feat-safe-wildcards
Adopts the new safe-wildcard dispatch primitive from
utopia-php/http#feat-safe-wildcards. Http::execute() is now the
re-entrant dispatch entry point and Http::match() is pure (returns
?RouteMatch). The removed Http::getRoute()/setRoute(),
Route::getMatchedPath(), Route::getPathValues() callsites are migrated
to the new API.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 21:09:41 +01:00
Harsh MahajanandGitHub 49bf24d388 Merge pull request #11955 from appwrite/feat-ser-401-custom-triggers
Feat: custom triggers for VCS builds
2026-05-21 19:08:38 +05:30
harsh mahajan f62d765bbf format 2026-05-21 18:46:18 +05:30
harsh mahajanandClaude Sonnet 4.6 268dd50106 Use Globstar validator directly, remove BuildTrigger wrapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 18:43:34 +05:30
Harsh MahajanandGitHub 4eb956b820 Merge branch '1.9.x' into feat-ser-401-custom-triggers 2026-05-21 16:37:24 +05:30
harsh mahajan 01a6d6e4d6 Inline VCS build trigger checks 2026-05-21 15:35:44 +05:30
harsh mahajan c1e902c79c Restore deployments on branch creation 2026-05-21 15:32:35 +05:30
harsh mahajan 9bcb4d7ca7 Clarify renamed PR file filtering 2026-05-21 15:31:05 +05:30
Matej BačoandGitHub f77205295b Merge pull request #12309 from appwrite/feat-public-organization-api
Feat: Public organization API
2026-05-21 10:25:59 +02:00
Matej BačoandGitHub 781df9d4cf Merge branch '1.9.x' into feat-public-organization-api 2026-05-21 09:42:32 +02:00
Jake Barnby 238b4e447d test: stabilize function logging executions 2026-05-21 18:38:59 +12:00
Jake Barnby 349ac6bbc1 test: stabilize account log pagination 2026-05-21 16:47:45 +12:00
Jake BarnbyandGitHub dca75b059b Merge pull request #12361 from appwrite/fix-smtp-template-regressions
Fix SMTP empty sender regressions
2026-05-21 12:42:51 +12:00
Jake Barnby fd145e988a test: align versions endpoint expectations 2026-05-21 12:18:11 +12:00
Jake Barnby c6ac0f28e4 chore: restore twig security update 2026-05-21 12:16:06 +12:00
Jake Barnby 52d7ebd41e chore: revert to 2.0.0-rc.1 2026-05-21 10:59:37 +12:00
Jake Barnby 41ff2ed48e chore: update lock 2026-05-21 10:37:36 +12:00
harsh mahajan 436064e7cc Revert test fixture change 2026-05-20 23:44:11 +05:30
harsh mahajan a3ae9f39ec Fix SMTP empty sender regressions 2026-05-20 23:40:41 +05:30
fogelito fd6146888b Merge branch '1.9.x' of https://github.com/appwrite/appwrite into users-skip-targets 2026-05-20 15:54:15 +03:00
Matej Bačo 4400eedbae Add proper test coverage 2026-05-20 13:56:47 +02:00
Matej Bačo cbcf86e362 Add missing delete endpoint 2026-05-20 13:49:40 +02:00
Matej Bačo e69b9eb428 PR cleanup 2026-05-20 13:49:29 +02:00
Harsh MahajanandGitHub 8cfc28f08c Merge pull request #12348 from appwrite/fix-smtp-template-empty-string-params
fix: allow empty string to clear optional SMTP and email template fields
2026-05-20 17:00:14 +05:30
harsh mahajan 5473be5756 chore: revert twig override, restore sdk-generator to 1.31.0 2026-05-20 16:56:56 +05:30
harsh mahajan a3fca62818 chore: bump twig/twig to ^3.26 to fix security advisories 2026-05-20 16:45:17 +05:30
harsh mahajan d397ab9b40 chore: update composer dependencies 2026-05-20 16:34:15 +05:30
Harsh MahajanandGitHub e59e726d38 Merge branch '1.9.x' into fix-smtp-template-empty-string-params 2026-05-20 16:28:06 +05:30
harsh mahajan 8272aa6fa2 fix: remove unnecessary null coalescing on senderEmail 2026-05-20 16:27:52 +05:30
harsh mahajan 43b9bf8152 fix: remove redundant senderEmail empty check before setFrom 2026-05-20 16:25:04 +05:30
fogelito 7312f4bfe0 Merge branch '1.9.x' of https://github.com/appwrite/appwrite into users-skip-targets 2026-05-20 13:50:31 +03:00
Chirag AggarwalandGitHub 5df3280131 Merge pull request #12357 from appwrite/fix/spec-regional-endpoint-validation
Fix generated spec endpoint and enum defaults
2026-05-20 15:21:12 +05:30
Chirag Aggarwal 515696dd36 Fix generated spec endpoint and enum defaults 2026-05-20 14:58:56 +05:30
Chirag AggarwalandGitHub ff76f3efa0 Merge pull request #12356 from appwrite/fix-account-server-specs-auth
Fix account server specs auth
2026-05-20 14:26:14 +05:30
Chirag Aggarwal 0d19894331 Move Web SDK to server platform 2026-05-20 14:09:07 +05:30
Chirag Aggarwal 1d9acf5f29 Fix account SDK specs auth 2026-05-20 14:03:20 +05:30
Harsh MahajanandGitHub d3994b436d Merge branch '1.9.x' into fix-smtp-template-empty-string-params 2026-05-20 11:40:47 +05:30
harsh mahajan cc51f42430 fix: allow SMTP connection test without senderEmail 2026-05-20 11:40:23 +05:30
Matej Bačo 8cf0438a98 Merge branch '1.9.x' into feat-public-organization-api 2026-05-19 17:20:33 +02:00
Matej Bačo b2ebfa9bb9 Revert unwanted tests 2026-05-19 17:20:15 +02:00
Matej BačoandGitHub ed14acfc75 Merge pull request #12353 from appwrite/fix-trace-ending-too-soon
Fix: subspan ending main request span
2026-05-19 16:57:59 +02:00
Matej Bačo 90df8efe0a Formatting fix 2026-05-19 15:50:48 +02:00
Matej Bačo f7b09c090b Fix subspan ending main request span 2026-05-19 15:23:34 +02:00
fogelito aa3a63c87b Merge branch '1.9.x' of https://github.com/appwrite/appwrite into users-skip-targets 2026-05-19 15:51:12 +03:00
harsh mahajan 8c903ab687 fix: allow empty string to clear optional SMTP and email template fields 2026-05-19 16:29:55 +05:30
Jake BarnbyandGitHub c5b8535a7f Merge pull request #12233 from appwrite/feat-dedicated-db
feat: dedicated database
2026-05-19 19:44:13 +12:00
Harsh MahajanandGitHub cf101b6a8e Merge branch '1.9.x' into feat-ser-401-custom-triggers 2026-05-19 12:35:41 +05:30
Jake BarnbyGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
218cc13b20 Update src/Appwrite/Platform/Workers/StatsResources.php
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-19 18:32:54 +12:00
282b5b2a6c fix(databases): map cursor lookup NotFound to collection 404
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/546b5830-100b-4a48-9d71-f19c204e269f

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-19 06:32:06 +00:00
948c702ad4 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-19 18:28:20 +12:00
c28f476462 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-19 18:24:06 +12:00
Jake BarnbyandGitHub 3078cc5941 Merge pull request #12345 from appwrite/codex/advisor-admin-key-auth
fix: restrict advisor auth types
2026-05-19 18:23:06 +12:00
harsh mahajanandClaude Sonnet 4.6 74a4ae19ee refactor: remove unnecessary comments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 11:28:04 +05:30
harsh mahajanandClaude Sonnet 4.6 3b3a04877f feat: remove CommitSkipPatterns - already available in 1.9.x
Keep only branch and path skip trigger logic. Commit message skip
is handled by the existing Contains(VCS_DEPLOYMENT_SKIP_PATTERNS) check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 11:21:04 +05:30
harsh mahajanandClaude Sonnet 4.6 a4a57bc64d Merge branch '1.9.x' into feat-ser-401-custom-triggers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 11:20:08 +05:30
Damodar LohaniandGitHub fd91490ae9 Merge pull request #12344 from appwrite/feat/file-size-actual
feat(storage): expose sizeActual in File response model
2026-05-19 09:02:12 +05:45
Jake BarnbyandGitHub 81d78ceb96 Merge branch '1.9.x' into codex/advisor-admin-key-auth 2026-05-19 14:53:56 +12:00
Jake Barnby 9233fe5b42 fix: restrict advisor auth types 2026-05-19 14:47:54 +12:00
Jake BarnbyandGitHub 3c1e07fc92 Merge pull request #12343 from appwrite/presence-api
added category to the presences scopes
2026-05-19 14:47:28 +12:00
Damodar LohaniandClaude Opus 4.7 27534133bb feat(storage): expose sizeActual in File response model
Adds the stored byte size (after compression and/or encryption) alongside
sizeOriginal so clients can see how much space a file actually occupies.
The sizeActual attribute is already persisted on file documents.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 02:11:49 +00:00
Jake Barnby 52b8e1b94d chore: sync 1.9.x into feat-dedicated-db 2026-05-19 13:32:46 +12:00
ArnabChatterjee20k 4d3c60b36c updated 2026-05-18 21:53:06 +05:30
Chirag AggarwalandGitHub 8d93922a52 Merge pull request #12339 from appwrite/chore/remove-router-deployment-fallback
Remove router deployment fallback
2026-05-18 21:50:49 +05:30
ArnabChatterjee20k 89d8c9285a Merge remote-tracking branch 'origin/presence-api' into presence-api 2026-05-18 21:49:59 +05:30
ArnabChatterjee20k ffe06180bc Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-05-18 21:49:37 +05:30
ArnabChatterjee20k 349db8d370 fix: improve error handling for Utopia DB exceptions in realtime API 2026-05-18 21:49:13 +05:30
ArnabChatterjee20kandGitHub 246ff8900b Merge branch '1.9.x' into presence-api 2026-05-18 21:31:56 +05:30
ArnabChatterjee20k 6f28f2e4f7 linting 2026-05-18 21:26:21 +05:30
ArnabChatterjee20k 42144ab9a0 added category to the presences scopes 2026-05-18 21:23:21 +05:30
Luke B. SilverandGitHub 836a89714d Merge pull request #12341 from appwrite/fix/cache-healthcheck
fix(health): use injected cache resource for healthcheck
2026-05-18 14:31:16 +01:00
Chirag AggarwalandGitHub f1d40a8e92 Merge pull request #12332 from appwrite/refactor/shared-di-resources
Consolidate shared DI resources
2026-05-18 18:38:38 +05:30
Chirag Aggarwal 05eead1857 Remove router deployment fallback 2026-05-18 18:34:12 +05:30
loks0nandClaude Opus 4.7 2cd422f4c2 fix(health): use injected cache resource for healthcheck
Inject the live cache resource and ping it directly instead of
rebuilding pool adapters from pools-cache. The container already
constructs the right adapter (pool, multiplexing, none), so the
healthcheck now exercises the actual configured cache.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 13:58:24 +01:00
Chirag Aggarwal b756a38252 Consolidate shared DI resources 2026-05-18 16:16:23 +05:30
Atharva DeosthaleandGitHub 4f676aac15 Merge pull request #12337 from appwrite/feat/sites-bun-deno-runtimes
feat(sites): allow bun and deno as build runtimes for JS frameworks
2026-05-18 16:12:18 +05:30
ArnabChatterjee20kandGitHub 7a711b7295 Merge pull request #11886 from appwrite/presence-api
Presence api
2026-05-18 15:25:03 +05:30
Atharva Deosthale 03e62dde94 feat(sites): allow bun and deno as build runtimes for JS frameworks
Merge BUN and DENO template runtimes into the runtimes list for every NODE-based Sites framework (analog, angular, nextjs, react, nuxt, vue, sveltekit, astro, tanstack-start, remix, lynx, react-native, vite, other). The Console's build-runtime dropdown reads framework.runtimes, so this is what surfaces bun-1.3 and deno-2.6 in the UI now that openruntimes/open-runtimes#485 has shipped SSR support for both runtimes.

The server-side WhiteList validator on POST /v1/sites already accepts any runtime in the global runtimes config, so CLI and REST clients have been able to pick bun/deno all along; this change only flips the UI gate.

Flutter stays flutter-only.
2026-05-18 15:16:59 +05:30
Shmuel FogelandGitHub b81b818c47 Merge pull request #12335 from appwrite/delete-executions-order-by
Delete executions order by
2026-05-18 11:24:03 +03:00
fogelito 95789b5aec Merge branch '1.9.x' of https://github.com/appwrite/appwrite into delete-executions-order-by 2026-05-18 11:02:30 +03:00
ArnabChatterjee20k 20c0c8081c feat: add documentation for presence API endpoints and update descriptions 2026-05-18 12:30:58 +05:30
fogelito 254606c4f9 Add orderDesc 2026-05-18 10:00:24 +03:00
Chirag AggarwalandGitHub 7cef1202f1 Merge pull request #12330 from appwrite/fix-unity-sdk-getting-started
Add Unity SDK getting started docs
2026-05-18 12:29:40 +05:30
ArnabChatterjee20k ff298cb206 updated clousure scope 2026-05-18 11:48:52 +05:30
ArnabChatterjee20k 9263ea3661 Merge remote-tracking branch 'origin/presence-api' into presence-api 2026-05-18 11:32:55 +05:30
ArnabChatterjee20k 8aa09cfcc1 fix: update checkForProjectUsage function to return void instead of Document 2026-05-18 11:32:37 +05:30
Chirag Aggarwal ab208a01b6 Update Unity docs for client factories 2026-05-18 11:22:24 +05:30
ArnabChatterjee20kandGitHub 80fffa32b8 Merge branch '1.9.x' into presence-api 2026-05-18 11:09:55 +05:30
ArnabChatterjee20k 664c24173e feat: update checkForProjectUsage function to return the project document 2026-05-18 11:06:33 +05:30
Chirag Aggarwal 5b3af53bdd Address Unity SDK docs review 2026-05-18 11:04:07 +05:30
ArnabChatterjee20k b2809c911d fix: improve presence log deletion handling and ensure accurate event triggering 2026-05-18 11:02:21 +05:30
Chirag Aggarwal febe332050 Add Unity SDK getting started docs 2026-05-18 10:56:29 +05:30
ArnabChatterjee20k d18de7d1e8 feat: add checkForProjectUsage function and integrate it into message handling 2026-05-18 10:42:11 +05:30
Luke B. SilverandGitHub 4e39506bca Merge pull request #12326 from appwrite/chore/bump-cace
chore: bump utopia-php/cache to ^3.0
2026-05-17 20:56:32 +01:00
loks0nandClaude Opus 4.7 1b945bdeea chore: bump utopia-php/cache to ^3.0
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 17:11:05 +01:00
fogelito 5c9f360744 Merge branch '1.9.x' of https://github.com/appwrite/appwrite into users-skip-targets 2026-05-17 17:17:09 +03:00
Torsten DittmannandGitHub 4a44af7308 Merge pull request #12209 from appwrite/fix/parallel-storage-chunk-upload
Fix parallel chunked uploads with pooled locks
2026-05-15 22:22:41 +04:00
Torsten Dittmann 37921a5694 Remove utopia-php/lock vcs repo 2026-05-15 21:20:20 +04:00
Torsten Dittmann 27bf229524 Simplify storage test assumptions 2026-05-15 21:12:14 +04:00
Torsten DittmannandGitHub e873f02b94 Merge branch '1.9.x' into fix/parallel-storage-chunk-upload 2026-05-15 20:17:08 +04:00
Torsten Dittmann 5750155591 Add pool-backed lock resource 2026-05-15 19:54:44 +04:00
Harsh MahajanandGitHub d2dcd82efa Merge pull request #12318 from appwrite/feat-email-template-reset-default
feat: add email template reset and get-default endpoints
2026-05-15 20:27:39 +05:30
harsh mahajan cb80f0e9c8 Remove email template integration test 2026-05-15 19:44:20 +05:30
harsh mahajan 6a2ddb221c Clean up template update diff and add locale test 2026-05-15 19:34:15 +05:30
harsh mahajan 6428b73966 Fix email template update System import 2026-05-15 19:05:01 +05:30
harsh mahajan 7b718e7dcc Add email template default integration test 2026-05-15 18:57:08 +05:30
harsh mahajan 15422dc7ca Remove email template reset API changes 2026-05-15 18:51:22 +05:30
harsh mahajan 0e7c50c181 Align console email template endpoint metadata 2026-05-15 18:45:38 +05:30
harsh mahajan 8781942633 Fix console email template spec enum names 2026-05-15 18:18:10 +05:30
harsh mahajan a614afe5c7 refactor: move email template defaults to console module and add tests
- Move GetDefault endpoint to Console module as Get.php
  (GET /v1/console/templates/email/:templateId, console.getEmailTemplate)
- Remove from Project module
- Add e2e tests for console.getEmailTemplate and reset=true
2026-05-15 18:03:12 +05:30
ArnabChatterjee20k cf80efbc3c feat: add support for 'getUsage' method in presences with range parameter 2026-05-15 18:01:32 +05:30
ArnabChatterjee20k ec1fad20dd Merge remote-tracking branch 'origin/presence-api' into presence-api 2026-05-15 17:51:33 +05:30
ArnabChatterjee20k 2c2e8bcabd fix: add expiration check for presence updates to prevent outdated entries 2026-05-15 17:51:08 +05:30
Torsten Dittmann b7bf14a18a Fix storage multipart upload preparation 2026-05-15 15:58:48 +04:00
harsh mahajan 0e157efabe chore: fix import ordering in Http.php 2026-05-15 17:17:32 +05:30
harsh mahajan 69735b383b feat: add email template reset and get-default endpoints
- Add GET /v1/project/templates/email/:templateId/default that returns
  Appwrite built-in defaults, ignoring any custom project overrides
- Add reset param to PATCH /v1/project/templates/email — when reset=true,
  clears the custom template entry and returns defaults without requiring
  SMTP to be enabled
2026-05-15 17:13:35 +05:30
Matej Bačo b627a7d6ff Org key tests 2026-05-15 12:46:27 +02:00
ArnabChatterjee20kandGitHub 14381f2268 Merge branch '1.9.x' into presence-api 2026-05-15 15:53:51 +05:30
Torsten Dittmann 97d6656687 Fix deployment upload lock TTL 2026-05-15 14:17:14 +04:00
Torsten Dittmann e6f3061103 Pin utopia-php/lock to 0.2.x 2026-05-15 14:15:08 +04:00
Torsten Dittmann 9acfb2a679 Merge branch '1.9.x' of https://github.com/appwrite/appwrite into fix/parallel-storage-chunk-upload 2026-05-15 14:02:58 +04:00
Torsten Dittmann c5123529ee Update graphql-php to 15.32 and fix storage tests
- Bump graphql-php to 15.32 in composer.json and update composer.lock
- Extend Storage/Files/Create with mergeUploadMetadata and pass path and
  deviceForFiles into the lock callback
- Remove obsolete type hints in Storage/Files/Preview/Get
- Update Storage tests to tolerate S3-backed signatures
2026-05-15 13:54:24 +04:00
Matej Bačo a0c27afec8 Revert test changes 2026-05-15 11:47:52 +02:00
Matej Bačo 5a0bb57db2 linter fix 2026-05-15 11:45:39 +02:00
Matej Bačo d422b7abdc Final reverts 2026-05-15 11:44:47 +02:00
Matej Bačo 34078ad9af Revert unwanted changes 2026-05-15 11:37:04 +02:00
Matej Bačo 2d94704948 Formatting fix 2026-05-15 11:30:06 +02:00
Matej Bačo cd54e91784 Deprecate old projects endpoints 2026-05-15 11:27:19 +02:00
Matej Bačo 79f536445e Revive old endpoints 2026-05-15 11:26:35 +02:00
Matej Bačo 11839c6dcc Remove old endpoint aliases 2026-05-15 11:26:22 +02:00
Matej Bačo a9eb9d47a5 Merge branch '1.9.x' into feat-public-organization-api 2026-05-15 11:21:17 +02:00
Matej BačoandGitHub 246f487687 Merge pull request #12317 from appwrite/feat-org-scopes-console-api
Feat: Org scopes Console API
2026-05-15 11:17:13 +02:00
Matej Bačo 88e7fdbd9e Improve tests 2026-05-15 10:55:13 +02:00
Chirag AggarwalandGitHub aada60bdb1 Merge pull request #12316 from appwrite/chore/queue-database-publisher-migration
Refactor database queue publisher
2026-05-15 14:18:46 +05:30
Chirag AggarwalandGitHub f43e55620b Merge pull request #12315 from appwrite/chore/update-platform-rc2
Update Utopia platform to rc2
2026-05-15 14:13:13 +05:30
Chirag Aggarwal bf3ac31157 chore: retrigger ci 2026-05-15 13:59:10 +05:30
Chirag Aggarwal e9b026647a chore: retrigger ci 2026-05-15 13:57:10 +05:30
Matej Bačo e93a2c7101 Add tests for org scopes console get endpoint 2026-05-15 10:23:40 +02:00
Matej Bačo 8d8a0e883e Add org scopes get console endpoint 2026-05-15 10:23:32 +02:00
Matej Bačo 75587e629e Improve quality of org scopes 2026-05-15 10:23:22 +02:00
Chirag Aggarwal ad88b827e6 Refactor database queue publisher 2026-05-15 13:22:35 +05:30
Chirag Aggarwal 4d1dabac66 Update Utopia platform to rc2 2026-05-15 13:21:35 +05:30
ArnabChatterjee20k a8670ee8fb fix: update authorization handling in message processing to prevent state leakage 2026-05-15 13:17:14 +05:30
Matej Bačo 5b109f2f8d Fix org key usecase 2026-05-14 19:52:14 +02:00
Matej Bačo 3e35b888a7 Fix bug 2026-05-14 19:47:59 +02:00
Matej Bačo bcd2bfe5b2 Fix security 2026-05-14 19:34:49 +02:00
Matej Bačo d7e29a4dae fix more tests 2026-05-14 19:03:14 +02:00
Matej Bačo 3596662fec Update more tests 2026-05-14 18:42:30 +02:00
Matej Bačo f40c7d415c Prioritize org id header 2026-05-14 18:26:47 +02:00
Matej Bačo 176d59e7c9 Fix broken test after breaking change
List ALL projects no longer exists - we now have list project per organization
2026-05-14 18:02:20 +02:00
Matej Bačo bb25a36cf5 Fix project creation 401 expectation tests 2026-05-14 17:53:03 +02:00
Matej Bačo f37e091b77 Fix specs generation 2026-05-14 17:50:36 +02:00
Matej Bačo 18380a679d Fix backwards compatibility 2026-05-14 17:46:13 +02:00
Matej Bačo 2a82604a2b Better backwards compatibility for project creation 2026-05-14 17:36:03 +02:00
Matej Bačo b618bf1353 Fix inability to CRUD projects 2026-05-14 17:26:08 +02:00
ArnabChatterjee20k 0e1cabe47a Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-05-14 18:25:16 +05:30
Harsh MahajanandGitHub 54693d9417 Merge pull request #12278 from appwrite/feat/skip-deployment-commit-patterns
Feat/skip deployment commit patterns
2026-05-14 18:22:06 +05:30
harsh mahajan 437b3bfa10 fix: lint 2026-05-14 18:08:59 +05:30
harsh mahajan 0b07305775 fix: remove redundant comment, rename commitSkip to validator, use getDescription in span 2026-05-14 18:05:04 +05:30
harsh mahajan b5f0ebb37b refactor: replace DeploymentSkipPatterns validator with VCS_DEPLOYMENT_SKIP_PATTERNS constant 2026-05-14 17:59:34 +05:30
harsh mahajan 3cc600d29a fix: revert validators to 0.2.* for auto updates 2026-05-14 17:56:09 +05:30
ArnabChatterjee20k 2cbd8bbb47 linting 2026-05-14 17:33:53 +05:30
ArnabChatterjee20k 12b2acd7f7 Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-05-14 17:31:24 +05:30
Harsh MahajanandGitHub 736196b58b Merge branch '1.9.x' into feat/skip-deployment-commit-patterns 2026-05-14 17:31:06 +05:30
harsh mahajan 25772f4585 chore: composer update all dependencies 2026-05-14 17:28:52 +05:30
fogelito 3684fee2a2 Merge branch '1.9.x' of https://github.com/appwrite/appwrite into users-skip-targets 2026-05-14 14:58:50 +03:00
harsh mahajan e3755679c5 refactor: extend Contains validator, keep only [skip ci] pattern 2026-05-14 17:26:32 +05:30
Matej Bačo aaa870e656 Formatting fixes 2026-05-14 13:52:34 +02:00
Matej Bačo 94274381ff agentic migration fixes 2026-05-14 13:52:25 +02:00
ArnabChatterjee20k 3703da188e refactor: enhance presence management to prevent cross-user overwrites 2026-05-14 17:17:27 +05:30
fogelito d9446fa892 address 2026-05-14 14:45:00 +03:00
Chirag AggarwalandGitHub 450c80ca5b Merge pull request #12312 from appwrite/fix/text-response-specs
Fix text response schemas in API specs
2026-05-14 16:56:32 +05:30
Chirag Aggarwal e1547023f6 Fix OpenAPI text redirect response schemas 2026-05-14 16:48:58 +05:30
Matej Bačo 8a4a6d83d4 Manual migration fixes 2026-05-14 13:06:39 +02:00
Chirag Aggarwal 6d0a76cf51 Fix text response schemas in API specs 2026-05-14 16:35:08 +05:30
Chirag AggarwalandGitHub a3fa12e5b3 Merge pull request #12305 from appwrite/chore/refactor-delete-queue-publisher
refactor: Migrate queueForDeletes to DeletePublisher and DeleteMessage
2026-05-14 16:31:03 +05:30
Chirag AggarwalandGitHub 659bc0dab7 Merge pull request #12307 from appwrite/codex/delete-orphaned-rules-1-9
[codex] Delete orphaned proxy rules on create
2026-05-14 16:28:33 +05:30
Matej Bačo b524b9f934 Fix scopes 2026-05-14 12:18:09 +02:00
premtsd-codeandGitHub 927a5cddc4 Merge pull request #11439 from appwrite/feat-platform-db-access
Add platform migration support
2026-05-14 11:13:53 +01:00
Chirag Aggarwal 6b51a56b2b Tidy deleteOrphanedRule conditionals 2026-05-14 15:42:52 +05:30
Chirag Aggarwal 8f481d6f2c refactor: keep delete publisher in shared container 2026-05-14 15:37:04 +05:30
Chirag Aggarwal 421a9974af Delete orphaned proxy rules on create 2026-05-14 15:33:29 +05:30
Matej Bačo 275a6fe078 merge endpoints as aliases 2026-05-14 12:01:51 +02:00
Matej Bačo 8b4c90e603 Introduce organization endpoints 2026-05-14 11:47:46 +02:00
Chirag Aggarwal 65d1e58e34 refactor: migrate delete queue publisher 2026-05-14 14:40:11 +05:30
ArnabChatterjee20k a4acd000c4 linting 2026-05-14 13:57:35 +05:30
ArnabChatterjee20k f7e5e825f2 Merge remote-tracking branch 'origin/presence-api' into presence-api 2026-05-14 13:29:19 +05:30
ArnabChatterjee20k 7dd85f9143 add ownership guard to prevent cross-user presence overwrite 2026-05-14 13:28:47 +05:30
ArnabChatterjee20kandGitHub 7da3591d2b Merge branch '1.9.x' into presence-api 2026-05-14 13:12:42 +05:30
premtsd-codeandGitHub beb119883a Merge branch '1.9.x' into feat-platform-db-access 2026-05-14 08:37:33 +01:00
Prem Palanisamy 1da8d38daf Pin utopia-php/migration to 1.* (resolves 1.12.0) 2026-05-14 08:36:46 +01:00
ArnabChatterjee20k faf13c502b reordered set in the realtime 2026-05-14 12:46:58 +05:30
ArnabChatterjee20k 43da03ef63 removed redundant container set 2026-05-14 12:38:11 +05:30
Harsh MahajanandGitHub 05fb3a5d8d Merge pull request #12304 from appwrite/fix-specs-platform-order
Fix: project specs navigation order
2026-05-14 12:31:41 +05:30
ArnabChatterjee20k 5e4de3931a added presences leaf names 2026-05-14 12:17:45 +05:30
ArnabChatterjee20k 2b7ed2aa24 removed stale 2026-05-14 12:15:15 +05:30
harsh mahajan 5b7d6f1ca6 Fix project specs navigation order 2026-05-14 12:13:13 +05:30
ArnabChatterjee20k 5123956790 reverted schema 2026-05-14 11:57:23 +05:30
ArnabChatterjee20k 28d0348ef5 fixed case 2026-05-14 11:55:25 +05:30
ArnabChatterjee20k ad0b2ebb06 changed to sentence case for the dispatcher messages 2026-05-14 11:53:40 +05:30
ArnabChatterjee20k a3542ed7fa feat: enhance presence handling by refactoring usage and event triggering logic 2026-05-14 11:48:41 +05:30
ArnabChatterjee20k 0aa8d402ae feat: streamline presence cleanup logic and enhance stale presence deletion 2026-05-14 11:36:55 +05:30
Jake BarnbyandGitHub a4d1043684 Merge pull request #12302 from appwrite/fix-transaction-deletes
fix: match transactions collection name in deletes worker
2026-05-14 17:48:18 +12:00
Chirag AggarwalandGitHub 10de0d4245 Merge pull request #12284 from appwrite/codex/fail-open-abuse-timelimit
Fail open on abuse timelimit failures
2026-05-14 11:02:16 +05:30
Prem Palanisamy 0497137d80 Merge remote-tracking branch 'origin/1.9.x' into feat-platform-db-access
# Conflicts:
#	composer.lock
2026-05-14 06:20:03 +01:00
Prem Palanisamy f05b55add7 test: use project API key for platform migration E2E; bump migration lock 2026-05-14 06:15:55 +01:00
Chirag Aggarwal 1ced50db1f Merge remote-tracking branch 'origin/1.9.x' into codex/resolve-12284-conflict
# Conflicts:
#	app/controllers/shared/api.php
2026-05-14 10:43:05 +05:30
Damodar LohaniandGitHub 64b0cdaab7 Merge pull request #12303 from appwrite/chore/utopia-audit-2.3
chore: bump utopia-php/audit to 2.3.x
2026-05-14 10:19:46 +05:45
Damodar LohaniandGitHub bce5fe8e09 Merge branch '1.9.x' into chore/utopia-audit-2.3 2026-05-14 10:07:53 +05:45
Damodar LohaniandClaude Opus 4.7 6116d113b8 chore: refresh utopia-php/audit to 2.3.2
Picks up the opt-in async cleanup setter (utopia-php/audit#117)
needed for the deletes worker on the cloud side.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 04:20:27 +00:00
Chirag AggarwalandGitHub 1fb1cec1b6 Merge pull request #12299 from appwrite/chore/refactor-function-queue-publisher
refactor: Migrate queueForFunctions to FunctionPublisher and FunctionMessage
2026-05-14 08:56:12 +05:30
Damodar LohaniandClaude Opus 4.7 fe5de129da chore: bump utopia-php/audit to 2.3.x
Brings in cursor pagination (cursorAfter/cursorBefore) and count(max)
support for the activities/audit endpoints. Pulls utopia-php/query 0.1.x
as a new transitive dependency. utopia-php/audit 2.3.x requires PHP 8.4,
satisfied by the appwrite/base:1.4.1 runtime image used on this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:16:48 +00:00
Jake BarnbyandClaude Opus 4.7 10eae0ab87 fix: match transactions collection name in deletes worker
The DELETE_TYPE_TRANSACTION constant was 'transaction' (singular), so
the worker's inner switch on $document->getCollection() (which returns
'transactions') never matched and every transaction deletion fell to
the default branch, logging "No lazy delete operation available for
document of type: transactions". Renamed to DELETE_TYPE_TRANSACTIONS
to align with every other constant used in that switch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 13:58:40 +12:00
Prem Palanisamy e9c16a8fd3 chore(deps): bump utopia-php/migration to feat-platform-db-access 2026-05-13 16:56:09 +01:00
Prem Palanisamy bddde5f298 Merge remote-tracking branch 'origin/1.9.x' into feat-platform-db-access
# Conflicts:
#	app/controllers/api/migrations.php
#	composer.json
#	composer.lock
#	src/Appwrite/Platform/Workers/Migrations.php
2026-05-13 16:36:39 +01:00
ArnabChatterjee20k 0466c82a42 fix: update error message for invalid permissions in PresenceRealtimeClientTest 2026-05-13 19:22:43 +05:30
ArnabChatterjee20k 995f844254 feat: refine permissions parameter handling in Presence action for clarity and consistency 2026-05-13 19:19:00 +05:30
ArnabChatterjee20k 18a26809af feat: improve user resolution during disconnect event for accurate actor context 2026-05-13 19:13:59 +05:30
ArnabChatterjee20k 42d3240d05 feat: refactor presence state usage and telemetry span naming for consistency 2026-05-13 19:12:11 +05:30
ArnabChatterjee20k 52c072489b Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-05-13 18:58:44 +05:30
ArnabChatterjee20k 602ceb55ef feat: refactor presence state usage across multiple files for consistency 2026-05-13 18:57:09 +05:30
ArnabChatterjee20k f09aec7651 feat: update presence handling and refactor related attributes for consistency 2026-05-13 18:52:14 +05:30
Chirag Aggarwal 85e2cf7d8b (refactor): Migrate queueForFunctions to FunctionPublisher and FunctionMessage 2026-05-13 18:28:05 +05:30
ArnabChatterjee20k f818f16ebe feat: simplify presence parameter validation by removing Nullable wrapper 2026-05-13 18:25:06 +05:30
Luke B. SilverandGitHub f6d2314e11 Merge pull request #12298 from appwrite/chore/normalize-span-keys
chore: normalize Span keys and centralize span lifecycle
2026-05-13 12:01:46 +01:00
loks0nandClaude Opus 4.7 24e108fdbf fix: use canonical project.id/user.id so trace sampler matches realtime and VCS spans
The sampler reads project.id from the span; realtime and VCS were storing it
under namespaced keys (realtime.project.id, vcs.github.event.repo.{id}.project.id),
causing all those spans to be dropped when _APP_TRACE_PROJECT_ID was set.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 11:03:28 +01:00
loks0nandClaude Opus 4.7 9a016d7727 docs: tighten span tracing section in AGENTS.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:32:41 +01:00
Chirag Aggarwal d9987dd645 Fix rate limit headers when abuse check is skipped 2026-05-13 15:00:51 +05:30
loks0nandClaude Opus 4.7 a0c4b7695e chore: normalize Span keys and centralize span lifecycle
Span attribute keys are now snake_case with dots only for child
relationships. Worker span lifecycle moved to app/worker.php; selective
trace filtering moved to the exporter sampler in app/init/span.php so
handlers only call Span::add.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:29:42 +01:00
ArnabChatterjee20k 20162a69be feat: ensure unique ID assignment for new presence documents 2026-05-13 14:26:23 +05:30
Chirag AggarwalandGitHub 86df9ca14a Merge pull request #12297 from appwrite/chore/bump-utopia-cache-2
Bump Utopia cache to 2.1
2026-05-13 14:20:14 +05:30
ArnabChatterjee20k f1e0ef2991 feat: refactor queue handling for events and realtime to use coroutine context 2026-05-13 14:04:41 +05:30
Chirag Aggarwal 476e9de2a8 Bump Utopia cache to 2.1 2026-05-13 12:49:17 +05:30
ArnabChatterjee20k a1f321d92e updated presence model 2026-05-13 12:38:47 +05:30
ArnabChatterjee20k 4e2bd9c307 feat: add presence already exists error handling and update exception messages 2026-05-13 12:30:54 +05:30
ArnabChatterjee20k e799589d11 feat: implement deduplication for presence delete events and enhance presence management 2026-05-13 12:20:12 +05:30
ArnabChatterjee20k efadf1d74b removed redundant 2026-05-13 11:18:09 +05:30
ArnabChatterjee20k 7f6482a7d2 Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-05-13 11:12:23 +05:30
fogelito 1435e2c526 use max 2026-05-13 08:41:14 +03:00
ArnabChatterjee20k 84f18341ed feat: add setOwnerPermissions method and update presence ownership logic 2026-05-13 11:09:56 +05:30
fogelito 2ea66a0d8b APP_LIMIT_SUBQUERY 2026-05-13 08:36:57 +03:00
Jake BarnbyandGitHub be2bb984c5 Merge pull request #12194 from appwrite/feat-insights-module
feat(insights): add insights module with CTA framework
2026-05-13 17:34:56 +12:00
Jake Barnby b058eb31f4 (refactor): inline CTAs as JSON on insights collection 2026-05-13 17:10:33 +12:00
Chirag AggarwalandGitHub 1c09d93a07 Merge pull request #12295 from appwrite/codex/update-sdk-generator-cover-image
Update SDK generator cover image config
2026-05-13 10:31:35 +05:30
Chirag Aggarwal 31fbc64ad2 Update SDK generator cover image config 2026-05-13 10:19:54 +05:30
Luke B. SilverandGitHub 35941ddc34 Merge pull request #12220 from appwrite/feat/utopia-http-resources-context
Migrate to utopia-php/http resources/context API
2026-05-12 23:01:29 +01:00
fogelito 2d5289c383 Merge branch '1.9.x' of https://github.com/appwrite/appwrite into users-skip-targets 2026-05-12 20:55:54 +03:00
fogelito 06068c0406 skip subQueryTargets 2026-05-12 20:51:57 +03:00
Matej BačoandGitHub 7421b29c45 Merge pull request #12292 from appwrite/fix-locale-tests
Fix: avatars tests
2026-05-12 16:53:31 +02:00
Matej Bačo af0c41dcf5 Fix avatars tests 2026-05-12 16:08:58 +02:00
Jake BarnbyandClaude Opus 4.6 f4133609db (fix): remove invalid index prefix lengths exceeding column size
The prefix length 700 exceeded the resourceId/parentResourceId column
size of 255 (Database::LENGTH_KEY), crashing the container at startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 02:05:55 +12:00
Jake BarnbyandClaude Opus 4.6 b85ca1536a (fix): remove redundant null coalesce on always-defined variable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 01:53:33 +12:00
Jake BarnbyandClaude Opus 4.6 304b0dab35 (fix): address greptile P1 review comments
- Add skipFilters to Reports/Get.php (was the only endpoint still
  triggering the full N+1 subquery cascade)
- Scale CTA batch limit dynamically (insightCount * MAX_CTA_COUNT)
  instead of fixed APP_LIMIT_SUBQUERY to prevent silent truncation
- Revert deleteReport to callback-based pagination so CTAs are not
  orphaned when a report has more than APP_LIMIT_SUBQUERY insights
- Add explicit prefix lengths (700) to _key_project_resource and
  _key_project_parent_resource indexes to stay under InnoDB 3072-byte limit
- Validate CTA service/method against ADVISOR_CTA_SERVICES and
  ADVISOR_CTA_METHODS enums in the CTAs validator

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 01:42:59 +12:00
Jake BarnbyandClaude Opus 4.6 fdb8e06f12 (test): fix misleading advisor e2e test names and assertions
- Rename testReportsAreReadOnly → testReportsCreateAndUpdateNotExposed
  (reports ARE deletable, they're not read-only)
- Extract testDeleteReportMissing with proper error type assertion
- Rename testInsightsAreReadOnly → testInsightsCreateUpdateDeleteNotExposed
- Fix insight create test to use correct URL (/reports/:id/insights)
  instead of nonexistent /manager/ prefix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 01:15:43 +12:00
Jake BarnbyandClaude Opus 4.6 9da4a3260d (refactor): improve advisor module perf, security, and maintainability
- Fix N+1 in Reports/XList (51→4 queries) via skipFilters + batch fetch
- Add skipFilters to Reports/Delete and cursor fetch (avoid loading all
  nested insights/CTAs just for ownership check)
- Fix N+1 in deleteReport worker (flat CTA deletion instead of per-insight)
- Add advisor entity cleanup on project deletion (reports, insights, CTAs)
- Remove resourceInternalId, parentResourceInternalId, $permissions from
  Insight response model (internal IDs leak DB internals, permissions unused)
- Remove dead subQueryInsightCTAs filter registration
- Remove stale enum-value comments from platform schema
- Fix _key_dismissedAt index to include projectInternalId
- Fix scope category from 'Other' to 'Advisor'
- Switch action base class from Utopia\Platform\Action to Appwrite\Platform\Action

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 01:13:37 +12:00
Jake BarnbyandGitHub 8c8a56ed43 Merge pull request #12273 from appwrite/refactor-target-fetch
fix(messaging): remove redundant user fetch
2026-05-13 01:00:26 +12:00
Matej BačoandGitHub 99fad4dca2 Merge pull request #12290 from appwrite/fix-sdk-generator-for-response-enums
Fix: Project SDK enum names
2026-05-12 14:55:48 +02:00
Matej Bačo b655f24172 Fix warning 2026-05-12 14:49:59 +02:00
Matej Bačo 5a748c2783 PR review fixes 2026-05-12 14:46:33 +02:00
Jake BarnbyandGitHub 87e93c9ff5 Merge pull request #12289 from appwrite/fix/tablesdb-runner-family
chore: pin TablesDB e2e to 8cpu runner
2026-05-13 00:42:36 +12:00
Matej Bačo 0ca52a1bdb Request enum params sdk method name 2026-05-12 14:39:13 +02:00
ArnabChatterjee20k 5827f2ece6 fix: correct presence usage decrement in triggerPresenceUsage calls 2026-05-12 18:05:51 +05:30
Jake Barnby a0ecffe961 chore: pin TablesDB e2e to 8cpu runner
After family=m7 was removed from the default e2e_service runner in #12274,
TablesDB inherits the bare 4cpu runner with no instance-family pinning. Under
paratest_processes: 3 the databases worker can't create attributes fast enough
for the polling loop, producing "Expected 'available', Actual 'processing'"
flakes across unrelated PRs. Match the Databases service override to give
TablesDB its own 8cpu runner.
2026-05-13 00:24:21 +12:00
Matej Bačo 17e9c313f0 Fix response format enum sdk names 2026-05-12 14:17:31 +02:00
ArnabChatterjee20k 62de39b858 removed todo comment 2026-05-12 17:44:28 +05:30
ArnabChatterjee20k c1cab2e41a removed redundant 2026-05-12 17:33:21 +05:30
Jake Barnby f9ff1166b9 (perf): batch-fetch CTAs across an insight set to collapse N+1 2026-05-12 23:24:26 +12:00
Chirag Aggarwal 1ca8282944 Remove unused request logError resource 2026-05-12 16:30:31 +05:30
Jake BarnbyandGitHub 4cd35e4d93 Merge branch '1.9.x' into feat-insights-module 2026-05-12 22:59:34 +12:00
Matej BačoandGitHub 5e1439f64e Merge pull request #12276 from appwrite/feat-rework-project-response-model
Feat: Rework project response model
2026-05-12 12:57:16 +02:00
loks0nandClaude Opus 4.7 e97dfb041a Bump utopia-php/http to 2.0-rc1 and utopia-php/platform to 1.0-rc1
These RC releases ship the new resources/context API that this PR
adopts. Drop the dev-branch alias on utopia-php/http now that 2.0.0-rc1
is tagged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 11:55:41 +01:00
Chirag Aggarwal e132784532 Fix cache auth role lookup after rate limit refactor 2026-05-12 16:24:50 +05:30
loks0nandClaude Opus 4.7 1cef07a2d4 Fix GraphQL coroutine context propagation
Promises\Swoole was already propagating the request container into child
coroutines, but using its own pre-existing key (__utopia_http_request_container)
rather than the new utopia-php/http key (__utopia__). With the keys
mismatched, the propagation was dead code and resolvers spawned from
webonyx coroutines saw an empty context, failing every GraphQL query
with 'Dependency utopia:graphql not found'.

Align the key so child coroutines actually inherit the request's
container (wrapped as a child Container, so request-scoped overrides
in the resolver can't bleed back into the outer request).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 11:47:52 +01:00
loks0nandClaude Opus 4.7 93e96a044d Fix bus resolver to look up context container per dispatch
The resources/context migration replaced the resolver closure with a
first-class callable from `$swoole->context()->get(...)`. That
captures the result of `$swoole->context()` once -- on the first
dispatch -- and binds the bus to the first request's per-request
container forever after.

Wrap the lookup in a fresh closure so each dispatch re-resolves
`context()` against the current Swoole coroutine. Restores the
behavior of the pre-migration `getContainer()` form.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 11:47:52 +01:00
loks0nandClaude Opus 4.7 040ce6e335 Simplify Swoole worker bootstrap
EVENT_START and EVENT_TASK were each constructing `new Http($swoole, 'UTC')`
purely as a vehicle to reach DI -- never configuring routing, never calling
->run(). Now that the Swoole adapter exposes resources() directly, the
Http instance is no longer needed: the resources container we passed into
the Server constructor is already in scope as $container.

Drop the throwaway Http construction, pass $container straight through, and
update createDatabase() to take Container instead of Http to match. Also
rename $swooleAdapter to $swoole for consistency with the rest of the file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 11:47:51 +01:00
Chirag Aggarwal a51fd49ed1 Refactor abuse rate limiting init hook 2026-05-12 16:17:23 +05:30
loks0nandClaude Opus 4.7 4bdcdb6f96 Migrate to utopia-php/http resources/context API
Adopts the new split DI containers in utopia-php/http: `resources()` for
boot-time wiring (shared across requests) and `context()` for per-request
state. Replaces the removed `getResource()`/`setResource()`/`getContainer()`
helpers throughout the HTTP entry point, controllers, GraphQL layer, and
installer.

Bumps the dependency chain accordingly: utopia-php/http to the dev branch
(aliased to 0.34.25 to satisfy platform's exact pin), servers 0.4.*,
queue 0.18.*, and pulled-along cli/platform/database upgrades.

Also tightens app/init/resources/request.php by collapsing single-return
factories to arrow functions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 11:41:42 +01:00
Chirag Aggarwal 7914fde9f1 Use error_log for abuse timelimit failures 2026-05-12 16:09:20 +05:30
Matej Bačo 930c23d7e3 Harden backwards compatibility attributes 2026-05-12 12:27:06 +02:00
ArnabChatterjee20k 95dd44115d refactor: update Dockerfile to remove composer stage and adjust presence handling in tests 2026-05-12 15:49:59 +05:30
Jake Barnby 45f3a64617 (perf): skip subquery filters when loading report for ownership-only check 2026-05-12 22:09:50 +12:00
Matej Bačo 548077f01e Fix flaky test 2026-05-12 11:27:24 +02:00
Matej BačoandGitHub bbd24e5029 Merge pull request #12285 from appwrite/fix-schedule-doc-missing
Fix: scheduler doc missing for functions
2026-05-12 11:26:13 +02:00
Matej Bačo 8bef181ad8 Fix scheduler doc missing for functions 2026-05-12 10:44:16 +02:00
Matej Bačo ff92fd229b Revert "Fix function scheduler document missing"
This reverts commit 5ef5ead98f.
2026-05-12 10:43:11 +02:00
Matej Bačo 5ef5ead98f Fix function scheduler document missing 2026-05-12 10:42:56 +02:00
63df500b6f refactor(constants): remove Advisor alias prefixes for insight/report enums
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/55c56d91-4db4-4501-b5cb-42159dbc9778

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-12 08:09:18 +00:00
Chirag Aggarwal d523c7425a Log abuse timelimit fail-open errors 2026-05-12 13:31:33 +05:30
Chirag Aggarwal 728298d2f5 Address abuse timelimit review comments 2026-05-12 13:23:39 +05:30
Chirag Aggarwal ac79a91aa9 Fail open on abuse timelimit failures 2026-05-12 13:12:13 +05:30
Atharva DeosthaleandGitHub 668b61e620 Merge pull request #12279 from appwrite/fix-codex-plugin
fix codex plugin
2026-05-12 12:29:06 +05:30
Jake BarnbyandGitHub ea28f12fb4 Merge pull request #12280 from appwrite/fix-migration-stuck-on-early-failure
fix(migrations): write _APP_MIGRATION_HOST in the installer-generated .env
2026-05-12 17:08:45 +12:00
Damodar LohaniandGitHub 45298f8e23 Merge pull request #12282 from appwrite/CLO-4320-fix-project-delete-event
Fix: remove invalid event label from project delete action
2026-05-12 10:47:33 +05:45
Damodar Lohani 0e87d0b483 fix: remove invalid event label from project delete action
Event::generateEvents() rejects the pattern 'project.delete' because
it parses 'delete' as a resource that must be present in route params.
The pre-migration route did not declare an event label, so functions
and webhooks were never triggered on project deletion. Restore that
behavior by removing the label.
2026-05-12 04:50:58 +00:00
Chirag AggarwalandGitHub fde30794ec Merge pull request #12246 from appwrite/refactor/mails-messaging-publishers
Migrate mails and messaging queues to publishers
2026-05-12 09:58:56 +05:30
Jake Barnby 51c65093b8 (feat): add appId and appInternalId to reports collection for third-party app submissions 2026-05-12 16:09:38 +12:00
premtsd-codeandGitHub 1f5ee53f3a Merge branch '1.9.x' into fix-migration-stuck-on-early-failure 2026-05-11 21:29:40 +01:00
Prem Palanisamy 3721e6b950 fix(migrations): write _APP_MIGRATION_HOST in generated .env (install & upgrade)
The migrations worker (Appwrite→Appwrite migrations + CSV/JSON imports
& exports) reads `_APP_MIGRATION_HOST` to call back into this instance's
API. `_APP_MIGRATION_HOST` was introduced in #11229 (1.8.x / 1.9.x) but
was never added to `app/config/variables.php`, so `appwrite install` /
`appwrite upgrade` never write it into the generated `.env`. With the
var unset, the migrations worker fails — on a fresh self-hosted install
the first export hangs with no error (#11853). (Contributors and CI
don't hit it because the repo's hand-maintained `.env` already has
`_APP_MIGRATION_HOST=appwrite`; only the *installer-generated* `.env` is
missing it.)

Add `_APP_MIGRATION_HOST` to `app/config/variables.php` with default
`appwrite` — the API service name in the standard Docker Compose setup,
which is what the repo's own `.env` and the cloud Helm charts already
use, and what the migration endpoint was hardcoded to before #11229.
`appwrite install` and `appwrite upgrade` now write it into the
generated `.env`, so fresh installs and upgrades have it set and the
migration/import/export flows work.

Scope: this PR fixes the install & upgrade paths only — it deliberately
doesn't change the worker code.

Fixes #11853
2026-05-11 21:26:26 +01:00
Levi van NoortandGitHub 103e219675 Merge pull request #12274 from appwrite/migrate-away-from-blacksmith-based-runners
refactor: enhance execution log checks in SitesCustomServerTest
2026-05-11 21:35:42 +02:00
Matej Bačo da3a3b939a Finalize PR 2026-05-11 20:22:50 +02:00
Levi van Noort 994a4e0fc7 chore: remove 'family=m7' from e2e_service runner configuration 2026-05-11 20:11:08 +02:00
Matej Bačo 36dec7c88f Fix more tests 2026-05-11 19:47:57 +02:00
Matej Bačo 5eb4c6b310 Fix tests 2026-05-11 18:38:30 +02:00
Matej Bačo 152b45087e Fix more tests 2026-05-11 17:01:00 +02:00
Levi van Noort 5fba2889cb chore: update e2e_service runner configuration to use m7 family 2026-05-11 16:58:36 +02:00
Levi van NoortandGitHub 9b4f8d7ac0 Merge branch '1.9.x' into migrate-away-from-blacksmith-based-runners 2026-05-11 16:16:55 +02:00
Atharva Deosthale 9056de0103 fix codex plugin 2026-05-11 19:38:06 +05:30
Matej Bačo 6cbf080156 Fix code quality 2026-05-11 16:04:39 +02:00
Matej Bačo 89ec639f87 Fix startup issues 2026-05-11 15:55:53 +02:00
Matej Bačo 870fe72bf6 Fix response filter 2026-05-11 15:40:45 +02:00
harsh mahajan dda38442ad refactor: simplify deployment skip patterns 2026-05-11 18:56:31 +05:30
Matej Bačo 5cd737b901 Backwards compatibility for GET /v1/project 2026-05-11 15:08:15 +02:00
harsh mahajan cc0207cfe4 fix: align appwrite skip directives 2026-05-11 18:32:14 +05:30
harsh mahajan fa63983fd7 fix: bracket appwrite skip directives 2026-05-11 18:11:48 +05:30
harsh mahajan 9a6d2a6d21 refactor: use fixed commit skip directives 2026-05-11 18:02:51 +05:30
Chirag Aggarwal cf1bb1a1cc build 2026-05-11 17:59:43 +05:30
Jake Barnby c18865423e Merge remote-tracking branch 'origin/1.9.x' into feat-insights-module
# Conflicts:
#	composer.lock
#	src/Appwrite/Platform/Workers/Migrations.php
2026-05-12 00:23:17 +12:00
Jake Barnby fec779de05 (chore): bump utopia-php/migration to 1.10.2 to match resolver signature 2026-05-12 00:15:49 +12:00
Chirag AggarwalandGitHub 330b11a64b Merge branch '1.9.x' into refactor/mails-messaging-publishers 2026-05-11 17:43:48 +05:30
harsh mahajan 73eb14d4bb fix: fail open for non-string commit messages 2026-05-11 17:19:55 +05:30
harsh mahajan 4684688fc7 feat: expose commit skip patterns 2026-05-11 17:17:01 +05:30
Jake BarnbyandClaude Opus 4.7 53d18a0d08 refactor(advisor): rename module to Advisor (service-named, top-level)
The module's namespace and directory now match the top-level service
name (`advisor`) instead of one of its resource names (`insights`):

- src/Appwrite/Platform/Modules/Insights -> .../Modules/Advisor
- src/Appwrite/Insights -> src/Appwrite/Advisor
- tests/unit/Insights -> tests/unit/Advisor
- Route group label flipped from `'insights'` to `'advisor'`
- Section-header comments aligned

Resource names (`insights`, `reports`, `insightCTAs`) and the
`Insight*`/`Report` response models stay — those are the resources the
service exposes, not the service itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 23:40:06 +12:00
Harsh MahajanandGitHub 68fdf3fbb4 Merge branch '1.9.x' into feat/skip-deployment-commit-patterns 2026-05-11 17:07:55 +05:30
harsh mahajanandClaude Sonnet 4.6 444d4b4e66 refactor: standalone regex matching for commit skip patterns
Replace directive-extraction approach with word-boundary regex matching
so plain-word patterns like "skip appwrite" and "appwrite skip" work
alongside bracket directives. Use \s+ between word tokens (required
space) and \s* only after ":" tokens (git trailer flexibility).
Add tests for "skip appwrite" and "appwrite skip" with case insensitivity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 17:04:58 +05:30
harsh mahajan 4a4f51622d fix: tighten commit skip directive matching 2026-05-11 17:04:58 +05:30
harsh mahajanandClaude Sonnet 4.6 2836f89b34 feat: skip deployment on commit message pattern match
Add providerCommitSkipPatterns array field to functions and sites.
Any commit message containing one of the patterns (case-insensitive
substring) skips the VCS-triggered deployment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 17:04:58 +05:30
Jake BarnbyandGitHub 4e73559eb7 Merge branch '1.9.x' into refactor-target-fetch 2026-05-11 23:31:18 +12:00
Jake BarnbyandGitHub 11acbc6db4 Merge pull request #12245 from appwrite/feat-bump-sdk-23
feat: bump utopia-php/abuse and utopia-php/migration to feat/sdk-23 branches
2026-05-11 23:30:10 +12:00
ArnabChatterjee20kandCopilot cad35c7d5c refactor: enhance presence deletion logic and add expiration check in Get action
Co-authored-by: Copilot <copilot@github.com>
2026-05-11 16:47:36 +05:30
Levi van Noort d8dbe15cb3 chore: update runner configuration for e2e_service jobs in ci workflow 2026-05-11 13:12:25 +02:00
Jake BarnbyandClaude Opus 4.7 d2e4ed6a11 test(messaging): skip users recipient test when push DSN not configured
CI lacks _APP_MESSAGE_PUSH_TEST_DSN, so new DSN(null) threw a TypeError
before the existing skip guard could run. Match the pattern used by
testSendPushNotification: gate on the env var first, then construct the DSN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:58:29 +12:00
Chirag AggarwalandGitHub 6ae113a969 Merge branch '1.9.x' into refactor/mails-messaging-publishers 2026-05-11 16:24:50 +05:30
Matej Bačo 2da400d5b4 Bump version to introduce response filter (for project get) 2026-05-11 12:53:01 +02:00
Jake BarnbyandClaude Opus 4.7 e8bdb954f2 docs(advisor): add per-endpoint reference docs
Each SDK method now points at its own markdown file under
docs/references/advisor/ instead of an inline heredoc, matching the
house convention used by every other module.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:51:58 +12:00
Matej Bačo 9fae4c965f Unpin console version to get latest automatically
Only during local development
2026-05-11 12:50:44 +02:00
Matej Bačo 83887b09b6 Improve project get tests 2026-05-11 12:47:53 +02:00
Jake Barnby e6a93d4a9f chore: bump limit 2026-05-11 22:37:44 +12:00
Matej Bačo 4e36040f47 Add project GET tests 2026-05-11 12:22:17 +02:00
Matej Bačo 5727d25f32 Add enum to project response models 2026-05-11 12:19:51 +02:00
Matej Bačo 1f7f1e7b31 Fix failing to startup issue 2026-05-11 12:16:04 +02:00
Matej Bačo 14493f7ea4 PR review fixes 2026-05-11 12:13:36 +02:00
Matej Bačo fd893a8b10 Migrate project GET endpoint 2026-05-11 12:10:38 +02:00
Matej Bačo beab655eba Code quality details 2026-05-11 12:03:48 +02:00
d8957a5468 fix(messaging): paginate user recipient target lookup
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/8ea97768-3304-4a3c-81e2-d62d6fe3ad80

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-11 10:03:40 +00:00
2a47ccc0f9 test(messaging): add users recipient e2e coverage and fix user target query limit
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/8ea97768-3304-4a3c-81e2-d62d6fe3ad80

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-11 10:03:02 +00:00
ArnabChatterjee20k 9f65c6ce41 updated namespace 2026-05-11 15:27:54 +05:30
Matej Bačo 443ebac71c Finish project response model 2026-05-11 11:56:22 +02:00
Jake BarnbyGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
3773a5a5dc Update src/Appwrite/Platform/Workers/Messaging.php
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-11 21:51:19 +12:00
51f1aee93a Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-11 21:50:38 +12:00
premtsd-codeandGitHub 0b2a9240b3 Merge branch '1.9.x' into feat-bump-sdk-23 2026-05-11 10:28:38 +01:00
Jake BarnbyandClaude Opus 4.7 7041c1d719 fix(migrations): resolve destination DSN per database type
utopia-php/migration 1.10.2 stopped propagating the source DSN to the
destination's `_databases.database` and added an opt-in `getDatabaseDSN`
resolver. Without it, the destination DB row gets a blank `database`
field, so reads on documentsdb/vectorsdb fall back to the project's
main DSN (mongodb in dedicated mode) and fail with "Vector types are
not supported by the current database".

Pass a resolver that builds the proper DSN from the destination
project's pools per database type, mirroring the logic
`Database/Create.php` already uses on first creation. Made
`constructDatabaseDSNFromProjectDatabase` public+static so the worker
can reuse it instead of duplicating the pool-selection logic.

This is a 1.9.x regression introduced by the migration 1.10.1 -> 1.10.2
bump; affects every PR that runs MongoDB-dedicated Migrations tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 21:25:53 +12:00
Matej Bačo 75d4e931bc Project response model rework beginning 2026-05-11 11:17:41 +02:00
Levi van Noort d949482094 chore: set bigger size on database related e2e 2026-05-11 11:10:41 +02:00
ArnabChatterjee20k 28621678dd changed presence to presences for the e2e tests 2026-05-11 14:35:01 +05:30
ArnabChatterjee20k 4b98579adf refactor: simplify user retrieval in Presence handler by directly instantiating User object 2026-05-11 14:29:47 +05:30
Levi van NoortandGitHub c95901b879 Merge branch '1.9.x' into migrate-away-from-blacksmith-based-runners 2026-05-11 10:50:29 +02:00
Levi van Noort 0fb2e208ab chore: add spot=false label to the runs-on based runners 2026-05-11 10:47:22 +02:00
Matej BačoandGitHub 76aed33a5c Merge pull request #12263 from appwrite/feat-google-oauth-prompt-param
Feat: Google OAuth2 "prompt" param
2026-05-11 10:47:13 +02:00
Prem Palanisamy c26ff1849a chore: bump utopia-php/abuse to 1.3.0 and migration to 1.11.0 (released) 2026-05-11 09:37:07 +01:00
Levi van Noort 702a8a83a0 test: add assertion for action execution logs content in SitesCustomServerTest 2026-05-11 10:30:08 +02:00
Levi van NoortandGitHub 5be05da5ca Merge branch '1.9.x' into migrate-away-from-blacksmith-based-runners 2026-05-11 10:27:46 +02:00
Levi van Noort fe5e5b8891 refactor: enhance execution log checks in SitesCustomServerTest 2026-05-11 10:17:55 +02:00
Jake Barnby add56d7365 chore: select 2026-05-11 19:54:48 +12:00
Jake Barnby 5c09d726ba fix(messaging): remove redundant user fetch 2026-05-11 19:51:26 +12:00
Jake BarnbyandClaude Opus 4.7 db4cb5361d fix(databases): fall back to project DSN when database attr is empty
`getDatabasesDB` used `??` to fall back from a database doc's `database`
attribute to the project DSN, but `??` only triggers on null. Migration
destinations end up with an empty-string `database` (the value is copied
from the source DB but isn't a valid DSN on the destination's pool),
which slipped past the fallback and surfaced as a 500 with
`new DSN('mysql://')` in the catch block. Use elvis so empty strings
fall back too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 19:12:48 +12:00
Jake BarnbyandClaude Opus 4.7 ad02ad3f81 fix(advisor): fall back to direct insight lookup beyond subquery cap
The report's embedded insights subquery is capped at APP_LIMIT_SUBQUERY,
so insights past position 1000 would 404 even though they exist. Try
the embedded slice first, then fall back to a scoped direct lookup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:23:20 +12:00
Prem Palanisamy a902c25363 refactor(migrations): extract destination DSN resolver to named method
Replaces the inline match closure with resolveDestinationDatabaseDsn(),
mirroring cloud's worker. Adds a docblock explaining why documentsdb /
vectorsdb keep the source DSN.
2026-05-11 06:52:23 +01:00
premtsd-codeandGitHub 04a6eaf5d4 Merge branch '1.9.x' into feat-bump-sdk-23 2026-05-11 05:54:00 +01:00
Prem Palanisamy f0fb7bf877 test: remove orphaned VectorsDB testGetCollectionLogs
Endpoint deleted in 96fe989f6d ("update composer dependencies and remove
obsolete log classes") but the two test methods calling it were left
behind. They have been failing with 404 on every PR since.
2026-05-11 05:53:03 +01:00
Prem Palanisamy 74fbbea2b3 chore: bump utopia-php/abuse cfd290a + migration 447a987 (PHP >=8.2) 2026-05-11 05:41:37 +01:00
735be3e892 fix(advisor): align nested insights list SDK method naming
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/7e091450-e0b2-4c2c-98a7-ba964e5436ca

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-11 03:25:41 +00:00
49bf1198b2 refactor(advisor): polish constants aliases and insight lookup helper
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/517a4586-d3e8-40b3-a3a9-f2d2ca82b0a0

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-11 02:08:29 +00:00
9186ce3f24 chore(advisor): document null index prefix lengths in reports index
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/517a4586-d3e8-40b3-a3a9-f2d2ca82b0a0

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-11 02:05:20 +00:00
bfd6cebbb0 fix(advisor): address review comments on tests, naming, docs, and get insight
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/517a4586-d3e8-40b3-a3a9-f2d2ca82b0a0

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-11 01:59:31 +00:00
Prem Palanisamy 4957f568cd chore: bump utopia-php/migration to 4dc7270 (empty teams guard) 2026-05-11 02:38:01 +01:00
Prem Palanisamy 64c9d8d85f chore: bump utopia-php/migration to a9bdfba (revert deno fallback) 2026-05-11 02:28:07 +01:00
Prem Palanisamy 7b5cb379c4 chore: bump utopia-php/migration to e5dc657 (deno runtime fallback) 2026-05-11 02:21:18 +01:00
Prem Palanisamy fd625fca7b fix(migrations): preserve source DSN for documentsdb/vectorsdb resolver
Migration lib 1.10.2's getDatabaseDSN resolver returns the value
written into destination's _databases.database. The previous resolver
always returned the project's main DSN (mongodb in default CI) for
every database type, including documentsdb / vectorsdb — which are
routed to their own adapters (mongodb / postgresql) per
_APP_DB_ADAPTER_DOCUMENTSDB / _APP_DB_ADAPTER_VECTORSDB.

The wrong DSN routed vectorsdb attribute creates back to mongodb,
producing 'Vector types are not supported by the current database'
on the MixedDatabases / VectorsDB migration tests.

Mirror cloud's resolver: keep the source DSN for documentsdb /
vectorsdb (they target dedicated hosts), use destination project's
main DSN otherwise.
2026-05-10 22:09:39 +01:00
Matej Bačo e45e5a09f4 Reorder tests to make them pass 2026-05-10 13:19:18 +02:00
Matej Bačo e3dc30ad93 PR review fixes 2026-05-10 12:03:05 +02:00
Matej Bačo 0406d9e04d improve copy 2026-05-10 11:13:23 +02:00
Matej Bačo fbfde6cc77 Implement google oauth prompt param. 2026-05-10 11:11:47 +02:00
Matej BačoandGitHub c6f91e18c4 Merge pull request #12254 from appwrite/fix-sateless-git-hints
Fix: Stateless git hints
2026-05-10 10:14:51 +02:00
Matej BačoandGitHub 6db1d2e5c0 Merge pull request #12256 from appwrite/chore-google-oauth-dual-read
Chore: Dual read for google oauth secret
2026-05-10 10:14:30 +02:00
Prem Palanisamy 817172c460 Merge branch '1.9.x' into feat-bump-sdk-23
# Conflicts:
#	composer.lock
2026-05-10 05:54:06 +01:00
Prem Palanisamy cd445ceccf chore: bump utopia-php/migration to 6deabc6 (Sites::create named args) 2026-05-10 05:49:23 +01:00
Chirag AggarwalandGitHub 87a32f65ee Merge pull request #12182 from appwrite/add-codex-plugin 2026-05-10 09:26:49 +05:30
Prem Palanisamy 698fde247f chore: bump utopia-php/migration to 0e88268 (createVariable variableId fix) 2026-05-10 04:46:40 +01:00
Matej Bačo 0e939ea9d7 PR review fixes 2026-05-09 12:58:47 +02:00
Matej Bačo a5ddc465e6 PR review fixes 2026-05-09 12:53:11 +02:00
Matej Bačo 76a41d70b0 Dual read for google oauth secret
Will allow future support for more params
2026-05-09 10:51:46 +02:00
Matej Bačo 43777ee6d9 Add unit tests for github hints 2026-05-09 10:16:19 +02:00
Matej Bačo 6ee2196fae Fix git hint regnerating nonstop 2026-05-09 09:54:54 +02:00
Jake BarnbyandClaude Opus 4.7 8fa9ead279 chore: merge 1.9.x into feat-insights-module
Resolves conflict in app/init/constants.php (kept 4327 cache buster, took 1.9.4 stable version).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:59:01 +12:00
Jake BarnbyandClaude Opus 4.7 83c0a32494 fix(advisor): make reports.target a TEXT column
target stores free-form URLs or resource IDs. URLs in the wild can
exceed the prior 2048-char cap, so switch the column to VAR_TEXT
(65535). The _key_project_target index already declares an explicit
700-char prefix length, so indexing still works on both MariaDB and
MongoDB.

Bump APP_CACHE_BUSTER for the schema change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 12:42:29 +12:00
Jake BarnbyandClaude Opus 4.7 b830c08257 fix(advisor): use VAR_TEXT for free-form fields
Switch markdown summaries, JSON CTA params, and the embedded
insights/ctas subQuery payloads from sized VAR_STRING to VAR_TEXT.
None of these fields are indexed and they all hold free-form data, so
removing the artificial size cap means we never have to revisit the
limit when an analyzer surfaces a longer report.

- reports.summary, insights.summary: markdown, no upper bound enforced
- reports.insights, insights.ctas: subQuery virtuals carrying full
  child rows
- insightCTAs.params: JSON parameter blob for arbitrary CTA actions

Bump APP_CACHE_BUSTER for the schema change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 12:30:45 +12:00
Jake BarnbyandClaude Opus 4.7 b747c97561 fix(advisor): address review comments on scopes and schema
- Register dedicated reports.write scope and switch deleteReport to it
  so cloud can issue narrowly-scoped delete keys without granting
  insights.write.
- Make insights.parentResourceInternalId optional with null default to
  match its companion parentResourceType/parentResourceId fields and
  unblock insights with no parent (e.g. database-level performance
  insights).
- Tighten Insight.reportId model description: insights always belong to
  a report, ad-hoc insights are not supported.
- Add reports.write to default test API key and admin role so existing
  e2e tests using serverHeaders() can hit the delete endpoint.
- Bump APP_CACHE_BUSTER for the schema change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 12:18:39 +12:00
Jake BarnbyGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
e27997dcbc Update src/Appwrite/Insights/Validator/CTAs.php
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-09 12:06:09 +12:00
6b64eb85b3 fix(advisor): require null-default VAR_ID schema fields
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/38cebef5-ed45-4283-9097-d48dd0fafb06

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-09 00:00:28 +00:00
Prem Palanisamy 5313460c7c fix(migrations): pass destination project DSN resolver to DestinationAppwrite
Migration lib's `_databases.database` resolver now defaults to empty
when no callable is supplied (utopia-php/migration ff3b444). The
runtime falls back via `$project->getAttribute('database')`, but tests
hit DSN("mysql://") and a 500 because that fallback is also empty for
fresh test projects.

Pass an explicit resolver returning the destination project's `database`
attribute so migrated databases store a usable DSN.
2026-05-08 17:53:09 +01:00
Prem Palanisamy c94ae409e8 chore: refresh composer.lock for utopia-php/fetch ^1.1 and logger 0.8.* 2026-05-08 16:50:04 +01:00
Prem Palanisamy 4844a4bf99 Merge branch '1.9.x' into feat-bump-sdk-23
# Conflicts:
#	composer.lock
2026-05-08 16:34:19 +01:00
Prem Palanisamy 687698001f chore: bump utopia-php/migration to 80e9a04 (SDK 23 nested typed object fixes) 2026-05-08 16:30:02 +01:00
Jake BarnbyandClaude Opus 4.7 5ba92c7ff4 test(proxy): clean up rules before deleting their site
testCreateRedirectRule reassigned $ruleId for the second redirect rule,
so only the second was tracked, and cleanupSite was called before
cleanupRule. Site deletion cascades to its rules, so cleanupRule then
saw a 404 and the strict assertEquals(204) blew up.

Track both rule IDs and tear them down before the site so the asserted
204 actually fires.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 02:09:48 +12:00
Jake BarnbyandClaude Opus 4.7 a23555783f test(advisor): initialise project before getNewKey
testReadWithAdvisorScopes called getNewKey() as its first action, but
getNewKey reads self::$project['$id'] without triggering project
creation. ParaTest runs each test method in a fresh worker, so
self::$project is empty until getProject() is called. The empty id
produced /v1/projects//keys, which doesn't match any route and 404s.

Call getProject() first, like every other consumer of getNewKey.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 01:49:22 +12:00
Harsh MahajanandGitHub acc0b2c184 Merge pull request #12252 from appwrite/fix/repository-branch-pagination-validator
fix: Support branch query validators in SDK generation
2026-05-08 19:18:04 +05:30
harsh mahajan 10e4341db2 Support branch query validators in SDK generation 2026-05-08 19:15:17 +05:30
ArnabChatterjee20k 6b7fb918cc refactor: streamline presence upsert logic and remove redundant presenceId parameter 2026-05-08 19:09:50 +05:30
Jake BarnbyandClaude Opus 4.7 83cf47e3fd fix(advisor): correct index lengths to fit MongoDB 1024 limit
The reports `_key_project_target` index was failing to create on MongoDB
because target's full size (2048) plus targetType (64) exceeded the 1024
character maximum. Set explicit lengths to truncate target to 700 chars
(total 765, under both Mongo 1024 and MariaDB 768 limits).

Also drop redundant explicit lengths from VAR_ID positions in the
insights and insightCTAs indexes — the validator rejects explicit
lengths on VAR_ID attributes, and they default to length 1 anyway.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 01:29:01 +12:00
Chirag AggarwalandGitHub 8a35e87cfd Merge pull request #12251 from appwrite/fix/graphql-preview-test-assertions 2026-05-08 18:13:14 +05:30
Chirag Aggarwal 9295869279 Fix GraphQL preview test assertions 2026-05-08 17:51:42 +05:30
Chirag AggarwalandGitHub 5472bff265 Merge pull request #12219 from abhay-dev2901/fix/scheduler-platform-env 2026-05-08 17:17:43 +05:30
AbhayandGitHub 27de465032 Merge branch '1.9.x' into fix/scheduler-platform-env 2026-05-08 17:14:42 +05:30
Chirag AggarwalandGitHub f2a59de804 Merge branch '1.9.x' into refactor/mails-messaging-publishers 2026-05-08 17:13:49 +05:30
Prem Palanisamy 4563706b76 chore: bump utopia-php/migration to a5195ba (SDK 23 typed models refactor) 2026-05-08 12:38:20 +01:00
Chirag AggarwalandGitHub be9bb5e34d Merge pull request #12249 from appwrite/fix/preview-conditional-transforms 2026-05-08 16:40:56 +05:30
Chirag AggarwalandGitHub 0a582daff0 Merge pull request #12248 from appwrite/codex/bump-logger-0.8.0 2026-05-08 16:38:05 +05:30
Torsten DittmannandGitHub 003647abd7 Merge branch '1.9.x' into fix/preview-conditional-transforms 2026-05-08 15:06:08 +04:00
Torsten Dittmann 9100e06bbe perf(storage): skip no-op imagick transforms and add transform value spans
- Only call crop() when dimensions > 0 or gravity differs from center
- Only call setOpacity() when opacity !== 1.0 (was always called due to !empty(1.0))
- Only call setBorder() when borderWidth > 0
- Only call setBorderRadius() when borderRadius > 0
- Only call setRotation() when rotation !== 0
- Add Span::add() with actual values inside each transform condition
- Leave output() unconditional (format conversion always applies)
2026-05-08 15:04:54 +04:00
Chirag Aggarwal fc83b4d986 Bump logger dependency 2026-05-08 16:25:46 +05:30
Harsh MahajanandGitHub 4954655e8b Merge pull request #12243 from appwrite/feat/repository-branch-search-pagination
feat: Add search and pagination for repository branches
2026-05-08 16:14:06 +05:30
ArnabChatterjee20kandGitHub 319483d41d Merge pull request #12244 from appwrite/fix-method-ids
Enhance URL parameter handling in OpenAPI3 and Swagger2 formats to su…
2026-05-08 15:35:05 +05:30
ArnabChatterjee20k 6da8c1cb12 updated 2026-05-08 15:21:52 +05:30
Aditya Oberai 5f6b1dda1a update composer lock 2026-05-08 09:35:38 +00:00
Aditya Oberai 8df7a628c8 Merge branch '1.9.x' into add-codex-plugin 2026-05-08 09:34:43 +00:00
Chirag Aggarwal 18ec96f124 Address Greptile feedback for queue publishers 2026-05-08 14:41:58 +05:30
ArnabChatterjee20k d303d6f807 Refactor path parameter detection in OpenAPI3 and Swagger2 by utilizing array flipping for improved performance and clarity in matching aliases. 2026-05-08 14:39:26 +05:30
ArnabChatterjee20k ffeb27e5a3 updated presence test 2026-05-08 14:36:59 +05:30
Chirag Aggarwal 34075322d7 Migrate mails and messaging queues to publishers 2026-05-08 14:32:11 +05:30
Prem Palanisamy 74b84ba38c feat: bump utopia-php/abuse and utopia-php/migration to feat/sdk-23 branches 2026-05-08 09:52:47 +01:00
ArnabChatterjee20k 07973dee2d Refactor URL parameter replacement logic in OpenAPI3 and Swagger2 to ensure accurate matching of path parameters by checking for trailing characters. 2026-05-08 14:19:51 +05:30
ArnabChatterjee20k 5cfaa0807d Refactor URL parameter matching in OpenAPI3 and Swagger2 to improve path parameter detection by checking for trailing characters. 2026-05-08 14:08:27 +05:30
ArnabChatterjee20k e181954dd1 removed regex 2026-05-08 14:01:04 +05:30
ArnabChatterjee20k 4b05a6cf8f Refactor URL parameter matching in OpenAPI3 and Swagger2 to use preg_match for improved accuracy with path aliases. 2026-05-08 13:52:25 +05:30
Harsh MahajanandGitHub fe4608a6c3 Merge branch '1.9.x' into feat/repository-branch-search-pagination 2026-05-08 13:50:51 +05:30
harsh mahajan d598773163 Adjust repository branch cursor typing for analysis 2026-05-08 13:50:24 +05:30
Levi van NoortandGitHub 79a4eac82f Merge pull request #12241 from appwrite/migrate-away-from-blacksmith-based-runners
refactor: migrate to different ci runners
2026-05-08 10:19:12 +02:00
ArnabChatterjee20k 29bbc7299a Enhance URL parameter handling in OpenAPI3 and Swagger2 formats to support aliases for path parameters. 2026-05-08 13:45:46 +05:30
harsh mahajan 370a823888 Use a query validator for repository branch pagination 2026-05-08 13:43:02 +05:30
harsh mahajan 3fbe77a27c Fix repository branch cursorBefore pagination 2026-05-08 13:31:32 +05:30
harsh mahajan 1a0a19a793 Add search and pagination for repository branches 2026-05-08 13:25:35 +05:30
Levi van Noort bd0e7456a7 refactor: update ci runners to use volume instead of disk 2026-05-08 09:08:42 +02:00
Levi van Noort 6a0d2d5314 chore: add bigger disks to the provisioned setup 2026-05-08 09:01:36 +02:00
ArnabChatterjee20k 46e86ce854 fix: drop expired presences at fetch time and clean up Presence model attributes 2026-05-08 12:28:33 +05:30
721b3c1229 style(advisor): add strict return types to report delete action
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/6496395d-5cbf-42ba-a3e0-d3c9e6ca901c

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-08 06:56:05 +00:00
f63ded6159 chore(advisor): simplify report cleanup delete queries
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/6496395d-5cbf-42ba-a3e0-d3c9e6ca901c

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-08 06:55:28 +00:00
56b9de097f fix(advisor): restore report delete with async nested cleanup
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/6496395d-5cbf-42ba-a3e0-d3c9e6ca901c

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-08 06:54:49 +00:00
ArnabChatterjee20k 5767ab8ac7 refactor: update triggerPresenceUsage to accept Document and streamline presence cleanup logic 2026-05-08 12:09:12 +05:30
ArnabChatterjee20k 5002afde09 optimizations
fix: update presence handling to include hostname and correct usage trigger parameter
improvement: removed an external findOne call to the db and used the timestamps for knowing upsert did an update or a create
2026-05-08 12:05:03 +05:30
Jake BarnbyandGitHub 602e1be296 Merge pull request #12226 from appwrite/spike/isPublishable-migration
Stop user-data errors leaking to Sentry from migrations worker (isPublishable)
2026-05-08 18:22:41 +12:00
Chirag AggarwalandGitHub e10d51c4eb Merge pull request #12240 from appwrite/fix/cache-control-callback 2026-05-08 11:39:30 +05:30
a9902c33df chore(advisor): clarify insights.write scope usage
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/8d7897b5-ac68-487d-954a-be717380bf66

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-08 06:08:17 +00:00
6d0eab2583 refactor(advisor): make insights API read-only in CE
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/8d7897b5-ac68-487d-954a-be717380bf66

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-08 06:07:23 +00:00
Chirag Aggarwal 3d35d140d1 Relax storage preview cache hit max age 2026-05-08 11:27:35 +05:30
ArnabChatterjee20k c13a0d1010 updated test 2026-05-08 11:27:11 +05:30
Chirag Aggarwal 8ebf4fee46 Use storage cache callback max age 2026-05-08 11:18:55 +05:30
Chirag Aggarwal 9337daf7ac Preserve default storage cache hit max age 2026-05-08 11:15:17 +05:30
0829b26508 refactor(insights): remove redundant payload field
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/a680e208-34b8-4bae-a7fd-51949112233a

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-08 05:44:20 +00:00
ArnabChatterjee20k f526fc8577 roles updated 2026-05-08 11:14:00 +05:30
ArnabChatterjee20k 63333a0cbf updated delete endpoint exceptions 2026-05-08 11:13:38 +05:30
ArnabChatterjee20k 0048d3f911 updated presence worker deletion to cap the 30 days limit 2026-05-08 11:10:36 +05:30
ArnabChatterjee20k 31adbe6189 fix: update presence permissions from read to write in tests and implementation 2026-05-08 11:02:25 +05:30
Chirag Aggarwal 2207fff44d Wait for storage preview cache hit in e2e 2026-05-08 10:52:31 +05:30
ArnabChatterjee20k a1811570b3 fix: update presence permissions from read to write in roles and HTTP handlers 2026-05-08 10:49:38 +05:30
Chirag Aggarwal 8e274012bd Relax storage preview cache max-age assertion 2026-05-08 10:48:12 +05:30
8c57ff161e fix(insights): use VAR_ID for project internal references
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/688345d8-e680-46c7-9002-82f73193461b

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-08 05:17:15 +00:00
0d65ffbb6c chore(insights): simplify report index lengths and orders
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/2e52811c-bf98-4b39-b3f7-64fabcef4cf6

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-08 05:13:38 +00:00
Chirag Aggarwal 75a30793ac Remove preview flag from storage cache config 2026-05-08 10:42:39 +05:30
Chirag Aggarwal 96e5193601 Fix storage preview cache e2e assertion 2026-05-08 10:18:34 +05:30
Chirag Aggarwal 48e2ee9acb Test storage preview cache headers 2026-05-08 10:04:40 +05:30
Chirag Aggarwal 5841d0a944 Inline storage preview cache control 2026-05-08 09:59:36 +05:30
Chirag Aggarwal bf76736971 Split storage cache control context 2026-05-08 09:57:33 +05:30
Chirag Aggarwal 707e5d231a Scope storage cache control to previews 2026-05-08 09:55:33 +05:30
Chirag Aggarwal 6abd88b8f1 Type storage cache control config 2026-05-08 09:44:00 +05:30
Chirag Aggarwal 1208aff569 Fix storage cache hook analyze 2026-05-08 08:52:24 +05:30
Chirag Aggarwal 7b5a5b8d19 Rename storage cache control hook 2026-05-08 08:44:35 +05:30
Chirag Aggarwal 20ab5b026e Add cached response cache-control callback 2026-05-08 08:33:37 +05:30
Jake BarnbyandGitHub db46a60168 Merge pull request #12237 from appwrite/chore/pin-github-actions
chore: pin github actions to sha and bump to latest
2026-05-08 13:37:59 +12:00
premtsd-codeandGitHub f2b96318ca Merge branch '1.9.x' into spike/isPublishable-migration 2026-05-07 21:37:53 +05:30
Luke B. SilverandGitHub 1d004240e6 Merge pull request #12236 from appwrite/refactor/builds-publishing
refactor: migrate queueForBuilds to publisherForBuilds pattern
2026-05-07 17:06:32 +01:00
Matej BačoandGitHub 98ba86b876 Merge pull request #12239 from appwrite/fix-console-email-features
Fix: Email policies for console project
2026-05-07 17:58:55 +02:00
Matej Bačo c3881f9974 Fix console email policy features 2026-05-07 17:13:26 +02:00
Levi van Noort b2080fb8f9 refactor: migrate to different ci runners 2026-05-07 17:07:13 +02:00
Jake BarnbyandGitHub 04171dc0b8 Merge pull request #12238 from appwrite/fix-method-ids
updated
2026-05-08 01:11:55 +12:00
ArnabChatterjee20k 21ba13a076 updated 2026-05-07 18:40:26 +05:30
Jake Barnby 708aea2532 chore: pin github actions to sha and bump to latest
Pin every third-party action in .github/workflows/ to a full commit SHA
with a trailing version comment, and bump to the latest stable release.
Defends against tag-rewrite supply-chain attacks while keeping versions
legible.
2026-05-08 01:07:12 +12:00
loks0nandClaude Opus 4.7 f947fde701 Restore platform fallback and null-template round-trip in BuildMessage
- toArray() falls back to Config::getParam('platform') when the
  in-memory platform is empty, matching the old Build event behavior so
  workers always receive a populated platform array.
- toArray() emits null for an absent template instead of an empty
  array, and fromArray() treats empty/null/missing template as null so
  the round-trip preserves the no-template case.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 13:56:46 +01:00
harsh mahajanandClaude Sonnet 4.6 90f5f9b30b refactor: standalone regex matching for commit skip patterns
Replace directive-extraction approach with word-boundary regex matching
so plain-word patterns like "skip appwrite" and "appwrite skip" work
alongside bracket directives. Use \s+ between word tokens (required
space) and \s* only after ":" tokens (git trailer flexibility).
Add tests for "skip appwrite" and "appwrite skip" with case insensitivity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 18:25:39 +05:30
Harsh MahajanandGitHub 408a12322a Merge branch '1.9.x' into feat-ser-401-custom-triggers 2026-05-07 18:17:27 +05:30
Prem Palanisamy 3676af925c Sanitize bubbled message for non-typed exceptions on migration document 2026-05-07 13:45:57 +01:00
Prem Palanisamy 81e57ea30e Revert gate fallback to always-publish on unknown exception types 2026-05-07 13:36:49 +01:00
Prem Palanisamy 98b4e9b063 Loosen utopia-php/migration constraint back to 1.* (matches 1.9.x baseline) 2026-05-07 13:30:28 +01:00
Jake Barnby 97477686fd chore: sync 1.9.x into feat-dedicated-db 2026-05-08 00:29:51 +12:00
loks0nandClaude Opus 4.7 7d0843cc7f Migrate queueForBuilds to publisherForBuilds pattern
Replaces the stateful Appwrite\Event\Build queue class with a stateless
BuildPublisher and BuildMessage DTO, matching the publisher pattern used
by audits, certificates, executions, migrations, screenshots, stats, and
usage. Call sites now enqueue messages directly instead of mutating a
shared event object and relying on the API shutdown hook.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 13:23:21 +01:00
Jake BarnbyGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
409cebb26e Apply suggestion from @greptile-apps[bot]
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-08 00:12:20 +12:00
Prem Palanisamy e4b51d0abb Drop custom utopia-php/migration VCS repo (now resolved via Packagist) 2026-05-07 13:10:31 +01:00
premtsd-codeandGitHub 76ddaf0317 Merge branch '1.9.x' into spike/isPublishable-migration 2026-05-07 17:17:55 +05:30
ArnabChatterjee20kandGitHub dfd6c2fbb4 Merge pull request #12235 from appwrite/fix-method-ids
updated methods
2026-05-07 16:56:06 +05:30
ArnabChatterjee20k 399f75e8e7 updated 2026-05-07 16:35:56 +05:30
ArnabChatterjee20k 7917506b58 updated oauth 2026-05-07 16:30:23 +05:30
Matej BačoandGitHub 461e07cfee Merge pull request #12234 from appwrite/chore-improve-project-response-tests
Chore: Improve project GET tests
2026-05-07 12:39:54 +02:00
ArnabChatterjee20k 706459e314 updated 2026-05-07 16:01:52 +05:30
ArnabChatterjee20k 7e9d6f33c8 updated methods 2026-05-07 15:56:44 +05:30
Matej Bačo a1e51d27eb PR review fixes 2026-05-07 11:57:14 +02:00
Matej Bačo 0b881ec58a Finish test improvements for get project 2026-05-07 11:45:25 +02:00
Jake BarnbyandClaude Opus 4.7 9de9e5bdf0 test: stabilise ProjectWebhooks and Proxy e2e suites
ProjectWebhooks: bump assertEventually timeouts from 15s to 30/60s. Under
ParaTest with 4 parallel processes the column/attribute worker can take
longer than the original 15s budget to flip status to 'available', which
manifested as a flaky 'available' vs 'processing' assertion in
testUpdateRow on retry. The 60s budget covers attribute and column
provisioning; the 30s budget covers webhook delivery and auto-disable
polling. The function deployment build keeps its existing 120s budget.

Proxy: testCreateRedirectRule was deleting the site before its rules and
only tracking one of two created rule IDs, so cleanupRule hit a 404 because
the site cascade had already removed the rule. Track both rule IDs
distinctly (301 and 307) and clean rules up before the site so cleanup is
deterministic regardless of cascade behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:37:15 +12:00
Jake BarnbyandClaude Opus 4.7 de361573f2 fix: thread gauges through StatsResources message envelope
PHPStan flagged $payload as undefined in StatsResources::action — the gauges
fast path was reading from a variable that never existed, so the optimization
silently no-op'd and always fell through to the full count. Add a typed
$gauges field to the StatsResources message DTO so the worker reads the
pre-computed metrics from the parsed envelope, and drop redundant null
coalescing on the array{metric, value} shape since PHPStan now proves the
keys exist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:30:13 +12:00
Matej Bačo c4a941015f Continue adding project get tests 2026-05-07 11:26:55 +02:00
Matej Bačo 379a388f61 auth security get project tests 2026-05-07 11:12:05 +02:00
Matej Bačo 38575d7620 Improve get project tests 2026-05-07 11:06:47 +02:00
Matej Bačo fe27ae0584 Fix failing tests 2026-05-07 10:58:05 +02:00
Matej Bačo 8ad106632e Improve tests further 2026-05-07 10:40:54 +02:00
Matej Bačo 4721523ea8 Improve project tests 2026-05-07 10:30:56 +02:00
Jake Barnby 917ce627bb chore: merge 1.9.x into feat-dedicated-db 2026-05-07 20:30:08 +12:00
Prem Palanisamy e7d3384660 Pin utopia-php/migration to 1.10.* (release with Sentry-leak fix) 2026-05-07 09:22:21 +01:00
premtsd-codeandGitHub 0ccb206a1d Merge branch '1.9.x' into spike/isPublishable-migration 2026-05-07 13:31:07 +05:30
Jake BarnbyandGitHub 3b2a01f470 Merge pull request #11910 from appwrite/feat/skip-duplicates
feat(migrations): add onDuplicate option to CSV/JSON/Appwrite imports
2026-05-07 19:21:47 +12:00
Jake BarnbyandGitHub a8f33ab964 Merge branch '1.9.x' into presence-api 2026-05-07 19:13:57 +12:00
Jake BarnbyandGitHub 0f67fd7618 Merge pull request #12210 from appwrite/realtime-messsage-dispatchers
Realtime messsage dispatchers (presence api)
2026-05-07 19:12:56 +12:00
premtsd-codeandGitHub 0b810e500f Merge branch '1.9.x' into spike/isPublishable-migration 2026-05-07 12:42:04 +05:30
ArnabChatterjee20k ec8d48327c reverted type error as query exception only thown 2026-05-07 12:34:34 +05:30
ArnabChatterjee20k ce07c6ffd5 added type error to the subscribe handler 2026-05-07 12:24:42 +05:30
Prem Palanisamy 8e9fd32566 spike: simplify gate fallback to always publish on unknown exception types 2026-05-07 07:41:47 +01:00
premtsd-codeandGitHub e621701cbc Merge branch '1.9.x' into feat/skip-duplicates 2026-05-07 12:00:15 +05:30
ArnabChatterjee20k 6679d02a35 refactor: rename connection to connectionId for consistency across handlers 2026-05-07 11:48:45 +05:30
Prem Palanisamy 85d7c27a38 spike: treat null exception code as publishable in fallback gate 2026-05-07 03:50:08 +01:00
Luke B. SilverandGitHub c89bf3084a Merge pull request #12229 from appwrite/chore/builds
Chore/builds
2026-05-06 22:47:57 +01:00
loks0n 9387c480f5 Add Span attributes to build worker for trace observability 2026-05-06 19:56:17 +01:00
premtsd-codeandGitHub 2e9841c9c2 Merge branch '1.9.x' into feat/skip-duplicates 2026-05-06 23:35:25 +05:30
loks0n 8201fea9ef Differentiate executor timeouts for builds, sync, and async executions
Adds Executor\Exception\Timeout (with timeoutSeconds) and translates it at
each call site into BUILD_TIMEOUT, FUNCTION_SYNCHRONOUS_TIMEOUT, or
FUNCTION_ASYNCHRONOUS_TIMEOUT instead of always using the misleading sync
function error. Build timeouts now append to streamed buildLogs rather
than replacing them, and the build worker reports its timeout via Span.
2026-05-06 15:54:11 +01:00
Matej BačoandGitHub bf52b1ba31 Merge pull request #12228 from appwrite/fix-refresh-session
Fix: Refreshing non-oauth sessions
2026-05-06 16:38:01 +02:00
Matej Bačo e834a95213 PR review improvements 2026-05-06 16:21:50 +02:00
Matej Bačo a9dd957a7a Fix test 2026-05-06 15:57:25 +02:00
Matej Bačo d2b551cd12 Fix refreshing nonoauth sessions 2026-05-06 15:50:18 +02:00
Prem Palanisamy f99f2cc1a9 Merge remote-tracking branch 'origin/1.9.x' into spike/isPublishable-migration
# Conflicts:
#	composer.json
#	composer.lock
2026-05-06 13:10:57 +01:00
Prem Palanisamy b094993f77 spike: convert worker throws to AppwriteException + isPublishable gate 2026-05-06 12:34:50 +01:00
Matej BačoandGitHub bc0501aaf2 Merge pull request #12224 from appwrite/fix-oauth-oidc-params
Fix: OAuth OIDC param consistency
2026-05-06 12:25:45 +02:00
Matej Bačo a65c6136f4 Improve code quality 2026-05-06 12:08:02 +02:00
Matej Bačo 78133a3c25 Merge branch '1.9.x' into fix-oauth-oidc-params 2026-05-06 11:46:09 +02:00
Matej Bačo 140cbb633d Fix tests 2026-05-06 11:45:35 +02:00
Matej Bačo 389146c625 oidc backwards compatibiltiy test 2026-05-06 11:32:00 +02:00
Matej BačoandGitHub ec927104fe Merge pull request #12221 from appwrite/fix-variables-api-feature-parity
Feat: Variables API feature parity
2026-05-06 11:27:32 +02:00
Matej Bačo dcef7ef559 OIDC param name improvement 2026-05-06 11:24:53 +02:00
ArnabChatterjee20kandCopilot 63e226c761 updated
Co-authored-by: Copilot <copilot@github.com>
2026-05-06 14:50:40 +05:30
Matej Bačo ff518a055f Merge branch '1.9.x' into fix-oauth-oidc-params 2026-05-06 11:17:42 +02:00
Matej Bačo 00ee9c6b0e Fix benchmark 2026-05-06 11:09:11 +02:00
Prem Palanisamy f9c5f41a1a Merge remote-tracking branch 'origin/1.9.x' into feat/skip-duplicates
# Conflicts:
#	composer.lock
2026-05-06 09:53:13 +01:00
ArnabChatterjee20k 6290a664e9 refactor: rename presence methods for consistency and clarity 2026-05-06 14:16:59 +05:30
Matej Bačo d27b578888 Fix more tests 2026-05-06 10:34:37 +02:00
Jake Barnby 6c6782dd28 Merge remote-tracking branch 'origin/1.9.x' into pr-12194-feat-insights-module
# Conflicts:
#	app/config/scopes/project.php
2026-05-06 20:33:40 +12:00
Jake BarnbyandClaude Opus 4.7 4660185a23 test(insights): fix CI failures from nesting refactor
Three breakages on the prior CI run:

1. PHPUnit 12 didn't propagate `@depends` data — chained tests got
   ArgumentCountError because they expected `array $data` but PHPUnit
   passed nothing. Convert all annotations to `#[Depends]` attributes,
   matching the modern style already used by the Migrations and
   VectorsDB suites.

2. `InsightsCustomConsoleTest` extends ProjectConsole, which doesn't
   set up a project API key. The trait's `serverHeaders()` hardcodes
   `x-appwrite-key`, so every Console test 401'd. Drop the Console
   class entirely — the manager Create endpoint is KEY-only by
   design, the Server class already exercises every code path, and a
   Console-side variant adds no real coverage.

3. `testCreateRequiresManagerScope` called `getNewKey()`, which lives
   on `ProjectCustom`. PHPStan flagged the call as undefined when the
   trait was analyzed against the (no-longer-existing) Console class.
   Move the test into `InsightsCustomServerTest.php` directly so it's
   only ever resolved against `ProjectCustom`.

Plus PHP 8.4 + match-exhaustive cleanups PHPStan caught while I was
in there:

- `?array $headers = null` instead of `array $headers = null` on every
  helper (PHP 8.4 deprecates implicit-nullable params).
- `?string $insightId = null` on `sampleInsight()`.
- `parentResourceType` match collapsed to `tablesDB => 'tables';
  databases/documentsDB/vectorsDB => 'collections'`. The earlier
  `$type` match already throws on unknown engine, so the fall-through
  default was unreachable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 20:20:54 +12:00
Matej Bačo 1ccf058c14 Fix failing tests 2026-05-06 10:19:30 +02:00
Jake BarnbyandGitHub 1273bcd33a Merge pull request #11673 from appwrite/big-int
Big int
2026-05-06 20:17:45 +12:00
Matej BačoandGitHub add2b7b64a Merge pull request #12208 from appwrite/feat-public-proxy-api
Feat: Public Proxy API
2026-05-06 10:15:57 +02:00
ArnabChatterjee20k ac80ba270b updated queries 2026-05-06 13:24:34 +05:30
ArnabChatterjee20k a0d77ba1b2 Merge remote-tracking branch 'origin/1.9.x' into big-int 2026-05-06 13:22:37 +05:30
ArnabChatterjee20k f9c18a6462 updated validator subscribe 2026-05-06 13:20:19 +05:30
Matej BačoandGitHub 63ac3bedca Merge pull request #12212 from appwrite/fix-param-name-consistency
Fix endpoint consistency (oauth get)
2026-05-06 09:42:22 +02:00
Matej Bačo a286b78a0b Fix function tests 2026-05-06 09:41:00 +02:00
Prem Palanisamy e63f9fd6a5 tests: send non-empty parent row data in TwoWayRecreate test
server-ce 1.9.x's tablesdb POST /rows tightened input validation: the
modular Documents/Create.php rejects `data => []` with a 400
"missing data" because TablesDB's Rows/Create.php inherits the strict
default of getSupportForEmptyDocument() = false (only DocumentsDB
overrides it to true). The test was relying on the older permissive
behavior to seed an empty parent row before the relationship cascade
links it.

Add a non-relationship `label` string column on the parents table and
populate it with `data => ['label' => 'p1']` so the POST passes the
empty-data guard. The test's actual assertion target — partner-side
pair-key dedup on DropAndRecreate — is unchanged.

Cascade fixes: testAppwriteMigrationOverwriteAttributeRecreate and
testAppwriteMigrationOverwriteSameSpecRecreate were failing in the
retry pass because TwoWayRecreate's bail at L1616 left source/dest
state uncleaned. Once TwoWayRecreate completes, those tests see a
clean project again.

Caught in CI run 25419479164 / job 74562934987 on the MongoDB
(dedicated) Migrations matrix.
2026-05-06 08:40:17 +01:00
ArnabChatterjee20k d2ea6ef2ca removed redundant dumps 2026-05-06 12:33:42 +05:30
Jake BarnbyandClaude Opus 4.7 68c354e09b refactor(insights): nest insights API under reports
Insights are children of reports — make the URL hierarchy reflect that.

Endpoints:
- POST   /v1/manager/reports/:reportId/insights        (manager Create)
- GET    /v1/reports/:reportId/insights                (List)
- GET    /v1/reports/:reportId/insights/:insightId     (Get)
- PATCH  /v1/reports/:reportId/insights/:insightId     (Update)
- DELETE /v1/reports/:reportId/insights/:insightId     (Delete)

`reportId` moves from optional body field to required path param. All
endpoints fetch the report first (404 REPORT_NOT_FOUND if missing or
in another project), then verify the insight's `reportInternalId`
matches before doing anything else.

Side effects:
- Event names nested: `reports.[reportId].insights.[insightId].create`
  etc. Top-level `insights.*` event tree removed from events.php.
- Realtime channel parser handles the nested form: a `reports.{rid}`
  event lights up `reports`, `reports.{rid}` channels; a nested
  `reports.{rid}.insights.{iid}` event also lights up
  `reports.{rid}.insights` and `reports.{rid}.insights.{iid}`.
- Audit resource paths nested similarly:
  `report/{request.reportId}/insight/{response.$id}`.
- listInsights query validator drops `reportId` from
  ALLOWED_ATTRIBUTES — it's path-scoped now, not a query filter.

Tests:
- E2E helpers `createInsight`/`getInsight`/`listInsights`/
  `updateInsight`/`deleteInsight` all take `reportId` as the first
  argument.
- New `createFixtureReport()` helper for standalone validation tests
  that need a parent.
- Dropped `testCreateWithoutReport` — reportId is mandatory now.
- `testCreateRejectsUnknownReport` now exercises the path-level 404
  rather than a body-level check.
- `testGet` and `testUpdateMissing` exercise the
  wrong-reportId-but-valid-insightId case (returns
  `report_not_found`).
- `testList` asserts every result carries the path's reportId, plus a
  404 case for a nonexistent parent.
- `testCreateForEachEngine` and the standalone create-rejection tests
  inline-create their own fixture report and clean up after.
- `testListSurvivesEmptyDatabase` renamed to
  `testListSurvivesEmptyReport` and uses a fresh fixture report.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:53:54 +12:00
ArnabChatterjee20k 1946ee86b2 feat: add realtime message handlers for authentication, ping, presence, subscribe, and unsubscribe 2026-05-06 12:20:06 +05:30
ArnabChatterjee20k 5eabeeea20 refactor: update payload validators to use closures for instantiation and improve error handling 2026-05-06 12:10:50 +05:30
Jake BarnbyandClaude Opus 4.7 1f4111d6f2 test(insights): drop summary update from testUpdate
User Update only accepts severity + status now — analyzer-controlled
fields like summary flow through the manager Create endpoint. The
previous testUpdate sent `summary: 'Updated summary.'` and asserted
it changed; Utopia silently dropped the unknown param so the
assertion would have failed.

Trim the call to severity-only and verify the analyzer fields
(including summary) are preserved on the response.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:34:04 +12:00
Jake BarnbyandClaude Opus 4.7 0b72dba817 refactor(insights): drop CTA key field
`key` was a leftover from when CTAs were embedded JSON — there's no
remaining reason to require analyzers to invent a within-insight
identifier. The execution layer is gone (no `cta.key` event format),
insights are immutable from the user side (analyzers re-ingest by
delete + recreate, so idempotent matching never happens), and `label`
already covers human-facing identification. The console can group/sort
CTAs by `service`+`method` if needed.

- Schema: drop `key` attribute and the UNIQUE
  `(insightInternalId, key)` index from insightCTAs. Required fields
  are now `label`, `service`, `method` (+ optional `params`).
- Validator no longer requires `key`. Drop the dup-key normalization
  loop in the manager Create endpoint — there's no semantic
  uniqueness to enforce.
- Response model: `InsightCTA` keeps `$id` + standard headers,
  `insightId` backref, and the four functional fields.
- E2E: drop sampleCTA's `$key` parameter, drop the
  testCreateRejectsDuplicateCTAIds test entirely, rename empty-fields
  test to testCreateRejectsCTAWithEmptyLabel and update the missing-
  fields tests to drop `key` from their payloads.
- Unit tests rewritten to drop `key`.
- Comment on the `insights.ctas` virtual attribute updated to
  reference the renamed `insightCTAs` collection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:29:40 +12:00
ArnabChatterjee20kandCopilot fc918d8b3c feat: add support for unsigned integer and bigint columns in database tests
Co-authored-by: Copilot <copilot@github.com>
2026-05-06 11:48:47 +05:30
Jake BarnbyandClaude Opus 4.7 c5dfc42a60 refactor(insights): rename ctas collection to insightCTAs
Disambiguate the platform-level collection name. Field/request-param
remains `ctas` (the embedded array on the insight response).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:12:17 +12:00
Jake BarnbyandClaude Opus 4.7 5404bfec75 refactor(insights): promote CTAs to own collection with backref
Embedding CTAs as a 16384-byte JSON blob on `insights` was the wrong
shape — they're real documents with their own lifecycle. Move them out.

Schema:
- New platform `ctas` collection. Each row carries `projectInternalId`,
  `projectId`, `insightInternalId`, `insightId` (backref), plus the
  CTA fields: `key`, `label`, `service`, `method`, `params`.
- Indexes: `(projectInternalId, insightInternalId)` for the subquery
  lookup and a UNIQUE `(insightInternalId, key)` so the per-insight
  uniqueness invariant lives at the DB layer (not just in PHP).
- The `ctas` field on `insights` becomes a virtual attribute backed by
  a new `subQueryInsightCTAs` filter that joins child docs at read
  time. Consumers still get CTAs embedded on the insight response —
  one round-trip from their perspective.
- The CTA descriptor's within-insight identifier renamed `id` → `key`
  (clashed with the document `$id`). Validator updated.

Endpoints:
- Manager Create now persists CTAs as separate `ctas` documents after
  the parent insight, then re-fetches the insight so the response
  carries the freshly-joined CTA list.
- User Update trimmed to user-controlled state only (`severity`,
  `status`). `title`, `summary`, `payload`, `ctas`, and `analyzedAt`
  are analyzer-controlled — analyzers re-ingest by deleting and
  POSTing again to the manager endpoint.
- Insight Delete cascades to CTAs.
- Report Delete cascades through Insights → CTAs.

Response model:
- InsightCTA gains the standard document headers (`$id`,
  `$createdAt`, `$updatedAt`) and an `insightId` backref. The
  caller-supplied identifier is now `key`.

Tests:
- E2E sampleCTA factory uses `key` everywhere; testCreate asserts the
  freshly-created CTA carries `$id`, `$createdAt`, `insightId`, and
  the right shape.
- Dropped the testUpdate*CTA* tests — user Update no longer accepts
  CTAs. testDismissViaUpdate now depends on testUpdate directly.
- Unit tests rewritten to validate `key` instead of `id`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:07:49 +12:00
ArnabChatterjee20k 93ce542d31 updated composer 2026-05-06 11:37:05 +05:30
Prem Palanisamy e6a9c68534 Merge remote-tracking branch 'origin/1.9.x' into feat/skip-duplicates
# Conflicts:
#	composer.lock
2026-05-06 07:06:59 +01:00
ArnabChatterjee20kandCopilot 03f7b62ff1 refactor: update database version constraints and simplify bigint handling in collection creation
Co-authored-by: Copilot <copilot@github.com>
2026-05-06 11:24:22 +05:30
Jake BarnbyandClaude Opus 4.7 38efdf18e2 feat(insights): add parent resource pointer
Eldad's review comment: insights about nested resources need a pointer
to the containing parent (the file-in-bucket pattern). Add three
optional fields:

- parentResourceType (plural noun, e.g. `tables`, `collections`)
- parentResourceId
- parentResourceInternalId

so an insight whose `resourceType=indexes` / `resourceId=_idx_status`
can also carry `parentResourceType=tables` / `parentResourceId=orders`
to identify the table that owns the index. All three are nullable for
top-level resources (e.g. a project-wide audit finding).

Schema, response model, manager Create endpoint, and the listInsights
query validator (parent fields are filterable). New compound index
`_key_project_parent_resource(projectInternalId, parentResourceType,
parentResourceId, $sequence)` to support the parent lookup pattern
the console will use ("show all insights for table X").

E2E factory generates a parent by default (engine-aware:
tables for tablesDB, collections for the others). New
testCreateWithoutParentResource exercises the top-level case;
testList gains a parent-resource filter assertion; testUpdate's
preserved-fields check picks up the new attributes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:25:34 +12:00
Jake BarnbyandClaude Opus 4.7 4fc3e9c386 refactor(insights): manager-only Create endpoint + native categories array
Insights are produced by internal Appwrite services (edge, executor,
background analyzers) — never by user clients. Move the ingestion
endpoint accordingly.

- Move Http/Insights/Create.php → Http/Manager/Insights/Create.php.
- Path: /v1/insights → /v1/manager/insights. SDK Method marked
  `hide: true` and namespaced under `manager` so generated SDKs don't
  expose it. Auth narrowed from [ADMIN, KEY] to [KEY] only.
- New scope `insights.manager`. Not granted by any user role
  (app/config/roles.php) — Cloud/edge teams configure their internal
  key issuance to grant it. `insights.write` description trimmed to
  the user-facing surface (update/dismiss/delete) since create is now
  manager-only.
- Reports, ListInsights, GetInsight, UpdateInsight, DeleteInsight
  remain at /v1/insights/*. Existing scopes unchanged.
- Reports `categories` switched from JSON-encoded string to a native
  array<string> column (size 64 per entry, up to 32 entries via the
  endpoint validator). MySQL JSON-array indexes are weak and we never
  query individual entries — read+rewrite only.
- E2E test API key in tests/e2e/Scopes/ProjectCustom.php gains
  insights.read/write/manager + reports.read/write so the manager
  endpoint is reachable from the test harness.
- E2E InsightsBase.createInsight() helper now POSTs /manager/insights.
- New testCreateRequiresManagerScope verifies a key with
  insights.read/write but no insights.manager is rejected with 401.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:20:49 +12:00
Damodar LohaniandGitHub ea4a31c6c6 Merge pull request #12222 from appwrite/CLO-2333-additional-indexes-1.9x
feat(indexes): add 4 missing indexes (supersedes #9629)
2026-05-06 10:50:44 +05:45
Damodar Lohani c9ad685e11 fix(indexes): use empty lengths for mixed-type composite to match existing pattern
greptile flagged missing lengths but the codebase has two conventions:
- all-string composite: [LENGTH_KEY, LENGTH_KEY] (_key_unique, _key_provider_providerUid)
- mixed/non-string composite: [] (e.g. ('enabled', 'type'), ('region', 'accessedAt'),
  ('targetInternalId', 'topicInternalId'), ('period', 'time'), ('metric', 'period', 'time'))

(teamInternalId, confirm) is string+boolean; closest match is
('enabled', 'type') which uses lengths => [] and orders => [].
Aligning to that established pattern instead of inventing
[LENGTH_KEY, 0].
2026-05-06 04:48:53 +00:00
Damodar Lohani 83d56a2f36 fix(indexes): set explicit lengths + orders on new indexes (greptile P1)
All four new indexes left lengths/orders as empty arrays; greptile
flagged the inconsistency vs every existing string-attribute index in
the file (e.g. _key_team uses [LENGTH_KEY], _key_unique uses
[LENGTH_KEY, LENGTH_KEY]).

- memberships._key_team_confirm: [LENGTH_KEY, 0] for (string, boolean)
  + [ORDER_ASC, ORDER_ASC]
- projects._key_teamInternalId: [LENGTH_KEY] + [ORDER_ASC]
- platforms._key_project_id: [LENGTH_KEY] + [ORDER_ASC]
- webhooks._key_project_id: [LENGTH_KEY] + [ORDER_ASC]
2026-05-06 04:44:58 +00:00
Jake BarnbyandClaude Opus 4.7 a1f64c6f71 refactor(insights): split CTA action into service + method
- InsightCTA model now exposes `service` (SDK namespace) and `method`
  (function name) instead of a single `action` string. Drops the
  string-splitting burden on every consumer and lets the console reach
  the right SDK method directly.

- Validator requires both `service` and `method` non-empty; same
  16-entry max still enforced.

- Endpoint normalization (Create + Update) splits the new shape into
  the persisted CTA descriptor.

- Constants split: INSIGHT_CTA_SERVICE_* (databases / tablesDB /
  documentsDB / vectorsDB) and INSIGHT_CTA_METHOD_* (createIndex).

- Insight model + InsightCTA model docs updated with the new field
  semantics and per-engine examples.

- E2E factory `sampleCTA($id, $engine)` emits the correct service and
  engine-appropriate params keys (tableId/columns for tablesDB;
  collectionId/attributes everywhere else). Engine matrix asserts
  `service` and `method` independently.

- Added e2e + unit coverage for the new failure modes (missing
  service, missing method, empty service, empty method).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:35:33 +12:00
Damodar Lohani 94c968e941 feat(indexes): add 4 missing indexes (CLO-2333)
- memberships: _key_team_confirm on (teamInternalId, confirm) for team-membership confirm-state queries
- projects: _key_teamInternalId on teamInternalId for team-scoped project lookups
- platforms: _key_project_id on projectId for user-facing-id lookups
- webhooks: _key_project_id on projectId for user-facing-id lookups

Re-applies the indexes from the stale PR #9629 (1.7.x base, conflicting)
onto a fresh 1.9.x branch. None of these are in 1.9.x today; existing
similar indexes target projectInternalId / teamId rather than the
user-facing projectId / teamInternalId queries this addresses.
2026-05-06 04:35:29 +00:00
Jake BarnbyandClaude Opus 4.7 8f79379b6e test(insights): full e2e + per-engine CTA action mapping
- Restructure InsightsBase trait with explicit helper methods
  (createInsight/getInsight/listInsights/updateInsight/deleteInsight,
  createReport/getReport/listReports/updateReport/deleteReport, plus
  sampleInsight/sampleCTA factories) — same shape ProxyBase uses.

- Add coverage for: report CRUD + duplicate-id rejection, invalid type
  rejection, list filtering by all allowed attributes, cursor
  pagination + missing-cursor, update preserving untouched fields,
  CTA validation edge cases (duplicate ids, empty fields, count > 16),
  dismissal round-trip + status filter, report cascade delete,
  unauthorized access (no server key), empty-result list.

- Engine-specific insight types (tablesDBIndex, documentsDBIndex,
  vectorsDBIndex, plus the legacy databaseIndex) so the CTA's `action`
  can map to the matching public API: databases.createIndex,
  tablesDB.createIndex, documentsDB.createIndex,
  vectorsDB.createIndex. dataProvider drives the engine matrix and
  asserts the right action lands in the persisted CTA. Constants for
  each action name live in app/init/constants.php.

- InsightCTA model docs spell out which action belongs to which engine
  and that the params keys differ between APIs (tableId/columns for
  tablesDB vs collectionId/attributes for the legacy / DocumentsDB /
  VectorsDB APIs).

- Insight model `type` description now lists every engine variant.

- CTAsTest gains coverage for object-shaped params, empty-action and
  empty-label rejection, and the default 16-entry cap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:25:49 +12:00
Jake BarnbyandClaude Opus 4.7 00565ea471 refactor(insights): metadata-only CTAs, platform DB, reports parent
Address review feedback on PR #12194:

- Pivot CTAs to pure descriptors (id/label/action/params). Drop the
  server-side execution layer: Action interface, registry, the
  databases.indexes.create CTA action, the params validator, the
  /v1/insights/:id/ctas/:id/executions endpoint, the InsightCTAExecution
  model, the INSIGHT_CTA_* errors, and the corresponding events. The
  console invokes the existing public API directly with the descriptor's
  action + params.

- Restore Databases\Indexes\Action.php to its pre-CTA shape and inline
  the index-create body back into Create.php (the createIndex helper
  was added solely for CTA reuse).

- Move insights collection from project DB to platform DB and add a
  parent reports collection alongside it. Insights carry projectId /
  projectInternalId for tenant scoping and an optional reportId for
  grouping. List endpoints filter by projectInternalId; Get/Update/
  Delete also enforce project ownership before touching the document.

- New Reports module with full CRUD (Create/Get/XList/Update/Delete),
  Report response model, Reports query validator, REPORT_NOT_FOUND /
  REPORT_ALREADY_EXISTS errors, reports.read / reports.write scopes,
  and reports.* event tree. Delete cascades to child insights.

- Update.php now mutates the loaded document via setAttribute (instead
  of passing a partial new Document), reuses CTAsValidator (instead of
  the looser ArrayList<JSON> + isset check), and rejects duplicate CTA
  ids.

- Create.php enforces unique CTA ids during normalization.

- CTAsValidator gained a configurable maxCount (default 16) so the
  Create path matches the Update path and the DB column size, and
  oversized payloads return a clean 400.

- Validator\Queries\Insights adds status and reportId to
  ALLOWED_ATTRIBUTES so dismissal / report workflows are filterable.

- Realtime channel parser guards $parts[1] for both insights and
  reports event names.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:46:07 +12:00
Prem Palanisamy 803f646239 Update composer.lock for utopia-php/migration da8205e 2026-05-05 17:19:40 +01:00
Matej BačoandGitHub 8918cc75c3 Merge pull request #12213 from appwrite/feat-list-endpoint-queries-oauth
Feat: queries & total params for list oauth providers
2026-05-05 17:16:09 +02:00
Matej Bačo f90f618bff Fix failing tests 2026-05-05 17:14:05 +02:00
Matej Bačo b382a9c3e5 Fix mistakes after AI 2026-05-05 16:53:24 +02:00
Matej Bačo 3fd79e3045 Merge branch '1.9.x' into fix-param-name-consistency 2026-05-05 16:52:10 +02:00
Matej Bačo 3e58500e13 Upgrade libs 2026-05-05 16:51:57 +02:00
Matej Bačo 1975ab2513 Fix linter 2026-05-05 16:45:15 +02:00
Matej Bačo 15f21daa2b Add tests 2026-05-05 16:45:09 +02:00
Matej Bačo 48fbb59113 pr review fixes 2026-05-05 16:44:47 +02:00
Matej Bačo 9b120e9db7 Remove unwanted changes 2026-05-05 16:29:09 +02:00
Matej Bačo 305e8e1ec7 Add request filter 2026-05-05 16:24:41 +02:00
Matej Bačo 93515fcc1d Finalize variables rework 2026-05-05 16:17:47 +02:00
Matej Bačo fd1a4542e7 Fix variables API feature parity 2026-05-05 16:09:17 +02:00
ArnabChatterjee20k 7ccf2b1a1e updated 2026-05-05 19:28:28 +05:30
ArnabChatterjee20k 57dd7cf952 updated 2026-05-05 19:22:00 +05:30
ArnabChatterjee20k 90e681a906 updated 2026-05-05 19:20:47 +05:30
ArnabChatterjee20k d043b4740e updated 2026-05-05 19:11:05 +05:30
ArnabChatterjee20k bca876acaf Merge remote-tracking branch 'origin/1.9.x' into big-int 2026-05-05 18:55:11 +05:30
ArnabChatterjee20k 6eb84c8235 Merge remote-tracking branch 'origin/presence-api' into realtime-messsage-dispatchers 2026-05-05 18:48:05 +05:30
harsh mahajan 61cee5892a fix: tighten commit skip directive matching 2026-05-05 18:23:14 +05:30
Harsh MahajanandGitHub a77558807c Merge branch '1.9.x' into feat-ser-401-custom-triggers 2026-05-05 17:58:54 +05:30
Matej BačoandGitHub 2fc044a7c9 Merge pull request #12218 from appwrite/fix-double-policies-scopes
Chore: Rename policies scope
2026-05-05 13:56:03 +02:00
abhay-dev2901 f36105a7af fix: pass platform env vars to function schedulers 2026-05-05 17:23:50 +05:30
ArnabChatterjee20k f455bb7a58 refactor: add checks for queueForEvents and queueForRealtime in container setup; enhance presence resolution in tests 2026-05-05 17:11:49 +05:30
Matej Bačo fef4cbf3b0 Fix missing new scope 2026-05-05 13:37:51 +02:00
Matej Bačo 32d30dfd9d Fix copy 2026-05-05 13:06:57 +02:00
Matej Bačo b0220292a7 Rename policies to prevent double scope usease 2026-05-05 12:58:06 +02:00
Prem Palanisamy 9ab546d743 composer: pin utopia-php/migration to 1.9.7 (released) 2026-05-05 09:11:18 +01:00
Chirag AggarwalandGitHub 67ab29ffeb Merge pull request #12211 from appwrite/feat/spec-unified-auth-headers 2026-05-05 12:53:26 +05:30
Jake BarnbyandGitHub 1a41a8ea66 Merge pull request #11632 from appwrite/backup-migration-multitype
Add backup policy migration support
2026-05-05 19:20:55 +12:00
ArnabChatterjee20k bdf6c660d9 Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-05-05 11:53:43 +05:30
ArnabChatterjee20k b0a2ef271a updated the queue for event propagation 2026-05-05 11:35:49 +05:30
Chirag AggarwalandGitHub 538fa94c79 Merge pull request #12216 from appwrite/fix/graphql-php-audit 2026-05-05 11:02:31 +05:30
Chirag Aggarwal 1a410c43d7 fix: update graphql php dependency 2026-05-05 10:54:01 +05:30
Matej Bačo 4d5bb30917 tests for oauth list endpoint ueries and total 2026-05-04 16:05:56 +02:00
Matej Bačo c35d5e348b Support queries in oauth list endpoint 2026-05-04 16:05:46 +02:00
Torsten Dittmann 5c7e08fdff Fix deployment upload analyze warning 2026-05-04 17:59:27 +04:00
Matej Bačo d2922e7d5d Fix failing tests 2026-05-04 15:49:05 +02:00
Matej Bačo b1c3fc0908 Update tests 2026-05-04 15:40:32 +02:00
Torsten Dittmann fd83090215 Fix parallel deployment chunk uploads 2026-05-04 17:34:53 +04:00
Matej Bačo d18f64d526 Fix endpoint consistency, apply request filter alias 2026-05-04 15:22:23 +02:00
ArnabChatterjee20k 2ed8247fba fix graphql tests 2026-05-04 17:39:34 +05:30
ArnabChatterjee20k b2b6fb6b69 Enhance validation logic in Realtime message validators. Update error handling to return specific validation error messages and streamline the validation process in SubscribePayloadValidator and UnsubscribePayloadValidator. 2026-05-04 17:25:39 +05:30
ArnabChatterjee20k 4822164e32 Inject global helpers into PresenceHandler for improved testability and maintainability. 2026-05-04 17:14:09 +05:30
ArnabChatterjee20k 42542aec61 Enhance error handling and validation in Realtime message dispatchers. Update error messages for clarity and enforce required parameters in AuthenticationHandler. 2026-05-04 17:11:35 +05:30
Chirag Aggarwal e81ae67745 fix(specs): align console Cookie description with client/server 2026-05-04 16:53:22 +05:30
Chirag Aggarwal 1e9b364f58 feat(specs): include union of auth headers in every platform's securityDefinitions
Each platform spec previously declared only the auth headers referenced by
endpoints in that platform's surface. The unified web SDK now exposes a
single Client class with cross-platform auth factories (fromSession,
fromCookie, fromDevKey, etc.), which means an SDK generator targeting one
platform still needs setter generation for headers that other platforms
expose. The web SDK currently papers over this with a webClientHeaders
augmentation in the SDK generator.

Add the missing securityDefinitions entries directly so every platform
spec carries the union, removing the need for SDK-side augmentation:

- client: + Cookie (for SSR cookie forwarding)
- server: + Cookie, DevKey
- console: + Session, DevKey

Per-endpoint security requirements continue to be filtered per platform
as before; only the securityDefinitions registry expands to declare
schemes that exist API-wide. SDKs that iterate the registry to emit
auth setters now produce the union without per-SDK fallback code.
2026-05-04 16:50:38 +05:30
Torsten Dittmann 6e19db130e Fix storage chunk upload events 2026-05-04 15:09:52 +04:00
ArnabChatterjee20k ec0f7cf683 Update error message in PresenceRealtimeClientTest for clarity on permissions validation 2026-05-04 16:39:47 +05:30
ArnabChatterjee20k ced5b88b57 Refactor message dispatching and handler parameters for improved clarity and consistency. Update AuthenticationHandler and PresenceHandler to enforce required parameters. 2026-05-04 16:37:35 +05:30
Torsten Dittmann d58929b621 Restore storage upload lock TTL 2026-05-04 15:00:16 +04:00
ArnabChatterjee20k 29092b277b Update AuthenticationHandler to make session parameter optional and add validation for empty session tokens. Update error message in PresenceRealtimeClientTest for clarity. 2026-05-04 16:22:54 +05:30
Torsten Dittmann 9927e4bd8e Fix storage upload lock contention handling 2026-05-04 14:34:42 +04:00
ArnabChatterjee20k 0e0e40f991 fixed the container inheritance bug 2026-05-04 15:59:25 +05:30
Torsten Dittmann 3db776e2e5 Update parallel upload test concurrency 2026-05-04 14:24:01 +04:00
Prem Palanisamy 561b482e54 Restore original sanitizeErrors decode/strip/encode flow 2026-05-04 11:20:00 +01:00
Matej Bačo d7d0ecb106 Fix failing tests 2026-05-04 12:17:34 +02:00
Torsten Dittmann 61ca4e3969 Fix parallel storage chunk upload state 2026-05-04 14:15:01 +04:00
Matej Bačo 879dc6873e review fixes 2026-05-04 12:07:52 +02:00
Matej Bačo 2263632f7d Fix all proxy create endpoints 2026-05-04 12:06:37 +02:00
Prem Palanisamy 3bb68ef455 Trim verbose comments in migrations worker outer catch 2026-05-04 11:03:51 +01:00
Matej Bačo 2231830826 Further proxy API improvements 2026-05-04 11:59:04 +02:00
Matej Bačo cd6f5c64f0 Improve proxy API quality 2026-05-04 11:48:02 +02:00
Matej Bačo d8349544d2 Revert unwanted change 2026-05-04 11:37:26 +02:00
Matej Bačo ec75f101bb SDK quality fix 2026-05-04 11:37:00 +02:00
Matej Bačo 9c9bde2ce6 Introduce project key console tests 2026-05-04 11:36:54 +02:00
Prem Palanisamy 07d60bb36d Capture bubbled exception in migration error report
The catch block recorded status='failed' but didn't surface the bubbling
exception's message on the migration document. Setup-time failures (e.g.
"Source project not found for provided projectId") left the user looking
at status='failed' with errors=[].

Capture the throwable in the catch and include it in the consolidated
errors list when finally serializes to the migration document.
2026-05-04 10:23:40 +01:00
Prem Palanisamy 581fdb26dd Update composer.lock for utopia-php/migration branch pin 2026-05-04 08:57:56 +01:00
harsh mahajanandClaude Sonnet 4.6 40c2e1a1fa feat: skip deployment on commit message pattern match
Add providerCommitSkipPatterns array field to functions and sites.
Any commit message containing one of the patterns (case-insensitive
substring) skips the VCS-triggered deployment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 13:09:38 +05:30
Prem Palanisamy c849e652b3 Pin utopia-php/migration to fix-migration-sentry-leak branch for testing
Wires the worker against the matching utopia-php/migration branch so
end-to-end testing of the Sentry-routing fix can run against both
sides of the change set.

Run `composer update utopia-php/migration --with-all-dependencies`
after pulling to refresh composer.lock. Revert before merging.
2026-05-04 08:36:38 +01:00
ArnabChatterjee20kandGitHub 554fdf5472 Merge pull request #12206 from appwrite/fix-query-syntax-errors
fix: resolve query syntax errors and improve error handling in Reques…
2026-05-04 12:57:39 +05:30
ArnabChatterjee20k 737c85822d multi line comment 2026-05-04 12:46:44 +05:30
ArnabChatterjee20k 1fc3a8803c multiline comment 2026-05-04 12:45:32 +05:30
Damodar LohaniandGitHub 44dd0e8c32 Merge pull request #12207 from appwrite/chore-bump-utopia-audit-2.2.2
chore: bump utopia-php/audit to 2.2.2
2026-05-04 12:58:48 +05:45
ArnabChatterjee20k c32294743a reverted e2e 2026-05-04 12:31:50 +05:30
Damodar Lohani f27172018f chore: bump utopia-php/audit to 2.2.2 2026-05-04 06:56:56 +00:00
ArnabChatterjee20k f5a7cfd2ea fix: resolve query syntax errors and improve error handling in Request class 2026-05-04 12:22:48 +05:30
Prem Palanisamy c2e4bc2ae3 Simplify sanitizeErrors now that jsonSerialize doesn't emit trace
With Migration\Exception::jsonSerialize() no longer including the stack
trace, sanitizeErrors no longer needs to decode/strip/re-encode each
entry. Reduce it to a single json_encode pass.
2026-05-04 07:27:27 +01:00
ArnabChatterjee20k 429454ca0e added handlers 2026-05-04 11:18:22 +05:30
Prem Palanisamy 4703e4ba14 Gate Sentry logging in migrations worker by exception type
The outer catch in the migrations worker now only calls logError when the
caught Throwable is not a MigrationException. User-facing setup errors
(invalid source type, missing project, etc.) are thrown as MigrationException
with appropriate codes and stay in the migration report only.

Removed the foreach loop that re-published collected errors to Sentry; with
the library-side fix in utopia-php/migration, items in $source->getErrors() /
$destination->getErrors() are by construction user errors that don't need
Sentry routing.

Hoisted setAttribute('errors', sanitizeErrors(...)) into finally so the
migration document always reflects the consolidated error list, including
on bug paths.
2026-05-04 05:05:00 +01:00
Luke B. SilverandGitHub 76e6239d32 Merge pull request #12204 from appwrite/chore/bump-image-1.4.1
chore: bump base image to 1.4.1
2026-05-03 20:18:03 +01:00
Matej BačoandGitHub 1da5b549af Merge pull request #12203 from appwrite/fix-missing-scopes-console
Fix: Add deprecated function scopes
2026-05-03 20:21:28 +02:00
loks0nandClaude Opus 4.7 92eceba218 chore: bump base image to 1.4.1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:21:00 +01:00
8f68a59a79 Apply suggestions from code review
Co-authored-by: Matej Bačo <matejbaco2000@gmail.com>
2026-05-03 19:51:56 +02:00
6051b8150c Apply suggestions from code review
Co-authored-by: Matej Bačo <matejbaco2000@gmail.com>
2026-05-03 19:51:10 +02:00
Matej Bačo 4e20e382d2 Add deprecated function scopes 2026-05-03 19:49:13 +02:00
Luke B. SilverandGitHub c96922422f Merge pull request #12201 from appwrite/chore/bump-image-1.3.1
chore: bump base image to 1.3.1
2026-05-03 15:27:45 +01:00
loks0nandClaude Opus 4.7 7f27851dab chore: bump base image to 1.3.1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 15:15:27 +01:00
Chirag AggarwalandGitHub f396064c49 Merge pull request #12198 from appwrite/add-rust-starter-function-template 2026-05-02 16:53:41 +05:30
Chirag Aggarwal 413930a15e Add Rust starter function template runtime 2026-05-02 12:19:07 +05:30
Chirag AggarwalandGitHub f672891c42 Merge pull request #12196 from appwrite/chore/update-runtimes-0.20 2026-05-01 19:33:01 +05:30
Chirag Aggarwal 65f926b4c5 Update appwrite runtimes to 0.20 2026-05-01 18:55:13 +05:30
Jake BarnbyandClaude Opus 4.7 56ef3b4cfa refactor(insights): nest CTA params validator under resource path
Moves Validator/CTAParams/DatabasesCreateIndex to
Validator/CTA/Databases/Index/Create so the validator hierarchy mirrors
the action hierarchy (Insights/CTA/Action/Databases/Indexes/Create).
Also tightens the Update and CTA execute endpoint descriptions to call
out the dismissal-via-status flow and what the execution result carries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:11:30 +12:00
Jake BarnbyandClaude Opus 4.7 719b1885bf refactor(insights): share index-create body between CE action and CTA
Move the body of the public databases create-index endpoint into a new
final `createIndex(...)` helper on the abstract Indexes Action so both
the HTTP-facing CE action and the insights CTA share one source of truth
for index validation, persistence, and queue dispatch.

The CTA's `Appwrite\Insights\CTA\Action` becomes a small interface
(`getName()` + `execute()`); the dispatcher now calls `$action->execute(...)`
directly instead of poking at Utopia Action internals via `getCallback()`
and `getParams()`. The CTA's `Create` extends the CE Indexes `Create`
so it inherits `createIndex()` for free, while keeping a no-op constructor
to skip the HTTP route registration that runs in the parent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:49:44 +12:00
Jake BarnbyandClaude Opus 4.7 1d215b2840 revert(insights): drop insight/project param declarations on CTA actions
The CTA execution dispatcher invokes the action callback positionally
with values that have already been validated (insight fetched and
asserted non-empty in the endpoint, project pulled from the request
context). Re-validating them through param declarations adds noise
without catching anything. Drop the declarations and the validator
classes/tests created for them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:22:28 +12:00
Jake BarnbyandClaude Opus 4.7 0fbf31bd9a refactor(insights): drop Document suffix from Insight/Project validators
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:18:27 +12:00
Jake BarnbyandClaude Opus 4.7 4d560bdff2 feat(insights): validate insight and project documents in CTA actions
Replace the no-op `fn () => true` validators (gated by skipValidation)
on the `insight` and `project` params with dedicated InsightDocument and
ProjectDocument validators. They check that the injected value is a
non-empty Document with the expected attributes, so a misconfigured
dispatcher or unbound injection fails fast with a useful message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:12:41 +12:00
Jake BarnbyandClaude Opus 4.7 69c637c72d style(insights): use aliased import for DatabasesIndexesCreate registry entry
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:01:31 +12:00
Jake BarnbyandClaude Opus 4.7 242c7f75ad refactor(insights): nest databases create-index CTA under resource path
Move the action class from Action/DatabasesCreateIndex.php to
Action/Databases/Indexes/Create.php so the directory mirrors the
underlying resource hierarchy. Action name follows: databases.createIndex
becomes databases.indexes.create, with the constant renamed to
INSIGHT_CTA_ACTION_DATABASES_INDEXES_CREATE.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:57:28 +12:00
Jake BarnbyandClaude Opus 4.7 f779c7aa3b refactor(insights): collapse dismissal into a status field on update
Dismissal was a sub-resource (POST /v1/insights/:id/dismissals) but a
dismissal is just a state transition, not a thing the client creates.
Drop the dedicated endpoint and add a `status` enum (`active` |
`dismissed`) to the insights schema, settable via the existing PATCH
update route. The server still derives `dismissedAt` and `dismissedBy`
on transition for audit/sorting, but the client-facing API is just a
single status toggle.

- Schema: add `status` attribute (default `active`)
- Constants: add `INSIGHT_STATUSES`
- Update endpoint: accept `status` param, derive dismissedAt/By on
  active <-> dismissed transitions
- Response model: add `status` rule
- Drop Insights/Dismissal/Create.php, the createInsightDismissal SDK
  method, the `insights.[id].dismissals.create` event, and the
  `insight.dismissal.create` audit
- E2E: replace testCreateDismissal with testDismissViaUpdate covering
  both directions of the toggle

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:36:31 +12:00
Jake BarnbyandClaude Opus 4.7 cd539d972a refactor(insights): capitalise CTA acronym in identifiers
Project-specific override of the default camelCase-acronyms convention:
namespaces, class names, file paths, and SDK method names use `CTA` in
all caps. Touches all insights surfaces — directories, response models,
validators, container resource keys, and SDK method names like
`createInsightCTAExecution`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:27:45 +12:00
Jake BarnbyandClaude Opus 4.7 d188bd7a2e refactor(insights): register CTA registry on the global $register
Persist the insight CTA registry across requests by attaching it to the
boot-time global $register, mirroring the pattern used for `geodb`,
`passwordsDictionary`, `hooks`, etc. The container resource now looks
the registry up from `$register` instead of rebuilding it on every
request.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:22:10 +12:00
Jake Barnby 3c4aceea48 ci(insights): add Insights to e2e test matrix 2026-05-01 14:04:49 +12:00
Jake Barnby e0d5164af2 test(insights): split e2e into base trait and server/console overlays
Extract the existing test methods into InsightsBase trait and
provide thin Custom{Server,Console}Test classes that compose the
base with the appropriate scope/side traits. Updates the dismissal
and CTA execution tests to the new sub-resource paths.
2026-05-01 14:04:36 +12:00
Jake Barnby da5a137b98 refactor(insights): nest CTA execution and dismissal as sub-resources
Move POST /v1/insights/:id/dismiss to /v1/insights/:id/dismissals
and POST /v1/insights/:id/ctas/:ctaId/trigger to
/v1/insights/:id/ctas/:ctaId/executions, with the corresponding
class moves into Http/Insights/Dismissal/Create.php and
Http/CTA/Execution/Create.php. Rename the response model to
InsightCtaExecution and update events.php to surface dismissal
and execution as resource events with create verbs. The reshape
matches the rest of the API where verbs hang off plural sub-resources.
2026-05-01 14:04:31 +12:00
Jake Barnby d15be21923 refactor(insights): use Ctas validator on create endpoint
Replace the inline CTA shape check with a dedicated Utopia validator
so the constraint surfaces as a normal 400 with a useful message
rather than a generic argument-invalid exception.
2026-05-01 13:42:01 +12:00
Jake Barnby f5d6f6e27c refactor(insights): use Utopia Platform Action and Registry
Replace the bespoke CtaAction/Registry with Utopia\Platform\Action
and Utopia\Registry\Registry. Implement DatabasesCreateIndex with
the full createDocument('indexes') + queue path used by the existing
indexes endpoint, validated via a dedicated Utopia validator. Drop
the obsolete unit tests (custom-Action contract) in favor of
validator-focused tests.
2026-05-01 13:40:27 +12:00
Jake Barnby 8beae59454 fix(insights): schema corrections from review
- Add inline comments listing possible values for enum-bounded attributes
- Use VAR_ID for resourceInternalId
- Drop trailing 0 from composite index lengths and use $sequence over $createdAt
- Drop redundant ORDER_ASC values (default direction)
2026-05-01 13:30:23 +12:00
Jake BarnbyandClaude Opus 4.7 68dc974271 test(insights): unit and e2e tests
Unit tests cover the CTA registry register/resolve/has/all behaviour
and the DatabasesCreateIndex action's name, scope, validation surface,
and not-implemented execute path. The e2e suite runs the full CRUD
lifecycle, dismiss, and CTA trigger paths against a real cloud project,
including authentication boundaries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:40:59 +12:00
Jake BarnbyandClaude Opus 4.7 236e594195 feat(insights): add dismiss and CTA trigger endpoints
Dismiss is a convenience that stamps `dismissedAt` and `dismissedBy` so
analyzers can see an insight has been acknowledged without losing the
record. CTA trigger looks up the action in the runtime registry,
validates the params blob, executes the action, and returns the result
as an `InsightCtaResult`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:40:53 +12:00
Jake BarnbyandClaude Opus 4.7 5a8be81484 feat(insights): add CRUD endpoints
Adds the create, get, list, update, and delete endpoints under the
`insights` SDK namespace. Mutating endpoints are admin/key-only because
insights are produced by analyzers; reads are open to sessions and JWTs
so console UIs can surface them. Updates use sparse documents so unset
fields keep their existing value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:40:47 +12:00
Jake BarnbyandClaude Opus 4.7 7f7be46547 feat(insights): add module skeleton and registration
Adds the platform module so the http service is discoverable, registers
it on the Appwrite platform alongside the other modules, exposes the
`insights` and `insights.[insightId]` realtime channels via the messaging
adapter, and ships the queries validator covering the indexed attributes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:40:34 +12:00
Jake BarnbyandClaude Opus 4.7 e1ddcd051c feat(insights): add schema, scopes, events, errors, constants
Wires the platform glue for insights: the `insights` collection on the
project database, the `insights.read` / `insights.write` scopes, the
`insights.[insightId]` event tree (including the nested `ctas.[ctaId].trigger`
event), the typed exceptions, and the runtime CTA registry resource.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:40:28 +12:00
Jake BarnbyandClaude Opus 4.7 1c8cc6fc92 feat(insights): add response models
Adds the Insight, InsightCta, and InsightCtaResult response models and
registers their model identifiers on the Response class so endpoints can
serialise insights consistently across the SDK surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:40:22 +12:00
Jake BarnbyandClaude Opus 4.7 1889ccdd12 feat(insights): add CTA framework
Introduces the call-to-action framework: a server-side registry plus an
Action interface that lets analyzers offer one-click remediations alongside
each insight. Ships a stub `databases.createIndex` action whose execute()
defers to a future cloud-side implementation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:40:16 +12:00
Prem Palanisamy b79637eef5 Re-add onDuplicate param to modular migration endpoints
1.9.x reorganized app/controllers/api/migrations.php into the
Platform/Modules/Migrations structure but dropped the onDuplicate
param. After the merge, every e2e migration test that passed
'onDuplicate' => 'overwrite' would 400 since the param wasn't in the
allowlist anymore.

Restoring it on the three endpoints that take row-level conflict
behavior: Appwrite/Create, CSV/Imports/Create, JSON/Imports/Create.
Each:
  - Imports OnDuplicate + WhiteList.
  - Adds optional ->param('onDuplicate', OnDuplicate::Fail->value,
    new WhiteList(OnDuplicate::values()), …).
  - Threads $onDuplicate through the action signature.
  - Stores it on the migration document's 'options' attribute so
    Workers/Migrations.php can pick it up via
    OnDuplicate::tryFrom($options['onDuplicate'] ?? '')
    ?? OnDuplicate::Fail.

Worker code already reads options['onDuplicate'] (unchanged) — no
edits needed there.
2026-04-30 17:01:57 +01:00
Prem Palanisamy b1906e5eb9 Merge remote-tracking branch 'origin/1.9.x' into feat/skip-duplicates
# Conflicts:
#	app/controllers/api/migrations.php
#	composer.lock
2026-04-30 16:26:43 +01:00
ArnabChatterjee20k 8cef822e8f Refactor presence management to use userInternalId instead of userId for improved consistency and clarity. Updated relevant methods in PresenceState and Upsert classes to reflect this change, ensuring proper handling of user identifiers across the application. 2026-04-30 18:50:46 +05:30
ArnabChatterjee20k 6dae78a206 linting 2026-04-30 18:48:54 +05:30
ArnabChatterjee20k 18ae81bbb0 Refactor presence management by introducing caching mechanisms for list responses. Added methods for loading, saving, and purging cache fields in the PresenceState class. Updated API endpoints to utilize caching, including purge functionality in Update and Delete actions. Enhanced tests to verify cache behavior during presence updates and deletions. 2026-04-30 18:33:20 +05:30
ArnabChatterjee20k e03218d8fa reverted last change 2026-04-30 18:24:38 +05:30
ArnabChatterjee20k 4f030619da temp gitignore 2026-04-30 18:03:25 +05:30
ArnabChatterjee20k e267dcf9ab updated 2026-04-30 18:01:51 +05:30
ArnabChatterjee20k 9c1cbecda4 bypassed sdk example? 2026-04-30 17:58:39 +05:30
ArnabChatterjee20k 8597d48e41 added additonal property key to rename on the sdk generator 2026-04-30 17:47:31 +05:30
ArnabChatterjee20k 971858078c updated 2026-04-30 16:50:04 +05:30
premtsd-codeandGitHub e14db61e30 Merge branch '1.9.x' into backup-migration-multitype 2026-04-30 16:42:11 +05:30
ArnabChatterjee20k 88bbb965f8 Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-04-30 16:41:48 +05:30
ArnabChatterjee20k 5510bc4f6a Update userInternalId attribute type to VAR_ID and remove lengths from index definition for consistency. 2026-04-30 16:38:26 +05:30
Matej BačoandGitHub 08ad7d7f71 Merge pull request #12192 from appwrite/fix-oauth-bugs
Fix: OAuth UX with required params
2026-04-30 13:07:52 +02:00
Prem Palanisamy 5ffb7c82e7 Merge 1.9.x into backup-migration-multitype 2026-04-30 11:55:01 +01:00
ArnabChatterjee20k d802c57cb9 Refactor presence management by introducing a new 'presenceLogs' collection and updating related API methods. Added error handling for presence not found scenarios and standardized parameter names across the Update and XList actions. Removed obsolete migration version 25 and adjusted migration mapping for version 1.9.4. 2026-04-30 16:22:28 +05:30
Matej Bačo 71300383b2 Update tests 2026-04-30 12:48:59 +02:00
Prem Palanisamy d4e32af792 Migrate appwrite to OnDuplicate::Overwrite ('overwrite')
Maintainer review on utopia-php/migration#171 renamed
OnDuplicate::Upsert -> OnDuplicate::Overwrite (value 'upsert' ->
'overwrite') to align with Appwrite terms (skip / overwrite / fail).
Applying the cross-repo ripple here:

- app/controllers/api/migrations.php: 3 endpoint param descriptions
  updated ('upsert' -> 'overwrite' in the help text). The validator
  still uses OnDuplicate::values() so it auto-picks up the new value.
- tests/e2e/Services/Migrations/MigrationsBase.php: all
  'onDuplicate' => 'upsert' -> 'overwrite'; method names
  testAppwriteMigrationUpsert* -> testAppwriteMigrationOverwrite*;
  comments / assertion messages / local var names switched.
- Left untouched: utopia's upsertDocuments operation, transaction
  TransactionState 'upsert' action, Operation validator — those refer
  to the database-level upsert primitive, not the OnDuplicate enum.

composer.lock: utopia-php/migration 7d71505 -> b8ae7bc.
2026-04-30 11:45:33 +01:00
Matej Bačo 8785aa9877 Fix nullable implementation oauth 2026-04-30 12:41:55 +02:00
Matej Bačo 62b7d5558f Mark params nonrequired 2026-04-30 12:36:45 +02:00
ArnabChatterjee20k 32dc870eaf Refactor presence API functions and update expiry test to improve clarity and maintainability. Renamed 'triggerPresenceEvent' to 'getQueueForEventsForProject' and adjusted expiry handling in tests for better validation. 2026-04-30 15:56:31 +05:30
Shmuel FogelandGitHub fbbab0f7e1 Merge pull request #12181 from appwrite/set-global-collection
Set global collection
2026-04-30 12:49:00 +03:00
Matej BačoandGitHub 86b9599a57 Merge pull request #12191 from appwrite/oauth-quality-improvements
Fix: oauth order; Fix apple secreting too much
2026-04-30 11:17:51 +02:00
Luke B. SilverandGitHub 526b390c15 Merge pull request #12189 from FerRubioMorales/fix/spanish-session-alert-translations
fix(locale): add Spanish session alert translations
2026-04-30 10:08:02 +01:00
Matej Bačo b73ba68bfb Fix oauth order; Fix apple secreting too much 2026-04-30 10:21:38 +02:00
ArnabChatterjee20k 2c06e4185f Update presence expiry datetime validation to enforce a 30-day maximum limit. This change standardizes the 'expiresAt' parameter across the Update and Upsert actions, ensuring consistent handling of presence expiry across the API. 2026-04-30 13:30:25 +05:30
ArnabChatterjee20k 869c35416d Refactor presence API to standardize scope and attribute names. Updated 'presence' to 'presences' across roles, scopes, and HTTP methods for consistency. Renamed 'expiry' to 'expiresAt' in various locations to improve clarity and maintainability. 2026-04-30 13:30:15 +05:30
fogelito d98bd8c972 Remove line 2026-04-30 10:44:21 +03:00
fogelito 81c580bf50 Merge branch '1.9.x' of https://github.com/appwrite/appwrite into set-global-collection 2026-04-30 10:42:09 +03:00
fogelito c0bba74eee set setGlobalCollections logs 2026-04-30 10:36:12 +03:00
ArnabChatterjee20k e6c3acb21c Refactor presence API by removing the Action class and updating related classes to extend PlatformAction. This change enhances consistency across the API methods and improves code maintainability. 2026-04-30 13:04:28 +05:30
frubio 337d47b1d9 fix(locale): add Spanish session alert translations 2026-04-30 09:33:08 +02:00
harsh mahajan e7405b4393 addressed comments 2026-04-30 13:01:00 +05:30
Harsh MahajanandGitHub 3eabca1d7a Merge branch '1.9.x' into feat-ser-401-custom-triggers 2026-04-30 12:10:16 +05:30
Chirag AggarwalandGitHub 8ffe48d948 Merge pull request #12179 from appwrite/fix/project-delete-platform-cleanup 2026-04-30 09:43:37 +05:30
Chirag Aggarwal 4050b9ded1 Continue project cleanup after resource failures 2026-04-30 09:28:22 +05:30
fogelito d099167d18 Shared env 2026-04-29 17:09:25 +03:00
fogelito 9d3255f5cd Update lock 2026-04-29 17:08:14 +03:00
fogelito f03cc847f8 Merge branch '1.9.x' of https://github.com/appwrite/appwrite into set-global-collection
# Conflicts:
#	composer.lock
2026-04-29 17:07:32 +03:00
Torsten DittmannandGitHub 6088fd55c8 Merge pull request #12138 from appwrite/feat-out-of-order-chunk-uploads 2026-04-29 18:04:57 +04:00
Matej BačoandGitHub 1aeee8c407 Merge pull request #12178 from appwrite/fix-developer-experience
Fix: Developer experiene with recent chagnes
2026-04-29 15:56:06 +02:00
adityaoberai 7648978cfa Add codex plugin to SDKs 2026-04-29 19:22:46 +05:30
fogelito 18b9769672 lock file 2026-04-29 16:14:21 +03:00
fogelito 8eed06678b formatting 2026-04-29 15:54:05 +03:00
fogelito 7a9a2899ff setGlobalCollections 2026-04-29 15:41:56 +03:00
Matej Bačo 36486ccc93 Fix tests 2026-04-29 14:41:19 +02:00
Chirag Aggarwal 794d8eac5b Fix project delete platform cleanup ordering 2026-04-29 17:55:06 +05:30
Matej Bačo 32ebfc6cb8 Fix backwards compatibility 2026-04-29 14:14:49 +02:00
Matej Bačo e1b8f5bf98 review improvements 2026-04-29 14:04:54 +02:00
Matej Bačo 4d86e67006 Fix missing scopes for tables 2026-04-29 14:03:44 +02:00
Matej Bačo b3e3b2a330 Fix missing index scopes 2026-04-29 14:00:14 +02:00
Matej Bačo e010bf25d5 Fix formatting 2026-04-29 13:57:16 +02:00
Harsh MahajanandGitHub ec984de510 Merge branch '1.9.x' into feat-ser-401-custom-triggers 2026-04-29 17:23:08 +05:30
Matej Bačo aaf91f3816 Improve scopes quality 2026-04-29 13:52:13 +02:00
Matej Bačo bae61e8a05 Improve developer experience of keys endpoints 2026-04-29 13:13:13 +02:00
Torsten DittmannandGitHub dfbf45f4cc Merge branch '1.9.x' into feat-out-of-order-chunk-uploads 2026-04-29 15:03:33 +04:00
Chirag AggarwalandGitHub c264db6146 Merge pull request #12177 from appwrite/codex/fix-1-9-trivy-sarif-categories 2026-04-29 16:13:30 +05:30
Chirag Aggarwal cccafeff0c Add nightly SARIF upload guards 2026-04-29 16:02:41 +05:30
Chirag Aggarwal 360d08f087 Preserve CI image for job retries 2026-04-29 16:01:15 +05:30
Chirag AggarwalandGitHub d8e97ae47d Merge pull request #12174 from appwrite/bump-docker-base-1.2.0 2026-04-29 15:59:24 +05:30
Chirag Aggarwal d13e6d75f0 Fix Trivy SARIF categories on nightly scan 2026-04-29 15:58:59 +05:30
Chirag Aggarwal 4447396859 Update base image to 1.2.1 2026-04-29 15:37:59 +05:30
Chirag AggarwalandGitHub 4d1f229ec7 Merge branch '1.9.x' into bump-docker-base-1.2.0 2026-04-29 15:32:32 +05:30
Chirag AggarwalandGitHub 4f733f5984 Merge pull request #12176 from appwrite/ci-ghcr-image-share 2026-04-29 15:32:22 +05:30
Chirag Aggarwal 701f557755 ci: clean up GHCR CI image after pipeline finishes
Every CI run pushes ghcr.io/<repo>/appwrite-dev:<sha> and nothing
removes it. On an active repo with many PRs the GHCR storage grows
without bound. Add a cleanup job that runs after all consumer jobs
complete (always, even if some fail) and deletes the SHA-tagged
package version via the Packages API.

Addresses Greptile feedback on appwrite/appwrite#12176.
2026-04-29 15:23:05 +05:30
Jake BarnbyandGitHub 8ab26aab44 Merge pull request #12171 from appwrite/migration-refractor
Refactor migrations API to module style
2026-04-29 21:44:19 +12:00
Chirag Aggarwal ec3aa2b54f ci: share docker image via GHCR instead of upload-artifact
The build job uploads the appwrite-dev image as an actions artifact
(~hundreds of MB), and 30+ E2E test jobs all pull it concurrently with
actions/download-artifact. GitHub Actions' artifact storage struggles
with that many parallel downloads and intermittently fails with
BlobNotFound or 'Artifact download failed after 5 retries'.

Push the built image to ghcr.io/<repo>/appwrite-dev:<sha> in the build
job and pull from GHCR in each test job. GHCR handles parallel image
fetches without throttling.

Mirrors appwrite-labs/cloud#3906.
2026-04-29 15:09:39 +05:30
Chirag AggarwalandGitHub 2d636ff7ec Merge branch '1.9.x' into bump-docker-base-1.2.0 2026-04-29 15:06:03 +05:30
ArnabChatterjee20k 9d6be1eef4 updated 2026-04-29 15:04:28 +05:30
ArnabChatterjee20k 06a05311a4 Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-04-29 15:04:20 +05:30
Matej BačoandGitHub fd42b8fa64 Merge pull request #12175 from appwrite/feat-console-key-scopes-endpoint
Feat: Console key scopes endpoint
2026-04-29 11:17:49 +02:00
Chirag Aggarwal 9d7df34590 fix: clean up php 8.5 runtime deprecations 2026-04-29 14:47:05 +05:30
ArnabChatterjee20k 58c248f4f2 Add migration for version 25 to ensure presenceLogs collection exists; update migration mapping for version 1.9.3. Remove redundant error assertion in PresenceTest. 2026-04-29 14:32:57 +05:30
ArnabChatterjee20k 59ee0901c9 Enhance presence API methods by adding detailed parameter specifications for update and upsert actions. Separate client-side and server-side SDK method definitions to clarify authentication requirements and improve usability. This update includes new parameters for presence management, ensuring better handling of user presence logs. 2026-04-29 14:20:38 +05:30
harsh mahajan 2d3c1086ae add endpoints 2026-04-29 13:50:40 +05:30
ArnabChatterjee20k e624040e57 Refactor presence API methods for clarity and consistency. Updated method names to include 'Presence' suffix for better identification. Enhanced presence state logic to support unique index-based upserts and improved test coverage for presence functionalities, including custom permissions and expiry handling. 2026-04-29 13:49:57 +05:30
Matej Bačo e75fc5b859 Add list scopes endpoint for Console 2026-04-29 10:08:31 +02:00
Jake BarnbyandGitHub 57b8305144 Merge pull request #12134 from appwrite/fix-realtime-span-exporter
added a guard to skip double import
2026-04-29 20:02:04 +12:00
Chirag AggarwalandGitHub a429fb5860 Merge branch '1.9.x' into bump-docker-base-1.2.0 2026-04-29 13:30:50 +05:30
Matej BačoandGitHub aca11ed073 Merge pull request #12170 from appwrite/feat-create-dynamic-keys
Feat: create dynamic keys
2026-04-29 09:58:22 +02:00
Chirag Aggarwal 86123c9e93 fix: update PHP extension path for xdebug cleanup in production 2026-04-29 13:27:04 +05:30
Chirag Aggarwal a58ea1123b chore: bump docker-base to 1.2.0 2026-04-29 13:21:17 +05:30
ArnabChatterjee20k 496b91480b updated 2026-04-29 11:33:48 +05:30
ArnabChatterjee20k b08f3bdc52 Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-04-29 11:29:59 +05:30
Harsh MahajanandGitHub 9c646c2893 Merge branch '1.9.x' into feat-ser-401-custom-triggers 2026-04-29 11:25:05 +05:30
ArnabChatterjee20kandGitHub dae9cbcf45 Merge pull request #12070 from appwrite/realtime-action-channels
Realtime action channels
2026-04-29 10:49:13 +05:30
Jake Barnby 77942af4ea Merge remote-tracking branch 'origin/1.9.x' into feat-dedicated-db 2026-04-29 13:53:52 +12:00
Matej Bačo 05f2d2b9cf Fix tests 2026-04-28 19:29:37 +02:00
Matej Bačo c1f61b22aa Merge branch '1.9.x' into feat-create-dynamic-keys 2026-04-28 17:18:36 +02:00
Matej Bačo 980762fc3e Rename from dynamic key to ephemeral key (api keys) 2026-04-28 17:18:06 +02:00
Matej Bačo c96836b1c0 Improve code quality of folder decoding project ID 2026-04-28 17:10:58 +02:00
Matej Bačo 15917ac7ba Fix failing tests 2026-04-28 17:05:30 +02:00
premtsd-codeandGitHub cd851bff24 Merge branch '1.9.x' into migration-refractor 2026-04-28 20:32:54 +05:30
Prem Palanisamy 3f5dcc81fd Refactor migrations API to module style 2026-04-28 15:57:41 +01:00
Matej Bačo f5a732d231 Add dynami key integration test 2026-04-28 16:47:39 +02:00
Matej Bačo 72dfd8a7bc Add E2E tests for dynamic keys 2026-04-28 16:45:00 +02:00
Matej Bačo 11f80fc2ed Solve key projectId backwards compatibility 2026-04-28 16:35:40 +02:00
Harsh MahajanandGitHub 547709a1d8 Merge pull request #12167 from appwrite/feat/impersonation-query-params
feat: add query param fallback for impersonation headers
2026-04-28 19:51:23 +05:30
Matej Bačo ccb0ddd578 Bug&test fixing 2026-04-28 16:18:36 +02:00
Matej Bačo b2ce95a0cd Dynamic key backwards compatibility 2026-04-28 16:14:10 +02:00
Matej Bačo ed9b47f6ce Migrate project jwt to dynamic api key 2026-04-28 15:57:37 +02:00
harsh mahajan 2a357511ea fix: use unique emails and phone in query param impersonation test 2026-04-28 19:17:25 +05:30
Harsh MahajanandGitHub 67d24d3ef1 Merge branch '1.9.x' into feat/impersonation-query-params 2026-04-28 19:11:14 +05:30
harsh mahajan 87ed7c3817 feat: add query param fallback for all impersonation params and simplify tests 2026-04-28 19:10:55 +05:30
Matej Bačo 8f176166c9 Re-introduce project JWT endpoint 2026-04-28 15:31:10 +02:00
Torsten Dittmann a0ef145b92 Merge branch '1.9.x' of https://github.com/appwrite/appwrite into feat-out-of-order-chunk-uploads 2026-04-28 17:10:56 +04:00
ArnabChatterjee20k 41b2962e64 updated usage name 2026-04-28 18:03:26 +05:30
ArnabChatterjee20k 25ae2e7314 Implement triggerStats function, add GraphQL source header, and enhance presence upsert logic with source detection. Also, introduce PresenceTest for GraphQL presence upsert validation. 2026-04-28 17:48:23 +05:30
ArnabChatterjee20k 8d378720b0 add presence API metrics and usage tracking 2026-04-28 17:19:24 +05:30
ArnabChatterjee20k 34f782d986 updated roles for the admin and members users 2026-04-28 17:16:24 +05:30
Matej BačoandGitHub 3d3f5934c6 Merge pull request #11993 from appwrite/feat-public-oauth2-endpoints
Feat: Public project OAuth2 configuration API
2026-04-28 12:41:50 +02:00
Torsten Dittmann 9e1f8af103 fix: persist sourceChunksTotal/Uploaded in finalization createDocument paths
Greptile review: Functions and Sites finalization branches reached via
single-chunk uploads or out-of-order last-chunk assembly omitted
sourceChunksTotal and sourceChunksUploaded in createDocument. This caused
the retry guard to evaluate 0 === 1 on retry, missing and queuing duplicate
builds.
2026-04-28 13:44:41 +04:00
Torsten DittmannandGitHub b055ff1066 Merge branch '1.9.x' into feat-out-of-order-chunk-uploads 2026-04-28 13:19:08 +04:00
harsh mahajan f0cbfbbbe4 fix: use assertEmpty for impersonatorUserId to match response model 2026-04-28 14:31:49 +05:30
Matej Bačo cb4cff120b Add Keycloak oauth support 2026-04-28 10:54:13 +02:00
Matej Bačo 49e6a38e7f Add fusionauth oauth 2026-04-28 10:43:16 +02:00
ArnabChatterjee20k 587a039493 fixed syntax 2026-04-28 13:57:31 +05:30
ArnabChatterjee20k 93ee8f45ea updated scopes 2026-04-28 13:50:11 +05:30
Matej Bačo dfa3ae5274 Fix tests 2026-04-28 10:19:36 +02:00
Matej Bačo 543765a22a Improve copy 2026-04-28 10:15:45 +02:00
Matej Bačo e2bb9a9161 Simplify oauth endpoints 2026-04-28 10:08:39 +02:00
harsh mahajan bda823ac0e chore: format 2026-04-28 13:38:00 +05:30
harsh mahajan 3dd5a51ba4 style: fix method argument spacing (Pint PSR-12) 2026-04-28 13:34:01 +05:30
harsh mahajan 5afc8f462d fix: allow same-site in CSRF guard to support Console on subdomains 2026-04-28 13:26:13 +05:30
harsh mahajan ed0c7b4e12 test: add CSRF attack prevention test for impersonateUserId query param 2026-04-28 13:24:15 +05:30
Matej Bačo d25707346f Add console oauth endpoint 2026-04-28 09:47:27 +02:00
harsh mahajan a3f6cf4645 fix: restrict CSRF guard to same-origin only, drop same-site 2026-04-28 13:00:18 +05:30
harsh mahajan 9a175c5098 test: add E2E tests for impersonateUserId query param and CSRF guards 2026-04-28 12:56:17 +05:30
Prem Palanisamy 8f32d01686 composer: bump utopia-php/migration to 7d71505 (createIndex pre-check before count/validator) 2026-04-28 08:10:39 +01:00
harsh mahajan 5465be6301 fix: make CSRF guard fail-closed by requiring explicit same-origin Sec-Fetch-Site 2026-04-28 12:27:57 +05:30
Prem Palanisamy e1209614c5 composer: bump utopia-php/migration to d37efed (track partner key for orphan cleanup) 2026-04-28 07:56:14 +01:00
ArnabChatterjee20k 5157da870f add presence specific read and write scope instead of depending on the users read and write scope 2026-04-28 12:19:03 +05:30
harsh mahajan 46a457bfa3 fix: block impersonateUserId query param on cross-site requests to prevent CSRF 2026-04-28 12:10:51 +05:30
harsh mahajan 4c989f99c3 fix: cast impersonateUserId query param to string to prevent array injection 2026-04-28 12:05:02 +05:30
harsh mahajan 8f1d73a6cb chore: clarify intentional header-only restriction for email/phone impersonation 2026-04-28 12:02:00 +05:30
harsh mahajan 01b5fa8ecb fix: restrict impersonation query param fallback to userId only
Remove query param fallback for impersonateEmail and impersonatePhone
to avoid PII exposure in server logs, browser history, and Referer
headers. Only impersonateUserId (an opaque internal ID) is safe to
pass via URL query param.
2026-04-28 11:58:25 +05:30
harsh mahajan d73b7a70d8 feat: add query param fallback for impersonation headers
Allow impersonation to be specified via URL query params
(?impersonateUserId, ?impersonateEmail, ?impersonatePhone) as a
fallback to the existing headers, enabling Console to embed
impersonation in direct file/image URLs where headers cannot be set.
2026-04-28 11:44:39 +05:30
Prem Palanisamy 40aa8cb1bd composer: bump utopia-php/migration to 4c3965b (tighten dropAttributeForRecreate) 2026-04-28 07:11:23 +01:00
ArnabChatterjee20k 6fabfe08ce Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-04-28 11:40:58 +05:30
Prem Palanisamy 4ae4531355 composer: bump utopia-php/migration to aa1e7c7 (drop+recreate fixes) 2026-04-28 07:06:25 +01:00
ArnabChatterjee20k f71a2dfddc changed the condition to app edition for the loading of the span 2026-04-28 11:07:16 +05:30
Damodar LohaniandGitHub cefd063c55 Merge pull request #12165 from appwrite/fix/CLO-4280-getheader-string-coerce
fix: coerce non-string header values in Request::getHeader
2026-04-28 10:43:40 +05:45
Prem Palanisamy 443f5cfb0e tests: switch attribute-recreate test to non-SDK change
After dropping createdAt from resolveSchemaAction, source-side recreate
no longer routes through DropAndRecreate via the outer decision. The
inner fallthrough still drops+recreates when the spec diff is a non-SDK
change, so this test now toggles 'array' (a non-SDK field) on recreate
to actually exercise the drop+recreate path it pins.

Also clarifies the two-way recreate test's docblock — with createdAt
gone and identical spec on recreate, it exercises spec-match + pair-key
dedup (both tolerate paths) rather than parent-side drop. End-state
assertions unchanged.
2026-04-28 05:55:38 +01:00
Damodar LohaniandGitHub c924cbcc59 Merge pull request #12166 from appwrite/fix/CLO-4279-favicon-empty-body
fix: guard DOMDocument::loadHTML against empty body in favicon endpoint
2026-04-28 10:32:32 +05:45
Prem Palanisamy 5a928f2c0f composer: bump utopia-php/migration to 6ec2c45 (drop createdAt) 2026-04-28 05:43:43 +01:00
Damodar LohaniGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
81321e82d1 Update src/Appwrite/Platform/Modules/Avatars/Http/Favicon/Get.php
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-28 10:05:01 +05:45
Prem Palanisamy f5730e8eed composer: bump utopia-php/migration to 36a1acf (spec-match order fix) 2026-04-28 05:16:46 +01:00
Damodar Lohani 30a511692b test: add unit coverage for Request::getHeader non-string coercion
Refs CLO-4280
2026-04-28 04:15:00 +00:00
Damodar Lohani 9637409831 fix: coerce non-string header values in Request::getHeader
Closes CLO-4280
2026-04-28 03:54:35 +00:00
Damodar Lohani c4f6b11706 fix: guard DOMDocument::loadHTML against empty body in favicon endpoint
Closes CLO-4279
2026-04-28 03:54:34 +00:00
Prem Palanisamy b6afef6efc tests: trim verbose multi-paragraph docblocks to one-liners 2026-04-28 04:44:19 +01:00
Prem Palanisamy 3fba7afd2e tests: pin spec-match guard + bump migration to c8d1789
testAppwriteMigrationUpsertSameSpecRecreateTolerates exercises the new
spec-match guard added in utopia-php/migration c8d1789. Source drops +
recreates a column with the EXACT same spec as before; createdAt
advances but specs match → action is forced to Tolerate. Asserts dest
column's $createdAt stays at first-migration value (proving Tolerate,
not DropAndRecreate). Row pass under Upsert still propagates source's
new row value.

Companion to testAppwriteMigrationUpsertAttributeRecreateDropsAndRecreates
which exercises the spec-DIFFERS path: same precondition (drop + recreate),
different outcome (DropAndRecreate vs Tolerate) gated on spec equality.

composer.lock: utopia-php/migration 24fd23b -> c8d1789 (spec-match guard).
2026-04-28 04:16:51 +01:00
Prem Palanisamy ac46ff802a composer: bump utopia-php/migration to 24fd23b (SDK-boundary lock-in test) 2026-04-28 03:45:25 +01:00
Prem Palanisamy 8cccc79935 composer: bump utopia-php/migration to 47933c1 (inline TwoWayPartner) 2026-04-28 03:31:25 +01:00
Prem Palanisamy b03c901fa7 tests: cover one-way DropAndRecreate + attribute-recreate scenarios
Two coverage gaps closed:

- testAppwriteMigrationUpsertOneWayRelationshipDropAndRecreate exercises
  the path that updateRelationshipInPlace gates off: one-way + onDelete
  change → returns false → falls through to DropAndRecreate via
  deleteRelationship. Coverage was lost when
  testAppwriteMigrationUpsertUpdatesRelationshipOnDeleteInPlace was
  converted to two-way to actually hit the in-place path.

- testAppwriteMigrationUpsertAttributeRecreateDropsAndRecreates pins
  the createdAt-different leaf path: source drops + recreates the
  attribute (createdAt advances), re-migration must DropAndRecreate
  on dest and re-flow the row data through the row pass. Companion to
  testAppwriteMigrationUpsertUpdatesAttributeInPlace which covers the
  same-createdAt + newer-updatedAt path.

Migration package already at 09c1b21 (the maintainability commit) from
the previous lock bump — no further composer.lock change needed.
2026-04-27 17:40:23 +01:00
Matej Bačo ad4178aa42 Fix missing lib params for domain 2026-04-27 18:33:30 +02:00
Prem Palanisamy 7b32fd0196 composer: bump utopia-php/migration to 09c1b21 (maintainability pass) 2026-04-27 17:32:14 +01:00
Matej Bačo 1f16b0d9e7 Fix failing startup 2026-04-27 18:21:21 +02:00
Matej Bačo 015aee087a Fix write only security 2026-04-27 18:04:22 +02:00
Matej Bačo 50d86c5b5d Update ci.yml 2026-04-27 17:45:52 +02:00
Matej Bačo 3d43530225 Fix failing test 2026-04-27 17:41:13 +02:00
Matej Bačo d0d536a2dd Improve test coverage 2026-04-27 17:40:49 +02:00
Matej Bačo 4b620bb31a Improve test coverage 2026-04-27 17:27:23 +02:00
Matej Bačo ca7f36a9b8 Fix bugs by improving tests 2026-04-27 17:17:57 +02:00
Matej Bačo ec3c7f1ad6 Fix failing oauth tests 2026-04-27 17:02:53 +02:00
Matej Bačo ecba11eba5 Brin back removed tests 2026-04-27 16:54:53 +02:00
Matej Bačo 7a96b024b3 Fix tests 2026-04-27 16:51:01 +02:00
Matej Bačo 4ba413fcc0 Fix bugs when implementing tests 2026-04-27 16:50:14 +02:00
Matej Bačo af95e71244 Add OAUth update tests 2026-04-27 16:02:19 +02:00
Matej Bačo ee1eea5c0c oauth tests setup 2026-04-27 15:51:54 +02:00
Matej Bačo b28b851bb2 microsoft oauth endpoint 2026-04-27 15:49:44 +02:00
Torsten Dittmann 2f2da98cca fix: adjust out-of-order test expectations and chunk sizes
- Functions/Sites: lower minimum chunk requirement from 3 to 2
- Sites: use random_bytes instead of str_repeat for non-compressible test data
- Remove assertions on sourceChunksTotal/Uploaded from response body (not in response model)
2026-04-27 17:46:20 +04:00
Prem Palanisamy c80c747e48 tests: pin two-way recreate partner-side dedup + bump migration
testAppwriteMigrationUpsertTwoWayRecreateSkipsPartnerSide exercises
the DropAndRecreate path on a two-way relationship that the partner-
side pair-key dedup guards. Source recreates the relationship between
runs, forcing parent-side createdAt diff. Test asserts the migration
completes cleanly and partner-table rows survive — without dedup, the
partner pass re-fires DropAndRecreate and destroys those rows.

composer.lock: utopia-php/migration c76de9a -> c13e77d (partner-side
pair-key dedup restored).
2026-04-27 14:45:39 +01:00
Torsten Dittmann 54997638e8 fix: persist sourceChunksUploaded on finalization and avoid variable shadowing
- Functions/Sites: include sourceChunksUploaded in updateDocument when finalizing existing deployments, fixing the retry guard
- Functions test: rename loop variable to avoid shadowing setup result
2026-04-27 17:24:51 +04:00
ArnabChatterjee20k 70b9c60e2c test(Messaging): validate that bare functions channel is not emitted in published channels 2026-04-27 18:46:04 +05:30
Torsten Dittmann 49d2db65e6 feat: support out-of-order chunked uploads
- Add APP_LIMIT_UPLOAD_CHUNK_SIZE constant (5MB) matching official SDKs
- Replace dynamic chunk calculation with fixed 5MB chunk math in all upload endpoints
- Remove -1 last-chunk sentinel that broke when last chunk arrived first
- Fix duplicate-retry guards: return existing resource instead of erroring for chunked uploads
- Add out-of-order e2e tests for Storage, Functions, and Sites
- Upgrade utopia-php/storage to 2.0.0 for device-level out-of-order assembly support
2026-04-27 17:15:00 +04:00
ArnabChatterjee20k 1fdcca9592 added a guard to skip double import 2026-04-27 18:33:47 +05:30
Prem Palanisamy dfde1be035 tests: cover two-way relationship onDelete in-place update + bump migration
The previous version of this test created a one-way relationship,
which falls through to DropAndRecreate (one-way + onDelete change is
gated off in updateRelationshipInPlace because utopia's
updateRelationship partner-cascade throws on one-way). It never
exercised the in-place path it was named for.

Converted to two-way (parents.kids ↔ children.parent), and asserted
both parent- and partner-side onDelete on dest. Partner-side
assertion is the regression guard for the partner-meta refresh that
was missing from updateRelationshipInPlace.

composer.lock: utopia-php/migration a36d95f -> c76de9a (partner-side
onDelete sync fix).
2026-04-27 14:03:02 +01:00
ArnabChatterjee20k cb8640b56f feat(Realtime): enhance channel management for user authentication and account actions 2026-04-27 18:24:52 +05:30
Matej Bačo a781325679 Add oauth read operations 2026-04-27 14:47:47 +02:00
ArnabChatterjee20k 3f12062259 updated 2026-04-27 17:54:48 +05:30
Matej Bačo 2e57500d7e WIP: Read endpoints for oauth 2026-04-27 14:16:43 +02:00
Matej Bačo a1a88ae57e Make oauth secret write only 2026-04-27 14:09:24 +02:00
ArnabChatterjee20k 9553f8a9f8 refactor(MessagingTest): update method visibility and naming conventions for consistency 2026-04-27 17:35:56 +05:30
Matej Bačo 15f94d99ca Add Kick OAuth adapter 2026-04-27 14:02:30 +02:00
ArnabChatterjee20k ca105ff9bc feat(Realtime): implement rebindAccountChannels method for userId changes and add corresponding tests 2026-04-27 17:31:31 +05:30
ArnabChatterjee20k 7e3114d733 linting 2026-04-27 17:26:27 +05:30
ArnabChatterjee20k ef4b9c4934 updated 2026-04-27 17:16:17 +05:30
Matej Bačo 2e960b90df Fix unused env variable 2026-04-27 13:38:26 +02:00
Prem Palanisamy 9be0ed8af0 Merge remote-tracking branch 'origin/feat/skip-duplicates' into feat/skip-duplicates 2026-04-27 12:35:35 +01:00
Prem Palanisamy 5f8a32798d tests: SDK-aligned UpdateInPlace coverage + bump migration to a36d95f
Three new e2e tests in MigrationsBase covering the schema reconciliation
paths added in utopia-php/migration:

- testAppwriteMigrationUpsertUpdatesAttributeInPlace: PATCH source
  required/default (SDK-reachable), assert dest reflects change and the
  pre-existing row's column data is preserved (drop+recreate would have
  wiped it).
- testAppwriteMigrationSkipPreservesAttributeDrift: leaf-level analog
  of the existing container-drift Skip test — guards Skip from ever
  consulting timestamps.
- testAppwriteMigrationUpsertUpdatesRelationshipOnDeleteInPlace: PATCH
  source onDelete cascade->restrict (SDK-reachable), assert dest
  reflects change and structural fields (relationType, twoWay) untouched.

composer.lock: utopia-php/migration 6e6f825 -> a36d95f (mechanical
helpers replacement, parseTimestamp dedup, match dispatch, comment trim).
2026-04-27 12:32:37 +01:00
ArnabChatterjee20k 1928605bd9 linting 2026-04-27 16:42:45 +05:30
ArnabChatterjee20k 340ce9d56b Add tests for channel conversion and event handling in Messaging
- Implement `test_convert_channels_rewrites_account_action_suffixes` to ensure
  that account action suffixes are correctly rewritten to user-scoped channels.
- Add `test_convert_channels_drops_account_actions_for_guest` to verify that
  account actions are dropped for guests without a user ID.
- Introduce `test_from_payload_does_not_suffix_account_for_nested_user_events`
  to confirm that nested user events do not leak action suffixes onto account channels.
2026-04-27 16:40:15 +05:30
ArnabChatterjee20k e6d5c216eb refactor(Realtime): update action extraction logic and enhance test method naming conventions 2026-04-27 16:06:00 +05:30
ArnabChatterjee20k d25ccb784d refactor(Realtime): remove SUPPORTED_ACTIONS constant and simplify action extraction logic 2026-04-27 15:59:34 +05:30
Matej Bačo 8ce7aa2abe Fix crashing http 2026-04-27 12:27:52 +02:00
ArnabChatterjee20k 78715e4a1a refactor(tests): rename test methods to snake_case and update assertions for action channels
- Changed test method names from camelCase to snake_case for consistency.
- Updated assertions to ensure action channels are correctly emitted and filtered.
- Improved readability and maintainability of the test suite by restructuring test cases.
2026-04-27 15:46:02 +05:30
Harsh MahajanandGitHub 1eb7bf7619 Merge branch '1.9.x' into feat-ser-401-custom-triggers 2026-04-27 15:14:58 +05:30
premtsd-codeandGitHub 557fef2c7b Merge branch '1.9.x' into feat/skip-duplicates 2026-04-27 15:12:17 +05:30
ArnabChatterjee20k df57ee2a32 added unit test 2026-04-27 13:43:23 +05:30
ArnabChatterjee20k 6d4a66fbb3 Enhance Realtime adapter to support action-channel awareness in subscriber checks and add corresponding tests 2026-04-27 13:30:18 +05:30
ArnabChatterjee20k 3aee54747c Enhance Realtime adapter to support delete action and add corresponding tests 2026-04-27 13:15:04 +05:30
ArnabChatterjee20k d2423a5bb5 added tests 2026-04-27 12:57:35 +05:30
ArnabChatterjee20k c0c053ff20 Enhance Realtime adapter with action channel support and tests
- Introduced ACTION_ALL and SUPPORTED_ACTIONS constants for better action handling.
- Updated channel subscription logic to support action suffixes.
- Added tests for action channel parsing and filtering in MessagingTest.
2026-04-27 12:52:52 +05:30
Prem Palanisamy ece468d1f7 Merge remote-tracking branch 'origin/1.9.x' into feat/skip-duplicates 2026-04-27 08:11:56 +01:00
Matej Bačo e4bfb38a57 add okta provider 2026-04-26 11:14:50 +02:00
Matej Bačo 0a7b7de197 Revert changes - default works as fallback for optional serverID 2026-04-26 10:59:29 +02:00
Matej Bačo 51c0767be2 Make okta server ID optional 2026-04-26 10:56:41 +02:00
Matej Bačo d25dac7d60 Manual quality improvmenets 2026-04-26 10:29:41 +02:00
Matej BačoandGitHub 1f18e16310 Merge branch '1.9.x' into feat-public-oauth2-endpoints 2026-04-25 12:45:34 +02:00
Matej Bačo d0f6daa67a Fix integration test 2026-04-25 12:05:35 +02:00
Matej Bačo 184399023c Add github integration test 2026-04-25 11:58:09 +02:00
Matej Bačo a588a62277 Prepare env for cicd integration with github oauth 2026-04-25 11:57:40 +02:00
Matej Bačo ffd0dbd406 Add OIDC endpoint 2026-04-25 10:20:00 +02:00
Matej Bačo 8200d079c6 Simplify specs 2026-04-24 16:37:27 +02:00
Matej Bačo d9d87f813f apple oauth endpoints 2026-04-24 16:31:21 +02:00
Prem Palanisamy dc9f48c948 composer: bump utopia-php/migration to 6e6f825 (relationships UpdateInPlace) 2026-04-24 15:09:42 +01:00
Matej Bačo db7acd4b8b More OAuth endpoints 2026-04-24 15:02:36 +02:00
Matej Bačo a62ca8612d More OAuth endpoints 2026-04-24 14:31:38 +02:00
Matej Bačo 975da667f5 Remove leftover 2026-04-24 14:23:19 +02:00
Matej Bačo 6cfb12c48b Improve OAuth SDK quality 2026-04-24 14:23:04 +02:00
Matej Bačo 8cdcd379c8 Add more oauth endpoints 2026-04-24 14:15:34 +02:00
ArnabChatterjee20k a17f9ce989 removed comments 2026-04-24 17:30:19 +05:30
ArnabChatterjee20k 053d25c64e Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-04-24 17:29:39 +05:30
ArnabChatterjee20kandGitHub 1b8123bf62 Merge pull request #11992 from appwrite/realtime-logs
added missing include for exporter in the realtime
2026-04-24 16:45:28 +05:30
ArnabChatterjee20kandGitHub 1ca75c73df Merge branch '1.9.x' into realtime-logs 2026-04-24 16:35:25 +05:30
Matej Bačo fe08978851 More OAuth provider endpoints 2026-04-24 12:58:32 +02:00
ArnabChatterjee20k 0633662695 removed dispatch experiment 2026-04-24 16:22:57 +05:30
ArnabChatterjee20k 89819db775 added exporter 2026-04-24 16:12:42 +05:30
Matej Bačo faf09ed7c5 Abstrated oauth response model 2026-04-24 12:38:12 +02:00
Matej Bačo c097d9fcdd Dropbox adapter 2026-04-24 12:20:48 +02:00
Matej Bačo dac184b281 abstract oauth adapters 2026-04-24 12:06:58 +02:00
Matej Bačo 335b1c2f6c Figma OAuth endpoint 2026-04-24 11:45:59 +02:00
Matej Bačo 5fbe6cba79 Improve github samples 2026-04-24 11:39:14 +02:00
Matej Bačo 36435d940d Add Discord OAuth endpoint 2026-04-24 11:35:30 +02:00
Matej Bačo 93f7a0d902 GitHub oauth endpoint 2026-04-24 11:17:18 +02:00
Matej Bačo 7fbfb6266b GitHub oauth response model 2026-04-24 10:56:39 +02:00
Matej BačoandGitHub 29b700d1ec Merge pull request #11981 from appwrite/feat-public-list-endpoints
Feat: Project public list endpoints
2026-04-24 10:08:23 +02:00
Prem Palanisamy 478a2a6e86 tests: Upsert drops orphan columns / Skip preserves them
testAppwriteMigrationUpsertDropsOrphanColumn: adds a column directly on
destination (simulating post-rename orphan or dest-only drift), runs
Upsert, asserts the orphan is dropped and source-declared column
survives. Covers the per-table orphan cleanup fired inside
createRecord before rows land.

testAppwriteMigrationSkipKeepsOrphanColumn: same setup, Skip mode.
Asserts the orphan survives, proving the cleanup is correctly gated
to Upsert only.
2026-04-24 07:30:29 +01:00
Prem Palanisamy 8292f7493d composer: bump utopia-php/migration to c2b8715 (rename orphan tracker to 'table' vocabulary) 2026-04-24 07:25:37 +01:00
Prem Palanisamy e728265d21 composer: bump utopia-php/migration to 36fdf26 (Upsert orphan cleanup + nullable timestamps) 2026-04-24 05:57:32 +01:00
Prem Palanisamy 84b6dfa9d5 composer: bump utopia-php/migration to bb21912 (SKIPPED status on tolerate) 2026-04-23 16:39:30 +01:00
Matej Bačo e3231393b9 Fix anayser 2026-04-23 16:06:45 +02:00
Matej Bačo 5beeca5a99 Placeholder test 2026-04-23 15:57:09 +02:00
Matej Bačo 4de3009f67 Fix analyser 2026-04-23 15:36:16 +02:00
Matej Bačo 4b3963512c Linter fix 2026-04-23 15:28:20 +02:00
Matej Bačo 8c634a95e4 Fix failing tests 2026-04-23 15:28:10 +02:00
Matej Bačo 7a3c001452 Re-add project removal tests 2026-04-23 15:22:40 +02:00
Matej Bačo a48fd13ced Add getPolicy + tests + move wrongly placed project tests 2026-04-23 15:19:49 +02:00
Matej Bačo 9c6ed9565e Remove tests of removed endpoints 2026-04-23 14:07:58 +02:00
Matej Bačo bdbc5b92df Fix after code review 2026-04-23 13:47:31 +02:00
Matej Bačo c246fb0f83 Project deletion tests 2026-04-23 13:41:11 +02:00
Matej Bačo a0a3849b16 Remove unsupported bulk endpoints 2026-04-23 13:37:32 +02:00
Matej Bačo b99139661e Migrate delete project endpoint 2026-04-23 13:37:19 +02:00
Matej Bačo 6d86b8fd0d Removal of project JWTs 2026-04-23 13:25:21 +02:00
Matej Bačo cef7a5197f List policies API 2026-04-23 13:24:39 +02:00
Matej Bačo c1dfeae323 Add queries to email tempaltes list 2026-04-23 13:06:05 +02:00
Matej Bačo 51fa0770a6 Add queries to mock numbers list 2026-04-23 12:43:45 +02:00
Matej BačoandGitHub d46403507c Merge pull request #11979 from appwrite/fix-membership-privacy
Fix: membership privacy bug on production
2026-04-23 10:47:54 +02:00
Matej Bačo 83724ce96f Console membership privacy test coverage 2026-04-23 10:37:35 +02:00
Matej Bačo 34930e6d67 Merge branch '1.9.x' into fix-membership-privacy 2026-04-23 10:18:32 +02:00
Matej BačoandGitHub 096f8041fd Merge pull request #11970 from appwrite/feat-mocks-public-api
Feat: Public mock phone APIs
2026-04-23 10:18:14 +02:00
Matej Bačo 9dad7cef9e Merge branch '1.9.x' into feat-mocks-public-api 2026-04-23 10:17:32 +02:00
Matej BačoandGitHub 9e23867f0a Merge pull request #11976 from appwrite/feat-auth-methods-api
Feat: Auth methods public API
2026-04-23 10:14:34 +02:00
Matej BačoGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
48353faa9b Apply suggestions from code review
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-23 10:13:01 +02:00
Matej Bačo c36b8fbabf Fix membershiip privacy bug on production 2026-04-23 10:07:32 +02:00
Luke B. SilverandGitHub 5a5cb1e74e Merge pull request #11977 from appwrite/perf/v20-investigation
perf: memoize request filter chain and V20 schema lookups
2026-04-23 07:38:03 +01:00
Prem Palanisamy 0a26608155 Merge branch 'feat/skip-duplicates' of https://github.com/appwrite/appwrite into feat/skip-duplicates 2026-04-23 07:15:36 +01:00
Prem Palanisamy 18694e0a28 TEMP: run Migrations across all adapters/modes on this branch 2026-04-23 07:10:23 +01:00
premtsd-codeandGitHub 07307c6131 Merge branch '1.9.x' into feat/skip-duplicates 2026-04-23 11:22:05 +05:30
Prem Palanisamy 0134832fb6 tests: cover Upsert UpdateInPlace for database/table + Skip preserves dest drift
Two new E2E tests exercising the schema-tolerance UpdateInPlace path
added in utopia-php/migration's DestinationAppwrite.

testAppwriteMigrationUpsertUpdatesContainerMetadata (positive):
- Fresh migration copies source database + table + column + row to dest.
- Mutates source database name (PUT /databases/:id) and table
  name/permissions/rowSecurity/enabled (PUT /tablesdb/:db/tables/:id).
- One-second sleep before mutation ensures source's $updatedAt is
  strictly greater than dest's at second granularity (strtotime
  comparison).
- Upsert re-migration asserts:
  - 'completed' status.
  - dest database name matches source's new name.
  - dest table name / enabled / rowSecurity match source's new values.
  - child row's 'name' attribute is untouched — UpdateInPlace only
    rewrites container metadata, not rows.

testAppwriteMigrationSkipPreservesContainerDrift (negative):
- Fresh migration, then mutate BOTH dest (simulating ops tightening
  permissions post-migration) and source (divergence).
- Skip re-migration asserts dest kept its tightened values — Skip's
  strict "don't touch" contract protects dev→prod cutover workflows
  from accidentally wiping ops-side drift on schema re-sync.

Both tests use performMigrationSync for strict 'completed' assertions.
Runtime ~18s combined. Existing testAppwriteMigrationRowsOnDuplicate
and testAppwriteMigrationReRunIsIdempotent regression-tested locally.
2026-04-23 06:22:26 +01:00
Prem Palanisamy 44b7b26adf composer: bump utopia-php/migration to e08d531
Picks up the UpdateInPlace branch — database/table metadata drift is
now reconciled on Upsert-newer (renames, enable toggles, table
permissions / documentSecurity) via updateDocument, without touching
child rows.
2026-04-23 06:06:30 +01:00
Chirag AggarwalandGitHub a8e6b1b683 Merge pull request #11963 from appwrite/chore/http-benchmark-comparison 2026-04-23 09:48:37 +05:30
Prem Palanisamy c1506643e9 composer: bump utopia-php/migration to cfba224
Picks up the PR #171 refactor + unit tests:
- resolveSchemaAction decision point consolidation
- deleteAttributeCompletely primitive (two-way cleanup in one place)
- Hardened sourceIsNewer against MySQL zero-date sentinel
- 14 unit tests locking the decision matrix
2026-04-23 00:12:09 +01:00
Prem Palanisamy 2ad95e5113 ci: restrict to Migrations service for fast feedback loop (TEMP)
Branch is iterating on utopia-php/migration's re-migration tolerance.
Other test matrices (unit, general, abuse, screenshots, benchmark, and
every other e2e service) add ~30+ minutes to CI without exercising code
this PR touches. Restrict the matrix to the Migrations service and skip
the unrelated test jobs until the migration work is ready to merge.

All jobs marked with 'TEMP:' comments + 'if: false' — revert to the
full matrix before merging to main.

Static analysis (lint, phpstan, composer audit, specs, locale, security)
still runs on every PR push.
2026-04-22 16:46:33 +01:00
Prem Palanisamy 715bd40b4a Tighten A→A re-migration tests against utopia-php/migration schema tolerance
utopia-php/migration's DestinationAppwrite now handles schema tolerance on
re-migration (PR #171 on feat/skip-duplicates): it pre-checks destination
`_metadata` for each database / table / column / index and tolerates in
Skip/Upsert mode. Re-runs no longer produce schema-level errors, so the
E2E tests can drop the status-tolerant workaround and assert strict
'completed' outcomes.

Changes:

- composer.json: pin utopia-php/migration to dev-feat/skip-duplicates (aliased
  to 1.9.99 for stability resolution). Will be replaced with a fixed 1.10.0
  tag once the migration PR lands.

- testAppwriteMigrationRowsOnDuplicate: replace the tolerant
  runMigrationAssertingRowSuccess helper with performMigrationSync on the
  Skip and Upsert re-runs. Asserts 'completed' status on every run,
  destination row content matches the expected value per mode (Mutated
  preserved on Skip, Original restored on Upsert). Helper method removed.

- testAppwriteMigrationReRunIsIdempotent (new): seeds two rows on source,
  runs the migration three times back-to-back (fresh, Skip re-run, Upsert
  re-run) against unchanged source data, asserts strict 'completed' on every
  run and row content is stable across all three. Exercises the
  schema-tolerance path end-to-end: every database/table/column on
  destination already exists with a matching spec, so DestinationAppwrite's
  pre-check returns Tolerate for every resource.
2026-04-22 16:38:32 +01:00
Matej Bačo b0939b92c3 Fix failing account tests 2026-04-22 17:02:22 +02:00
Chirag Aggarwal 3d66078fe9 Increase benchmark iterations 2026-04-22 19:46:27 +05:30
Chirag Aggarwal 9a6a597710 Address benchmark hardening review 2026-04-22 19:38:48 +05:30
Chirag Aggarwal c15e8d0126 Harden benchmark failure guard 2026-04-22 19:30:01 +05:30
Chirag Aggarwal 7b25d778d4 Trim benchmark scenarios 2026-04-22 19:21:51 +05:30
loks0n 3283b0bec0 perf: memoize request filter chain and V20 schema lookups
A phpspy profile of a production databases worker showed the V20
backwards-compat request filter accounting for ~40% of in-request
samples on `databases.listDocuments` traffic. Two compounding causes:

1. `Request::getParams()` re-ran the entire filter chain on every
   invocation. The framework and app call `getParams()` several times
   per request (route param binding, `cacheIdentifier()`, action
   injection, logging), so V20's recursive schema walk executed N times
   with identical inputs.
2. Inside `V20::getRelatedCollectionKeys`, the `databases/$databaseId`
   document was fetched at every recursion frame (up to
   RELATION_MAX_DEPTH = 3), and sibling relationships pointing at the
   same related collection each did their own `getDocument` call.

This commit:

- Memoizes the post-filter params on `Request`. The cache is
  invalidated by `addFilter`, `resetFilters`, and `setRoute`. `Request`
  is constructed per HTTP request (app/http.php), so the memo is
  naturally request-scoped. Helps every request filter version, not
  just V20.
- Splits V20's walk into an entry point that resolves the database
  namespace once and a pure recursive helper.
- Caches the collection `attributes` array per
  `(databaseNamespace, collectionId)` on the filter instance, so shared
  related collections collapse to one `getDocument` call. Missing or
  errored lookups are cached as `null` to avoid retry storms.
2026-04-22 14:49:13 +01:00
ArnabChatterjee20kandGitHub 49f8d6e89c Merge pull request #11971 from appwrite/realtime-logs
Structured Logging
2026-04-22 19:10:36 +05:30
Matej Bačo a0274a7b6f Fix failing tests 2026-04-22 15:12:58 +02:00
Chirag AggarwalandGitHub 277a09847e Merge pull request #11975 from appwrite/t3code/database-tables-retry-fix
[codex] Stabilize database e2e CI retries
2026-04-22 18:34:29 +05:30
Chirag AggarwalandGitHub cdc5654c26 Merge branch '1.9.x' into chore/http-benchmark-comparison 2026-04-22 18:24:50 +05:30
Matej Bačo bb4fdefee7 New tests for auth methods (base + integration) 2026-04-22 14:51:10 +02:00
ArnabChatterjee20k c2e5bbe0f7 updated 2026-04-22 18:11:32 +05:30
Chirag Aggarwal b2d24080b9 Stabilize database e2e CI retries 2026-04-22 18:08:45 +05:30
ArnabChatterjee20k b006858d0c dedupe 2026-04-22 17:52:45 +05:30
Chirag AggarwalandGitHub a0f30f608d Merge pull request #11974 from appwrite/fix/no-rule-without-deployment 2026-04-22 17:52:02 +05:30
Matej Bačo a85c5e582c Add auth method APIs (public) 2026-04-22 14:19:04 +02:00
ArnabChatterjee20k 6d1def7716 removed redundant span attributes 2026-04-22 17:45:58 +05:30
ArnabChatterjee20k f0f1e1c412 updated 2026-04-22 17:44:18 +05:30
Chirag AggarwalandGitHub 11f85e2477 Merge branch '1.9.x' into chore/http-benchmark-comparison 2026-04-22 17:24:19 +05:30
Chirag AggarwalandGitHub dbb1d1139e Merge branch '1.9.x' into fix/no-rule-without-deployment 2026-04-22 17:20:31 +05:30
Chirag AggarwalandGitHub f0e2a8b2c4 Merge pull request #11972 from appwrite/t3code/sentry-bug-fix 2026-04-22 17:18:15 +05:30
ArnabChatterjee20k 46e778ea90 updated 2026-04-22 17:13:35 +05:30
ArnabChatterjee20k fd9fe5d9ce corrected the position 2026-04-22 17:07:53 +05:30
ArnabChatterjee20k 59e0383264 updated 2026-04-22 17:01:08 +05:30
ArnabChatterjee20k 0f81bc2da9 Refactor telemetry logging in realtime events for consistency and clarity
- Updated span logging keys to use camelCase for uniformity across connection and message events.
- Added checks to ensure project and user IDs are only logged if they are not empty, enhancing data integrity.
- Improved error handling and logging structure to maintain consistency in telemetry data.
2026-04-22 16:57:51 +05:30
ArnabChatterjee20k 17e3d03b40 Add telemetry logging for subscribed channels and queries in realtime events
- Introduced new arrays to capture subscribed channels and passed queries during connection and message events.
- Enhanced span logging to include details about channels and queries for better monitoring and analysis.
- Updated telemetry data structure to reflect the new metrics, improving traceability of realtime interactions.
2026-04-22 16:55:52 +05:30
Chirag Aggarwal c97435d95c Stabilize benchmark wait metric tags 2026-04-22 16:53:35 +05:30
ArnabChatterjee20k b2ad7237ab Add detailed telemetry logging for realtime connection events
- Introduced span logging for connection open and close events, capturing metrics such as inbound and outbound bytes, subscription counts, and response codes.
- Enhanced error handling with logging of exceptions during connection lifecycle.
- Updated the structure of the telemetry data to include project and user IDs for better traceability.
2026-04-22 16:52:54 +05:30
ArnabChatterjee20k 57d777f80a revert format 2026-04-22 16:46:18 +05:30
Chirag Aggarwal d1962dbc62 Shorten local benchmark command 2026-04-22 16:45:16 +05:30
Matej Bačo 6648a1987b Fix tests 2026-04-22 13:14:43 +02:00
ArnabChatterjee20k c60e85ca84 Merge remote-tracking branch 'origin/1.9.x' into realtime-logs 2026-04-22 16:44:34 +05:30
ArnabChatterjee20k ca1cf1982f added wide events inside the structured one logging per message instead of a discrete logs 2026-04-22 16:44:20 +05:30
Chirag Aggarwal f50ca0281b Drop dead ternary guards now that outer check ensures deployment 2026-04-22 16:43:28 +05:30
Chirag AggarwalandGitHub 2f12717f30 Merge pull request #11969 from appwrite/t3code/spec-overlap-verification 2026-04-22 16:28:34 +05:30
Chirag Aggarwal f934259c31 Skip preview rule when no deployment exists on function create 2026-04-22 16:26:41 +05:30
Chirag AggarwalandGitHub 838dbc52e3 Merge branch '1.9.x' into t3code/sentry-bug-fix 2026-04-22 16:23:25 +05:30
Chirag Aggarwal e7d9ef74c4 Fix deployment single chunk content range
Fixes CLOUD-3JN6
2026-04-22 16:05:12 +05:30
Chirag Aggarwal d106e1d5bb Fix session alert test payload 2026-04-22 15:57:32 +05:30
Matej Bačo d1ade3872e Fix failing tests 2026-04-22 12:22:00 +02:00
Chirag Aggarwal 0f64f54221 Harden benchmark rerun metrics 2026-04-22 15:49:27 +05:30
Chirag Aggarwal 00512df4ca Clean up spec enum validation reporting 2026-04-22 15:45:16 +05:30
Matej Bačo 9065d9ada4 Add mocks scopes 2026-04-22 12:13:10 +02:00
ArnabChatterjee20k e0fec8f550 updated 2026-04-22 15:42:17 +05:30
Chirag Aggarwal 205c283935 Remove unused JWT benchmark setup 2026-04-22 15:33:52 +05:30
Chirag Aggarwal 038be90969 Preserve nested items enum validation 2026-04-22 15:33:18 +05:30
Matej Bačo 355d4323fc Fix tests not running 2026-04-22 12:01:42 +02:00
Matej Bačo 7578b5644c AI review fixes 2026-04-22 12:00:15 +02:00
Chirag Aggarwal 240cdf43e5 Simplify local benchmark command 2026-04-22 15:24:47 +05:30
Chirag Aggarwal 481eaf7530 Merge remote-tracking branch 'origin/1.9.x' into t3code/spec-overlap-verification 2026-04-22 15:24:34 +05:30
Chirag Aggarwal 2390d40731 Remove specs task unit test 2026-04-22 15:24:13 +05:30
Chirag Aggarwal f75a7269c9 Address benchmark review simplifications 2026-04-22 15:23:35 +05:30
Chirag Aggarwal 4f74394e8f Fix spec enum name validation 2026-04-22 15:21:41 +05:30
Matej Bačo f770277ea5 New mock phones tests 2026-04-22 11:42:39 +02:00
Matej Bačo eeadba3b59 Add missing endpoint in email templates 2026-04-22 11:36:54 +02:00
Matej Bačo 2e42633e12 Add public mocks API for phones 2026-04-22 11:30:39 +02:00
Chirag Aggarwal affd5876ab Add spec enum service overlap validation 2026-04-22 14:49:35 +05:30
Chirag Aggarwal dfd39d3946 Tolerate benchmark cleanup failures 2026-04-22 14:25:59 +05:30
Chirag Aggarwal 7d7fcea8c0 Ensure benchmark failures fail CI 2026-04-22 14:16:12 +05:30
Chirag Aggarwal 33d3f82a58 Merge remote-tracking branch 'origin/1.9.x' into chore/http-benchmark-comparison
# Conflicts:
#	.github/workflows/ci.yml
2026-04-22 14:05:45 +05:30
Matej BačoandGitHub af531ee4f9 Merge pull request #11964 from appwrite/feat-public-project-policies
Feat: public project policies
2026-04-22 10:23:10 +02:00
Chirag Aggarwal 73a77b8dcc Show benchmark throughput 2026-04-22 13:45:50 +05:30
Chirag Aggarwal 4b1b2972e9 Merge remote-tracking branch 'origin/1.9.x' into chore/http-benchmark-comparison
# Conflicts:
#	.github/workflows/ci.yml
2026-04-22 13:34:45 +05:30
Matej Bačo 72bb6378c2 Leftover 2026-04-22 10:00:19 +02:00
Matej Bačo bfa1960d8a Remove unneeded const 2026-04-22 10:00:10 +02:00
Matej Bačo e530bf41f7 Post-merge fix 2026-04-22 09:59:00 +02:00
Matej Bačo 0d27c59cb8 Merge branch '1.9.x' into feat-public-project-policies 2026-04-22 09:57:48 +02:00
Matej BačoandGitHub 97e611029e Merge pull request #11900 from appwrite/feat-project-smtp-endpoints
Feat: Public API for project SMTP endpoints
2026-04-22 09:52:09 +02:00
Matej Bačo efc37c68ec Merge branch '1.9.x' into feat-project-smtp-endpoints 2026-04-22 09:50:08 +02:00
Chirag Aggarwal b3f305f9a8 Record storage upload wait metric 2026-04-22 13:02:16 +05:30
Chirag Aggarwal 32508e7251 Avoid reserved TablesDB benchmark column name 2026-04-22 12:53:13 +05:30
Chirag Aggarwal a98b9f2319 Handle malformed optional benchmark summaries 2026-04-22 12:06:32 +05:30
Chirag Aggarwal a0ef5968fb Document local HTTP benchmark command 2026-04-22 11:58:30 +05:30
Chirag Aggarwal bc637ad25f Merge remote-tracking branch 'origin/1.9.x' into chore/http-benchmark-comparison
# Conflicts:
#	.github/workflows/ci.yml
2026-04-22 11:38:04 +05:30
Chirag AggarwalandGitHub 5b6dd5f75a Merge pull request #11949 from appwrite/chore/phpstan-level-4 2026-04-22 11:17:21 +05:30
Jake BarnbyandGitHub 2c973387c5 Merge pull request #11967 from appwrite/fix/listrows-total-int-cast
fix: cast cached total to int in listDocuments/listRows
2026-04-22 17:02:43 +12:00
Chirag Aggarwal 3b9c604eb8 Harden benchmark comparison run 2026-04-22 09:45:51 +05:30
Jake BarnbyandClaude Opus 4.7 8a841a6971 fix: cast cached total to int in listDocuments/listRows
Redis stringifies scalars on save, so on a cache hit the `total` field
was served as a string. Flutter SDK (and any strictly-typed client) then
failed with `TypeError: "37": type 'String' is not a subtype of type 'int'`.
The cache-miss path returned an int from `count()`, which is why only
repeat requests with `ttl > 0` tripped the bug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:14:30 +12:00
Chirag Aggarwal c973ca0a5d Address PHPStan level 4 review feedback 2026-04-22 09:34:03 +05:30
Chirag Aggarwal 7c486ddcef Keep benchmark comment on missing summary 2026-04-22 09:26:36 +05:30
Chirag Aggarwal cb7f2ec693 Show top benchmark request waits 2026-04-22 09:19:26 +05:30
Chirag Aggarwal 3cc7b833db Fix k6 benchmark diagnostics 2026-04-22 09:06:54 +05:30
Chirag Aggarwal 9ca84a56c9 Switch HTTP benchmark back to k6 2026-04-22 08:51:51 +05:30
Damodar LohaniandGitHub af05ad5784 Merge pull request #11953 from appwrite/CLO-4204-slow-query-hook
feat: add afterQuery hook to listRows (tablesdb) action
2026-04-22 07:25:24 +05:45
Jake Barnby 8ca59f22c9 Merge remote-tracking branch 'origin/1.9.x' into feat-dedicated-db 2026-04-22 11:41:42 +12:00
Matej Bačo 39af9c544d Remove unnessessary backwards compatibility 2026-04-21 18:22:44 +02:00
Luke B. SilverandGitHub 10b6955e98 Merge pull request #11965 from appwrite/fix/support-worker-num
fix: honor _APP_WORKERS_NUM in realtime
2026-04-21 17:14:08 +01:00
loks0nandClaude Opus 4.7 7e963e8439 fix: honor _APP_WORKERS_NUM in realtime
Realtime was ignoring _APP_WORKERS_NUM and always computing workers as
CPU × _APP_WORKER_PER_CORE, making it impossible to cap the worker count
without also changing the per-core multiplier. Prefer _APP_WORKERS_NUM
when set, falling back to the CPU × per-core calculation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 16:55:56 +01:00
Matej Bačo d9d5a81337 Fix 0->null backwrds compatibility 2026-04-21 17:51:31 +02:00
Matej Bačo 193891a7c9 Fix analyzer 2026-04-21 17:38:32 +02:00
Matej Bačo 70cb5ca68a linter fix 2026-04-21 17:34:59 +02:00
Matej Bačo c8a1746119 Fix existing tests 2026-04-21 17:34:44 +02:00
Matej Bačo 06eb550e98 Finalize tests 2026-04-21 16:56:00 +02:00
Matej Bačo 6c89a05a60 Fix 0 session to mean unlimited 2026-04-21 16:38:53 +02:00
Matej BačoandGitHub 1b993ede0f Merge branch '1.9.x' into feat-project-smtp-endpoints 2026-04-21 16:15:00 +02:00
Matej Bačo 648ffcdcfa Fix event labels 2026-04-21 16:07:52 +02:00
Matej Bačo 0be94a7aff Fix tests 2026-04-21 15:57:30 +02:00
Matej Bačo eba145ee2d More integration tests 2026-04-21 15:13:27 +02:00
Matej Bačo 99d230c70d Integration tests 2026-04-21 14:39:02 +02:00
Chirag Aggarwal 4ac1b68bbc Fix OpenAPI enum keys analysis 2026-04-21 18:03:18 +05:30
Chirag Aggarwal 211ac32080 Guard benchmark account-dependent flows 2026-04-21 17:59:09 +05:30
Chirag Aggarwal 30bf9deae0 Remove benchmark failure rows from output 2026-04-21 17:54:36 +05:30
Chirag Aggarwal 196b04a39c Polish benchmark comment details 2026-04-21 17:49:50 +05:30
Matej Bačo 6adabae620 Add policy tests 2026-04-21 14:05:46 +02:00
Matej Bačo 5f9dc0fcd8 Req & res filters, review fixes 2026-04-21 13:58:36 +02:00
Chirag Aggarwal b9d01617a4 Address benchmark review hardening 2026-04-21 17:24:26 +05:30
Chirag Aggarwal 9c65609d73 Check benchmark summary writes 2026-04-21 17:13:00 +05:30
Chirag AggarwalandGitHub da4dcd8505 Merge branch '1.9.x' into chore/phpstan-level-4 2026-04-21 17:08:46 +05:30
Matej Bačo d0f853d4cd Add more project policies 2026-04-21 13:38:27 +02:00
Chirag Aggarwal 63b2a1fb7f Harden benchmark baseline reporting 2026-04-21 17:07:05 +05:30
Chirag Aggarwal 30cfbb2d99 Polish benchmark reporting 2026-04-21 16:58:31 +05:30
Matej Bačo 2f272f0480 Cleanup unused markdowns descriptions 2026-04-21 13:27:08 +02:00
Matej Bačo 4317ee5617 Move some of auth settings to project policies 2026-04-21 13:11:42 +02:00
Chirag Aggarwal ef08d5a04c Run before benchmark with base image 2026-04-21 16:33:09 +05:30
Chirag Aggarwal 566eebfaec Fix benchmark PNG fixture 2026-04-21 16:31:57 +05:30
Chirag Aggarwal 51bc3dc1d5 Migrate HTTP benchmark to PHP 2026-04-21 16:22:04 +05:30
Chirag Aggarwal 83f182b444 Address benchmark review feedback 2026-04-21 16:10:08 +05:30
Chirag Aggarwal dcd01a8fb0 Tidy benchmark PR comment 2026-04-21 16:06:30 +05:30
Chirag Aggarwal 6aeb2d2be0 Fix benchmark before branch comparison 2026-04-21 15:51:30 +05:30
Chirag Aggarwal 2cfe40e98e Compare benchmark against base branch 2026-04-21 15:11:24 +05:30
Chirag Aggarwal 15e45df81e Address HTTP benchmark review feedback 2026-04-21 15:07:20 +05:30
Chirag Aggarwal e4f74a3fb1 Run curated HTTP benchmark in CI 2026-04-21 14:56:33 +05:30
Chirag Aggarwal 774a0d7022 Improve HTTP benchmark coverage 2026-04-21 14:48:12 +05:30
Damodar LohaniandGitHub 1dc9217c65 Merge branch '1.9.x' into CLO-4204-slow-query-hook 2026-04-21 14:31:45 +05:45
Damodar Lohani 50bd2877f4 chore: shorten afterQuery docblock 2026-04-21 08:46:21 +00:00
Damodar Lohani f465e2267a chore: tighten afterQuery docblock 2026-04-21 08:43:20 +00:00
Damodar Lohani b4f1652286 refactor: simplify afterQuery DB timing to single wall-clock bracket
Replaces the per-call $measure closure with a single $dbStart
timestamp taken right before the fetch block and a single subtraction
right after it. Drops 6 lines of HOF indirection plus the $measure
variable, at the cost of including cache GET/SET time (~0.5–5ms) in
measurements when ttl > 0. For slow-query logging at a 100ms+
threshold that noise is negligible, and the default ttl=0 path has
no cache ops at all so the measurement is pure DB engine time.

The bracket captures the cursor lookup, find/count, and transaction
state calls — everything between "query parsed" and "fetch done",
as intended. processDocument's post-fetch relationship work is still
outside the bracket, matching the original design.
2026-04-21 08:41:11 +00:00
Damodar Lohani 4e4860e7f8 refactor: move afterQuery hook into base listDocuments action
Moves the DB-duration measurement and afterQuery() hook from the
tablesDB-specific Rows/XList into the shared
Databases/Collections/Documents/XList base. Because TablesDB Rows and
DocumentsDB Documents both extend the legacy listDocuments base, a
single override now covers all three endpoints: legacy
listDocuments, listDocumentsDBDocuments, and tablesDB listRows.

TablesDB Rows drops the ~200-line action() duplicate and keeps only
the path/params/SDK overrides it needs, plus the extra
->inject('utopia') so its injection chain matches the new base action
signature. DocumentsDB Documents gets the same one-line inject
addition. Net -165 lines of duplication removed.

Behaviour is unchanged for CE (afterQuery() is a no-op); downstream
distributions overriding afterQuery() now observe every list-documents
/ list-rows call site for free.
2026-04-21 08:28:12 +00:00
Matej Bačo cce04b3bd1 Improve test coverage 2026-04-21 10:28:11 +02:00
Harsh MahajanandGitHub 7568964b7c Merge pull request #11905 from appwrite/feat-add-telemetry-for-ss-success-rates
feat(sites): add telemetry to ss rates
2026-04-21 13:54:05 +05:30
Damodar LohaniandGitHub 82af29c0cf Merge branch '1.9.x' into CLO-4204-slow-query-hook 2026-04-21 14:04:29 +05:45
Harsh MahajanandGitHub c6672e93cf Merge branch '1.9.x' into feat-add-telemetry-for-ss-success-rates 2026-04-21 13:07:40 +05:30
ArnabChatterjee20kandGitHub ac905a80b5 Merge pull request #11954 from appwrite/realtime-time-metric
Realtime time metric
2026-04-21 11:14:27 +05:30
ArnabChatterjee20k 5df65d5417 updated 2026-04-21 10:58:34 +05:30
ArnabChatterjee20k abd6f0add2 updated 2026-04-21 10:52:21 +05:30
ArnabChatterjee20k 25854c84f5 Merge remote-tracking branch 'origin/1.9.x' into realtime-time-metric 2026-04-21 10:44:30 +05:30
Damodar Lohani 5724d19426 Merge remote-tracking branch 'origin/CLO-4204-slow-query-hook' into CLO-4204-slow-query-hook 2026-04-21 04:34:38 +00:00
Damodar Lohani 7c84337c5f Merge remote-tracking branch 'origin/1.9.x' into CLO-4204-slow-query-hook 2026-04-21 04:32:28 +00:00
Matej Bačo b1d37bc4be Fix remaining test failures 2026-04-20 23:03:16 +02:00
Matej Bačo 4808cad081 Fix most of Project tests 2026-04-20 23:01:53 +02:00
Matej Bačo 72cd7671c4 Fix projects tests 2026-04-20 23:01:00 +02:00
Matej Bačo c3e411fcaa Fix tests 2026-04-20 22:47:47 +02:00
Matej Bačo 8c31e9f206 Server error fixes 2026-04-20 22:36:47 +02:00
Matej Bačo a4ad1b6df3 Code quality improvs 2026-04-20 22:35:35 +02:00
Matej Bačo 9e94f15f02 Finalize tests 2026-04-20 22:23:34 +02:00
Matej Bačo e27719108b Add tests 2026-04-20 22:15:03 +02:00
Matej Bačo 8bf2f54a51 Leftovers 2026-04-20 22:04:46 +02:00
Matej Bačo 8c03db70e9 Finalize templates design 2026-04-20 22:04:20 +02:00
Prem Palanisamy 19f02a5129 composer: sync lock content-hash after merge from 1.9.x 2026-04-20 16:44:36 +01:00
Prem Palanisamy d0603c4d28 tests: add Appwrite->Appwrite row onDuplicate test with tolerant poller 2026-04-20 16:38:59 +01:00
Matej Bačo 8ea69e0321 Fix password visibility test 2026-04-20 16:57:23 +02:00
Matej Bačo dfec2b3cb7 Improve test coverage 2026-04-20 16:11:22 +02:00
premtsd-codeandGitHub 187d0c0cf4 Merge branch '1.9.x' into feat/skip-duplicates 2026-04-20 19:41:01 +05:30
ArnabChatterjee20kandGitHub 69d778ab05 Merge pull request #11946 from appwrite/migration-via-api
added project region
2026-04-20 19:40:23 +05:30
Prem Palanisamy fc6bd7232e tests: add Appwrite->Appwrite row migration onDuplicate test 2026-04-20 14:50:57 +01:00
Prem Palanisamy e878b0b403 tests: cover onDuplicate on JSON import endpoint + column readiness wait 2026-04-20 14:43:45 +01:00
Matej Bačo 848f09956e Improve backwards compatibility test coverage 2026-04-20 15:36:49 +02:00
Matej Bačo 51f50b161c Improved backwards compatibility 2026-04-20 15:28:05 +02:00
ArnabChatterjee20kandGitHub 53d3713bfe Merge pull request #11957 from appwrite/realtime-add-new-messages
Realtime add new messages
2026-04-20 18:46:25 +05:30
ArnabChatterjee20k b8385fe927 updated 2026-04-20 18:27:48 +05:30
Prem Palanisamy f3c2502a8c composer: widen database/migration pins to minor-version wildcards 2026-04-20 13:57:44 +01:00
Matej Bačo 52e3319a86 Linter fix 2026-04-20 14:50:12 +02:00
Matej Bačo 8b41aed919 Post-merge removal 2026-04-20 14:49:43 +02:00
ArnabChatterjee20k b2233193d5 updated 2026-04-20 18:19:12 +05:30
Matej Bačo ba4430801d Merge branch 'feat-project-templates-api' into feat-project-smtp-endpoints 2026-04-20 14:49:04 +02:00
Matej Bačo 1c1ec43150 Removeal post-merge 2026-04-20 14:47:01 +02:00
Matej Bačo 2f62cced0a Merge branch '1.9.x' into feat-project-smtp-endpoints 2026-04-20 14:46:42 +02:00
Matej Bačo afb8f70316 Fix tests 2026-04-20 14:42:52 +02:00
Prem Palanisamy 18f9dfb64e Bump utopia-php/migration to 1.9.2, use OnDuplicate enum 2026-04-20 13:16:45 +01:00
ArnabChatterjee20k 78eeac6d14 Add unsubscribe functionality and enhance subscription handling in Realtime tests 2026-04-20 17:38:01 +05:30
ArnabChatterjee20k 9f65177649 Add unsubscribe functionality to Realtime adapter 2026-04-20 17:37:54 +05:30
ArnabChatterjee20k e9ea39a822 Enhance Realtime adapter: support union of channels/roles on subscription and add unsubscribeSubscription method 2026-04-20 17:37:45 +05:30
Matej Bačo 2097b0a0b0 Better support for post-smtp changes 2026-04-20 14:04:53 +02:00
Matej Bačo 78ef52cc9e Manual QA fixes 2026-04-20 13:11:11 +02:00
Torsten Dittmann 31461c9c74 Merge branch '1.9.x' into feat/command-execute-migration 2026-04-20 15:02:49 +04:00
Torsten Dittmann e9070744e8 Update console and VCS dependencies 2026-04-20 14:58:45 +04:00
Matej Bačo f040a4dc31 More backwards compatibility 2026-04-20 11:58:55 +02:00
Matej Bačo 56385ce167 Backwards compatibility 2026-04-20 11:48:45 +02:00
Matej Bačo bc592903db Support reply to name 2026-04-20 11:47:06 +02:00
Matej Bačo 06a2d48deb Linter fix 2026-04-20 11:29:13 +02:00
Matej Bačo c4d9c3dc4f Improve endpoint quality 2026-04-20 11:28:21 +02:00
Prem Palanisamy 8fa28257be Migrations: replace overwrite/skip with onDuplicate enum string, bump database to 5.3.22 2026-04-20 10:28:05 +01:00
Damodar LohaniandGitHub b36f1edeb0 Merge branch '1.9.x' into CLO-4204-slow-query-hook 2026-04-20 14:04:10 +05:45
Damodar Lohani eef443f07e chore: bump utopia-php/database to 5.3.22 for Query::fingerprint 2026-04-20 07:53:36 +00:00
ArnabChatterjee20k 62f7f25cb5 updated 2026-04-20 12:18:38 +05:30
harsh mahajan 63f04f893d fix composer 2026-04-20 12:18:26 +05:30
ArnabChatterjee20k 4b94d14f1e updated time metric 2026-04-20 12:13:53 +05:30
Hemachandarandharsh mahajan aa5543ac0a filter out null values 2026-04-20 12:02:07 +05:30
Hemachandarandharsh mahajan 60e51548e8 more tests 2026-04-20 12:02:07 +05:30
Hemachandarandharsh mahajan e802102c8f fix exclusion sub-directory 2026-04-20 12:02:07 +05:30
Hemachandarandharsh mahajan 87f29c6889 include previous_filename 2026-04-20 12:02:07 +05:30
Hemachandarandharsh mahajan a70d241da0 fix path trigger 2026-04-20 12:02:07 +05:30
Hemachandarandharsh mahajan e17cdc2bd4 fix pattern logic 2026-04-20 12:02:07 +05:30
Hemachandarandharsh mahajan a4f174ea7e update VCS 2026-04-20 12:02:07 +05:30
Hemachandarandharsh mahajan 175fcb51a8 remove default 2026-04-20 12:00:27 +05:30
Hemachandarandharsh mahajan 772358ff51 more tests 2026-04-20 12:00:27 +05:30
Hemachandarandharsh mahajan abad809a91 get PR files in Appwrite 2026-04-20 12:00:27 +05:30
Hemachandarandharsh mahajan 076e64886e validator pattern 2026-04-20 11:59:57 +05:30
harsh mahajan 88107ee7b3 feat: implement custom triggers for VCS builds 2026-04-20 11:57:57 +05:30
ArnabChatterjee20k 12f76d74b1 added subscriptions telemetry and worker labelling to connections and subscriptions 2026-04-20 11:52:43 +05:30
Chirag AggarwalandGitHub 9f504cd065 Merge pull request #11950 from appwrite/add-claude-plugin 2026-04-20 11:48:40 +05:30
Jake BarnbyandGitHub 9dc27e9ca5 Merge pull request #11894 from appwrite/chore-remove-shared-v1
refactor: Remove shared tables V1/V2 versioning
2026-04-20 18:16:12 +12:00
ArnabChatterjee20k 2bff4192ee added telemetry for the connected clients 2026-04-20 11:45:05 +05:30
ArnabChatterjee20k a9fb17b067 added telemetry for message arriaval delay 2026-04-20 11:39:50 +05:30
Jake Barnby db3d00b1da Merge remote-tracking branch 'origin/1.9.x' into chore-remove-shared-v1 2026-04-20 18:04:26 +12:00
Jake Barnby db720fbb22 (chore): update lock 2026-04-20 17:58:06 +12:00
Jake Barnby d92580470d Merge remote-tracking branch 'origin/1.9.x' into feat-dedicated-db 2026-04-20 17:57:01 +12:00
Atharva Deosthale 5ab42b8d32 update composer lock 2026-04-20 11:26:31 +05:30
ArnabChatterjee20k 8de7b41929 updated 2026-04-20 11:06:07 +05:30
Damodar Lohani a5c0a920ba feat: add afterQuery hook to list-documents/rows action
Wrap each database call (find, count, transaction list/count) with a
measuring closure so the actual DB duration is known — cache hits
report near-zero, cache misses report only the DB time, not cache
save / response serialization.

After the response is sent, invoke a protected afterQuery() hook with
the measured duration, the database/collection documents, and both
parsed + raw query arrays. CE impl is a no-op; downstreams (e.g.,
cloud) can override it to log slow queries without relying on HTTP
shutdown hooks or route-path matching.

Exceptions from afterQuery are swallowed so observability never
breaks the response.
2026-04-20 05:32:49 +00:00
Chirag Aggarwal 37a2b1cbd9 fix: restore executions limit cleanup behind a runtime env flag
Per review feedback on the PHPStan cleanup, the two `if
($executionsRetentionCount > 0 && ENABLE_EXECUTIONS_LIMIT_ON_ROUTE)`
blocks in `app/controllers/general.php` and
`src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php`
were load-bearing feature flags, not dead code. Removing them silently
dropped the ability to turn the cleanup on later.

Changes:

- Convert `ENABLE_EXECUTIONS_LIMIT_ON_ROUTE` from
  `const ... = false;` to a `define()` backed by the new
  `_APP_EXECUTIONS_LIMIT_ON_ROUTE` env var (defaults to `disabled`).
  PHPStan can no longer fold the `&&` away since the value is now
  runtime-resolved, so the guarded blocks are live again.
- Restore the `/* cleanup */` block in the `router()` helper in
  `app/controllers/general.php`.
- Restore the two cleanup blocks in `Functions/Http/Executions/Create.php`
  (one on the async-scheduled return path, one on the sync-response
  path), and re-add the `DeleteEvent $queueForDeletes` /
  `int $executionsRetentionCount` injections plus the
  `Appwrite\Event\Delete` import.

Runtime behavior is identical to main (flag off by default); operators
can now flip it via env without a code change.
2026-04-20 08:54:31 +05:30
Matej Bačo 6b66923f18 Fix delete response placeholder audit label 2026-04-19 19:36:24 +02:00
Chirag Aggarwal adb4e4ef36 ci: fix benchmark by pulling compose from GitHub raw for the latest tag
`https://appwrite.io/install/compose` now returns a 308 redirect to the
HTML install docs (`/docs/advanced/self-hosting/installation`) instead
of serving the compose file, so the Benchmark job's "Installing latest
version" step was downloading 0 bytes and `docker compose up -d` died
with "empty compose file". This has been failing the Benchmark job on
every recent PR, not just this one.

Resolve the latest release tag via the GitHub API, then fetch the
compose file and `.env` from `raw.githubusercontent.com` at that tag.
Switched both curl calls to `-fsSL` so they fail loudly on non-2xx
responses or redirect loss instead of silently writing empty files.
2026-04-19 20:34:51 +05:30
Atharva Deosthale 56165ee3d9 add claude plugin to static sdk 2026-04-19 18:39:19 +05:30
Chirag Aggarwal d86258a6f6 fix: restore runtime guards and widen types missed by PHPStan cleanup
Three follow-ups from CI that the level-4 pass got wrong:

1. `account.php` / `users.php`: `Document::find()` returns `mixed`
   (specifically `Document|false` in practice), not `Document`. The
   earlier `@var Document $oldTarget` docblocks were lies, and the
   runtime `instanceof Document` guards were load-bearing — removing
   them caused `Call to a member function isEmpty() on false` 500s
   on the `PATCH /v1/users/:id/email` and `/phone` endpoints (and the
   analogous `/v1/account/email`, `/v1/account/phone` flows). Dropped
   the misleading `@var` docblocks and restored
   `$oldTarget instanceof Document && !$oldTarget->isEmpty()`.

2. `Installer/Runtime/Config::setEnabledDatabases()` is a boundary
   that actually takes arbitrary user/compose input — not a trusted
   `string[]`. The `is_string($v)` filter was covering for that, and
   `ConfigTest::testSetEnabledDatabasesFiltersInvalid` explicitly
   asserts it. Widened the PHPDoc to `array<mixed>` and restored
   `is_string($v) && $v !== ''` in the filter.

3. `OAuth2/Apple::getAppSecret()` wrapped `json_decode` in a
   `try/catch (\Throwable)` — but `json_decode` without
   `JSON_THROW_ON_ERROR` returns `null` on failure, it doesn't throw.
   PHP 8.3's PHPStan flagged the catch as dead (PHP 8.5 didn't, which
   is why it slipped through locally). Replaced with
   `if (!\is_array($secret)) throw`, which preserves the original
   "invalid secret" guard.
2026-04-19 17:52:51 +05:30
Chirag Aggarwal d2230f8fe7 chore: bump PHPStan to level 4 and fix all new errors
Raises `phpstan.neon` level from 3 to 4 and fixes the 549 new errors
that level 4 surfaces across 157 files. Fixes are root-cause — no
`@phpstan-ignore`, no `@var` casts, no baseline entries, no widened
types. A handful of latent bugs were fixed along the way:

- `app/controllers/general.php`: path-traversal guard was negating
  `\substr(...)` before the strict comparison (`!\substr(...) === $base`
  was always `false === $base`). Rewritten as `\substr(...) !== $base`.
- `src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php`
  and `.../TablesDB/Logs/XList.php`: were importing the raw Matomo
  `DeviceDetector` (whose `getDevice()` returns `?int`) but treating the
  result as an array with `deviceName/deviceBrand/deviceModel` keys.
  Swapped to `Appwrite\Detector\Detector`, matching the wrapper already
  used a few lines below for `$os`/`$client`.
- `src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`: a match
  key was checking `$resourceKey === 'functions'` when `$resourceKey`
  is `'functionId'|'siteId'` — always false. Switched to the intended
  `$resource->getCollection() === 'functions'` check.
- `src/Appwrite/OpenSSL/OpenSSL.php`: `encrypt()` return type tightened
  to `string|false` to match `openssl_encrypt`; this lets callers'
  `=== false` error handling remain meaningful.
- `app/controllers/api/messaging.php`: removed a dead
  `array_key_exists('from', [])` branch in the Msg91 provider (empty
  array literal; branch was unreachable).

Large cleanup categories across the 549 fixes:
- Removed redundant `?? default` on array offsets and expressions that
  PHPStan now knows are non-nullable.
- Removed unreachable statements (mostly `return;` after `throw` or
  `markTestSkipped()`).
- Removed redundant `is_array`/`is_string`/`is_bool`/`instanceof` checks
  on already-narrowed types.
- Added `default =>` arms (or throwing arms) to non-exhaustive matches
  on `string`/`mixed` input.
- Removed dead `$document === false` branches where method return types
  were tightened to non-nullable `Document`.
- Removed unused properties (`$version` on Etsy/Zoom OAuth2, `$paths` on
  Installer State, `$source` on MigrationsWorker, `$account2` on two
  GraphQL auth tests), unused traits (`ApiVectorsDB`, `DatabaseFixture`),
  and an unused `cleanupStaleExecutions` task method.
- Replaced `assertTrue(true)` and redundant `assertIsArray`/`assertIsString`/
  `assertNotNull` assertions with `addToAssertionCount(1)` or
  `assertNotEmpty` where the runtime type was already known.
2026-04-19 17:31:20 +05:30
Matej BačoandClaude Opus 4.7 69d53cb2d4 Remove unused email template list response model
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 11:03:36 +02:00
Matej Bačo 447375dcbf Fix tests 2026-04-19 10:53:11 +02:00
Matej Bačo afab349a77 self review fixes 2026-04-19 10:43:57 +02:00
Matej Bačo 2a95cfd5a3 Final template API rework 2026-04-19 10:35:57 +02:00
Matej BačoandClaude Opus 4.7 8a1f8c71b2 Temporary removal of listEmailTemplates
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:15:48 +02:00
ArnabChatterjee20k 2793bcac38 updated 2026-04-18 23:11:04 +05:30
ArnabChatterjee20k 13f48797d4 added project region 2026-04-18 23:01:41 +05:30
Matej Bačo 0a71efc244 improve test coverage 2026-04-18 11:23:53 +02:00
Matej Bačo fcc7a56f4a Fix list endpoint 2026-04-18 11:01:26 +02:00
Luke B. SilverandGitHub 0aab3e9a43 Merge pull request #11941 from appwrite/fix/avif
fix: include project ID in storage preview cache key
2026-04-17 20:28:11 +01:00
Jake BarnbyandClaude Opus 4.7 fc714de11d (diag): remove realtime-auth-diag / realtime-project-diag logging
Root causes are fixed: Realtime now purges both projects:$projectId
and users:$userId from the shared cache before the respective reads,
eliminating the cross-process negative-cache races that surfaced as
intermittent 'Session is not valid.' failures on
appwrite-labs/cloud#3214's Realtime (dedicated) E2E.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 07:00:39 +12:00
Jake BarnbyandClaude Opus 4.7 92e8334169 (fix): purge cached project document before Realtime auth lookup
Cross-process negative-cache race in Realtime's onMessage handler: when
the WebSocket opens against a just-created project, an earlier router
probe or subscription lookup may have cached projects:\$projectId as
empty before the project actually existed. Although the HTTP worker
purges on createDocument, a narrow window lets Realtime re-populate the
stale empty entry in its own process cache, after which getProjectDB()
sees an empty Document and falls back to getConsoleDB(), which looks
for users in the console namespace and returns nothing — sessionVerify
then fails with 'Session is not valid.'

Surfaces on cloud#3214 as a recurring Realtime (dedicated) E2E failure.
Diagnosed via [realtime-project-diag] + [realtime-auth-diag]
instrumentation — the logs show consoleDb=appwrite consoleNs=console15x
(correct), project lookup empty, and subsequent user lookup landing in
the console namespace.

Purge is bounded: one cache DEL per WS message (not per read), runs
only when a projectId is present, and only forces the single
projects:\$projectId key to be re-read from adapter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 06:13:44 +12:00
Matej Bačo be5eeb1aba Fix failing tests 2026-04-17 19:50:34 +02:00
loks0nandClaude Sonnet 4.6 08b43dce50 fix: ksort after project injection to keep cache key order stable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 18:45:00 +01:00
Jake Barnby 8716497d1f (diag): log project lookup result before getProjectDB fallback 2026-04-18 05:42:49 +12:00
loks0nandClaude Sonnet 4.6 ad3bdee6c1 fix: include project ID in storage preview cache key
Cache key never included the project ID, so two projects with the same
bucketId, fileId, and transform params would share a cache key. On a
cache hit, Appwrite re-validates the bucket from the cached resourceType
(another project's bucket), which doesn't exist in the requesting
project's DB, throwing storage_bucket_not_found.

Fix: add 'project' to cache.params on the preview route (covers query
param case) and fall back to the X-Appwrite-Project header in
cacheIdentifier() for authenticated requests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 18:34:13 +01:00
Matej Bačo e00efaea68 Fix bug 2026-04-17 18:15:49 +02:00
Jake Barnby d8486612de (diag): re-add realtime-auth-diag with session ids + shared-env 2026-04-18 04:10:44 +12:00
Luke B. SilverandGitHub be214f7968 Merge pull request #11940 from appwrite/fix/avif
fix: do not cache error responses for storage preview, bump utopia-php/image to 0.8.5
2026-04-17 17:03:29 +01:00
Matej Bačo 64d182ac6a Add tests for templates 2026-04-17 17:49:20 +02:00
Matej Bačo bb38bf4248 Improve code quality 2026-04-17 17:42:46 +02:00
loks0nandClaude Sonnet 4.6 956285d522 fix: do not cache error responses for storage preview, bump utopia-php/image to 0.8.5
Cache write hook now checks HTTP status code before writing to prevent
failed AVIF (or any other) conversions from poisoning the cache.
Bumps utopia-php/image to 0.8.5 which fixes AVIF/HEIC output by using
native Imagick instead of the deprecated magick convert shell command.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 16:37:25 +01:00
Matej Bačo dc704fdb51 Improved xlist endpoint 2026-04-17 17:27:19 +02:00
Matej Bačo e388d2f6a3 List tempaltes endpoint 2026-04-17 17:22:55 +02:00
Aditya OberaiandGitHub fc62ef2fcc Merge pull request #11926 from appwrite/update-react-admin-template
Update React Admin template metadata
2026-04-17 20:32:45 +05:30
Matej Bačo b01ec03723 Fix analyze bug 2026-04-17 17:01:27 +02:00
Matej Bačo 489b2c4e21 Add new scopes 2026-04-17 16:45:04 +02:00
Matej Bačo 1a46fc2006 Move template APIs under project API 2026-04-17 16:43:17 +02:00
Torsten Dittmann 03be7ade3d Refactor command execution to use Utopia\Command 2026-04-17 18:31:48 +04:00
Luke B. SilverandGitHub c1b7aff2d9 Merge pull request #11934 from appwrite/feat/build-timeout
feat: use buildTimeout from message payload in build worker
2026-04-17 15:07:27 +01:00
loks0nandClaude Sonnet 4.6 7df1814203 refactor: rename buildTimeout to timeout in payload and buildDeployment param
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 14:26:38 +01:00
loks0nandClaude Sonnet 4.6 8f39783d7a refactor: remove jwtExpiry alias, use timeout directly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 14:26:38 +01:00
loks0nandClaude Sonnet 4.6 4043153df3 fix: pass buildTimeout as parameter to buildDeployment to fix PHPStan error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 14:26:38 +01:00
loks0nandClaude Sonnet 4.6 9765c7f0e3 feat: use buildTimeout from message payload in build worker
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 14:26:38 +01:00
Matej BačoandGitHub eddd159af8 Merge pull request #11932 from appwrite/feature/remove-/status-from-project-paths-upgrade-to-platform-0
Remove /status from project endpoint paths; upgrade to platform 0.13
2026-04-17 15:14:58 +02:00
ArnabChatterjee20kandGitHub 17de886296 Merge pull request #11936 from appwrite/realtime-time-metric
Realtime time metric
2026-04-17 18:31:51 +05:30
Matej Bačo 27b0e48296 Remove Status suffix from project event names
- project.updateServiceStatus → project.updateService
- project.updateProtocolStatus → project.updateProtocol
2026-04-17 14:53:59 +02:00
Jake BarnbyandClaude Opus 4.7 1225aa4861 (fix): translate NotFound from find() into 404 on listDocuments
When a DocumentsDB collection's metadata document exists but the
backing store (e.g. a freshly restored dedicated shard) has no table,
Utopia\Database\Database::find() throws NotFound. Previously this
bubbled to the HTTP layer as a generic 500. Catch it in the same
try/catch that already handles OrderException, QueryException, and
Timeout, and translate into COLLECTION_NOT_FOUND/TABLE_NOT_FOUND via
getParentNotFoundException() so the caller sees the correct 404.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 00:47:33 +12:00
Jake Barnby e8006dce6e (diag): remove realtime-auth-diag logging (root cause fixed in cloud) 2026-04-18 00:37:38 +12:00
ArnabChatterjee20k 6b2054d0b5 Merge remote-tracking branch 'origin/realtime-time-metric' into realtime-time-metric 2026-04-17 18:02:18 +05:30
ArnabChatterjee20k df0f7ba581 added bucket boundary 2026-04-17 18:02:04 +05:30
Jake Barnby 894d771a7c (diag): include db/namespace/tenant/host in realtime-auth-diag 2026-04-18 00:17:13 +12:00
Matej Bačo c484c487a9 Update tests 2026-04-17 13:19:20 +02:00
Matej Bačo 47f3ab930b Remove /status from project paths; Upgrade to platform 0.13 2026-04-17 13:14:34 +02:00
Jake BarnbyandClaude Opus 4.7 4e928ee08b (diag): log Realtime session-verify failure context
Temporary instrumentation to diagnose the 'Session is not valid.' failure
on appwrite-labs/cloud#3214's Realtime (dedicated) E2E. Emits:
  - project ID in scope
  - user ID from the session payload
  - whether the user document was found in the project DB
  - session count on the returned user document
  - session-secret prefix being verified
  - sessionVerify result

This will reveal whether the race is (a) user doc missing in project DB
(routing/provisioning issue) or (b) user doc found but sessions array
empty or mismatched (session write path issue). Revert once the root
cause is identified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 23:08:44 +12:00
Matej BačoandGitHub c8c3c68b0e Merge pull request #11912 from appwrite/feat-fallback-email-template
Feat: Fallback email custom template
2026-04-17 12:49:16 +02:00
ArnabChatterjee20kandGitHub facae65f08 Merge pull request #11927 from appwrite/realtime-time-metric
Added delay metric
2026-04-17 16:16:16 +05:30
ArnabChatterjee20kandGitHub ef6711e317 Merge branch '1.9.x' into realtime-time-metric 2026-04-17 16:00:18 +05:30
Jake BarnbyandClaude Opus 4.7 4da9873b83 (fix): purge cached user document before Realtime session verification
Cross-process read-after-write race: the HTTP worker writes a new session
into the user document on /account login endpoints, then the client sends
an authentication frame over a different Swoole process (Realtime). Cache
propagation between processes is not guaranteed to be observed on the
very next read, so sessionVerify() occasionally fails with a stale user
document whose sessions array does not yet contain the just-created one.

Purge the cached user locally before the read so sessionVerify sees the
freshly-written session deterministically. Overhead is bounded: one DEL
on cache + one extra primary-key read, executed once per WebSocket
authentication frame (not per message).

Surfaces on PR appwrite-labs/cloud#3214 as an intermittent
'Session is not valid.' failure in RealtimeConsoleClientTest +
RealtimeCustomClientTest manual-authentication cases in dedicated mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 22:27:28 +12:00
Jake Barnby 082110bba8 Merge branch '1.9.x' into feat-dedicated-db 2026-04-17 22:25:51 +12:00
Matej BačoandGitHub e06b06a21b Merge branch '1.9.x' into feat-fallback-email-template 2026-04-17 11:53:40 +02:00
Matej Bačo c97dd78335 Fix tests 2026-04-17 11:40:05 +02:00
Matej Bačo bf9bb22ac5 New tests 2026-04-17 11:30:24 +02:00
Matej Bačo 1b826df8f9 Non-URL locale to allow optional 2026-04-17 11:24:59 +02:00
Matej Bačo 11f23fdcfa Rework email templates PR after discussions 2026-04-17 10:52:21 +02:00
ArnabChatterjee20k b5ec92964c updated telemetry 2026-04-17 14:08:42 +05:30
ArnabChatterjee20k 71b74e21a3 added delay metric 2026-04-17 13:36:48 +05:30
ArnabChatterjee20k 7167fe2fb4 Merge remote-tracking branch 'origin/presence-api' into presence-api 2026-04-17 12:18:13 +05:30
ArnabChatterjee20k d1c3ee0290 updated upsert logic 2026-04-17 12:17:57 +05:30
ArnabChatterjee20kandGitHub 6bd3821804 Merge branch '1.9.x' into presence-api 2026-04-17 12:16:56 +05:30
ArnabChatterjee20k 1d32faff4f updated realtime 2026-04-17 12:04:55 +05:30
ArnabChatterjee20k e67016584f updated the schema 2026-04-17 12:02:26 +05:30
ArnabChatterjee20k a4b6d9a635 updated tests and reduce the timeouts 2026-04-17 12:02:17 +05:30
ArnabChatterjee20k 1f27769cc9 added catching of the exceptions 2026-04-17 12:02:07 +05:30
ArnabChatterjee20k 0d2a4da347 updated the presence helper 2026-04-17 12:01:56 +05:30
ArnabChatterjee20k ee34e2b587 created a helper for the duplicated logic between realtime and http api 2026-04-17 12:01:34 +05:30
ArnabChatterjee20k 52dbf14d79 updated ci 2026-04-17 12:01:04 +05:30
Aditya Oberai 1e797b3f01 Update React Admin template metadata 2026-04-16 17:00:28 +00:00
ArnabChatterjee20k 70f42a0dff added presences to the tests 2026-04-16 18:16:09 +05:30
ArnabChatterjee20k 2ef62b309e Refactor presence upsert logic to handle duplicate entries. Added exception handling for MongoDB adapter to update existing documents instead of creating new ones when presenceId mismatches occur. 2026-04-16 18:14:13 +05:30
ArnabChatterjee20k a480d8ed73 updated realtime 2026-04-16 18:11:01 +05:30
Jake BarnbyandGitHub f1b2dd7335 Merge pull request #11925 from appwrite/atharva/compose-fixes-appwrite
Self hosted installer compose fixes
2026-04-17 00:21:50 +12:00
ArnabChatterjee20k 4fea92c9cb Refactor realtime authorization handling and enhance presence event tests. Updated authorization roles synchronization in the realtime connection and added exception handling for user retrieval. Improved connection pool size logic for PubSub workers and added comprehensive tests for presence events to ensure correct message ordering and validation. 2026-04-16 17:47:34 +05:30
Chirag AggarwalandGitHub 50c379c5c3 Merge pull request #11924 from appwrite/feat/specs-provider-repo-list-discriminator 2026-04-16 16:57:37 +05:30
Atharva Deosthale 463e5acf50 compose fixes 2026-04-16 16:57:19 +05:30
Chirag Aggarwal 807e8bec8b feat(specs): add discriminator for provider repository list response union
Add ProviderRepositoryFrameworkList and ProviderRepositoryRuntimeList
model classes with conditions and type field so the listRepositories
endpoint's oneOf response gets a discriminator on the type property.
2026-04-16 16:29:42 +05:30
Harsh MahajanandGitHub f167049b51 Merge branch '1.9.x' into feat-add-telemetry-for-ss-success-rates 2026-04-16 15:23:45 +05:30
harsh mahajan 93b9500a95 align it with cloud pattern 2026-04-16 15:22:45 +05:30
ArnabChatterjee20k e7072574ad updated tests 2026-04-16 14:13:01 +05:30
ArnabChatterjee20k d84f71e728 updated 2026-04-16 14:10:20 +05:30
ArnabChatterjee20k 5f41f7fb86 Merge remote-tracking branch 'origin/1.9.x' into big-int 2026-04-16 14:07:49 +05:30
Chirag AggarwalandGitHub 935c1e40eb Merge pull request #11921 from appwrite/feat/specs-discriminator-unions 2026-04-16 14:03:54 +05:30
Chirag Aggarwal e472d98fe3 Revert "refactor(specs): rename x-propertyNames/x-mapping to x-discriminator-properties/x-union-typemap"
This reverts commit 05d70f8826.
2026-04-16 13:55:36 +05:30
Matej Bačo 7fe65eec57 Restruture endpoints 2026-04-16 10:23:31 +02:00
Matej Bačo 4cf375de6d Re-add removed test 2026-04-16 10:17:08 +02:00
Matej Bačo 19d0eb66c0 Fix tests 2026-04-16 10:09:38 +02:00
Chirag Aggarwal 05d70f8826 refactor(specs): rename x-propertyNames/x-mapping to x-discriminator-properties/x-union-typemap 2026-04-16 13:32:05 +05:30
Chirag Aggarwal 98ec9e45c4 fix(specs): narrow Detection type enum to each subclass's own value
Each Detection subclass now declares only its own type value in the enum
rather than sharing the full ['runtime', 'framework'] list. This prevents
SDK validators from accepting invalid values on concrete models.
2026-04-16 13:16:13 +05:30
Chirag Aggarwal 6dc17c91bc trigger greptile 2026-04-16 13:08:14 +05:30
Chirag Aggarwal 1493b7b8a6 feat(specs): unified discriminator with compound support and algo conditions
Unify getDiscriminator to produce a single discriminator object for both
single-key and compound cases. Single-key returns standard {propertyName,
mapping}. Compound falls back to extending the object with x-propertyNames
and x-mapping for multi-property discrimination.

Simplify call sites: OpenAPI3 uses 'discriminator', Swagger2 uses
'x-discriminator' — no more split keys.

Add conditions to all 7 Algo models (AlgoArgon2, AlgoBcrypt, AlgoMd5,
AlgoPhpass, AlgoScrypt, AlgoScryptModified, AlgoSha) to enable
discriminator generation for hashOptions unions.
2026-04-16 13:02:57 +05:30
ArnabChatterjee20k 05add106c1 Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-04-16 12:44:29 +05:30
ArnabChatterjee20k 0e3a017d50 added realtime trigger 2026-04-16 12:35:50 +05:30
Chirag Aggarwal 965836c8b4 fix(specs): use swagger discriminator extension mapping 2026-04-16 12:28:53 +05:30
Chirag Aggarwal 4545989c91 fix(specs): remove type rule from list models, keep only on specific models 2026-04-16 12:22:37 +05:30
Chirag Aggarwal b71d42d226 fix(specs): rename getDisciminator typo and extract shared model resolution
Fix misspelled method name (getDisciminator -> getDiscriminator) across
Format, OpenAPI3, and Swagger2. Extract duplicated model-resolution
lambda into Format::resolveModels(). Fix copy-pasted descriptions in
ProviderRepository list models.
2026-04-16 11:29:16 +05:30
ArnabChatterjee20k d28cce761d updated to have permissions order deterministic 2026-04-16 11:20:52 +05:30
ArnabChatterjee20k abb96f650b fix analyze 2026-04-16 11:18:39 +05:30
Chirag Aggarwal 945cdb3a99 refactor(specs): inline model resolution 2026-04-16 11:16:25 +05:30
Chirag Aggarwal a0db023860 refactor(specs): simplify discriminator resolution 2026-04-16 11:15:08 +05:30
ArnabChatterjee20k 1785a78fa1 added event validation tests 2026-04-16 11:15:04 +05:30
ArnabChatterjee20k 3f06b5cd1a Enhance presence deletion by adding payload to event queue after document removal 2026-04-16 11:13:37 +05:30
Chirag Aggarwal 6a7280e7dd refactor(specs): inline discriminator condition checks 2026-04-16 11:12:43 +05:30
Chirag Aggarwal 680cb04de7 feat(specs): add discriminators for polymorphic responses 2026-04-16 11:07:07 +05:30
Damodar LohaniandGitHub 6e50ed050d Merge pull request #11787 from appwrite/CLO-4175-allow-delete-with-memberships
Allow deleting user account with active memberships
2026-04-16 08:16:23 +05:45
Damodar Lohani f78b5c6596 Merge remote-tracking branch 'origin/1.9.x' into CLO-4175-allow-delete-with-memberships 2026-04-16 01:16:49 +00:00
Matej Bačo 55001a7daa New integration tests 2026-04-15 19:27:26 +02:00
Matej Bačo 6d2876ab26 New E2E tests 2026-04-15 19:01:35 +02:00
Matej Bačo b510194f00 Expose "worldwide" locale 2026-04-15 18:57:37 +02:00
Matej Bačo 8fd1c5d620 Remove worldwide to not be user-facing 2026-04-15 18:54:18 +02:00
Matej Bačo 590f063694 Remove remaining sms leftover 2026-04-15 18:40:29 +02:00
Matej Bačo 90e1433878 Fix agent mistake 2026-04-15 18:38:08 +02:00
Matej Bačo 2b42487198 Linter fix 2026-04-15 18:30:06 +02:00
Matej Bačo 53ed9462bd More cleanup of sms templates 2026-04-15 18:29:43 +02:00
Matej Bačo 0da185e689 Refactor fixes 2026-04-15 18:17:55 +02:00
Matej Bačo dc39af50a1 Support for worldwide fallback custom template for all project emails 2026-04-15 18:05:46 +02:00
Matej Bačo 6da132db46 Remove SMS templates and support null locale for mail templates 2026-04-15 18:05:27 +02:00
Matej BačoandGitHub b2a02e3807 Merge pull request #11909 from appwrite/fix-protocol-exception
Fix: protocol & service exception
2026-04-15 16:59:49 +02:00
Matej Bačo e7f78b3e01 Fix shutdown event errors 2026-04-15 15:29:05 +02:00
Prem Palanisamy c5fe71684a Add E2E tests for CSV import overwrite/skip flags
Three new test methods in MigrationsBase, following the existing
testCreateCSVImport setup pattern:

- testCreateCSVImportSkipDuplicates
  Seeds documents.csv, mutates one row, re-imports with skip=true.
  Asserts the mutated row keeps its mutated value (not overwritten
  by the CSV's original value) and the row count stays at 100.

- testCreateCSVImportOverwrite
  Seeds documents.csv, mutates one row, re-imports with overwrite=true.
  Asserts the mutated row is restored to the CSV's original value
  (proving upsertDocuments actually replaced the row) and the row
  count stays at 100.

- testCreateCSVImportDefaultFailsOnDuplicate
  Regression guard: re-imports documents.csv with no flags. Asserts
  the migration goes to status=failed with errors populated, proving
  the default duplicate-throws behavior is preserved.

All three share a prepareCsvImportFixture() helper that sets up
database + table (name, age columns) + bucket + documents.csv
upload. Returns the known first-row id + original name/age so tests
can mutate and assert on a predictable row.

Reuses the existing documents.csv fixture (100 rows with \$id as the
first column). No new fixture files needed.
2026-04-15 14:23:02 +01:00
Harsh MahajanandGitHub 682a56c03c Merge branch '1.9.x' into feat-add-telemetry-for-ss-success-rates 2026-04-15 18:46:07 +05:30
Jake Barnby 95d5d1bdd9 Merge remote-tracking branch 'origin/1.9.x' into feat-dedicated-db 2026-04-16 01:15:10 +12:00
Prem Palanisamy d44820e00c Add overwrite and skip options to migration imports
Exposes two new optional boolean params on the three migration
creation endpoints so CSV / JSON / appwrite-to-appwrite imports can
choose how to handle rows whose IDs already exist at the destination.

Endpoints updated (app/controllers/api/migrations.php):
- POST /v1/migrations/appwrite
- POST /v1/migrations/csv/imports
- POST /v1/migrations/json/imports

Parameter semantics:
- overwrite=true  -> destination uses upsertDocuments instead of
                     createDocuments; existing rows are replaced
                     with imported values
- skip=true       -> destination wraps createDocuments in
                     skipDuplicates; existing rows are preserved
                     unchanged, duplicate-id rows silently no-op
- both false      -> default; fails fast on DuplicateException
                     (original behavior, unchanged)
- both true       -> overwrite wins (upsert subsumes skip)

Both params are stored in the migration Document's options array
(matches the existing pattern for destination behavior config like
path, size, delimiter, bucketId, etc.) and read back in the worker's
processDestination() to instantiate DestinationAppwrite with the
new constructor params.

Feature-branch note: depends on utopia-php/migration#feat/skip-duplicates
(DestinationAppwrite constructor params) which in turn depends on
utopia-php/database#852 (skipDuplicates scope guard). composer.json is
temporarily pinned to dev-feat/skip-duplicates and
dev-csv-import-upsert-v2 respectively; both must be reset to proper
release versions once the upstream PRs merge.
2026-04-15 14:15:08 +01:00
Matej Bačo 7376c5b517 Fix protocol endpoint causing InvalidArgumentException 2026-04-15 15:09:40 +02:00
harsh mahajan 730c62bda6 remove usage stats 2026-04-15 18:35:09 +05:30
ArnabChatterjee20kandGitHub db84c79802 Merge pull request #11907 from appwrite/update-events-not-firing
added backward compat
2026-04-15 18:32:21 +05:30
ArnabChatterjee20k cd6a6956d6 added channels and events generation 2026-04-15 18:16:51 +05:30
ArnabChatterjee20k 6ba810a4da fix unit tests 2026-04-15 17:49:33 +05:30
ArnabChatterjee20k 6d9b787816 updated string replacement 2026-04-15 17:38:21 +05:30
ArnabChatterjee20k 7b8fb409b1 added database filtering 2026-04-15 17:33:57 +05:30
ArnabChatterjee20k 1fb78115e8 added backward compat 2026-04-15 17:23:18 +05:30
harsh mahajan 9567f1f4e8 Merge branch 'origin/1.9.x' into feat-add-telemetry-for-ss-success-rates 2026-04-15 17:21:32 +05:30
ArnabChatterjee20k 7e18e6f8c5 updated realtime and tests 2026-04-15 16:30:17 +05:30
ArnabChatterjee20k 968b1c0861 updated configs 2026-04-15 16:11:05 +05:30
Chirag AggarwalandGitHub 49c93c635d Merge pull request #11851 from appwrite/chore-migrate-audits-certificates-screenshots-to-publishers 2026-04-15 15:13:18 +05:30
Chirag AggarwalandGitHub 5010cdedbb Merge pull request #11903 from appwrite/fix/ci-mongodb-retry 2026-04-15 14:33:15 +05:30
Chirag AggarwalandGitHub 13413c05c3 Merge pull request #11880 from appwrite/fix/graphql-coroutine-safe-response 2026-04-15 14:14:57 +05:30
Chirag Aggarwal dc185be836 collapse 2026-04-15 13:52:20 +05:30
ArnabChatterjee20k 2ce39768c3 added delete of presence on disconnect 2026-04-15 13:38:34 +05:30
Chirag AggarwalandGitHub 29be9b6019 Merge branch '1.9.x' into chore-migrate-audits-certificates-screenshots-to-publishers 2026-04-15 13:26:52 +05:30
ArnabChatterjee20k 69bc935517 Add permission handling for presence updates and enhance error validation in realtime messages 2026-04-15 13:21:26 +05:30
ArnabChatterjee20kandGitHub 1247b1c719 Merge pull request #11852 from appwrite/docsdb-fixes
Docsdb fixes
2026-04-15 13:18:41 +05:30
harsh mahajan 23233d0dcb add try catch 2026-04-15 11:59:02 +05:30
harsh mahajan 37540aa4f5 addressed comments 2026-04-15 11:52:39 +05:30
harsh mahajan db0a971384 feat(sites): add screenshot success telemetry to usage stats 2026-04-15 11:43:53 +05:30
Chirag AggarwalandGitHub 445e86361c Merge pull request #11904 from appwrite/fix/sdk-push-no-force 2026-04-15 10:27:40 +05:30
Chirag Aggarwal 80197b566c fix: replace force-push with regular push in SDK release task
The SDK push task used `git push --force-with-lease` which fails on
repos with branch protection rules that disallow force pushes. Instead,
checkout the existing remote dev branch and commit on top of it so a
regular push is always a fast-forward.
2026-04-15 10:22:50 +05:30
Chirag Aggarwal 8671533878 fix: remove orphaned docblock from deleted test 2026-04-15 10:13:37 +05:30
Chirag Aggarwal f51f02375a test: remove flaky concurrent session race condition test
testEmailPasswordSessionNotCorruptedByConcurrentRequests relies on
timing-sensitive curl_multi orchestration with hardcoded delays to
reproduce a cache race window. This makes it inherently flaky in CI
where resource pressure shifts the timing unpredictably.
2026-04-15 09:52:32 +05:30
Chirag Aggarwal d40df613de fix: run ProjectWebhooks tests sequentially in CI
ProjectWebhooks tests have shared state dependencies (e.g. index
creation must complete before assertions). Running with --functional
(parallel methods) causes flaky failures where indexes are still
'processing' instead of 'available'.
2026-04-15 09:51:26 +05:30
ArnabChatterjee20kandGitHub ebc3febc38 Merge branch '1.9.x' into docsdb-fixes 2026-04-15 09:42:05 +05:30
Chirag Aggarwal e77bfae091 fix: add restart policy to MongoDB container for flaky CI starts
MongoDB's official Docker entrypoint uses a two-phase startup: a
temporary mongod for user/db init, then the real mongod. Under CI
resource pressure the port may not be released between the two
phases, causing mongod to exit with code 48 (address already in use).

Adding restart: on-failure:3 lets Docker handle the transient failure
natively. On restart the data directory already exists so the
entrypoint skips the two-phase init entirely, avoiding the race.
2026-04-15 09:19:02 +05:30
Chirag Aggarwal f9efd803c1 refactor: remove unnecessary IIFE wrappers in resolver methods 2026-04-15 08:55:00 +05:30
Chirag Aggarwal 54d6470163 refactor: move acquire/release into ResolverLock as instance methods 2026-04-15 08:51:14 +05:30
Chirag Aggarwal 50640b5bb9 refactor: inline createResolverResponse — only called once 2026-04-15 08:50:14 +05:30
Chirag Aggarwal 4d4ba508ef fix: use spl_object_hash for lock keys instead of WeakMap
Replace WeakMap with a plain array keyed by spl_object_hash($utopia)
as suggested in review. Entry is cleaned up in the finally block to
prevent leaks.
2026-04-15 08:47:41 +05:30
Chirag Aggarwal 4f2f9fedfa fix: resolve merge conflict keeping both lock and route restore
Merge conflict in Resolvers.php between the coroutine lock
(fix/graphql-coroutine-safe-response) and the otel route restore
(fix-gql-route-reset from 1.9.x). Both changes are needed:
the lock serialises concurrent resolvers while the route restore
prevents otel span clobbering.
2026-04-15 08:40:12 +05:30
Matej Bačo 732538504d Fix backwards compatibility 2026-04-14 17:42:26 +02:00
Matej Bačo d3dcbfe567 Leftover chaneges, 2026-04-14 17:20:22 +02:00
Matej Bačo 905d2a8eaa Fix tests 2026-04-14 17:20:03 +02:00
Matej Bačo b5e46c1a60 E2E integration tests 2026-04-14 17:09:37 +02:00
Matej Bačo a3a8ad88fe formatting fix 2026-04-14 17:07:06 +02:00
Matej Bačo 9d6428d5d5 Improve tests 2026-04-14 17:06:57 +02:00
Matej Bačo d7f8ca3f01 Improve endpoint quality 2026-04-14 17:04:22 +02:00
Matej Bačo 56bcc0d09f Fix tests 2026-04-14 17:01:56 +02:00
Matej Bačo 8b0c60d2f9 Fix bugs 2026-04-14 17:01:46 +02:00
Matej Bačo ffdfe4bbf8 Add new tests 2026-04-14 16:55:39 +02:00
Matej Bačo 556ca10ed9 Register endpoints 2026-04-14 16:52:38 +02:00
Matej Bačo 193beb76fe add SMTP endpoints 2026-04-14 16:50:07 +02:00
Chirag AggarwalandGitHub 8fcba8b5c9 Merge pull request #11893 from appwrite/codex/disable-graphql-functional 2026-04-14 19:19:14 +05:30
Jake BarnbyandGitHub 6c8b34c230 Merge pull request #11885 from appwrite/fix-gql-route-reset
(fix): reset route to avoid clobbering otel
2026-04-15 01:26:02 +12:00
Chirag Aggarwal b2884ddb88 Use audit message context helper 2026-04-14 18:23:24 +05:30
Chirag Aggarwal 82798fa5a3 Simplify audit message construction 2026-04-14 18:18:25 +05:30
Shimon NewmanandGitHub 377ade3c44 Merge pull request #11892 from appwrite/schedule-functions-debug-2
Schedule functions debug
2026-04-14 15:32:29 +03:00
Shimon NewmanGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
71533aaaf3 Update app/cli.php
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-14 15:17:37 +03:00
shimon ccbe7e5d3f Merge branch '1.9.x' of github.com:appwrite/appwrite into schedule-functions-debug-2 2026-04-14 15:11:56 +03:00
Chirag AggarwalandGitHub bea7739d7e Merge branch '1.9.x' into codex/disable-graphql-functional 2026-04-14 17:21:55 +05:30
Jake BarnbyandGitHub 76320d652a Merge pull request #11887 from appwrite/fix/composer-audit-graphql-php
Upgrade graphql-php to v15.31.5
2026-04-14 23:46:45 +12:00
Chirag Aggarwal 6d5968e2ea ci: disable functional GraphQL e2e 2026-04-14 16:51:46 +05:30
Jake Barnby fd8fedca18 (refactor): Remove shared tables V1/V2 versioning 2026-04-14 22:51:36 +12:00
shimon 70c380fa36 feat: add tracing support for execution events in log and worker classes 2026-04-14 13:36:29 +03:00
shimon 512a7ae2bd feat: enhance function scheduling with tracing support 2026-04-14 13:36:19 +03:00
Chirag AggarwalandGitHub 49d3cd980f Merge branch '1.9.x' into fix/composer-audit-graphql-php 2026-04-14 15:07:22 +05:30
Luke B. SilverandGitHub 39a396810a Merge pull request #11881 from appwrite/feat/45-min-build-timeout
feat: increase default build timeout to 45 minutes
2026-04-14 09:28:29 +01:00
Jake BarnbyandGitHub c52ca65905 Revert "Safe delete shared tables v1" 2026-04-14 19:15:41 +12:00
Jake BarnbyandGitHub 1d307178bb Merge pull request #11691 from appwrite/delete-project-shared-table-v1
Safe delete shared tables v1
2026-04-14 18:00:00 +12:00
fogelito c3ed2ff6ce Merge branch '1.9.x' of https://github.com/appwrite/appwrite into delete-project-shared-table-v1 2026-04-14 08:05:44 +03:00
Chirag Aggarwal efadf17bfe Fix GraphQL 15 static analysis 2026-04-14 10:26:59 +05:30
Chirag Aggarwal bcfec8d5de Align graphql version pinning style 2026-04-14 09:35:25 +05:30
Chirag Aggarwal 4b2e22d9da Fix graphql-php audit vulnerability 2026-04-14 09:27:14 +05:30
ArnabChatterjee20k 45e22e2243 Merge remote-tracking branch 'origin/1.9.x' into docsdb-fixes 2026-04-13 22:44:07 +05:30
ArnabChatterjee20k 26ff78d98a updated expiry tests 2026-04-13 22:39:56 +05:30
ArnabChatterjee20k 0935c60f3c added users.read, users.write currently for the scoping 2026-04-13 22:37:17 +05:30
ArnabChatterjee20k 54fa813bc6 Implement presence expiry management and enhance user ID handling in presence actions. Add automated tests for expired presence deletion during maintenance. 2026-04-13 22:31:46 +05:30
ArnabChatterjee20k f9d0cb7daf Refactor presence tests to handle unauthorized access for client side operations and simplify presence setup logic. 2026-04-13 21:08:23 +05:30
ArnabChatterjee20k c5d5946fd2 updated scopes 2026-04-13 21:08:17 +05:30
ArnabChatterjee20k af41c64135 Merge remote-tracking branch 'origin/1.9.x' into presence-api 2026-04-13 20:47:32 +05:30
ArnabChatterjee20k 63afcad23d tests 2026-04-13 20:47:13 +05:30
ArnabChatterjee20k eb76c31fdd updated aciton source 2026-04-13 20:46:30 +05:30
ArnabChatterjee20k 51fd96f4bf added crud operation 2026-04-13 20:28:40 +05:30
ArnabChatterjee20k 14351c7338 updated model 2026-04-13 20:28:31 +05:30
ArnabChatterjee20k 01c6cc86ce Presence api response models 2026-04-13 20:28:23 +05:30
Matej BačoandGitHub 0a21f3b139 Merge pull request #11884 from appwrite/fix-webhook-api-security
Fix: Webhook API secret rotation and security
2026-04-13 15:52:55 +02:00
Chirag AggarwalandGitHub 5d53685976 Merge branch '1.9.x' into chore-migrate-audits-certificates-screenshots-to-publishers 2026-04-13 19:11:43 +05:30
Chirag AggarwalandGitHub d869c39783 Merge branch '1.9.x' into fix/graphql-coroutine-safe-response 2026-04-13 19:02:06 +05:30
Chirag Aggarwal fe02964ebd fix: finalize graphql coroutine response isolation 2026-04-13 19:01:20 +05:30
Jake Barnby cc8eb62c83 (chore): rename 2026-04-14 01:15:06 +12:00
Luke B. SilverandGitHub b1ad6ea87c Merge pull request #11883 from appwrite/fix/large-execution-payload
fix: trim execution queue payload to project ID only
2026-04-13 14:13:43 +01:00
Jake Barnby d52d6c0bf0 (fix): reset route to avoid clobbering otel 2026-04-14 01:13:32 +12:00
Chirag AggarwalandGitHub 86cfea0edb Merge branch '1.9.x' into chore-migrate-audits-certificates-screenshots-to-publishers 2026-04-13 18:41:52 +05:30
Matej Bačo db406b0a27 Fix tests 2026-04-13 15:08:20 +02:00
Matej Bačo 2585518e33 Fix bugs 2026-04-13 15:08:13 +02:00
Chirag Aggarwal a1342b4b9d fix: update audit context usage 2026-04-13 18:32:38 +05:30
Matej Bačo c9fceb870c Fix folder structure 2026-04-13 14:57:24 +02:00
Matej Bačo 9f1ec356d1 Formatting fix 2026-04-13 14:53:25 +02:00
Matej Bačo 28d285d5c5 Improved tests for webhook edge cases 2026-04-13 14:53:06 +02:00
loks0nandClaude Sonnet 4.6 a941d8b855 fix: trim execution queue payload to project ID only
The v1-executions queue was serialising the full project document
(OAuth providers, webhooks, keys, auths config, permissions, etc.)
into every message. The worker DI system already re-fetches the
complete project from the platform database using only the project ID,
so the full document was wasted bytes.

Override trimPayload() in the Execution event class to include only
$id in the project stub, reducing message size significantly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 13:52:09 +01:00
Chirag Aggarwal d40a355d9d refactor: simplify audit event context 2026-04-13 18:21:39 +05:30
Matej Bačo 3263133e5f Implement secure webhook interfaces 2026-04-13 14:50:06 +02:00
Chirag Aggarwal f4f5494b85 fix: isolate graphql resolver responses 2026-04-13 17:48:48 +05:30
Chirag Aggarwal fc0fd2f6ac fix: scope graphql resources to resolver coroutine 2026-04-13 17:10:38 +05:30
Chirag Aggarwal 70a75c2e7b fix: scope graphql resolver lock to request 2026-04-13 16:47:33 +05:30
Chirag Aggarwal 6bc2168e29 fix: isolate graphql resolver request state 2026-04-13 16:34:27 +05:30
fogelito a3ba66c90a Merge branch '1.9.x' of https://github.com/appwrite/appwrite into delete-project-shared-table-v1
# Conflicts:
#	composer.lock
2026-04-13 13:29:03 +03:00
ArnabChatterjee20kandGitHub c24d724000 Merge pull request #11749 from appwrite/db-workers-memory
added reset in db worker for queue for realtime
2026-04-13 15:47:54 +05:30
Luke B. SilverGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
df5ccc10ad Apply suggestion from @greptile-apps[bot]
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-13 10:48:20 +01:00
loks0nandClaude Sonnet 4.6 2807d6cd9a feat: increase default build timeout to 45 minutes
Raises _APP_COMPUTE_BUILD_TIMEOUT default from 900s (15 min) to
2700s (45 min) to support longer-running builds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 09:57:27 +01:00
ArnabChatterjee20kandGitHub 20e2f2284f Merge branch '1.9.x' into db-workers-memory 2026-04-13 13:43:14 +05:30
Matej BačoandGitHub 7a0d69c826 Merge pull request #11839 from appwrite/feat-services-protocols-apis
Feat: services protocols public apis
2026-04-13 09:38:17 +02:00
Matej Bačo feedec80f2 Merge branch '1.9.x' into feat-services-protocols-apis 2026-04-13 09:17:09 +02:00
Damodar LohaniandClaude Opus 4.6 18b1c344e6 fix: serialize batched GraphQL queries for coroutine safety
When Swoole coroutine hooks are enabled (SWOOLE_HOOK_ALL), batched
GraphQL queries execute in parallel coroutines that share a single
Response object. Concurrent coroutines interleave writes to the
shared response payload, causing data mixing between queries.

Cloning the response is not viable because cookies/headers written
by the action (e.g. session tokens) must reach the real HTTP response.

Instead, serialize the critical section (execute → getPayload) using
a Swoole Channel as a coroutine-safe mutex. This ensures only one
batched query writes to the Response at a time while preserving
cookie/header propagation. The lock is released before resolve/reject
callbacks so downstream processing remains concurrent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:54:15 +00:00
ArnabChatterjee20k c6f16cde4a updated schema 2026-04-13 11:44:01 +05:30
Damodar LohaniandGitHub f4d40a1289 Merge pull request #11879 from appwrite/fix/graphql-batch-sent-reset
fix: reset response sent state between batched GraphQL queries
2026-04-13 11:35:22 +05:45
Damodar LohaniandGitHub 1e65f075e6 Merge branch '1.9.x' into fix/graphql-batch-sent-reset 2026-04-13 11:23:23 +05:45
Jake BarnbyandGitHub 68725d9262 Merge pull request #11860 from appwrite/fix-cache-fallback
(fix): cache fallback
2026-04-13 17:34:51 +12:00
Damodar LohaniandClaude Opus 4.6 5b805d686b fix: reset response sent state between batched GraphQL queries
utopia-php/http 0.34.20 added a guard that skips the action if
$response->isSent() is true. In batched GraphQL requests the resolver
reuses a single Response across all queries — after the first query's
action calls send(), subsequent queries hit the guard, their actions
are skipped, and stale/null payloads are returned.

Add Response::clearSent() to the Appwrite Response subclass (which can
access the protected $sent property from the parent) and call it in
Resolvers::resolve() before each execute(). This ensures each batched
query gets a fresh sent state while keeping the guard active for normal
request paths.

Also bumps utopia-php/http from 0.34.19 to 0.34.20 so CE CI tests
against the same version used by downstream consumers (cloud).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 05:32:04 +00:00
Chirag AggarwalandGitHub 584acafb1d Merge branch '1.9.x' into feat-services-protocols-apis 2026-04-13 10:45:42 +05:30
Chirag AggarwalandGitHub dce7856b77 Merge pull request #11848 from appwrite/fix/spec-generator-console-pr82 2026-04-13 10:40:47 +05:30
Chirag Aggarwal a6af609317 Remove scopes spec override, now fixed at source in #11839 2026-04-13 10:33:46 +05:30
Chirag Aggarwal 035f6244e1 Revert "fix: require scopes for project keys"
This reverts commit 8deafcaf4d52a59cc2e1b27c7a128e8b7843afa4.
2026-04-13 10:33:46 +05:30
Chirag Aggarwal 723cb1a488 fix: require scopes for project keys 2026-04-13 10:33:46 +05:30
Chirag Aggarwal 815209ebb0 fix: address sdk spec review feedback 2026-04-13 10:33:46 +05:30
Chirag Aggarwal 53c74582fc refactor: simplify request parameter spec overrides 2026-04-13 10:33:46 +05:30
Chirag Aggarwal 78bbe77580 fix: align project sdk spec generation 2026-04-13 10:33:45 +05:30
0c3871a681 fix: pass response to Http::execute() in GraphQL resolver (#11876)
Http::execute() now requires a Response parameter as of utopia-php/http
0.34.20. The GraphQL resolver was only passing route and request,
causing all GraphQL queries to fail with "Internal server error".

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:33:32 +12:00
Chirag AggarwalandGitHub b59eba4ec6 Merge pull request #11874 from appwrite/fix/11763-installer-compose-executor 2026-04-13 08:52:39 +05:30
Damodar LohaniandGitHub c6e32940f4 Merge branch '1.9.x' into CLO-4175-allow-delete-with-memberships 2026-04-13 07:21:38 +05:45
Chirag Aggarwal cb4c97f2ee chore: remove installer compose regression test 2026-04-12 14:11:51 +05:30
Chirag Aggarwal 2ee2ea09a0 fix(installer): sync compose template executor image 2026-04-12 13:56:49 +05:30
Chirag AggarwalandGitHub a31cb67ec5 Merge branch '1.9.x' into chore-migrate-audits-certificates-screenshots-to-publishers 2026-04-11 22:56:10 +05:30
Chirag AggarwalandGitHub 4ec84a147f Merge pull request #11861 from appwrite/fix-edge-pzj-rule-deployment-resource-type-optional
Make rule deploymentResourceType optional for non-deployment rules
2026-04-11 22:08:17 +05:30
Chirag AggarwalandGitHub e583de4650 Merge pull request #11858 from appwrite/fix-cve-2026-40194-phpseclib-bump
Bump phpseclib to 3.0.51 for CVE-2026-40194
2026-04-11 22:07:47 +05:30
Chirag Aggarwal 98af2a5eb3 fix: make rule deploymentResourceType optional 2026-04-11 22:05:01 +05:30
Jake BarnbyandClaude Opus 4.6 e3ad0f85de fix: narrow cache try-catch to avoid swallowing query exceptions
Wrap only cache load/save calls in try-catch instead of the entire
cache block. This prevents OrderException, QueryException, and Timeout
from $find() being caught and retried, which would double DB calls and
hide real query errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 03:42:47 +12:00
Jake Barnby a26382ac51 (chore): lockfile 2026-04-12 03:25:07 +12:00
Jake Barnby 9ba182d8a0 (fix): cache fallback 2026-04-12 03:22:21 +12:00
Matej Bačo 27fc8058b9 Fix failing tests 2026-04-11 14:19:05 +02:00
Matej Bačo c5bd8c712f Upgrade libs 2026-04-11 11:31:35 +02:00
Matej Bačo a1267b1bff Backwards compatibiltiy tests 2026-04-11 11:16:43 +02:00
Matej Bačo 18d17ea945 Webhook endpoints quality improvements 2026-04-11 11:00:56 +02:00
Matej Bačo fabd9559c4 Tests for backwards compatibility 2026-04-11 10:22:03 +02:00
Matej Bačo ec637d4417 Mark key scopes required 2026-04-11 10:19:14 +02:00
Chirag Aggarwal 5ecd15a5f5 fix: register certificate publisher in cli 2026-04-11 09:07:51 +05:30
Chirag Aggarwal 4523e86b91 fix: bump phpseclib to 3.0.51 2026-04-11 09:01:42 +05:30
Chirag Aggarwal ec5472f1ed chore: remove unrelated queue resources 2026-04-11 08:57:06 +05:30
Chirag AggarwalandGitHub 9ae804f8ae Merge branch '1.9.x' into chore-migrate-audits-certificates-screenshots-to-publishers 2026-04-11 08:49:23 +05:30
Luke B. SilverandGitHub 1ea108c2ce Merge pull request #11854 from appwrite/feat/remove-err
feat: remove error logs
2026-04-10 14:18:33 +01:00
loks0n 0a864e51b8 feat: remove error logs 2026-04-10 14:17:24 +01:00
ArnabChatterjee20k 0cce480592 typo 2026-04-10 17:23:43 +05:30
ArnabChatterjee20k b622b092a8 updated 2026-04-10 17:22:22 +05:30
ArnabChatterjee20k 96fe989f6d update composer dependencies and remove obsolete log classes 2026-04-10 17:15:31 +05:30
Chirag Aggarwal 0de26be6e6 chore: address review feedback 2026-04-10 16:44:24 +05:30
Chirag Aggarwal dc0a5c88b7 refactor: migrate audits certificates screenshots to publishers 2026-04-10 16:44:00 +05:30
Chirag AggarwalandGitHub c6dd7de216 Merge pull request #11850 from appwrite/chore-migrate-selected-queues-to-publishers
Migrate executions, migrations, and stats resources to publishers
2026-04-10 15:13:15 +05:30
Chirag Aggarwal f77a64bff9 chore: address publisher PR nits 2026-04-10 14:00:57 +05:30
ArnabChatterjee20k d13dbae0fe added allowance of empty payload for documentsdb 2026-04-10 13:59:45 +05:30
Luke B. SilverandGitHub f552a1ba15 Merge pull request #11844 from appwrite/feat/storage-cache-spans
feat: add tracing spans for storage file preview timing and cache state
2026-04-10 09:18:42 +01:00
Chirag Aggarwal 7282c5d51f chore: remove unused execution exclusion 2026-04-10 13:25:32 +05:30
Chirag Aggarwal 82ec75d582 chore: address PR review feedback 2026-04-10 13:12:08 +05:30
Chirag Aggarwal 6bf6142667 refactor: migrate selected queues to publishers 2026-04-10 13:02:00 +05:30
Chirag AggarwalandGitHub 938e65cb02 Merge pull request #11831 from appwrite/codex/remove-realtime-http-dependency
Use dedicated connection resources in realtime
2026-04-10 12:54:45 +05:30
ArnabChatterjee20kandGitHub 114de91f48 Merge pull request #11767 from appwrite/realtime-query-message-payload
Realtime query message payload
2026-04-10 12:06:00 +05:30
ArnabChatterjee20k 2e6f3f5c14 typo 2026-04-10 11:13:03 +05:30
ArnabChatterjee20k 7b3d9bae03 updated authorization 2026-04-10 11:04:44 +05:30
Chirag Aggarwal d81a1154e3 refactor: isolate realtime connection resources 2026-04-10 10:19:41 +05:30
Chirag Aggarwal a944c65660 refactor: move worker message resources 2026-04-10 09:43:32 +05:30
Chirag Aggarwal 856046dc82 shrink the size 2026-04-10 09:28:17 +05:30
Chirag Aggarwal 2ca551123d use connection container 2026-04-10 09:25:00 +05:30
Chirag Aggarwal c861d45749 Merge branch '1.9.x' into codex/remove-realtime-http-dependency 2026-04-10 09:02:23 +05:30
loks0nandClaude Sonnet 4.6 4a43969da9 fix: use consistent dot notation for all storage span attribute names
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 19:53:20 +01:00
loks0nandClaude Sonnet 4.6 6fa4122910 fix: rename storage span attributes to use dot notation for ids
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 19:52:25 +01:00
loks0nandClaude Sonnet 4.6 1d27101770 feat: add tracing spans for storage file preview timing and cache state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 19:49:57 +01:00
Luke B. SilverandGitHub 9214decc8d Merge pull request #11843 from appwrite/fix/session-mails
fix: set project on mail queue in session mails listener
2026-04-09 18:30:24 +01:00
loks0nandClaude Sonnet 4.6 ee4ae3bd47 fix: set project on mail queue in session mails listener
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 18:26:07 +01:00
Luke B. SilverandGitHub 53a114e7e4 Merge pull request #11842 from appwrite/fix/storage-cache
fix: storage preview cache misses and stale cache eviction
2026-04-09 17:27:27 +01:00
loks0nandClaude Sonnet 4.6 f2df9cb93a fix: storage preview cache misses and stale cache eviction
Three bugs causing storage preview cache to be ineffective:

1. Cache keys included the `token` auth parameter, so requests using
   resource tokens always generated unique keys and never hit cache.
   Introduced `cache.params` label for routes to opt-in specific params
   into the cache key; preview now declares only the transform params.

2. Cache hits never refreshed `accessedAt` in the DB or the filesystem
   file mtime, because `$response->send()` in the init hook skips the
   shutdown hook. After 30 days the maintenance job evicted still-active
   cache entries, and after the original 30-day filesystem TTL the cache
   file expired — causing periodic full re-renders. The cache-hit path
   now updates both on the APP_CACHE_UPDATE (24h) interval.

3. `updateDocument` in the preview action passed the full file document
   instead of a sparse one when updating `transformedAt`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 17:05:14 +01:00
Matej Bačo d69726487e PR review fixes 2026-04-09 16:58:42 +02:00
Matej Bačo 5fccb8cc28 Improve tests 2026-04-09 16:57:44 +02:00
Luke B. SilverandGitHub f371237fd5 Merge pull request #11642 from appwrite/feat/mails-listener
feat: extract session alert email into Mails listener
2026-04-09 15:29:16 +01:00
Matej Bačo 21a0d60c98 Fix tests 2026-04-09 16:13:54 +02:00
Matej Bačo 4eb8534294 Fix tests 2026-04-09 16:08:11 +02:00
Matej Bačo c95f905bce New services and protocols tests 2026-04-09 15:58:28 +02:00
Matej Bačo 0293da1e22 Improve test for backwards compatibility 2026-04-09 15:54:00 +02:00
Matej Bačo a4a0c4175d Implement new endpoints in /v1/project for services and protocols 2026-04-09 15:45:06 +02:00
Harsh MahajanandGitHub 9548d18a3e Merge pull request #11838 from appwrite/ix-missing-worker-executions-template
fix(installer): add missing worker-executions service to compose template
2026-04-09 19:06:42 +05:30
Matej Bačo d3c73fbb49 Add endpoints to control protocols and services 2026-04-09 15:34:50 +02:00
Harsh MahajanandGitHub 7928175387 Merge branch '1.9.x' into ix-missing-worker-executions-template 2026-04-09 18:53:39 +05:30
Matej Bačo 8818187740 Introduce req&res filters for 1.9.1 2026-04-09 15:21:58 +02:00
Matej Bačo 75324b24fc Improve skill 2026-04-09 15:21:24 +02:00
Matej Bačo d6d118f4ab Bump version to 1.9.1 2026-04-09 15:19:58 +02:00
Matej Bačo e998739998 Add agent skill to increase patch version 2026-04-09 15:18:26 +02:00
loks0nandClaude Sonnet 4.6 dd29967e99 refactor: tighten Mails listener with guard clauses and lean event
- SessionCreated event now carries only domain data (no isFirstSession)
- Mails listener uses ordered guard clauses, deferring the DB query
  until cheaper checks pass
- Drop $user Document allocation in favour of direct array access
- Inline FileName validator and $smtpEnabled into their use sites
- Extract $isBranded to eliminate duplicate APP_BRANDED_EMAIL_BASE_TEMPLATE check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:01:13 +01:00
loks0nandClaude Sonnet 4.6 e7f5ae9306 fix: remove stale @param platform from SessionCreated docblock
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:01:13 +01:00
loks0nandClaude Sonnet 4.6 4133ec99ae feat: extract session alert email into Mails listener
Moves session alert email side effect out of the account controller
into a dedicated `Mails` listener that reacts to a new `SessionCreated`
bus event. The event is now always dispatched on session creation; the
listener owns all conditional logic (first session, sessionAlerts flag,
email-link sessions, user email presence).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:01:13 +01:00
Matej BačoandGitHub 6dd63ee152 Merge pull request #11655 from appwrite/copilot/add-test-for-deleting-partially-uploaded-file
Fix deletion of partially-uploaded (pending) files
2026-04-09 14:58:57 +02:00
ArnabChatterjee20k 912dbda159 updated type 2026-04-09 18:16:09 +05:30
ArnabChatterjee20k 410a050244 updated 2026-04-09 18:04:01 +05:30
ArnabChatterjee20k 9cf45816c2 added triggering stats for messaging based subscription during the start 2026-04-09 17:38:25 +05:30
Harsh MahajanandGitHub 386fc995e6 Update compose.phtml 2026-04-09 17:36:07 +05:30
Matej Bačo d6451b8fad Merge branch '1.9.x' into copilot/add-test-for-deleting-partially-uploaded-file 2026-04-09 14:02:06 +02:00
ArnabChatterjee20kandGitHub 920ddd18e6 Merge branch '1.9.x' into realtime-query-message-payload 2026-04-09 17:24:57 +05:30
Harsh MahajanGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
ff9334ab78 Update app/views/install/compose.phtml
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-09 17:20:55 +05:30
Harsh MahajanandGitHub c494e96f79 Merge branch '1.9.x' into ix-missing-worker-executions-template 2026-04-09 17:18:22 +05:30
harsh mahajan fd78f0f7df fix(installer): add missing worker-executions service to compose template 2026-04-09 17:13:54 +05:30
ArnabChatterjee20k b3c6e0e67f removed size from the conditions 2026-04-09 17:10:47 +05:30
ArnabChatterjee20k 5c0c0b44c0 Merge remote-tracking branch 'origin/big-int' into big-int 2026-04-09 17:08:09 +05:30
ArnabChatterjee20k 4f599fc75c updated 2026-04-09 17:07:47 +05:30
Chirag AggarwalandGitHub 4ff10dcacc Merge pull request #11837 from appwrite/fix-11795-revert-project-variableid
Revert "fix: make variableId optional in createProjectVariable endpoint"
2026-04-09 16:53:10 +05:30
Chirag Aggarwal 7a995fe759 Revert "Merge pull request #11795 from rathi-yash/fix-11765-global-variable-creation"
This reverts commit 597b20a6cb, reversing
changes made to 20f80ac067.
2026-04-09 16:25:59 +05:30
ArnabChatterjee20kandGitHub 928aa9c909 Merge branch '1.9.x' into big-int 2026-04-09 14:43:51 +05:30
ArnabChatterjee20k 0ea8379065 added the changes 2026-04-09 14:11:21 +05:30
Damodar LohaniandGitHub 227606baef Merge pull request #11835 from appwrite/fix/map-deprecated-platform-types-in-project-response
fix: map deprecated platform types in project response
2026-04-09 14:23:31 +05:45
Damodar LohaniandClaude Opus 4.6 f315e759f3 fix: map deprecated platform types in subQueryPlatforms filter
The subQueryPlatforms database filter loads platforms as a sub-attribute
when project documents are fetched. Old platform type values stored in
the database (e.g. flutter-android, flutter-ios) were not being mapped
to the new consolidated types before being included in the project
response sent to the frontend/console.

This adds Platform::mapDeprecatedType() to the filter so all platforms
returned as part of a project document have their types mapped
consistently, complementing the existing mapping in the dedicated
platform Get and List endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 08:31:39 +00:00
Damodar LohaniandGitHub 8a6fba75a5 Merge pull request #11833 from appwrite/fix/deprecated-platform-origin-validation
fix: Add backwards-compat origin validation for deprecated platform types
2026-04-09 13:41:30 +05:45
Damodar LohaniandClaude Opus 4.6 196c76ac70 fix: map deprecated platform types in read endpoints for backwards compatibility
Old platform types stored in the database (e.g. flutter-web, apple-ios,
react-native-android) are now mapped to the new consolidated types (web,
apple, android, windows, linux) before being sent in API responses. This
ensures the response models' $conditions correctly select the right model
for each platform document.

Adds Platform::mapDeprecatedType() as a reusable static method and applies
the mapping in both Get and XList platform endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 07:47:42 +00:00
Damodar LohaniandClaude Opus 4.6 7f82484436 remove migration changes from V24.php
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 07:36:58 +00:00
Damodar LohaniandClaude Opus 4.6 db18caf739 fix: add backwards-compatible origin validation for deprecated platform types
Platform::getHostnames() and Platform::getSchemes() only handled the new
consolidated type names, causing "invalid origin" errors for projects still
using old granular types (flutter-*, apple-*, react-native-*, unity) stored
in the database. Add switch fall-through cases for all deprecated type values
and a V24 migration to convert old types to their new equivalents.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 07:31:58 +00:00
ArnabChatterjee20k 0c734f8f12 updated 2026-04-09 12:02:13 +05:30
Chirag AggarwalandGitHub 6693e28c93 Merge pull request #11830 from appwrite/feat/specs-task-no-http
remove HTTP server bootstrap from specs task
2026-04-09 11:46:37 +05:30
ArnabChatterjee20k 229ab28427 Merge remote-tracking branch 'origin/1.9.x' into big-int 2026-04-09 11:15:59 +05:30
ArnabChatterjee20k 45dc259df3 updated validator 2026-04-09 11:14:28 +05:30
Chirag Aggarwal bf489ce13b Fix requestRoutePath fallback 2026-04-09 11:09:21 +05:30
Chirag Aggarwal 357d6482f9 Remove realtime HTTP app dependency 2026-04-09 10:52:31 +05:30
Chirag Aggarwal eec72a915a inline specs container setup 2026-04-09 10:45:24 +05:30
Chirag Aggarwal ae29e4c424 drop specs task format test 2026-04-09 10:43:39 +05:30
Chirag Aggarwal 05dc264df9 remove specs task HTTP bootstrap 2026-04-09 10:41:04 +05:30
Chirag AggarwalandGitHub 597b20a6cb Merge pull request #11795 from rathi-yash/fix-11765-global-variable-creation
fix: make variableId optional in createProjectVariable endpoint
2026-04-09 09:34:39 +05:30
Chirag AggarwalandGitHub 6b7d9853f9 Merge branch '1.9.x' into fix-11765-global-variable-creation 2026-04-09 09:24:52 +05:30
Jake Barnby 8412790436 Merge remote-tracking branch 'origin/1.9.x' into feat-dedicated-db 2026-04-09 14:02:49 +12:00
Damodar LohaniandGitHub d6f51a96a5 Merge branch '1.9.x' into CLO-4175-allow-delete-with-memberships 2026-04-09 07:32:30 +05:45
Damodar LohaniandGitHub 20f80ac067 Merge pull request #11580 from appwrite/feat-audit-user-type-distinction
feat: distinguish user types in audit logs
2026-04-09 06:55:43 +05:45
My Name 8bfa659120 test: add test case for createProjectVariable without variableId 2026-04-08 15:18:13 -04:00
Yash RathiandGitHub 65e44d76a1 Merge branch '1.9.x' into fix-11765-global-variable-creation 2026-04-08 14:50:58 -04:00
Jake BarnbyandGitHub bb07808661 Merge pull request #11820 from appwrite/fix-ttl-message 2026-04-09 03:39:48 +12:00
Luke B. SilverandGitHub d18550ff54 Merge pull request #11826 from appwrite/fix/cookie-test-hack
fix: use cURL cookie engine for RFC 6265 compliant cookie handling in e2e client
2026-04-08 16:17:04 +01:00
loks0nandClaude Sonnet 4.6 84dc921d41 fix: replace utopia-php/framework with http, fix RFC 6265 cookie handling
utopia-php/framework was the old name for utopia-php/http. Replacing it
with utopia-php/http 0.34.19 which fixes getCookie() to use Swoole's
native cookie store (populated via php_raw_url_decode) instead of
re-parsing the raw Cookie header without URL-decoding.

This fixes a production auth bug where Swoole's setcookie() URL-encodes
base64 session values (+ → %2B, / → %2F, = → %3D) in Set-Cookie headers.
RFC 6265 clients (Dart, Swift) reflect these verbatim; the old getCookie()
returned %2B/%2F/%3D to base64_decode() which produced corrupted output,
rejecting valid sessions.

Also updates the e2e test client to use cURL's built-in RFC 6265 cookie
engine (CURLOPT_COOKIEFILE) instead of parse_str() which silently
URL-decoded values, masking the bug in tests. Adds a cookie roundtrip
assertion to testCreateAccountSession.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 15:07:46 +01:00
loks0nandClaude Sonnet 4.6 e2d7dd837d fix: use cURL cookie engine instead of parse_str for RFC 6265 compliance
parse_str() URL-decodes cookie values, causing the test client to behave
differently from real clients (Dart, Swift) which store values verbatim
per RFC 6265. This masked a production bug where base64 session values
containing %3D%3D would fail to decode on real devices.

Replaces the manual Set-Cookie header parsing with cURL's built-in cookie
engine (CURLOPT_COOKIEFILE='') and reads cookies via CURLINFO_COOKIELIST,
which stores and returns values verbatim without any decoding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 15:07:46 +01:00
Jake BarnbyandGitHub 9feb204fd5 Merge pull request #11611 from appwrite/feat-x-oauth2-provider 2026-04-09 01:32:31 +12:00
Jake Barnby 3880f181b3 fix(databases): propagate purge parameter to documentsdb updateCollection 2026-04-09 01:19:18 +12:00
Harsh Mahajan 44a37e9e20 Use Exception for X OAuth2 PKCE encryption errors
Align with other OAuth2 adapters that throw base Exception for
configuration and crypto failures instead of RuntimeException.

Made-with: Cursor
2026-04-08 18:41:42 +05:30
Harsh Mahajan e6cfedd340 addressed greptile comment 2026-04-08 18:27:36 +05:30
Harsh Mahajan e4d1178e71 simplified code 2026-04-08 17:56:37 +05:30
Harsh Mahajan 929bdcef25 Merge branch '1.9.x' into feat-x-oauth2-provider 2026-04-08 17:55:00 +05:30
Harsh Mahajan 3f725c6be9 changes 2026-04-08 17:44:49 +05:30
Jake BarnbyGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
990a32dd9e Update src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-08 23:31:55 +12:00
Jake Barnby c5b8ed9cc1 feat(databases): cache list responses without requiring a select query 2026-04-08 23:02:34 +12:00
Matej BačoandGitHub fec573d23b Merge pull request #11821 from appwrite/chore-public-project-api
Chore: Mark project response format sub-formats as public
2026-04-08 12:32:58 +02:00
Matej Bačo a144968d70 Fix formatting 2026-04-08 12:08:32 +02:00
Matej Bačo 6fa0724404 Mark project response format sub-formats as public 2026-04-08 11:52:12 +02:00
Jake Barnby b1ce71e6b0 (chore): fmt 2026-04-08 21:35:49 +12:00
Jake Barnby 939092726c test(databases): add regression for purge=true list cache invalidation 2026-04-08 21:31:42 +12:00
Jake Barnby c6f8599c75 feat(databases): add purge parameter to updateCollection and updateTable 2026-04-08 21:31:38 +12:00
Jake Barnby 91d8519940 refactor(databases): restructure list response cache and clarify ttl description 2026-04-08 21:31:34 +12:00
Matej BačoandGitHub a90f79f1c1 Merge pull request #11650 from appwrite/feat-public-project-keys
Feat: Public keys API
2026-04-08 11:15:04 +02:00
Matej Bačo f880b6e8c3 Fix failing tests 2026-04-08 10:52:20 +02:00
Matej Bačo b8d65326e6 Fix failing tests 2026-04-08 10:34:18 +02:00
Matej Bačo a9fd82e406 New tests 2026-04-08 10:32:20 +02:00
Matej Bačo a8c2491fbb Fix platform scopes 2026-04-08 10:17:48 +02:00
Jake BarnbyandGitHub 7f6486ec80 Merge pull request #11762 from bhardwajparth51/fix-10923-realtime-atomic-payload 2026-04-08 20:16:55 +12:00
Matej Bačo 388cec1737 Merge branch '1.9.x' into feat-public-project-keys 2026-04-08 10:16:22 +02:00
Parth BhardwajandGitHub 2f5a49a37d Merge branch '1.9.x' into fix-10923-realtime-atomic-payload 2026-04-08 13:39:54 +05:30
Matej BačoandGitHub 1f93184c42 Merge pull request #11615 from appwrite/feat-public-platform-api
Feat: public platform API
2026-04-08 10:08:10 +02:00
Jake BarnbyandGitHub 7d9cf48ca3 Merge pull request #11750 from appwrite/bump-database-version2 2026-04-08 20:04:25 +12:00
Matej Bačo eef2a7abdf Fix scopes 2026-04-08 10:01:52 +02:00
Matej Bačo c7a022ba43 Simplify after discussions 2026-04-08 09:54:57 +02:00
Matej Bačo 96a84a8fd7 Merge branch '1.9.x' into feat-public-project-keys 2026-04-08 09:45:15 +02:00
Matej Bačo cea242c66f Merge branch '1.9.x' into feat-public-platform-api 2026-04-08 09:29:54 +02:00
Matej Bačo ce4eb563b3 AI review fixes 2026-04-08 09:29:12 +02:00
Matej Bačo 2307d637fb Revert new patch version 2026-04-08 09:10:02 +02:00
premtsd-codeandGitHub 0aa72aafae Merge branch '1.9.x' into bump-database-version2 2026-04-08 11:32:22 +05:30
Chirag AggarwalandGitHub 6e1f0d57af Merge pull request #11817 from appwrite/fix/specs-unresolved-model
fix: throw RuntimeException for unresolved response models in spec generation
2026-04-08 11:08:30 +05:30
Chirag Aggarwal 62b6ef06e6 fix: add swoole extension to specs CI job 2026-04-08 10:49:50 +05:30
Chirag Aggarwal f5ab593261 fix: make Project model public for server SDK spec generation
The project.updateLabels route uses AuthType::KEY which makes it
available on the server platform, but the Project model had public=false
causing it to be filtered out during spec generation.
2026-04-08 10:47:37 +05:30
Chirag Aggarwal dd4a43b78c fix: throw RuntimeException for unresolved response models in spec generation
Spec generation silently produced a fatal error when a response model
string could not be resolved to a registered model object. Now throws a
clear RuntimeException in both Swagger2 and OpenAPI3 formats, for both
single and array model responses.

Also adds a CI job to run spec generation on every PR so unresolved
models are caught before merge.
2026-04-08 10:41:43 +05:30
premtsd-codeandGitHub c4c56283e5 Merge branch '1.9.x' into bump-database-version2 2026-04-08 10:33:58 +05:30
Chirag AggarwalandGitHub f71a98a527 Merge pull request #11816 from appwrite/fix/email-session-cache-purge
fix: persist session before purging user cache in email/password login
2026-04-08 10:25:19 +05:30
premtsd-codeandGitHub 2258668769 Merge branch '1.9.x' into bump-database-version2 2026-04-08 10:15:53 +05:30
Chirag Aggarwal 6dba407aed test: add E2E test for email/password session cache race condition
Adds testEmailPasswordSessionNotCorruptedByConcurrentRequests which
reproduces the cross-worker Redis cache race that caused 401s after
login. The test fires a login request, waits for it to reach the cache
purge point, then injects concurrent GET /v1/account requests that
re-cache a stale user document. Verifies the new session is immediately
usable.

Fails against the old ordering (purge before create), passes with the
fix (create before purge).
2026-04-08 10:10:16 +05:30
Luke B. SilverandGitHub 7eef845556 Merge pull request #11794 from blueberry-adii/doc-11793-fix-readme-auth-link
fix appwrite auth broken link in readme
2026-04-07 22:15:29 +01:00
loks0nandClaude Sonnet 4.6 7781d377ae fix: persist session before purging user cache in email/password login
Swap the order of createDocument('sessions') and purgeCachedDocument('users')
in the email/password session creation flow. Previously, the cache was purged
before the session was written, opening a race window in Swoole's async
environment where a concurrent account.get() could re-cache the user with no
sessions, causing sessionVerify to fail with a 401. This matches the correct
ordering already used by the token-based flows (magic URL, OTP, phone).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 21:44:24 +01:00
Chirag AggarwalandGitHub 59bf4663ca Merge pull request #11808 from appwrite/fix/sdk-dev-branch-sync
fix: reset SDK dev branch to base branch before pushing
2026-04-08 00:40:56 +05:30
premtsd-codeandGitHub 6653dbbb92 Merge branch '1.9.x' into bump-database-version2 2026-04-07 20:47:13 +05:30
Prem Palanisamy d7d20ccb29 Remove (int) cast from setTenant in getDatabasesDB same-pool branch 2026-04-07 15:35:20 +01:00
Yash RathiandGitHub 2d54986b09 Merge branch '1.9.x' into fix-11765-global-variable-creation 2026-04-07 10:25:08 -04:00
Matej Bačo 9ea0b2bc2f formatting fix 2026-04-07 15:56:54 +02:00
Matej Bačo 715727853b Fix unit test 2026-04-07 15:56:42 +02:00
Matej Bačo 43d4f709d5 Revert composer changes 2026-04-07 15:00:03 +02:00
Matej Bačo f40050fe6f Revert lockfile changes (failing tests) 2026-04-07 14:51:40 +02:00
Prem Palanisamy 308a534d98 Merge branch 'bump-database-version2' of https://github.com/appwrite/appwrite into bump-database-version2 2026-04-07 13:16:56 +01:00
Prem Palanisamy 35a72c4f08 Remove (int) cast from setTenant in separate-pool branches 2026-04-07 13:10:16 +01:00
Matej Bačo 34dfcba45c Linter fix 2026-04-07 14:08:16 +02:00
Chirag AggarwalandGitHub cb1e31bcd4 Merge pull request #11814 from appwrite/codex/bump-utopia-framework-0-34-18
[codex] Bump utopia-php/framework to 0.34.18
2026-04-07 17:38:06 +05:30
Matej Bačo 3e4e7fc0cd Merge branch '1.9.x' into feat-public-platform-api 2026-04-07 14:08:00 +02:00
ArnabChatterjee20kandGitHub da2444ede2 Merge branch '1.9.x' into realtime-query-message-payload 2026-04-07 17:36:29 +05:30
ArnabChatterjee20k bc224de751 Add userId to connection info in Realtime adapter and simplify userId fetching 2026-04-07 17:35:48 +05:30
Chirag Aggarwal 9403e4d65d Bump utopia-php/framework to 0.34.18 2026-04-07 17:35:32 +05:30
Matej BačoandGitHub 07a49835a6 Merge pull request #11812 from appwrite/chore-migrate-labels-api
Chore: Migrat elabels API
2026-04-07 14:05:31 +02:00
Jake Barnby fdca14e659 (feat): precomputed guage write in stats resources 2026-04-07 23:45:59 +12:00
Matej Bačo d32de6f217 Improve tests 2026-04-07 13:30:35 +02:00
Matej Bačo 9b00ce4f1d Add new tests 2026-04-07 13:28:35 +02:00
ArnabChatterjee20k ca62504b5a Enhance realtime message handling to support initial connection payload and improve query subscription logic 2026-04-07 16:55:10 +05:30
Jake Barnby e3b1eead55 Merge remote-tracking branch 'origin/1.9.x' into feat-dedicated-db
# Conflicts:
#	app/config/collections/projects.php
2026-04-07 23:24:08 +12:00
Matej Bačo 8f6c8f9d8d Migrate to new endpoint 2026-04-07 13:21:36 +02:00
Matej Bačo 23fcb284a1 Fix backwards QA 2026-04-07 12:46:25 +02:00
premtsd-codeandGitHub ffdd0815a7 Merge branch '1.9.x' into bump-database-version2 2026-04-07 15:56:47 +05:30
Prem Palanisamy 4260324153 Merge branch '1.9.x' into bump-database-version2
Resolve merge conflicts in app/init/resources.php and app/worker.php
caused by the DI container migration (Http::setResource/Server::setResource
to $container->set). Port separate-pool shared tables logic for
getDatabasesDB to the new file locations (request.php and message.php)
with the correct $databaseDSN->getParam('namespace') fix.
2026-04-07 11:25:06 +01:00
Matej Bačo d66813d3cf Fix tests + QA fixes 2026-04-07 12:07:21 +02:00
Chirag AggarwalandGitHub 0ce92da41f Merge pull request #11810 from appwrite/codex/fix-post-merge-test-regressions
[codex] Fix post-merge e2e test regressions
2026-04-07 15:10:53 +05:30
Chirag Aggarwal e8ef4e40d7 fix post-merge e2e test regressions 2026-04-07 15:05:07 +05:30
Chirag AggarwalandGitHub d200b07466 Merge pull request #11809 from appwrite/codex/fix-console-null-route
[codex] Handle null console routes in API middleware
2026-04-07 14:42:08 +05:30
Chirag Aggarwal 92abfb31aa fix null route guard placement 2026-04-07 14:40:18 +05:30
Chirag Aggarwal 6c56eee0f4 test console route not found error type 2026-04-07 14:39:48 +05:30
Chirag Aggarwal 399c37d943 fix console null route handling 2026-04-07 14:33:43 +05:30
Matej Bačo f3acadd53c Fix linter 2026-04-07 10:43:57 +02:00
Matej Bačo 331fcee710 Merge branch '1.9.x' into feat-public-platform-api 2026-04-07 10:43:11 +02:00
Chirag AggarwalandGitHub 951a96ae01 Merge pull request #11564 from appwrite/feat/migrate-di-container
feat: migrate from static Http::setResource() to DI Container
2026-04-07 13:58:10 +05:30
Damodar LohaniandGitHub ed1680837d Merge branch '1.9.x' into feat-audit-user-type-distinction 2026-04-07 14:11:22 +05:45
Chirag Aggarwal 7864a5b9d1 fix: use --force-with-lease on SDK dev branch push
After resetting dev to the base branch, the remote dev may have
diverged history from squash merges. A plain push would be rejected
as non-fast-forward. Using --force-with-lease safely overwrites
the remote since we just fetched.
2026-04-07 12:52:09 +05:30
Chirag Aggarwal f11bd7ce0e fix: reset SDK dev branch to base branch before pushing
The dev branch was being reset to origin/dev, which retains stale
commits after squash merges. This caused recurring merge conflicts
and inflated PR diffs. Using `checkout -B dev <baseBranch>` ensures
dev always starts fresh from the default branch.
2026-04-07 12:50:18 +05:30
Parth BhardwajandGitHub fffc6795a7 Merge branch '1.9.x' into fix-10923-realtime-atomic-payload 2026-04-07 08:49:25 +05:30
Damodar LohaniandGitHub 8442a1e612 Merge branch '1.9.x' into CLO-4175-allow-delete-with-memberships 2026-04-07 06:27:57 +05:45
Shimon NewmanandGitHub ed91549681 Merge pull request #11805 from appwrite/revert-schedule-functions-updates
revert ScheduleFunctions.php updates
2026-04-06 22:38:05 +03:00
shimon 04173600ae revert ScheduleFunctions.php updates 2026-04-06 22:33:18 +03:00
ArnabChatterjee20k d5fe5c34af Validate subscribe payload format in realtime message handling 2026-04-06 17:14:12 +05:30
ArnabChatterjee20k 6bc9adece8 Refactor realtime message handling to send subscriber keys and add comprehensive tests for subscription message upsert behavior 2026-04-06 17:10:57 +05:30
ArnabChatterjee20k 97d46c6273 Remove redundant subscription removal call in realtime message handling 2026-04-06 16:58:08 +05:30
ArnabChatterjee20k 9d78a8e6b6 Add stats tracking for outbound subscription messages in realtime 2026-04-06 16:57:06 +05:30
ArnabChatterjee20k d12a6f5168 Refactor realtime message payload handling for improved validation and parsing 2026-04-06 16:41:42 +05:30
ArnabChatterjee20k 0f47e6ea28 Enhance subscription message documentation for clarity on upsertion behavior 2026-04-06 15:59:22 +05:30
premtsd-codeandGitHub 77ceb15d7d Merge branch '1.9.x' into bump-database-version2 2026-04-06 15:44:40 +05:30
ArnabChatterjee20k 592629587d Remove unused query assertion methods and improve comment clarity in RealtimeQueryBase 2026-04-06 14:07:25 +05:30
ArnabChatterjee20k 187fde4a4e Refactor realtime subscription handling and enhance query validation in tests 2026-04-06 14:05:42 +05:30
Chirag Aggarwal b74d4d45f9 Merge request-scoped cookie resources 2026-04-06 13:21:33 +05:30
Chirag AggarwalandGitHub aa9a58b44d Merge pull request #11799 from appwrite/codex/request-scoped-cookie-domain
Use request-scoped cookie domain resource
2026-04-06 13:18:27 +05:30
Chirag Aggarwal 59a773e9a0 Document migration host local-domain handling 2026-04-06 12:47:06 +05:30
Chirag Aggarwal e3053bb83d Remove dead cookie config defaults 2026-04-06 12:44:48 +05:30
Chirag Aggarwal 1f7fc4bd40 Use request-scoped domain verification 2026-04-06 12:43:05 +05:30
Chirag Aggarwal d1b59ff3f3 Remove unused cookie domain locals 2026-04-06 12:30:48 +05:30
Chirag Aggarwal 221b52bac0 Add request-scoped cookie domain resource 2026-04-06 12:30:25 +05:30
Chirag Aggarwal b8ed30db55 Fix CORS header override for analyze 2026-04-06 12:23:50 +05:30
Chirag AggarwalandGitHub be56317bf2 Merge branch '1.9.x' into feat/migrate-di-container 2026-04-06 12:13:31 +05:30
fogelito 31728c9b72 Update lock 2026-04-06 09:29:58 +03:00
fogelito 9d0fc9e5b1 Merge branch '1.9.x' of https://github.com/appwrite/appwrite into delete-project-shared-table-v1 2026-04-06 09:19:38 +03:00
Chirag AggarwalandGitHub 2dce141d17 Merge pull request #11798 from appwrite/codex/request-response-no-static-state
Remove request and response static state
2026-04-06 11:03:15 +05:30
Damodar LohaniandGitHub d421b5ff30 Merge pull request #11797 from appwrite/fix-cors-paused-project
fix: add CORS headers to error responses
2026-04-06 10:42:00 +05:45
Chirag Aggarwal b8eb0810c2 Make response sensitive mode instance-scoped 2026-04-06 10:24:32 +05:30
Chirag Aggarwal cb74a5756a Remove request and response static state 2026-04-06 10:20:18 +05:30
Chirag AggarwalandGitHub d1549b225b Merge branch '1.9.x' into feat/migrate-di-container 2026-04-06 08:35:58 +05:30
Damodar LohaniandClaude Opus 4.6 ba25849871 fix: resolve cors safely in error handler to avoid cascading failures
- Remove cors from inject chain; resolve via getResource() inside
  try-catch so DB failures don't cascade when resolving the cors
  resource dependency chain (cors -> allowedHostnames -> rule -> DB)
- Use override:true on addHeader to prevent duplicate CORS headers
  when init() already set them before the exception was thrown
- Degrades gracefully: if cors resolution fails, error response is
  sent without CORS headers (same behavior as before this PR)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 02:59:08 +00:00
Damodar LohaniandClaude Opus 4.6 44f3bbae03 fix: add CORS headers to error responses
The Http::error() handler was missing CORS headers, causing browsers to
block error responses (e.g. 403 PROJECT_PAUSED) with a generic CORS
error instead of showing the actual error message. This injects the cors
resource into the error handler and adds CORS headers before sending the
error response, matching the pattern already used in Http::init().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:40:07 +00:00
My Name 2cd91f509b fix: make variableId optional in createProjectVariable endpoint 2026-04-05 18:23:58 -04:00
Aditya a06ba57384 fix appwrite auth broken link in readme 2026-04-05 23:39:13 +05:30
Shimon NewmanandGitHub 9ac686ae31 Merge pull request #11790 from appwrite/schedule-functions-fix
Improve schedule function logic
2026-04-05 19:34:49 +03:00
Shimon NewmanandGitHub 7385bc1d9f Merge branch '1.9.x' into schedule-functions-fix 2026-04-05 19:22:44 +03:00
Chirag AggarwalandGitHub dffcfe8ee4 Merge branch '1.9.x' into feat/migrate-di-container 2026-04-05 21:16:30 +05:30
Chirag AggarwalandGitHub e3f373b8d2 Merge pull request #11772 from appwrite/codex/fix-vectordb-migration-flake
[codex] Fix flaky VectorsDB metadata bootstrap in migrations
2026-04-05 21:16:18 +05:30
Chirag AggarwalandGitHub 8b591f3522 Merge branch '1.9.x' into codex/fix-vectordb-migration-flake 2026-04-05 21:13:18 +05:30
Chirag Aggarwal 452440f3c0 fix: use released cli container support 2026-04-05 21:03:17 +05:30
Chirag AggarwalandGitHub 4e10cabec0 Merge pull request #11789 from appwrite/codex/fail-missing-spec-docs
[codex] Fail specs when referenced docs are missing
2026-04-05 20:58:24 +05:30
Chirag Aggarwal 5d1da00138 refactor: remove redundant desc guards 2026-04-05 20:12:25 +05:30
Chirag Aggarwal b236e2546b lock file 2026-04-05 20:10:00 +05:30
Chirag Aggarwal 412d09b801 remove unrelated changes 2026-04-05 20:06:13 +05:30
Chirag Aggarwal 5ab28ad99a docs: add missing json migration references 2026-04-05 19:52:48 +05:30
shimon 9be447aacf Update enqueue timer and improve schedule function logic
Reduced the ENQUEUE_TIMER constant from 60 seconds to 30 seconds. Modified the condition for currentTick to use less than or equal to (<=) instead of less than (<) for better accuracy in scheduling. Changed return statement to continue in case of missing schedule key to enhance flow control.
2026-04-05 17:20:31 +03:00
Chirag Aggarwal 66e68aea14 fix: fail specs when docs are missing 2026-04-05 19:37:29 +05:30
fogelito a332eb5f32 Merge branch '1.9.x' of https://github.com/appwrite/appwrite into delete-project-shared-table-v1 2026-04-05 11:42:25 +03:00
Damodar LohaniandClaude Opus 4.6 cc82b1a5cf fix: don't promote non-owners on account deletion, leave team orphaned instead
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 07:15:35 +00:00
Damodar LohaniandClaude Opus 4.6 ba32012744 fix: filter unconfirmed members from owner count, ownership transfer, and primary user transfer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 07:11:32 +00:00
fogelito 775d21e5ee lock 2026-04-05 10:08:29 +03:00
Damodar LohaniandClaude Opus 4.6 8f6530d1e7 fix: remove unused $userInternalId from closure
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 05:34:04 +00:00
Damodar LohaniandClaude Opus 4.6 4297c70f58 fix: address review feedback — safer orphan approach, veteran ordering, deduplicate transfer
- Remove team deletion for sole owner+sole member case; let orphan teams
  be cleaned up by Cloud's inactive project cleanup (safer, avoids
  accidental data loss)
- Add explicit ordering by $createdAt so the most veteran member gets
  ownership transfer, with limit(1) for clarity
- Remove confirm filter on primary user transfer in membership deletion
  so all members (including unconfirmed) are considered
- Remove redundant ownership transfer from Deletes worker since the API
  controller already handles it before queueing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 05:22:02 +00:00
Damodar LohaniandGitHub f6484294da Merge branch '1.9.x' into feat-audit-user-type-distinction 2026-04-05 08:04:38 +05:45
Damodar LohaniandClaude Opus 4.6 16ed60a5c3 Filter unconfirmed members when transferring team ownership
Prevent unconfirmed (pending invite) members from being promoted to
owner or set as the team's primary user during membership/account
deletion by adding a Query::equal('confirm', [true]) filter to the
relevant findOne queries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 02:02:09 +00:00
Damodar LohaniandClaude Opus 4.6 d831b93934 Allow deleting user account with active memberships
Instead of blocking account deletion when the user has confirmed team
memberships, handle memberships gracefully during deletion:

- Sole owner + sole member: delete the team and queue project cleanup
- Sole owner + other members: transfer ownership to the next member
- Non-owner / multiple owners: no special handling needed (worker cleans up)

Also update the Deletes worker to transfer the team's primary user
reference when removing a deleted user's memberships.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 01:43:05 +00:00
Chirag AggarwalandGitHub f3b9121a14 Merge branch '1.9.x' into feat/migrate-di-container 2026-04-04 15:57:12 +05:30
Eldad A. FuxandGitHub cf8519bfa5 Revise Appwrite description and product details
Updated the description of Appwrite to emphasize its open-source nature and capabilities. Added detailed product descriptions for Appwrite services.
2026-04-04 08:41:23 +02:00
Eldad A. FuxandGitHub 860cf8cd57 Revise README content and structure
Updated the README to enhance clarity and remove outdated information.
2026-04-04 08:32:46 +02:00
Chirag Aggarwal c978b6f34f Stabilize function deployment activation in tests 2026-04-03 23:58:25 +05:30
Chirag Aggarwal f3f2855fe5 Remove final formatting-only diff 2026-04-03 23:44:19 +05:30
Chirag Aggarwal 3cb53f0604 Drop unrelated formatting churn from VectorsDB fix 2026-04-03 23:43:51 +05:30
Chirag Aggarwal a5f45b46e9 Handle raced VectorsDB metadata bootstrap errors 2026-04-03 23:41:44 +05:30
bhardwajparth51 2c1813198d Simplify comments in Realtime E2E test 2026-04-03 22:12:24 +05:30
Chirag Aggarwal 130c2221ec Fix VectorsDB metadata bootstrap race 2026-04-03 22:12:21 +05:30
bhardwajparth51 912ea37af6 Address review feedback: Remove redundant Realtime triggers, correctly reorder hydration, and add E2E tests 2026-04-03 22:07:24 +05:30
Parth BhardwajandGitHub ccc74dea74 Merge branch '1.9.x' into fix-10923-realtime-atomic-payload 2026-04-03 20:28:49 +05:30
Torsten DittmannandGitHub c08e5f706c Merge pull request #11745 from appwrite/fix-yahoo-oauth-scopes 2026-04-02 19:44:10 +04:00
bhardwajparth51 a6c6a5624a fix: ensure realtime event payload is populated for all atomic subclasses 2026-04-02 21:04:01 +05:30
bhardwajparth51 ee5bb6d73d fix: ensure realtime event payload is populated for atomic operations 2026-04-02 20:47:23 +05:30
ArnabChatterjee20k bfbf180aee Refactor realtime message handling and enhance query validation tests 2026-04-02 18:32:27 +05:30
ArnabChatterjee20k d8a3b53641 Refactor code structure for improved readability and maintainability 2026-04-02 17:35:55 +05:30
Chirag Aggarwal 3018b478ba Fix database transaction and vectors migration flakiness 2026-04-02 17:25:13 +05:30
ArnabChatterjee20k df4dbcf607 updated user roles 2026-04-02 17:02:46 +05:30
Chirag Aggarwal 094fe90499 Merge remote-tracking branch 'origin/1.9.x' into feat/migrate-di-container
# Conflicts:
#	app/worker.php
2026-04-02 16:37:58 +05:30
Matej BačoandGitHub 58a90cca4a Merge pull request #10643 from appwrite/feat-disposable-emails
feat: add support for blocking disposable email addresses
2026-04-02 13:03:32 +02:00
ArnabChatterjee20k 29b0ebb3bd updated query subscription 2026-04-02 16:28:00 +05:30
ArnabChatterjee20k f0ccd1f586 added message based query payload to realtime 2026-04-02 15:56:56 +05:30
Matej Bačo 90e705f8c5 Improve docs 2026-04-02 12:26:03 +02:00
Matej Bačo 17076e4a00 Fix formatting 2026-04-02 11:55:32 +02:00
Matej Bačo 7c50bbc500 Merge branch '1.9.x' into feat-disposable-emails 2026-04-02 11:05:19 +02:00
Chirag Aggarwal 4df5f4a18f fix: scope composer lock to intended package updates 2026-04-02 14:06:45 +05:30
Chirag Aggarwal e8bcc94187 fix: keep composer lock scoped to intended updates 2026-04-02 13:59:52 +05:30
Prem Palanisamy a071c715bc Merge remote-tracking branch 'origin/1.9.x' into bump-database-version2 2026-04-02 10:10:03 +02:00
Prem Palanisamy 52981e0164 fix: restore full CI matrix and revert test/endpoint changes to 1.9.x 2026-04-02 10:09:36 +02:00
Chirag Aggarwal e576cd5082 Merge branch '1.9.x' into feat/migrate-di-container 2026-04-02 13:32:31 +05:30
Chirag AggarwalandGitHub 387668bb47 Merge pull request #11748 from appwrite/feat-smtp-messaging-adapter
Replace PHPMailer with utopia-php/messaging SMTP adapter
2026-04-02 12:11:58 +05:30
Chirag Aggarwal 30befc6a60 fix: remove strict pool size exception 2026-04-02 11:41:35 +05:30
Chirag Aggarwal d13644a47a fix: make pool sizing runtime-aware 2026-04-02 11:38:19 +05:30
Chirag Aggarwal 4a905a6ac9 Merge branch '1.9.x' into feat/migrate-di-container
Resolve conflicts keeping DI container migration (container->set pattern)
while incorporating 1.9.x fixes: PHPStan unused variable cleanup in
GraphQL Resolvers, (int) casts in Builds.php, and phpstan-baseline removal.
2026-04-02 11:17:32 +05:30
Chirag Aggarwal b6e95f4502 test: remove redundant headers empty-key case 2026-04-02 11:05:03 +05:30
Chirag Aggarwal 1900492aad Merge branch '1.9.x' into feat-smtp-messaging-adapter 2026-04-02 10:58:09 +05:30
Prem Palanisamy cb9d3869f0 Merge remote-tracking branch 'origin/1.9.x' into bump-database-version2
# Conflicts:
#	src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Create.php
2026-04-02 06:53:05 +02:00
Chirag AggarwalandGitHub 96f4ef7d22 Merge pull request #11751 from appwrite/chore/remove-phpstan-baseline
chore: remove phpstan baseline
2026-04-02 10:22:21 +05:30
Chirag AggarwalandGitHub 8e02a5a227 Merge pull request #11757 from appwrite/air/fix-flaky-migration-test-580aa1bc-d
Fix flaky vectordb migration test
2026-04-02 10:19:15 +05:30
Prem Palanisamy d17ae517d3 fix: replace exists+create with try/catch for vectorsdb metadata init 2026-04-02 06:26:55 +02:00
Chirag Aggarwal c2dd8aceda use stable 2026-04-02 09:42:59 +05:30
Chirag Aggarwal 24fcf4247c Fix flaky vectordb migration test 2026-04-02 09:41:20 +05:30
Chirag Aggarwal 2dc20ef0eb Update utopia-php/messaging lock to latest 2026-04-02 09:31:48 +05:30
Chirag Aggarwal 04943b6313 Pass recipient display name in EmailMessage to field
Use associative array format ['email' => ..., 'name' => ...] for
the to field so the recipient display name appears in the To header.
2026-04-02 09:29:57 +05:30
Chirag Aggarwal eb366cf94b fix: preserve cors max age type 2026-04-02 08:25:59 +05:30
Chirag Aggarwal 77b4f8b7a0 style: apply formatter 2026-04-02 08:23:51 +05:30
Chirag Aggarwal 24bd4c3d7b fix: preserve migration resource stats 2026-04-02 08:11:57 +05:30
Prem Palanisamy 25d9891f2b fix: use email probe in JSON export test, format fixes, add requireAdapter skips to Databases VectorsDB/DocumentsDB tests 2026-04-02 04:39:56 +02:00
Damodar LohaniandGitHub a928fcd40f Merge pull request #11754 from appwrite/claude/sync-1.9.x-fix-tests-VoCBt
Fix PHPStan baseline and code quality issues
2026-04-02 08:17:54 +05:45
Claude 2d34301834 fix: add missing userType field to legacy log endpoints
The users.php and messaging.php legacy controllers were missing the
userType field in their log output, creating an inconsistency with the
new audit user type distinction feature. Also adds missing mode field
to users.php logs endpoint.

https://claude.ai/code/session_01J9gKXwbHoLggsGwJi6KUnM
2026-04-02 01:13:01 +00:00
Claude cf7259e060 Merge remote-tracking branch 'origin/1.9.x' into claude/sync-1.9.x-fix-tests-VoCBt 2026-04-02 01:05:56 +00:00
Prem Palanisamy 8c9f9c6f58 fix: inherit project shared tables for separate pools, skip cross-engine migration tests 2026-04-02 02:05:10 +02:00
Chirag Aggarwal 33f8e35b62 chore: remove phpstan baseline 2026-04-01 23:01:11 +05:30
Chirag Aggarwal 2c1060f57a Fix null array key in HeadersTest
Cast null to string to avoid invalid array key type error
while preserving the test's intent of validating empty keys.
2026-04-01 21:31:08 +05:30
Chirag Aggarwal 30e0ca81bd Fix Usage::fromArray() to use static return type with new static()
Keep covariant return type with parent Base::fromArray(). The
new static() is safe here because the cloud subclass constructor
is backwards-compatible via optional params.
2026-04-01 21:30:36 +05:30
Chirag Aggarwal 92cc382a6b Change Usage::fromArray() return type from static to self
The cloud subclass has a different constructor signature so
new static() is unsafe. Use self since subclasses that need
deserialization should override fromArray() themselves.
2026-04-01 21:25:22 +05:30
Prem Palanisamy c322cc3ffe Merge remote-tracking branch 'origin/1.9.x' into bump-database-version
# Conflicts:
#	src/Appwrite/Event/Message/Usage.php
2026-04-01 17:34:13 +02:00
Chirag Aggarwal 7d428ffe83 trigger ci 2026-04-01 21:03:07 +05:30
Chirag Aggarwal fb96aecbef Use new static() in Usage::fromArray() for late static binding
Required since the class is no longer final and the return type
is static, so subclasses get the correct type.
2026-04-01 19:52:16 +05:30
Matej Bačo 80d5d4716a Remove base errors 2026-04-01 15:54:26 +02:00
Matej Bačo c1dde09070 Merge branch '1.9.x' into feat-disposable-emails 2026-04-01 15:14:10 +02:00
ArnabChatterjee20k cf99269bc5 Add reset method to Realtime class for clearing event state
This new method resets the event state for long-lived worker processes by clearing subscribers, context, and user-related fields, ensuring no stale state affects subsequent triggers.
2026-04-01 18:08:45 +05:30
ArnabChatterjee20k 2a184288f4 removed throw 2026-04-01 18:05:56 +05:30
ArnabChatterjee20k 9237f387fc added reset in db worker for queue for realtime 2026-04-01 17:59:28 +05:30
Chirag Aggarwal d531c29fc8 Remove final from Usage class to allow cloud override 2026-04-01 17:20:34 +05:30
Matej Bačo b554243447 Merge branch '1.9.x' into feat-public-platform-api 2026-04-01 13:35:46 +02:00
Chirag Aggarwal 7044601603 lock file 2026-04-01 17:05:14 +05:30
Chirag Aggarwal 53bc126015 Remove stale PHPMailer baseline entry from PHPStan
The PHPMailer $Port type error no longer occurs since registers.php
now uses the SMTP adapter instead of PHPMailer directly.
2026-04-01 17:01:15 +05:30
Chirag Aggarwal f1dc468e50 Restore replyTo elseif logic to preserve old behavior
customMailOptions replyTo and smtp replyTo are mutually exclusive,
matching the original PHPMailer implementation.
2026-04-01 16:37:08 +05:30
Chirag Aggarwal 15b2ab321e fix: restore pool size validation to prevent silent connection exhaustion
The old guard that threw when workerCount > instanceConnections was
removed during the DI migration, causing pool size to silently floor
to 1. This can lead to connection exhaustion on multi-core hosts.
2026-04-01 16:34:58 +05:30
Chirag Aggarwal ce63b00cf4 lock file 2026-04-01 16:29:06 +05:30
Chirag Aggarwal b9aaecba25 Replace PHPMailer with utopia-php/messaging SMTP adapter
Use Utopia\Messaging\Adapter\Email\SMTP instead of raw PHPMailer
for the smtp register, Mails worker, and Doctor task. This enables
swapping email adapters (e.g. Resend) via DI override in downstream
repos by type-hinting against the EmailAdapter base class.
2026-04-01 16:25:33 +05:30
ArnabChatterjee20k 71b28eae5a updated db 2026-04-01 15:55:26 +05:30
ArnabChatterjee20k 9cb2ea1135 Merge remote-tracking branch 'origin/bump-database-version-bkp' into big-int 2026-04-01 15:54:56 +05:30
Chirag Aggarwal 789870b545 fix: preserve multi-value headers like Set-Cookie instead of comma-joining
addHeader() already accumulates multiple values for the same key into an
array internally, so calling it once per value is the correct approach.
Comma-joining violates RFC 6265 for Set-Cookie headers.
2026-04-01 15:43:14 +05:30
Chirag Aggarwal c9f7b7f0d9 fix: address PR review findings from code review
- Add Console::error() fallback in Bus::dispatch() so listener failures
  are visible even without telemetry (C1/M7)
- Remove duplicate $max/$sleep assignments in createDatabase (M1)
- Remove duplicate @param in Event::generateEvents docblock (M2)
- Remove unused $plan parameter from plan resource factory (M3)
- Fix inconsistent indentation in certificate init block (L2)
- Add explicit return null in session resource factory (M6)
2026-04-01 15:42:15 +05:30
Chirag Aggarwal cba7e53898 chore: fix composer 2026-04-01 15:37:34 +05:30
Chirag Aggarwal fb26da5df1 analyze fixes 2026-04-01 15:15:48 +05:30
Torsten Dittmann d9c606a1c2 fix(oauth): update Yahoo OAuth scopes from deprecated Social Directory API to OIDC
The Yahoo OAuth provider was using deprecated Social Directory API scopes
('sdct-r' and 'sdpp-w') which are no longer valid and causing authentication
failures with the error: invalid_scope

Changes:
- Replace deprecated scopes 'sdct-r' (Social Directory Contacts Read) and
  'sdpp-w' (Social Directory Profile Write) with standard OIDC scopes
- Add 'openid' scope for OpenID Connect authentication
- Add 'profile' scope for basic profile information
- Add 'email' scope for email address access

These new scopes align with Yahoo's OpenID Connect implementation and are
listed in their discovery document at:
https://api.login.yahoo.com/.well-known/openid-configuration

The Yahoo adapter already uses the OIDC userinfo endpoint
(https://api.login.yahoo.com/openid/v1/userinfo), so these scopes are the
correct choice for authentication.

Custom scopes passed via the API are still supported and will be merged
with these defaults via the base OAuth2 class constructor.

Fixes: Yahoo OAuth authentication returning 'invalid_scope' error
2026-04-01 13:15:22 +04:00
Chirag Aggarwal eb8455bd76 revert 2026-04-01 14:29:20 +05:30
Chirag AggarwalandGitHub a76a03d988 Merge branch '1.9.x' into feat/migrate-di-container 2026-04-01 14:22:13 +05:30
Prem Palanisamy d9eb69aa47 Merge remote-tracking branch 'origin/1.9.x' into bump-database-version 2026-04-01 10:41:39 +02:00
Chirag AggarwalandGitHub a5b0378138 Merge pull request #11737 from appwrite/codex/phpstan-baseline-part-2
[codex] Fix PHPStan baseline cleanup issues (part 2)
2026-04-01 13:23:15 +05:30
Chirag AggarwalandGitHub 320068a576 Merge pull request #11740 from appwrite/speed-up-phpstan
Speed up PHPStan analysis with result caching
2026-04-01 13:01:07 +05:30
Chirag Aggarwal 3cd90ae629 fix analyze 2026-04-01 12:59:51 +05:30
Damodar LohaniandGitHub e250b413f0 Merge branch '1.9.x' into feat-audit-user-type-distinction 2026-04-01 12:58:09 +05:45
Harsh MahajanandGitHub 8ccfb1aebb Merge branch '1.9.x' into feat-x-oauth2-provider 2026-04-01 12:17:05 +05:30
Harsh Mahajan 9da4f19d4f fix: pkce flow 2026-04-01 12:11:40 +05:30
Chirag Aggarwal 44f33473bd Use composer analyze in CI to stay in sync with local workflow 2026-04-01 12:04:31 +05:30
Chirag Aggarwal 358f1b78a8 Speed up PHPStan analysis with result caching
Configure a project-local result cache directory so PHPStan only
re-analyses files that changed. In CI, persist the cache across
runs with actions/cache and suppress progress output.
2026-04-01 12:00:32 +05:30
Chirag Aggarwal cc04c682b0 chore: use phpstan-baseline.neon from 1.9.x 2026-04-01 11:49:43 +05:30
Chirag Aggarwal 66ba483b6a chore: remove inapplicable phpstan baseline entries from 1.9.x merge
$register variable.undefined (app/http.php) and binary op (app/worker.php) suppressions don't apply to this branch's rewritten DI container code.
2026-04-01 11:48:41 +05:30
Chirag Aggarwal 908e408480 Merge remote-tracking branch 'origin/1.9.x' into feat/migrate-di-container
# Conflicts:
#	app/init/resources.php
#	composer.json
#	composer.lock
#	phpstan-baseline.neon
2026-04-01 11:46:13 +05:30
Jake BarnbyandGitHub 1f6b9d94bf Merge pull request #11739 from appwrite/readme-update-1.9.0-installation 2026-04-01 05:53:38 +00:00
Aditya OberaiandGitHub a734c8cd46 Update installation commands in readme for 1.9.0 to include self-hosted wizard 2026-04-01 11:20:45 +05:30
Chirag Aggarwal 1788e1bd6c Address PR review feedback 2026-04-01 11:15:59 +05:30
Prem Palanisamy 7f9ce1ca85 Merge remote-tracking branch 'origin/1.9.x' into bump-database-version 2026-04-01 07:37:57 +02:00
Chirag Aggarwal 983adf3ffd Fix analyze regressions in PHPStan cleanup 2026-04-01 11:00:26 +05:30
Chirag Aggarwal f2ea0b9b48 Fix PHPStan baseline cleanup issues (part 2) 2026-04-01 10:20:20 +05:30
premtsd-codeandGitHub d38c921781 Merge branch '1.9.x' into bump-database-version 2026-04-01 05:23:37 +01:00
ArnabChatterjee20k 3c20ea47f5 Merge remote-tracking branch 'origin/1.9.x' into big-int 2026-04-01 09:51:07 +05:30
Jake BarnbyandGitHub 44610462b3 Merge pull request #11735 from appwrite/lohanidamodar-patch-2 2026-04-01 03:57:20 +00:00
Damodar LohaniandGitHub 28ece7de02 Change Usage class from final to non-final 2026-04-01 09:36:26 +05:45
Jake BarnbyandGitHub 8c6d4d8b36 Merge pull request #11734 from appwrite/fix-defaults 2026-04-01 02:50:09 +00:00
Jake Barnby 2ebc6f70ef (fix): param default 2026-04-01 15:49:40 +13:00
Damodar LohaniandGitHub 2b7690d6db Merge pull request #11732 from appwrite/claude/add-deployment-hook-method-prkpM
Add beforeCreateGitDeployment hook for deployment validation
2026-04-01 08:27:50 +05:45
Damodar LohaniandGitHub d9af799cc7 Merge branch '1.9.x' into claude/add-deployment-hook-method-prkpM 2026-04-01 08:09:46 +05:45
Damodar LohaniandGitHub 3ed1ca736d Merge pull request #11731 from appwrite/claude/update-php-runtimes-hNh1r
Update dependencies
2026-04-01 07:57:25 +05:45
Jake BarnbyandGitHub 9fa35db838 Merge pull request #11733 from appwrite/fix-defaults 2026-04-01 02:08:45 +00:00
Claude b6e020389b Remove docblock from beforeCreateGitDeployment hook
https://claude.ai/code/session_01HP1N9hHbqMzxm5QmaoGhyZ
2026-04-01 02:07:36 +00:00
Jake Barnby ccc0cfbfdc (fix): migrate default 2026-04-01 15:04:52 +13:00
Claude b91506fc2d Rename hook to beforeCreateGitDeployment
https://claude.ai/code/session_01HP1N9hHbqMzxm5QmaoGhyZ
2026-04-01 02:03:59 +00:00
Claude 9ffc23946c Add validateGitDeployment hook method to Deployment trait
Add a no-op protected method that Cloud can override to enforce
billing/block checks before processing git deployments. The hook
is called inside the foreach loop after project validation, so any
exception it throws is caught and logged as an error.

https://claude.ai/code/session_01HP1N9hHbqMzxm5QmaoGhyZ
2026-04-01 02:02:18 +00:00
Jake BarnbyandGitHub 8bb6b5cd2a Merge pull request #11646 from appwrite/feat/import-export-json 2026-04-01 01:56:47 +00:00
Claude afea4ca57b Update appwrite/php-runtimes to 0.19.5
https://claude.ai/code/session_01KXrbPzuXNzRhn38xm9zqwJ
2026-04-01 01:44:07 +00:00
Jake BarnbyandGitHub b30b89ade9 Merge pull request #11730 from appwrite/fix-v24 2026-04-01 01:11:54 +00:00
Jake Barnby 829cf887dc (fix): missing users case 2026-04-01 14:07:59 +13:00
Prem Palanisamy 32728ae4a0 Merge remote-tracking branch 'origin/1.9.x' into bump-database-version 2026-04-01 02:16:20 +02:00
premtsd-codeandGitHub d862a64874 Merge branch '1.9.x' into feat/import-export-json 2026-03-31 22:54:12 +01:00
Prem Palanisamy 168166b9c1 bump utopia-php/database to 5.3.19 and fix shared-mode CI failures
- Bump utopia-php/database from 5.3.17 to 5.3.19

- Remove invalid (int) cast on tenant sequence in shared tables mode

- Fix DSN construction for documentsdb/vectorsdb: filter empty strings
  from explode(), skip pool filtering when shared tables env vars unset,
  fail fast when no pool found

- Use dedicated mode for separate database pools in getDatabasesDB
  since shared tables can't work across engines (PostgreSQL integer
  _tenant vs MongoDB UUID tenant). Auto-init schema on first use.

- Add documentsdb/vectorsdb shared tables env vars to CI workflow

- Fix testChannelTablesDBRowUpdate race condition with deterministic
  event drain loop
2026-03-31 21:24:22 +02:00
Luke B. SilverandGitHub 142e671c7d Merge pull request #11720 from appwrite/codex/phpstan-core-type-docs-1
[codex] Fix PHPStan core type and PHPDoc issues (part 1)
2026-03-31 19:42:26 +01:00
Chirag Aggarwal b4085d1083 Fix token trait PHPStan static access 2026-03-31 22:23:37 +05:30
Chirag Aggarwal 18ed6a9c59 Fix more PHPStan static access issues 2026-03-31 22:04:37 +05:30
fogelito e42a26fdad comment 2026-03-31 19:20:13 +03:00
fogelito 2c4770d29b Database 5.3.19 2026-03-31 19:18:32 +03:00
Chirag Aggarwal 3d64ccd056 Fix more PHPStan docblock issues 2026-03-31 21:48:14 +05:30
Chirag Aggarwal 4f73eb021f Fix PHPStan core type and PHPDoc issues (part 1) 2026-03-31 21:44:20 +05:30
premtsd-codeandGitHub 7dcfd3ff3d Update description for get-queue-audits endpoint 2026-03-31 12:38:48 +01:00
Prem Palanisamy 4dfdfb5e59 Merge remote-tracking branch 'origin/1.9.x' into feat/import-export-json 2026-03-31 12:37:17 +01:00
Prem Palanisamy 5d1009b324 fix: correct resourceType routing, schemaless validation, and E2E tests for migrations
- Add getDatabaseResourceType() helper to map database types to resource constants
- Use database-specific resourceType for CSV/JSON import/export instead of hardcoded TYPE_DATABASE
- Skip attribute validation for schemaless databases (DocumentsDB/VectorsDB) in exports
- Parse JSON export queries in migration worker
- Restore MigrationsBase from 1.9.x and append VectorsDB/DocumentsDB E2E tests
2026-03-31 12:35:18 +01:00
Chirag AggarwalandGitHub 4568316345 Merge pull request #11715 from appwrite/fix-specs-defaults
Fix spec generation defaults for _APP_HOME and _APP_SYSTEM_TEAM_EMAIL
2026-03-31 15:52:14 +05:30
Jake BarnbyandGitHub c444bccacc Merge pull request #11716 from appwrite/fix-installer 2026-03-31 10:09:42 +00:00
Jake Barnby f9aee4de5d (fix): clear stale install data before starting new installation 2026-03-31 23:08:31 +13:00
fogelito 2a9e423cb1 Temporary disabling deletes from internal collections 2026-03-31 12:54:33 +03:00
Chirag Aggarwal 037878cc75 fix: use correct defaults for spec generation
Use https://appwrite.io and team@appwrite.io as defaults for _APP_HOME
and _APP_SYSTEM_TEAM_EMAIL in spec generation, instead of [HOSTNAME]
and team@localhost.test placeholders.
2026-03-31 15:11:11 +05:30
Jake BarnbyandGitHub 416b26ac7d Merge pull request #11710 from appwrite/fix-installer
(fix): guard against missing Host header in dispatch
2026-03-31 08:30:23 +00:00
Jake Barnby b47ac00ca8 (refactor): rename migrate param and add --migrate flag to upgrade task 2026-03-31 21:08:29 +13:00
Jake Barnby 2f53d09c5b (feat): add database migration step to upgrade installer 2026-03-31 20:58:33 +13:00
Jake Barnby 1fa1aa8621 (fix): guard against missing Host header in dispatch 2026-03-31 20:58:22 +13:00
fogelito 14869cc6d6 Safe delete shared tables v1 2026-03-31 09:59:53 +03:00
Jake BarnbyandGitHub ec20fb59e2 Merge pull request #11689 from appwrite/fix-installer 2026-03-31 03:51:20 +00:00
Jake Barnby 7e49cf0bed Revert "(fix): increase GraphQL schema polling timeouts to match CI expectations"
This reverts commit 4e32497be1.
2026-03-31 16:48:32 +13:00
Jake BarnbyandClaude Opus 4.6 4e32497be1 (fix): increase GraphQL schema polling timeouts to match CI expectations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:26:20 +13:00
Jake Barnby fe6f79c9c6 (fix): restore ip key in tracking payload 2026-03-31 16:02:22 +13:00
Jake Barnby 5c84efdd7c (fix): upgrade UI — remove min-height, hide secret key row, fix copy 2026-03-31 15:58:45 +13:00
Jake Barnby 02d234ad3a (fix): detect existing installation as upgrade for web installer 2026-03-31 15:58:41 +13:00
Jake Barnby 8b7459f634 (fix): address review comments — coroutine guard and hostIp key 2026-03-31 15:15:51 +13:00
Jake Barnby 100cbf50f0 Merge remote-tracking branch 'origin/feat-auto-detect-cli' into fix-installer
# Conflicts:
#	src/Appwrite/Platform/Tasks/Install.php
2026-03-31 15:04:56 +13:00
Jake Barnby c7c59f4e7b Fix key 2026-03-31 15:04:04 +13:00
Jake Barnby a955dff55a (fix): run install tracking in coroutine to avoid blocking worker 2026-03-31 14:59:31 +13:00
Jake Barnby 7bcd5b4ebc (fix): remove swallowed exception around tracking call 2026-03-31 14:46:04 +13:00
Jake Barnby dfb23612b0 (docs): rewrite AGENTS.md with module structure and action patterns 2026-03-31 14:44:24 +13:00
Jake Barnby 4e71a54fae (fix): send SSE done event before tracking to prevent installer hang 2026-03-31 14:44:20 +13:00
Jake BarnbyandGitHub ea032c2804 Merge pull request #11679 from appwrite/fix/remove-documentsdb-spatial 2026-03-30 23:40:03 +00:00
Prem Palanisamy a80ecd0cb6 fix: alphabetize imports, update phpstan baseline count for migrations tests 2026-03-30 17:24:58 +01:00
Prem Palanisamy 8be7c6182e fix: update composer.lock for utopia-php/database branch 2026-03-30 17:16:05 +01:00
premtsd-codeandGitHub 3bb6a8bcc8 Merge branch '1.9.x' into feat/import-export-json 2026-03-30 16:15:16 +01:00
ArnabChatterjee20k ad7f83db11 updated actions 2026-03-30 20:32:03 +05:30
ArnabChatterjee20k c4b3956bef emtpy 2026-03-30 20:26:23 +05:30
Prem Palanisamy c72d438a10 fix: remove INDEX_SPATIAL from DocumentsDB index creation 2026-03-30 15:55:37 +01:00
Prem Palanisamy 0085d93aeb fix: add version alias for database branch 2026-03-30 15:48:05 +01:00
Prem Palanisamy 3b9b0c96c4 use utopia-php/database fix/mongo-order-case branch for order case normalization 2026-03-30 15:46:20 +01:00
Prem Palanisamy 2611bf4af1 fix: add setPlatform to JSON import trigger for consistency 2026-03-30 15:43:21 +01:00
ArnabChatterjee20k 5fa4551400 Refactor bigint handling in validation and action classes to unify range validation logic 2026-03-30 20:10:13 +05:30
Prem Palanisamy de219de31d fix: restore original databasetype block in CSV export 2026-03-30 15:26:09 +01:00
Prem Palanisamy d8bbd82556 fix: remove duplicate database fetch, add null-safe queries fallback, add schemaless comment 2026-03-30 14:55:05 +01:00
ArnabChatterjee20k 2fb54e0846 Add BigInt support with dedicated bigintRange format and update related logic 2026-03-30 18:59:24 +05:30
ArnabChatterjee20k dfbf22cf3c updated format 2026-03-30 18:36:19 +05:30
ArnabChatterjee20k a5b2021d1e empty 2026-03-30 18:17:14 +05:30
ArnabChatterjee20kandGitHub fb28b31906 Merge branch '1.9.x' into big-int 2026-03-30 18:02:29 +05:30
ArnabChatterjee20k 737bfc599a fixed analyze 2026-03-30 18:00:43 +05:30
ArnabChatterjee20k 819863559e Enhance BigInt support across database attributes and validation, updating types and tests for consistency. 2026-03-30 17:45:25 +05:30
ArnabChatterjee20k f6cdcfb823 Merge remote-tracking branch 'origin/big-int' into big-int 2026-03-30 17:39:25 +05:30
ArnabChatterjee20k e5d818ba44 updated 2026-03-30 17:38:24 +05:30
ArnabChatterjee20kGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
19487cf56b Update src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/BigInt/Create.php
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-30 17:34:36 +05:30
ArnabChatterjee20k 5741041f33 updated tests 2026-03-30 17:32:49 +05:30
ArnabChatterjee20k 89eb5c2254 Refactor response model attribute and column types to prioritize BigInt over Integer for more accurate type matching in AttributeList, Collection, ColumnList, and Table classes. 2026-03-30 17:14:38 +05:30
Harsh MahajanandGitHub 5d962e21b0 Merge pull request #11674 from appwrite/fix-vcs-sdk-models
fix: merge duplicate SDK responses in VCS repository list and detections
2026-03-30 16:49:56 +05:30
Harsh Mahajan 551c83dd2c fix: merge duplicate SDK responses in VCS repository list and detections 2026-03-30 16:34:26 +05:30
ArnabChatterjee20k 77d195f0c3 updated response models 2026-03-30 16:17:44 +05:30
ArnabChatterjee20k a9ed74c6a8 added tests to migration 2026-03-30 16:11:42 +05:30
Harsh Mahajan fe99470374 revert test env change 2026-03-30 16:09:42 +05:30
ArnabChatterjee20k 01db1efbc4 added tests 2026-03-30 16:08:21 +05:30
ArnabChatterjee20k ffa3f741ed added bigint columns 2026-03-30 15:53:06 +05:30
ArnabChatterjee20k 03c73ac0aa added big int attributes 2026-03-30 15:50:59 +05:30
Chirag AggarwalandGitHub d964c5a439 Merge pull request #11671 from appwrite/codex/reduce-specs-memory-retention
[codex] Reduce specs task memory retention
2026-03-30 14:48:27 +05:30
Chirag Aggarwal 4850fb54ed Disallow --release=yes with --mode=examples 2026-03-30 14:19:02 +05:30
Chirag Aggarwal 796bc13c19 Skip spec version prompt when creating SDK releases
When --release=yes, the Appwrite spec version is not needed since
the release flow uses the SDK version from config. This moves the
version prompt and validation behind a !$createRelease guard and
hoists the release block before the spec/SDK generation setup.
2026-03-30 14:11:36 +05:30
Chirag Aggarwal 30e080c4d3 Address review: race-safe mkdir and separate encode/write errors 2026-03-30 14:04:20 +05:30
Prem Palanisamy aaebeec61e fix: remove spatial from documentsdb indexes, parse JSON export queries, skip schema validation for schemaless exports 2026-03-30 09:09:50 +01:00
ArnabChatterjee20kandGitHub 1789d2ce89 Merge pull request #11658 from appwrite/update-db-size
updated db size
2026-03-30 13:37:18 +05:30
Chirag Aggarwal 239a9c5457 Reduce specs task memory retention 2026-03-30 13:32:57 +05:30
ArnabChatterjee20kandGitHub 995e9fdf39 Merge branch '1.9.x' into update-db-size 2026-03-30 13:07:30 +05:30
Damodar LohaniandGitHub 8ce42fe057 Merge pull request #11553 from appwrite/claude/apply-cloud-user-pattern-az7qg
Use injected user document for privilege checks
2026-03-30 10:24:58 +05:45
Claude c14ebab1a0 Fix unintentional merge artifacts in Upsert.php and XList.php
Revert auth types in Bulk/Upsert.php back to [AuthType::ADMIN, AuthType::KEY]
and remove duplicate query filter in Databases/XList.php that were accidentally
introduced during the 1.9.x merge.

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-30 04:11:52 +00:00
Claude f1ea496764 fix: add missing inject('user') to Transactions/Operations/Create and
remove duplicate inject('user') from XList in DocumentsDB/VectorsDB

- DocumentsDB and VectorsDB Transactions/Operations/Create.php were
  missing ->inject('user') needed by parent action method
- DocumentsDB and VectorsDB Collections/Documents/XList.php had
  duplicate ->inject('user') calls - removed the extra one

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-29 03:55:17 +00:00
Claude 95b6da085b fix: add missing ->inject('user') to DocumentsDB and VectorsDB child classes
The parent action methods in Databases/Collections/Documents require a
User $user parameter, but 10 child classes in DocumentsDB (6) and
VectorsDB (4) were missing the ->inject('user') call in their
constructor inject chains. This caused fatal errors when those
endpoints were hit during E2E tests.

Files fixed:
- DocumentsDB: Delete, Get, Update, XList, Attribute/Increment, Attribute/Decrement
- VectorsDB: Delete, Get, Update, XList

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-29 03:26:51 +00:00
Claude 32005c0a49 fix: remove redundant new User(getArrayCopy()) wrapping
Since setDocumentType('users', User::class) is registered on all
database instances, getDocument('users', ...) already returns User
instances. The new User($doc->getArrayCopy()) pattern was redundant
and could lose internal state managed by the database layer.

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-29 03:04:43 +00:00
Damodar LohaniandGitHub 98c63a87d1 Merge branch '1.9.x' into claude/apply-cloud-user-pattern-az7qg 2026-03-29 08:30:11 +05:45
Chirag Aggarwal ff903e7cbb lock file 2026-03-28 20:39:08 +05:30
Chirag AggarwalandGitHub 10ebf942b1 Merge branch '1.9.x' into feat/migrate-di-container 2026-03-28 20:36:08 +05:30
Chirag AggarwalandGitHub c5b9ac3dba Merge pull request #11664 from appwrite/codex/static-sdk-platform-1-9-x
[codex] Move static SDKs off platform specs
2026-03-28 20:22:14 +05:30
Chirag Aggarwal be50318e5d chore: update composer lock 2026-03-28 17:26:21 +05:30
Chirag Aggarwal f901b6e0ac feat: move static SDKs off platform specs 2026-03-28 17:24:20 +05:30
premtsd-codeandGitHub 53d0e14f97 Merge branch '1.9.x' into feat/import-export-json 2026-03-28 04:23:07 +00:00
Matej Bačo d9a75c5938 Improved backwards compatibility 2026-03-27 15:34:34 +01:00
ArnabChatterjee20kandGitHub 325b1d67b4 Merge branch '1.9.x' into update-db-size 2026-03-27 19:47:35 +05:30
ArnabChatterjee20k 3ff7cadcab updated project size 2026-03-27 18:39:26 +05:30
Matej Bačo bb80e50d01 Self review after refactor 2026-03-27 14:00:44 +01:00
Matej Bačo 644840ec66 Refactor to new platforms interfaces 2026-03-27 13:45:58 +01:00
Matej Bačo 71e714c66b Remove extra logging 2026-03-27 12:49:16 +01:00
Matej Bačo b2b187c57d review fixes 2026-03-27 12:35:48 +01:00
Chirag AggarwalandGitHub f2c8500d73 Merge pull request #11654 from appwrite/fix-specs
Fix issues in generated specs
2026-03-27 12:50:01 +05:30
copilot-swe-agent[bot]andMeldiron ecc76c7520 fix: address review feedback - log abort exception, fix test cleanup
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/045e63f6-9a81-447b-ba79-7391ab97fb01

Co-authored-by: Meldiron <19310830+Meldiron@users.noreply.github.com>
2026-03-26 18:34:19 +00:00
copilot-swe-agent[bot]andMeldiron 898e8e214b fix: allow deletion of partially-uploaded (pending) files and add test
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/8d14b17a-78d8-48c6-b6d8-51d9697adde0

Co-authored-by: Meldiron <19310830+Meldiron@users.noreply.github.com>
2026-03-26 18:04:41 +00:00
Aditya Oberai babb0196e4 Fix examples in MFAType model 2026-03-26 16:29:39 +00:00
Matej Bačo 43db2ddf6e Fix default null 2026-03-26 16:24:28 +01:00
Matej Bačo 7371b68418 Fix tests compatibility 2026-03-26 15:51:03 +01:00
Matej Bačo 7f4d5f692d Fix non-expiry key actions 2026-03-26 15:44:50 +01:00
Matej Bačo 8113854a89 AI review fixes 2026-03-26 15:37:57 +01:00
Matej Bačo eb097a037b Finish security todos 2026-03-26 15:35:40 +01:00
Matej Bačo cbfdd27834 Public keys Apis 2026-03-26 15:00:20 +01:00
Matej Bačo 113c266881 Fix audit resource ID missing 2026-03-26 14:43:38 +01:00
Chirag AggarwalandGitHub 9353fbfb74 Merge pull request #11614 from appwrite/feat-rust-sdk
feat: add Rust SDK support
2026-03-26 18:59:43 +05:30
Chirag Aggarwal 918f1d37d4 phpstan 2026-03-26 18:37:45 +05:30
Chirag Aggarwal 196acd45a2 lock file 2026-03-26 18:26:17 +05:30
Harsh Mahajan 614db7388e fix: push 2026-03-26 17:59:30 +05:30
Chirag Aggarwal ef6fdc6c72 lock file 2026-03-26 17:47:58 +05:30
Chirag Aggarwal 915a8bfbe0 Merge branch '1.9.x' into feat-rust-sdk 2026-03-26 17:43:47 +05:30
Prem Palanisamy 741267c6c5 fix: rename locale keys to dataExport, use {{type}} placeholder for CSV/JSON 2026-03-26 12:09:10 +00:00
Prem Palanisamy e85080499a fix: generalize export handler for CSV and JSON — download URL, email, dynamic file extension 2026-03-26 11:54:54 +00:00
Prem Palanisamy ee1ca5ace6 fix: remove email verification from vectorsdb export test (tested separately) 2026-03-26 11:36:50 +00:00
Prem Palanisamy b36472f0da add E2E tests for vectorsdb and documentsdb JSON import/export 2026-03-26 11:24:14 +00:00
Prem Palanisamy 52ae8b3880 fix: use type-specific resources for JSON endpoints, add JSON source/destination to worker 2026-03-26 09:23:45 +00:00
Prem Palanisamy 8f09e74462 fix: bump migration to 1.9.*, fix dataExportType property 2026-03-26 07:41:23 +00:00
Prem Palanisamy 30907d716f cleanup: remove duplicate setProject, remove stale spec files 2026-03-26 06:55:28 +00:00
DarshanandPrem Palanisamy eb46855e72 regen: specs. 2026-03-26 06:44:53 +00:00
DarshanandPrem Palanisamy 45557e5929 add: missing doc. 2026-03-26 06:44:36 +00:00
DarshanandPrem Palanisamy 6ad6a5dea3 specs. 2026-03-26 06:44:22 +00:00
DarshanandPrem Palanisamy 098f7aa3e3 update: comment. 2026-03-26 06:43:49 +00:00
DarshanandPrem Palanisamy f8c8c17757 add: tests;
fix: tests.
2026-03-26 06:43:49 +00:00
DarshanandPrem Palanisamy 5b1ee93927 fix: endpoint. 2026-03-26 06:43:04 +00:00
Claude 5fbaa7ab6e fix: revert unintentional changes from rebase conflict resolution
Restore docker-compose.yml, Install.php, VectorsDB.php, and
progress.js to match 1.9.x — these were accidentally modified
during the rebase.

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 05:15:31 +00:00
Jake BarnbyandClaude e8a0a895ed (chore): update lock 2026-03-26 02:48:43 +00:00
Jake BarnbyandClaude 560ebeaddd (fix): installer stale resume redirect and account-setup phase delay 2026-03-26 02:48:39 +00:00
Jake BarnbyandClaude 0731cc1535 (fix): installer step ordering, initial container count, and proc_close timeout 2026-03-26 02:48:39 +00:00
ArnabChatterjee20kandClaude 983d0b8fad fix: remove unused repository entry and update sdk-generator version 2026-03-26 02:48:35 +00:00
ArnabChatterjee20kandClaude 92423acc01 Revert "Revert "Documentsdb + vectordb (latest)"" 2026-03-26 02:48:35 +00:00
Claude 42414a46b0 fix: address review comments for User class pattern
- general.php: add instanceof guard in error handler to prevent calling
  isPrivileged() on a plain Document if getResource('user') returns
  an unexpected type
- graphql.php: add setUser() calls on request/response in graphql group
  init so sensitive field filtering works correctly for GraphQL routes
- api.php: fix session group init type hint from Document to User for
  consistency with all other init blocks

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:48:02 +00:00
Claude b7bd86d634 fix: add missing inject('user') to TablesDB child classes
TablesDB classes override __construct() from their parent Database
classes but were missing the ->inject('user') call that the parent
action() methods now require after the static-to-instance migration.

Affected: Rows Get/Update/Delete, Column Increment/Decrement,
and Transactions Operations Create.

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:47:57 +00:00
Claude 7a39da0afd fix: remove unused Document imports from Delete.php and Get.php
https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:47:57 +00:00
Claude 9aa488c961 fix: wrap getDocument('users') results in User instances
The user resource and realtime handlers return Document objects from
getDocument(), but isPrivileged()/isApp() are now instance methods on
the User class. Wrapping results with new User() ensures the correct
type is returned for all code paths.

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:47:57 +00:00
Claude cfc325635d fix: convert static isPrivileged() call to instance method in error handler
The error handler in general.php was calling User::isPrivileged()
statically, but the method was converted to an instance method.
This caused a fatal error on every request.

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:47:57 +00:00
Claude 5c47d4f48b fix: remove duplicate return and update PHPStan baseline
- Remove duplicate `return false` in User::sessionVerify() (dead code)
- Update PHPStan baseline: change static method refs to instance method
  for isApp/isPrivileged, remove stale unreachable code entry

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:47:56 +00:00
Claude 7aff75ae1c refactor: convert User::isApp() and User::isPrivileged() from static to instance methods
All call sites now use $user->isApp() and $user->isPrivileged() instance
syntax instead of static User::isApp() / $user::isPrivileged() calls.
Added setUser() to Request class for consistency with Response.

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:47:56 +00:00
Claude 651a24a211 fix: correct import ordering in Tokens XList.php
https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:47:56 +00:00
Claude 6536463d49 fix: update PHPStan baseline and remove unused Document imports
- Remove getRoles() baseline entry (User type hint resolves it)
- Adjust foreach.nonIterable count from 5 to 4
- Adjust addRole argument.type count from 3 to 2
- Remove unused Document imports from 4 files

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:47:56 +00:00
Claude 82d7926c4b fix: use User type hint instead of Document for $user parameter
PHPStan correctly flagged that Document::isPrivileged() doesn't exist.
Changed type hints from Document $user to User $user in all action
signatures where $user::isPrivileged() is called, since the runtime
instance is always a User (or subclass).

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:47:38 +00:00
Claude 6041468fc4 fix: correct import ordering in Storage Delete.php
https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:46:48 +00:00
Claude 669f323156 refactor: use $user:: for isPrivileged() to make privilege checks extensible
Replace all static User::isPrivileged() calls with $user::isPrivileged()
across the codebase. Since $user is resolved via setDocumentType, this
allows subclasses to override the privilege check without CE needing to
know about downstream-specific roles.

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:46:48 +00:00
Harsh MahajanandGitHub cbc5259db3 Merge branch '1.9.x' into feat-x-oauth2-provider 2026-03-25 23:29:04 +05:30
Matej Bačo 0d6d6a0a35 Make tests pass 2026-03-25 16:11:01 +01:00
Matej Bačo e8d4065af5 Improve tests human review 2026-03-25 15:54:10 +01:00
Matej Bačo 008110b3f7 Platform API tests 2026-03-25 15:47:11 +01:00
Matej BačoandGitHub 1ab05acc93 Merge pull request #11641 from appwrite/fix-missing-deployments
Fix: missing deployment on new branch without PR
2026-03-25 13:55:50 +01:00
Matej Bačo 2f66ae5533 AI review fixes 2026-03-25 13:28:16 +01:00
Matej Bačo 87c462ebd4 Revert local setup 2026-03-25 13:22:55 +01:00
Matej Bačo f89621cf74 Fix double deployments 2026-03-25 13:17:07 +01:00
Harsh MahajanandGitHub efb0cae4e0 Merge branch '1.9.x' into feat-x-oauth2-provider 2026-03-25 16:49:54 +05:30
Jake Barnby 4fb8d873b1 (docs): Add documentsdb and vectorsdb reference docs 2026-03-25 23:41:49 +13:00
Chirag AggarwalandGitHub c5186324ed Merge pull request #11639 from appwrite/improve-sdk-pr-summary
feat: improve SDK PR summary with platform grouping and clipboard copy
2026-03-25 15:29:11 +05:30
Matej Bačo b95b4f12f9 Fix missing deployment on new branch 2026-03-25 10:55:17 +01:00
Jake Barnby 11fc170d72 (docs): Add documentsdb and vectorsdb reference docs 2026-03-25 22:53:03 +13:00
Chirag AggarwalandGitHub 52efca0832 Merge pull request #11640 from appwrite/revert-upsert-auth
fix: revert bulk upsertDocuments auth
2026-03-25 15:03:11 +05:30
Chirag Aggarwal ee3d4dfeee fix: revert bulk upsertDocuments auth 2026-03-25 14:49:36 +05:30
Chirag Aggarwal b742f1c50d improve log 2026-03-25 12:49:17 +05:30
premtsd-codeandGitHub 28882a4414 Merge branch '1.9.x' into backup-migration-multitype 2026-03-25 06:57:37 +00:00
Chirag Aggarwal f3fcdec1be remove copy 2026-03-25 12:21:05 +05:30
Chirag Aggarwal 647099efc7 fix: address PR review feedback
- Guard clipboard prompt with posix_isatty() to avoid blocking CI/automated runs
- Add Wayland support (wl-copy) and improve error message for missing clipboard tools
- Validate comma-separated platform names against getPlatforms()
2026-03-25 12:06:19 +05:30
Chirag Aggarwal d535b6b39b feat: improve SDK PR summary with platform grouping and clipboard copy
- Group PR links by platform (Client, Console, Server) in the summary
- Add option to copy PR summary to clipboard in markdown format
- Support comma-separated platform selection (e.g. "client,server")
2026-03-25 11:13:37 +05:30
Jake Barnby 467b2a9281 (feat): Increase databases.database attribute size to 2048 2026-03-25 18:39:49 +13:00
Chirag AggarwalandGitHub 2057eab918 Merge pull request #11638 from appwrite/update-sdks-script
chore: update sdks script
2026-03-25 10:45:10 +05:30
Chirag Aggarwal f89b3274de review comments 2026-03-25 10:17:25 +05:30
Chirag Aggarwal 956edae593 fix empty git commit 2026-03-25 10:05:16 +05:30
Chirag Aggarwal 617d6fe1eb update logging 2026-03-25 10:01:25 +05:30
Chirag Aggarwal a7cdfed253 chore: update sdks script 2026-03-25 09:49:49 +05:30
Jake Barnby dc6b2ce3aa (feat): auto-detect cli params to force non-interactive installer 2026-03-25 13:52:58 +13:00
Prem Palanisamy d993214810 Revert "Add policies.read/write scopes to test API key"
This reverts commit 2838642bae.
2026-03-24 17:22:31 +00:00
Prem Palanisamy 2838642bae Add policies.read/write scopes to test API key 2026-03-24 17:09:27 +00:00
Matej Bačo ae99d59aba CodeQL review 2026-03-24 14:52:58 +01:00
Prem Palanisamy bd03ec7a50 Update composer.lock for migration feature branch 2026-03-24 13:36:42 +00:00
Prem Palanisamy 6128a049f2 Point migration dep to feature branch 2026-03-24 13:33:36 +00:00
premtsd-codeandGitHub 9ba6bfba62 Merge branch '1.9.x' into backup-migration-multitype 2026-03-24 13:13:23 +00:00
Jake BarnbyandGitHub 7fcc640652 Merge pull request #11634 from appwrite/fix-installer-state
Fix installer state
2026-03-24 13:09:48 +00:00
Jake Barnby 10a6e8832b (fix): auto-detect upgrade mode and database from existing config files 2026-03-25 02:07:53 +13:00
Jake Barnby 74adda8e77 (fix): redirect to step 1 when install resume fails instead of blank page 2026-03-25 02:07:47 +13:00
premtsd-codeandGitHub 6f73602dd7 Merge branch '1.9.x' into backup-migration-multitype 2026-03-24 13:01:29 +00:00
Matej Bačo a06aaaf9ca Remove Db schema changes 2026-03-24 13:55:54 +01:00
Jake BarnbyandGitHub a92ad3aa8b Merge pull request #11624 from appwrite/fix-installer-state 2026-03-24 12:47:48 +00:00
Chirag AggarwalandGitHub 4970d1b6f2 Merge branch '1.9.x' into feat/migrate-di-container 2026-03-24 18:15:45 +05:30
Matej Bačo 094cd180b5 Remove leftover 2026-03-24 13:41:12 +01:00
Matej Bačo a8f43f3486 Update DB schema 2026-03-24 13:36:11 +01:00
Matej Bačo 038f4b5992 More backwards compatibility fixes 2026-03-24 13:33:15 +01:00
Prem Palanisamy b1e97b4539 Merge 1.9.x and add new API key scopes 2026-03-24 12:14:50 +00:00
Jake Barnby 4de9ec7fba Revert "fix: address review comments on installer state PR"
This reverts commit a659038ad2.
2026-03-25 01:08:14 +13:00
Jake BarnbyandClaude Opus 4.6 a659038ad2 fix: address review comments on installer state PR
- Restore postgresql in compose.phtml allowedDbServices for consistency
  with WhiteList validators, JS defaults, and compose template sections
- Log errors in performReset catch block instead of swallowing silently
- Move $currentStep assignment before waitForApiReady so timeout errors
  are attributed to the correct step
- Replace blocking fgets loop in execWithContainerProgress with
  non-blocking stream_select polling to prevent unbounded hangs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:05:47 +13:00
Jake Barnby 9564c9b065 (chore): update lock 2026-03-25 01:04:38 +13:00
Jake Barnby 4da726029c (fix): installer stale resume redirect and account-setup phase delay 2026-03-25 00:56:42 +13:00
Matej Bačo c903fb87ac Fix backwards compatibility 2026-03-24 12:53:25 +01:00
Jake Barnby f016d4b7cd (fix): auto-detect existing database type instead of blocking upgrades 2026-03-25 00:39:17 +13:00
Jake Barnby 2a7925b362 (fix): installer resume detects terminal snapshots and redirects cleanly 2026-03-25 00:39:14 +13:00
Jake Barnby cfe600c653 Merge branch 'fix-installer-state' of github.com:appwrite/appwrite into fix-installer-state
# Conflicts:
#	src/Appwrite/Platform/Tasks/Install.php
2026-03-25 00:27:47 +13:00
Jake Barnby 22e1969895 (fix): installer step ordering, initial container count, and proc_close timeout 2026-03-25 00:02:21 +13:00
Jake BarnbyandGitHub 49688a6c75 Merge pull request #11628 from appwrite/patch-list-db 2026-03-24 11:00:55 +00:00
Jake Barnby d0978d891f (fix): installer step ordering, initial container count, and proc_close timeout 2026-03-24 23:53:56 +13:00
Jake Barnby 0cf206cacf (fix): installer progress counter display and dynamic step messages 2026-03-24 23:53:27 +13:00
Jake Barnby b9b5d396b8 Update console 2026-03-24 23:43:04 +13:00
ArnabChatterjee20k 2b33dc3c72 updated merging of user and current queries 2026-03-24 16:07:31 +05:30
ArnabChatterjee20k 9e595588bc lint 2026-03-24 16:03:28 +05:30
ArnabChatterjee20k 20bd7af370 added a fallback isnulll 2026-03-24 15:59:42 +05:30
Matej Bačo e5841c4cc0 Fix syntax error 2026-03-24 11:29:24 +01:00
Matej Bačo bb7815bd57 Merge branch '1.9.x' into feat-public-platform-api 2026-03-24 11:18:18 +01:00
ArnabChatterjee20k 3b2c8a4d36 added schema for presence api 2026-03-24 14:29:36 +05:30
Jake Barnby 5ca30d37f7 (fix): tolerate console signup restriction in installer account creation 2026-03-24 21:36:32 +13:00
Jake Barnby 76684874e9 (feat): installer improvements — reset, state resilience, container progress, SSL email fallback 2026-03-24 21:25:57 +13:00
Jake Barnby 4fffeda596 (chore): bump console image to 7.8.25 and drop postgresql from allowed databases 2026-03-24 21:25:51 +13:00
Jake Barnby 1acfef5f5d (fix): set installer session cookie domain to match Appwrite convention 2026-03-24 21:25:47 +13:00
Jake BarnbyandGitHub d4f7d51df8 Merge pull request #11621 from appwrite/fix-installer-certificates 2026-03-24 06:24:40 +00:00
Jake Barnby 5b5020fda4 fix: address review — normalize hostname for cert check, add cache bypass, improve fallback copy 2026-03-24 18:18:56 +13:00
Chirag Aggarwal 0c33d981a7 fix analyze 2026-03-24 10:47:41 +05:30
Jake Barnby a1441174f2 fix: update installer module test to expect 7 actions including CertificateGet 2026-03-24 17:57:58 +13:00
Chirag Aggarwal 36d8ad6e7c lock file 2026-03-24 10:21:40 +05:30
Chirag Aggarwal fbce66d500 fix merge conflict 2026-03-24 10:19:34 +05:30
Jake Barnby e58f0b6378 fix: address review — pass HTTPS port to certificate check, use resolved protocol for console button, add hex length guard 2026-03-24 17:46:22 +13:00
Chirag Aggarwal 89db65299d Merge remote-tracking branch 'origin/1.9.x' into feat/migrate-di-container 2026-03-24 10:15:38 +05:30
Jake Barnby c5c78e0f2b Merge remote-tracking branch 'origin/1.9.x' into fix-installer-certificates 2026-03-24 16:55:54 +13:00
Jake BarnbyandGitHub 2e5d7a53eb Merge pull request #11586 from appwrite/revert-11585-revert-11402-sync-mongodb 2026-03-24 03:54:33 +00:00
Jake Barnby 60b5f4433c (feat): add SSL certificate check step to web installer redirect flow 2026-03-24 16:53:43 +13:00
Jake Barnby 56ce0d4b47 Update lock 2026-03-24 16:04:40 +13:00
Jake BarnbyandGitHub 8666bf1325 Merge branch '1.9.x' into revert-11585-revert-11402-sync-mongodb 2026-03-24 03:03:02 +00:00
Chirag Aggarwal ea6a05be4f fix analyze 2026-03-24 07:51:23 +05:30
Chirag Aggarwal 355acf6824 Merge branch '1.9.x' into feat/migrate-di-container 2026-03-23 23:01:30 +05:30
Chirag Aggarwal c2988efa08 chore: use stable 2026-03-23 22:51:49 +05:30
HemachandarandGitHub d53cad2b0f perf: simplify repository authorized checks (#11616)
* perf: simplify repository authorized checks

* search repos
2026-03-23 22:18:35 +05:30
Luke B. SilverandGitHub 9d976fdc9f Merge pull request #11607 from appwrite/fix/failed-open-realtime-publishing
fix: fail open realtime publishing
2026-03-23 17:44:49 +01:00
Matej Bačo 8ccf5094f8 fix multi-model conditions 2026-03-23 16:42:14 +01:00
Matej Bačo 39f2d24907 AI code review fixes 2026-03-23 16:10:12 +01:00
Matej Bačo d3c5a425e7 Implement public platform API 2026-03-23 15:46:11 +01:00
Prem Palanisamy 043e7efb55 Update phpseclib to 3.0.50 to fix CVE-2026-32935 2026-03-23 13:46:58 +00:00
Prem Palanisamy 91cda84772 Update composer.lock after rebase onto 1.9.x 2026-03-23 13:46:58 +00:00
Prem Palanisamy 90641041ef Pass projectInternalId to destination migration 2026-03-23 13:46:58 +00:00
Prem Palanisamy e2f2ce3da8 Change console-key endpoint from GET to POST 2026-03-23 13:46:58 +00:00
Prem Palanisamy 374e1f8782 Add unit tests for scopedProjectId and source fields in Key::decode() 2026-03-23 13:46:58 +00:00
Prem Palanisamy b0a0c1552e Update composer.lock for utopia-php/migration dev branch 2026-03-23 13:46:58 +00:00
Prem Palanisamy 4020335aac Add E2E test for platform migration 2026-03-23 13:46:58 +00:00
Prem Palanisamy 75c26259a1 Add expire field to migration console key response 2026-03-23 13:46:58 +00:00
Prem Palanisamy 4e8feff46e Add platform migration support 2026-03-23 13:46:58 +00:00
Prem Palanisamy 4438e08f64 Add platform DB access via console-key endpoint 2026-03-23 13:46:57 +00:00
Harsh Mahajan 85703d29e1 addressed greptile suggestions 2026-03-23 19:08:12 +05:30
Harsh MahajanandGitHub d3c11b5c93 Merge branch '1.9.x' into feat-x-oauth2-provider 2026-03-23 18:51:10 +05:30
Chirag Aggarwal ce5d5cf6ef docs: add Rust SDK getting started guide 2026-03-23 17:58:24 +05:30
Chirag Aggarwal 2017183ecc Merge branch '1.9.x' into feat-rust-sdk 2026-03-23 17:56:22 +05:30
Matej BačoandGitHub 995deea801 Merge pull request #11577 from appwrite/feat-public-project-variables-api
Feat: Public project variables API
2026-03-23 13:24:10 +01:00
Chirag Aggarwal 1a1740ac7a feat: add Rust SDK support
Add Rust SDK entry to server platform config, wire up the Rust language
class in the SDK generation task, update sdk-generator to dev-rust branch,
and create the changelog directory.
2026-03-23 17:53:58 +05:30
Harsh MahajanandGitHub 6f177a0a7a Merge branch '1.9.x' into feat-x-oauth2-provider 2026-03-23 17:50:29 +05:30
Harsh Mahajan dc48bb35ef added pkce to base 2026-03-23 17:49:42 +05:30
Matej Bačo 0114e260f0 Fix tests 2026-03-23 12:56:23 +01:00
Matej Bačo 7ab474b963 Fix failing tests 2026-03-23 12:33:08 +01:00
Matej Bačo 10da066075 Merge branch '1.9.x' into feat-public-project-variables-api 2026-03-23 12:21:11 +01:00
Matej Bačo 01142bba2c Merge branch '1.8.x' into feat-public-project-variables-api 2026-03-23 12:21:06 +01:00
Matej BačoandGitHub 71146118d2 Merge pull request #11561 from appwrite/fix-oauth-token-flow-provider-param
Fix: OAuth2 token missing provider name
2026-03-23 12:17:29 +01:00
Matej Bačo 682105c068 Rework without schema changes 2026-03-23 11:52:40 +01:00
Matej Bačo 07ff923d38 Merge branch '1.9.x' into fix-oauth-token-flow-provider-param 2026-03-23 11:41:42 +01:00
Matej Bačo 2c5e029116 Merge branch '1.8.x' into fix-oauth-token-flow-provider-param 2026-03-23 11:41:39 +01:00
ArnabChatterjee20k 336bc3a74f Merge remote-tracking branch 'origin/revert-11585-revert-11402-sync-mongodb' into revert-11585-revert-11402-sync-mongodb 2026-03-23 15:20:22 +05:30
ArnabChatterjee20k 8a80ec12ed removed comments 2026-03-23 15:17:09 +05:30
Harsh Mahajan 8218f36d34 code rabbit comment 2026-03-23 13:32:06 +05:30
Harsh Mahajan 0fe906c538 feat: Add X OAuth 2.0 provider 2026-03-23 13:21:04 +05:30
ArnabChatterjee20kandGitHub b7f06a76aa Merge pull request #11608 from appwrite/fix/vectorsdb-console-improvements
Register missing vectorsdb listDocumentLogs endpoint
2026-03-23 13:07:31 +05:30
Prem Palanisamy a9acba916a fix: format VectorsDB registry import order 2026-03-23 06:40:48 +00:00
ArnabChatterjee20k fa97823385 Merge remote-tracking branch 'origin/revert-11585-revert-11402-sync-mongodb' into revert-11585-revert-11402-sync-mongodb 2026-03-23 10:48:04 +05:30
ArnabChatterjee20k 8ae07ac61f Merge remote-tracking branch 'origin/1.9.x' into revert-11585-revert-11402-sync-mongodb 2026-03-23 10:47:23 +05:30
ArnabChatterjee20kandGitHub ffbe17f34e Merge branch '1.9.x' into revert-11585-revert-11402-sync-mongodb 2026-03-23 10:47:02 +05:30
ArnabChatterjee20k 5466f55cfa removed logs 2026-03-23 10:46:53 +05:30
ArnabChatterjee20k 6c8338c3e9 Revert "added logging"
This reverts commit b630426893.
2026-03-23 10:44:35 +05:30
Chirag Aggarwal 4641596a6d use stable 2026-03-23 10:28:26 +05:30
Chirag Aggarwal d932527561 add null collacing 2026-03-23 10:27:10 +05:30
Chirag Aggarwal 89c072e223 fix analyze 2026-03-23 10:20:45 +05:30
Chirag Aggarwal 6421bc8689 fn name 2026-03-23 10:08:19 +05:30
Chirag Aggarwal d008d9bff0 merge conficts 2026-03-23 10:01:27 +05:30
Chirag Aggarwal f6dc6359bb Merge branch '1.9.x' into feat/migrate-di-container 2026-03-23 10:01:03 +05:30
Prem Palanisamy 28ba11c71e register vectorsdb listDocumentLogs endpoint 2026-03-22 23:02:34 +00:00
ArnabChatterjee20kandPrem Palanisamy a14d51321a refactor: remove debug output and enhance collection creation test with eventual assertion 2026-03-22 23:02:34 +00:00
ArnabChatterjee20kandPrem Palanisamy d8f3d581cc added log vectordb colleciton creation 2026-03-22 23:02:34 +00:00
loks0n be76990bb6 fix: fail open realtime publishing 2026-03-22 18:50:03 +01:00
Steven NguyenandGitHub 6e59e1307d Merge pull request #11602 from appwrite/copilot/update-trivy-in-workflow
Updating trivy in GitHub Actions workflow
2026-03-22 07:12:32 -07:00
Damodar LohaniandGitHub 31410879a7 Merge branch '1.9.x' into feat-audit-user-type-distinction 2026-03-22 08:36:13 +05:45
Damodar Lohani 343e352b17 fix: prevent overwriting user type in audit queue if already set 2026-03-22 02:31:46 +00:00
Damodar LohaniandGitHub b38ea72407 Merge branch '1.8.x' into feat-audit-user-type-distinction 2026-03-22 08:05:29 +05:45
eldadfux 34f23bdc9f Merge branch '1.8.x' into feat-disposable-emails
Made-with: Cursor

# Conflicts:
#	app/controllers/api/projects.php
2026-03-21 19:32:07 +01:00
copilot-swe-agent[bot]andstnguyen90 24848a872c chore: pin trivy-action to safe v0.35.0 SHA to fix compromised 0.20.0 tag
Co-authored-by: stnguyen90 <1477010+stnguyen90@users.noreply.github.com>
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/ad20d09a-e80d-4611-9959-2e35c3413736
2026-03-21 16:47:45 +00:00
ArnabChatterjee20k b630426893 added logging 2026-03-20 21:05:24 +05:30
Matej Bačo 0a5a8c5b6c Merge branch '1.8.x' into 1.9.x 2026-03-20 16:28:16 +01:00
Matej BačoandGitHub 875637bf35 Merge pull request #11533 from appwrite/feat-user-impersonation
Add impersonation feature for user management
2026-03-20 16:13:51 +01:00
ArnabChatterjee20k 1aa86708f3 added error loggins to check 2026-03-20 17:59:52 +05:30
Jake BarnbyandGitHub 6410c2dcdf Merge pull request #11592 from appwrite/chore-update 2026-03-20 11:03:13 +00:00
Chirag Aggarwal 032638e896 fix 2026-03-20 15:35:28 +05:30
Jake BarnbyGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
3a40b6b629 Apply suggestion from @greptile-apps[bot]
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-20 22:18:56 +13:00
Jake BarnbyandClaude Opus 4.6 ef0954cdda fix: propagate isUpgrade flag from Upgrade to Install for CLI path
Install::action() hardcoded isUpgrade=false, so the CLI upgrade path
never rewrote compose/env files. Added a protected property that
Upgrade sets before calling parent::action().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 21:56:06 +13:00
Chirag Aggarwal 10cc6a8040 fix global pools state 2026-03-20 14:09:43 +05:30
Chirag Aggarwal 9ecdbf5950 func exists 2026-03-20 13:13:07 +05:30
Jake BarnbyandClaude Opus 4.6 d4c9af2eb2 fix: strip surrounding quotes when parsing .env file values
Env::__construct() now strips " and ' wrapping from values so that
_APP_DB_ADAPTER="mariadb" is read as mariadb, not "mariadb".
Without this, the upgrade flow rejected the existing database adapter
because the quoted value didn't match the whitelist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:21:50 +13:00
Chirag Aggarwal 55b436c67b lock file 2026-03-20 12:17:28 +05:30
eldadfux f74a60dba4 Merge origin/1.8.x into feat-disposable-emails
Made-with: Cursor
2026-03-20 07:43:55 +01:00
Chirag Aggarwal 05defcc6e0 Merge branch '1.9.x' into feat/migrate-di-container 2026-03-20 12:12:29 +05:30
ArnabChatterjee20k 3baf2681df fix: remove appwrite-mongo-express service and update database type query filters in XList 2026-03-20 11:59:26 +05:30
Jake BarnbyandClaude Opus 4.6 1ad2cd68ef fix: rewrite compose/env files during upgrade so new image versions are applied
useExistingConfig was preventing the compose template from being
rewritten on non-local upgrades, leaving old image version tags in
place. Also fix Upgrade reading hardcoded .env instead of
getEnvFileName().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:12:47 +13:00
ArnabChatterjee20k 04680d68f5 fix: remove unused repository entry and update sdk-generator version 2026-03-20 11:39:40 +05:30
ArnabChatterjee20k 9a70445395 Merge remote-tracking branch 'origin/1.9.x' into revert-11585-revert-11402-sync-mongodb 2026-03-20 11:29:37 +05:30
Jake BarnbyandGitHub dabe98d4a1 Merge pull request #11591 from appwrite/chore-update 2026-03-20 05:54:39 +00:00
Jake Barnby c9d023991d (test): add e2e test for $sequence query type validation per adapter 2026-03-20 18:03:17 +13:00
Jake Barnby 91e252382b (test): add strict type assertions and list coverage for V21 $sequence filter 2026-03-20 17:33:15 +13:00
Jake BarnbyandClaude Opus 4.6 47408f04dc fix: always cast $sequence to string for SDK type safety
Revert the is_int preservation — SDKs declare $sequence as string,
so the API must always return a string. Updated tests to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 17:24:21 +13:00
Jake BarnbyandClaude Opus 4.6 827dd2d039 fix: preserve integer type for $sequence and handle nested relationship casting
- Document/Row model filters now preserve int type for $sequence instead of always casting to string
- V21 response filter recursively casts $sequence on nested relationship documents
- Added unit tests for nested document/row $sequence casting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 16:36:04 +13:00
Jake Barnby ed1ab85356 Remove exclude for CI 2026-03-20 15:41:15 +13:00
Jake Barnby c155c88a31 Fix stan 2026-03-20 15:37:23 +13:00
Jake Barnby df58831d92 Update DB 2026-03-20 14:23:59 +13:00
Jake Barnby aef9f447f7 Update lock 2026-03-20 14:17:40 +13:00
Jake Barnby 48e99e70ac fix: remove 1.8.2 references, upgrade path is 1.8.1 to 1.9.0 2026-03-20 14:09:07 +13:00
Jake Barnby 2007fbf241 Merge remote-tracking branch 'origin/1.8.x' into chore-update
# Conflicts:
#	app/init/constants.php
#	composer.json
#	composer.lock
#	src/Appwrite/Migration/Migration.php
2026-03-20 14:04:46 +13:00
Jake Barnby 2673af7b83 fix: propagate database param in CLI install and fix upgrade conflict detection 2026-03-20 13:26:18 +13:00
Jake Barnby 21d7e2dfb5 chore: update utopia-php/database to fix metadata reconciliation 2026-03-20 13:08:34 +13:00
Jake Barnby 9235a4cc15 test: add V21 request and response filter unit tests 2026-03-20 13:08:27 +13:00
Jake Barnby fb21211ae8 fix: return \$sequence as string and add V21 backward-compat filters 2026-03-20 13:08:19 +13:00
Jake Barnby 616762f696 fix: harden migrate task for MongoDB compat and 1.8.x upgrades 2026-03-20 13:08:12 +13:00
Jake Barnby 3b5ba2a2b0 feat: add V24 migration for 1.9.0 schema changes 2026-03-20 13:08:05 +13:00
Jake Barnby 7ae1cf4db0 chore: bump version to 1.9.0 and update changelog 2026-03-20 13:07:55 +13:00
Steven NguyenandGitHub c537d09b5c Merge pull request #11589 from appwrite/copilot/update-origin-test-cases-tauri
Add tauri:// as a supported origin scheme
2026-03-19 16:06:35 -07:00
eldadfux efeb3b4a00 fixes 2026-03-19 23:02:41 +01:00
eldadfux aa89128d10 Fixes 2026-03-19 22:54:45 +01:00
eldadfux e8c0ab0cc1 Fixed email validator 2026-03-19 22:44:04 +01:00
eldadfux b81f3f8267 Merge origin/1.8.x into feat-user-impersonation 2026-03-19 22:12:38 +01:00
copilot-swe-agent[bot]andstnguyen90 b79132556c feat: add support for tauri://localhost as an allowed origin
Co-authored-by: stnguyen90 <1477010+stnguyen90@users.noreply.github.com>
2026-03-19 20:14:24 +00:00
copilot-swe-agent[bot] 99c76689ee Initial plan 2026-03-19 20:12:17 +00:00
Chirag Aggarwal 6700340ef3 fix realtime 2026-03-19 23:26:49 +05:30
Chirag Aggarwal 4224f6ea5a merge conficts 2026-03-19 22:15:31 +05:30
Chirag Aggarwal 8d925f3670 merge conficts 2026-03-19 21:35:19 +05:30
Chirag Aggarwal d2875c9bf6 Merge branch '1.8.x' into feat/migrate-di-container 2026-03-19 21:35:06 +05:30
Chirag Aggarwal c002f4afe3 fix specs generation 2026-03-19 21:23:00 +05:30
ArnabChatterjee20kandGitHub c7907932e4 Revert "Revert "Documentsdb + vectordb (latest)"" 2026-03-19 20:30:42 +05:30
Matej Bačo bdd3c2f9f5 Fix failing tests 2026-03-19 15:33:36 +01:00
Matej Bačo 1754d6cc81 Fix failing tests 2026-03-19 15:21:22 +01:00
Matej Bačo 351efa6cf2 Fix backwards compatibility 2026-03-19 15:00:20 +01:00
Jake BarnbyandGitHub 1aa2c7ff2b Merge pull request #11585 from appwrite/revert-11402-sync-mongodb 2026-03-19 13:50:22 +00:00
ArnabChatterjee20kandGitHub 9917f95dfd Revert "Documentsdb + vectordb (latest)" 2026-03-19 19:18:27 +05:30
Matej Bačo 8af0efafd4 Merge branch '1.8.x' into feat-public-project-variables-api 2026-03-19 14:17:26 +01:00
Matej BačoandGitHub 7e7cac017c Merge pull request #11582 from appwrite/fix-webhooks-duplication
Fix: webhook endpoints duplication
2026-03-19 14:16:30 +01:00
Jake BarnbyandGitHub b8cb146eb9 Merge pull request #11402 from appwrite/sync-mongodb 2026-03-19 12:30:05 +00:00
Jake BarnbyandGitHub 9ed6ac89f5 Merge pull request #11574 from appwrite/feat-installer 2026-03-19 12:19:15 +00:00
Luke B. SilverandGitHub 09313d0500 Merge pull request #11583 from appwrite/ci/blackmisth-runners
ci: use blacksmith runners for slowest e2e services
2026-03-19 12:08:09 +00:00
Chirag Aggarwal ecb8104340 register resources 2026-03-19 17:37:47 +05:30
Chirag Aggarwal e6090800e2 register resources 2026-03-19 17:35:31 +05:30
Damodar LohaniandGitHub 92b7760e94 Merge branch '1.8.x' into feat-audit-user-type-distinction 2026-03-19 17:44:50 +05:45
Damodar LohaniGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
fe988f4489 Update app/controllers/shared/api.php
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-19 17:44:35 +05:45
loks0nandClaude Sonnet 4.6 3a9e4305d3 ci: switch docker image sharing from cache to artifacts
actions/cache uses a runner-local cache backend, so GitHub-hosted
runners and Blacksmith self-hosted runners cannot share the same cache
entry. Switch to actions/upload-artifact@v7 / download-artifact@v7
which use GitHub's artifact storage, accessible from all runner types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 11:54:50 +00:00
loks0nandClaude Sonnet 4.6 4b163c02ad ci: use blacksmith runners for slowest e2e services
Route the 6 slowest e2e test services (Databases, Sites, Functions,
Avatars, Realtime, TablesDB) to blacksmith-4vcpu-ubuntu-2404 runners
based on timing data from CI. All other services continue using
ubuntu-latest.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 11:44:12 +00:00
ArnabChatterjee20k 331fb7f689 fixed php stan issue 2026-03-19 17:10:45 +05:30
Matej Bačo b80d76e287 Fix failing test 2026-03-19 12:38:35 +01:00
ArnabChatterjee20k feddd77066 fixed analyze 2026-03-19 17:02:27 +05:30
ArnabChatterjee20k a55f3e1db8 updated env 2026-03-19 16:59:18 +05:30
Jake BarnbyandClaude Opus 4.6 9bc263ab22 fix: correct stale progress file cleanup status detection
Steps are keyed by step name (e.g. 'env-vars'), not by status value.
The old lookup used status constants as step keys, so $status was
always null and terminal-state cleanup never triggered. Detect
terminal state by checking for $data['error'] (failure) or all steps
having 'completed' status.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 00:29:03 +13:00
Jake Barnby 6c76783c09 Merge remote-tracking branch 'origin/1.8.x' into feat-installer
# Conflicts:
#	composer.lock
2026-03-20 00:25:32 +13:00
Matej Bačo 835bb49562 Merge branch '1.8.x' into fix-webhooks-duplication 2026-03-19 12:20:37 +01:00
Matej Bačo ab43d4995b Upgrade webhook tests 2026-03-19 12:20:33 +01:00
ArnabChatterjee20k f1e2ce3e09 Merge remote-tracking branch 'origin/1.8.x' into sync-mongodb 2026-03-19 16:42:25 +05:30
ArnabChatterjee20k 6fa34aef04 linting 2026-03-19 16:39:19 +05:30
Jake BarnbyandClaude Opus 4.6 d8748c9054 fix: make Webhook model public and fix Installer autoload path
The Webhook model was marked as non-public but is now exposed via
AuthType::KEY in the new Webhooks module, causing spec generation
to crash when building server SDK specs. The Installer Server.php
had a top-level require_once for vendor/autoload.php that fails
when CE is used as a composer dependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 23:50:21 +13:00
Matej Bačo 6f8a54273d AI review fixes 2026-03-19 11:47:02 +01:00
Matej Bačo 37a7c70c2b Fix webhook endpoints duplication 2026-03-19 11:27:13 +01:00
ArnabChatterjee20k 7071e6f080 Merge remote-tracking branch 'origin/1.8.x' into sync-mongodb 2026-03-19 15:55:28 +05:30
Jake BarnbyandGitHub 4c1a360797 Merge pull request #11578 from appwrite/fix/database-shared-table-reconciliation 2026-03-19 10:21:02 +00:00
Jake Barnby f7ea7b165f Update DB 2026-03-19 23:20:44 +13:00
ArnabChatterjee20k 29fcdcd766 update composer dependencies and fix database DSN retrieval in Create.php 2026-03-19 15:48:54 +05:30
Jake Barnby 62f91f746e (fix): clear stale installer lock and progress files on startup 2026-03-19 23:15:20 +13:00
Jake Barnby d27aad6e67 (feat): add enabledDatabases config to hide unsupported databases in installer 2026-03-19 22:10:23 +13:00
Chirag AggarwalandGitHub 0237cced6b Merge branch '1.8.x' into feat/migrate-di-container 2026-03-19 14:10:20 +05:30
Chirag Aggarwal 625ec4ce91 sync changes 2026-03-19 14:09:04 +05:30
Jake BarnbyandClaude Opus 4.6 3d55c27b10 fix: resolve PHPStan analysis errors
- Remove redundant !empty($previewRuleId) check in Deployment trait (always truthy within sites condition)
- Remove stale baseline ignores for $dbForPlatform in cli.php and $database in http.php

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 21:35:14 +13:00
Jake Barnby 30b18bef2b Update deps 2026-03-19 21:08:36 +13:00
ArnabChatterjee20k 221b5d104c updated migration 2026-03-19 13:20:46 +05:30
ArnabChatterjee20k 826ac1abe9 dummy commit to check some tests and edge cases 2026-03-19 12:45:16 +05:30
Chirag Aggarwal 5339592b3f update http 2026-03-19 10:20:47 +05:30
Chirag Aggarwal 9f1d092717 analyze fixes 2026-03-19 10:17:25 +05:30
Chirag Aggarwal e38a4e4347 update queue 2026-03-19 10:08:40 +05:30
Chirag Aggarwal fdbc5b6737 update queue 2026-03-19 10:07:08 +05:30
Chirag Aggarwal fa2b7955d0 update dependencies 2026-03-19 10:04:47 +05:30
Damodar LohaniandClaude Opus 4.6 8b3d3c6f8b feat: distinguish user types in audit logs
Introduce granular audit user types to differentiate between regular
users, console admins, guests, and the various API key scopes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 03:55:22 +00:00
Jake Barnby e1c327235a (feat): add dark mode support to installer 2026-03-19 15:03:58 +13:00
premtsd-codeandGitHub e6bf327b25 Remove worker number setting from CI workflow
Removed the setting for the number of workers in the CI configuration.
2026-03-18 18:29:18 +00:00
Prem Palanisamy 7e0bf72a89 ci: reduce database worker count to 2 for E2E tests 2026-03-18 16:29:46 +00:00
Prem Palanisamy 1d27986fa1 ci: increase database worker count to 4 for E2E tests 2026-03-18 16:28:27 +00:00
Matej Bačo 4bfb958146 fix events 2026-03-18 16:21:26 +01:00
Matej Bačo 33e3e5e63d Fix noop patch call scenario 2026-03-18 16:17:04 +01:00
Matej Bačo 564f56e0f5 Finalize tests 2026-03-18 16:12:47 +01:00
Matej Bačo 1a6a66406b Improve E2E tests for sites&functions 2026-03-18 16:00:03 +01:00
Matej Bačo 84316fdfb5 Add project variable tests 2026-03-18 15:41:43 +01:00
Matej Bačo a6eac0a01e Remvoe breaking change 2026-03-18 15:24:56 +01:00
Matej Bačo 412d858c49 AI comments fixes 2026-03-18 15:23:19 +01:00
premtsd-codeandGitHub 7d7bb0591f Merge branch '1.8.x' into fix/database-shared-table-reconciliation 2026-03-18 13:58:04 +00:00
Prem Palanisamy 9de6ec7279 fix: use database branch with shared-table reconciliation fix 2026-03-18 13:56:05 +00:00
Matej Bačo ba94bff8d4 Public project variables API 2026-03-18 14:48:31 +01:00
HemachandarandGitHub 6b573524e7 fix: Ensure rules are deleted properly (#11575)
* fix: Ensure rules are deleted properly

* tests
2026-03-18 19:02:03 +05:30
Chirag Aggarwal ea11af1e15 chore: worker changes 2026-03-18 17:50:25 +05:30
Chirag AggarwalandGitHub 6cbae88131 Merge branch '1.8.x' into feat/migrate-di-container 2026-03-18 17:14:45 +05:30
Chirag Aggarwal 03b2755aaf fix async execution 2026-03-18 16:43:14 +05:30
Matej BačoandGitHub 12d274a72c Merge pull request #11566 from appwrite/feat-platform-endpoints
Feat: Public project APIs
2026-03-18 11:57:18 +01:00
ArnabChatterjee20kandGitHub 979cfb0ba8 Merge pull request #11570 from appwrite/fix/vectorsdb-console-improvements
fix: vectorsdb SDK spec and DatabaseType enum
2026-03-18 16:18:16 +05:30
Luke B. SilverandGitHub f36821148f Merge branch '1.8.x' into chore/bump-databases 2026-03-18 09:46:21 +00:00
eldadfux 85fcc52b84 Merge origin/1.8.x into feat-user-impersonation 2026-03-18 10:23:03 +01:00
Chirag AggarwalandGitHub 2095a94bc5 Merge branch '1.8.x' into feat/migrate-di-container 2026-03-18 14:19:59 +05:30
Chirag Aggarwal df7796d5cb restore phpunit 2026-03-18 14:19:43 +05:30
ArnabChatterjee20k 6b851cccdb updated composer 2026-03-18 13:57:18 +05:30
ArnabChatterjee20k 540979f9fb updated migration test 2026-03-18 13:35:42 +05:30
ArnabChatterjee20k 2add5e2d25 fix: update required field comment and rename createTextEmbedding method 2026-03-18 12:38:54 +05:30
ArnabChatterjee20k 8d58383c2e Merge remote-tracking branch 'origin/1.8.x' into sync-mongodb 2026-03-18 11:38:37 +05:30
Chirag Aggarwal d177e3adfc fix pool issue 2026-03-18 11:27:26 +05:30
Jake Barnby a3bb7af634 (chore): improve wait time 2026-03-18 18:44:13 +13:00
Jake Barnby 114016bbca (fix): preserve session cookie across installer redirect 2026-03-18 17:50:47 +13:00
Chirag Aggarwal d04ac5c3e6 fix pool issue 2026-03-18 09:42:18 +05:30
Eldad A. FuxandGitHub 6c383b41f5 Merge pull request #11573 from appwrite/fix-realtime-errors
Fix realtime errors
2026-03-17 22:48:30 +01:00
Prem Palanisamy 8ba35d8810 fix: vectorsdb SDK spec and DatabaseType enum
- Fix createTextEmbeddings SDK parameters (texts, model instead of databaseId, collectionId, documents)
- Add vectorsdb to DatabaseType enum in Database response model
2026-03-17 16:12:48 +00:00
Chirag Aggarwal 60939da801 fix graphql 2026-03-17 21:39:50 +05:30
loks0nandClaude Sonnet 4.6 73a01d56a7 chore: bump utopia-php/database to stable 5.3.15 release
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:15:02 +00:00
Matej Bačo 6d8bf6c0dd AI review fixes 2026-03-17 13:56:27 +01:00
Matej Bačo bf375d1d40 Fix bugs 2026-03-17 13:52:50 +01:00
Matej Bačo fd05827089 Implement tests 2026-03-17 13:27:53 +01:00
Matej Bačo ac2b35004d Add more public webhook endpoints 2026-03-17 13:03:26 +01:00
Chirag Aggarwal cdb301a293 fix PHPStan errors without regenerating baseline
- Fix dispatch() type hint to use \Swoole\Http\Server instead of Utopia adapter
- Remove unused $register from go() closure in http.php
- Remove unnecessary ?? '' on non-nullable $hostname
- Remove unsupported override: param from addHeader() call
- Update Resolvers.php for new getResource()/execute() signatures
- Migrate Installer/Server.php from static Http::setResource() to container
- Remove stale baseline entries, add 1 for pre-existing Deployment.php issue
2026-03-17 17:30:42 +05:30
Chirag Aggarwal 14a9aa890f fix issues 2026-03-17 17:18:57 +05:30
Chirag Aggarwal d3833d06d5 Merge 1.8.x (accept theirs) 2026-03-17 17:15:54 +05:30
Chirag Aggarwal 5ba19ec763 fix alias 2026-03-17 17:14:35 +05:30
Luke B. SilverandGitHub c4b7bb8ee9 Merge pull request #11568 from appwrite/ci/phpstan-baseline-2
ci: reduce PHPStan level to 3 and regenerate baseline
2026-03-17 11:25:58 +00:00
loks0nandClaude Sonnet 4.6 48853cdbe1 ci: reduce PHPStan level to 3 and regenerate baseline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 11:16:59 +00:00
Chirag Aggarwal 0564936c4f update file name 2026-03-17 16:29:21 +05:30
Chirag Aggarwal c2795376d8 update server 2026-03-17 16:25:52 +05:30
Matej Bačo 6b6092c9ec Add more webhook endpoints 2026-03-17 11:20:18 +01:00
Matej Bačo 677bb048cc Introduce new webhooks API 2026-03-17 11:03:18 +01:00
Chirag AggarwalandGitHub 2b898dfe50 Merge branch '1.8.x' into feat/migrate-di-container 2026-03-17 15:22:02 +05:30
Chirag Aggarwal fa1404be52 cleanup 2026-03-17 15:20:29 +05:30
Chirag Aggarwal be87675e1c fix realtime 2026-03-17 15:03:55 +05:30
Chirag Aggarwal b1edf75c4e phpunit 2026-03-17 14:29:04 +05:30
Chirag Aggarwal 8a0f092342 fix: only throw container lookup errors 2026-03-17 14:28:37 +05:30
Chirag Aggarwal 87b02de612 baseline 2026-03-17 11:32:15 +05:30
Chirag Aggarwal f8e9f71de3 move resources 2026-03-17 11:19:17 +05:30
Chirag Aggarwal c055c32371 enable coroutines 2026-03-17 10:52:35 +05:30
Chirag Aggarwal d60858cd8f fix phpstan 2026-03-17 10:37:49 +05:30
Chirag Aggarwal 22bd655ad1 fix autoload 2026-03-17 10:28:08 +05:30
Chirag Aggarwal c900b22dc0 fix connection container and view class 2026-03-17 09:52:55 +05:30
Chirag Aggarwal 27e5ade92b fix login 2026-03-17 09:43:54 +05:30
Chirag Aggarwal d9c1b9db2a chore: register request resources seperately 2026-03-17 08:49:43 +05:30
Luke B. SilverandGitHub b093a3fb24 Merge pull request #11563 from appwrite/test/flaky-usage
fix: use console headers for attribute status polling in usage tests
2026-03-16 18:47:55 +00:00
Chirag Aggarwal eb399c7bd0 wip 2026-03-16 23:45:18 +05:30
Chirag Aggarwal b8e51366d7 add missing compression methods 2026-03-16 23:32:31 +05:30
Chirag Aggarwal b75cd993be missing trusted ip headers 2026-03-16 23:27:34 +05:30
Chirag Aggarwal a1bc503ce4 formatting 2026-03-16 23:23:03 +05:30
Chirag Aggarwal e475a7ac5a lock file 2026-03-16 23:19:07 +05:30
loks0nandClaude Sonnet 4.6 3dc9a10fab fix: use console headers for execution status polling in usage tests
Switch function execution status polling in testPrepareFunctionsStats to
use console headers so the non-deterministic number of poll requests does
not skew the project request count assertion or cause null-status failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 17:41:25 +00:00
Chirag Aggarwal 2dc24dabfd Merge branch '1.8.x' into feat/migrate-di-container 2026-03-16 23:10:09 +05:30
Chirag Aggarwal aaa2a0525f feat: migrate from static Http::setResource() to DI Container
Upgrade utopia-php/framework from 0.33.x to 0.34.x which removes the
static Http::setResource() API. Resources are now registered on a
Utopia\DI\Container instance.

- Replace 81 Http::setResource() calls in resources.php with $container->set()
- Refactor http.php to use Swoole HttpServer adapter with shared container
- Refactor realtime.php to use FPM adapter with global container
- Refactor cli.php to use direct $cli->setResource() calls
- Update Specs.php to use local container + FPM adapter
- Update Migrate.php to inject console document instead of creating Http instance
- Update GraphQL Schema.php to use instance setResource()
2026-03-16 23:00:36 +05:30
Luke B. SilverandGitHub 2142905409 Merge branch '1.8.x' into test/flaky-usage 2026-03-16 17:28:27 +00:00
loks0nandClaude Sonnet 4.6 af401eb950 fix: use console headers for attribute status polling in usage tests
Switch attribute/column status polling in testPrepareDatabaseStatsCollectionsAPI
and testPrepareDatabaseStatsTablesAPI to use console headers so the non-deterministic
number of poll requests does not skew the project request count assertion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 17:27:41 +00:00
Matej Bačo afd8d8a020 Implement a fix to oauth missing provider 2026-03-16 16:57:35 +01:00
Matej Bačo 90f0282ce3 Implement oauth2 token flow tests 2026-03-16 16:31:08 +01:00
Luke B. SilverandGitHub 392926bc1a Merge pull request #11247 from appwrite/feat-installer
Add web installer
2026-03-16 14:54:09 +00:00
Jake BarnbyandClaude Opus 4.6 06c5a2845f fix: restore deployment polling timeout in Realtime testChannelExecutions
The timeout parameters were accidentally removed in daaf1d7f77, causing
the assertEventually to use the default timeout which is too short for
deployment builds in CI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 01:30:55 +13:00
Jake BarnbyandClaude Opus 4.6 e6272f7a35 fix: update utopia-php/database to fix shared tables collection recreation
Points to fix-collection-recreate branch which prevents the orphan
reconciliation logic from dropping and recreating shared physical tables
when a new tenant creates collections. In shared-tables mode, a
DuplicateException from the adapter means the table is shared with other
tenants, not an orphan from a partial failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 00:58:44 +13:00
Jake BarnbyandClaude Opus 4.6 e8790c405b fix: sync composer.lock content-hash with composer.json
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 00:10:15 +13:00
Jake Barnby 83a8e2811d Update DB 2026-03-17 00:08:01 +13:00
Jake BarnbyandClaude Opus 4.6 41017bb0af fix: revert app/sdks exclusion (not present in CI)
The app/sdks directory is gitignored and doesn't exist in CI. Removing
the exclusion to avoid PHPStan failing on a non-existent path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 23:48:22 +13:00
Jake BarnbyandClaude Opus 4.6 5478d3d601 fix: regenerate PHPStan baseline and exclude SDK tests
The baseline had gone stale after System::getEnv signature changed from
string|null to string. Regenerated to remove unmatched patterns and add
new installer code entries. Also excluded app/sdks from analysis since
those are auto-generated SDK files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 23:30:19 +13:00
Jake Barnby 176a29d608 Remove redundant dep 2026-03-16 21:42:37 +13:00
Jake Barnby ee732581d8 Update DB 2026-03-16 21:40:06 +13:00
Jake Barnby a94649fb58 Merge remote-tracking branch 'origin/1.8.x' into feat-installer
# Conflicts:
#	composer.json
#	composer.lock
2026-03-16 21:25:10 +13:00
Chirag AggarwalandGitHub 366da19b61 Merge pull request #11472 from appwrite/update-python-sdk-readme
chore: update python sdk readme with pydantic models
2026-03-16 13:40:36 +05:30
Luke B. SilverandGitHub 5a56a2fc35 Merge pull request #11551 from appwrite/ci/better-checks
ci: migrate checks to use setup-php and setup-node
2026-03-15 18:59:35 +00:00
eldadfux 468eff690d Merge origin/1.8.x into feat-user-impersonation
Made-with: Cursor
2026-03-15 19:53:57 +01:00
eldadfux 5e04480cc0 Merge branch '1.8.x' of origin into feat-disposable-emails
Made-with: Cursor
2026-03-15 19:53:04 +01:00
loks0nandClaude Sonnet 4.6 5e3f698b57 chore: remove HeadersBench entries from phpstan baseline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 18:51:07 +00:00
loks0nandClaude Sonnet 4.6 a86217bf4b chore: remove phpbench dependency and benchmark files
phpbench pulled in doctrine/annotations which has a security advisory.
The bench script and its only benchmark file are unused in CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 18:48:24 +00:00
loks0nandClaude Sonnet 4.6 4863b29754 ci: install dependencies before composer audit
composer audit requires packages to be installed to check them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 18:43:51 +00:00
loks0nandClaude Sonnet 4.6 e0a2b5c1ef ci: unset COMPOSER_NO_AUDIT for audit step
setup-php sets COMPOSER_NO_AUDIT=1 by default, which causes composer
audit to skip. Override it to 0 for the audit step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 18:42:36 +00:00
loks0nandClaude Sonnet 4.6 bc2a0d573c ci: resolve merge conflict with 1.8.x
Use composer analyze (renamed from composer check in 1.8.x).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 18:40:38 +00:00
loks0nandClaude Sonnet 4.6 e1bb4fc2cc ci: migrate checks to use setup-php and setup-node instead of Docker
Replace docker-based composer/node invocations in the format and analyze
jobs with shivammathur/setup-php@v2 and actions/setup-node@v4. Split
locale check and composer validate/audit into dedicated jobs for clarity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 18:38:50 +00:00
Luke B. SilverandGitHub 79f907ff55 Merge pull request #11550 from appwrite/ci/phpstan-baseline
ci: upgrade PHPStan to v2 with baseline and full codebase coverage
2026-03-15 18:12:52 +00:00
loks0nandClaude Sonnet 4.6 937d94972f ci: upgrade PHPStan to v2 with baseline and full codebase coverage
- Upgrade phpstan/phpstan from 1.12.* to ^2.0
- Raise analysis level to max
- Expand paths from 3 specific dirs to src/, app/, bin/, tests/
- Generate baseline capturing 95,365 existing errors for incremental adoption
- Rename composer script from `check` to `analyze`
- Add --memory-limit=1G to handle larger scan scope

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 17:49:49 +00:00
Luke B. SilverandGitHub aefa51f261 Merge pull request #11539 from appwrite/chore-speedup-tests
Chore: Speed-up deployments in tests
2026-03-15 17:16:26 +00:00
loks0nandClaude Sonnet 4.6 f9ecd28dd0 Revert composer.lock to 1.8.x
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 16:57:39 +00:00
Matej Bačoandloks0n 7e6db9a99b Fix tests missing lock files 2026-03-15 16:39:12 +00:00
Matej Bačoandloks0n c618372d37 linter fix 2026-03-15 16:39:12 +00:00
Matej Bačoandloks0n 975a38bf43 Speed-up deployments in tests 2026-03-15 16:39:12 +00:00
Luke B. SilverandGitHub 6b21968413 Merge pull request #11549 from appwrite/test/split-databases
Split TablesDB e2e tests into a separate CI job
2026-03-15 16:36:14 +00:00
loks0nandClaude Opus 4.6 286e0e5802 Fix import ordering in TablesDB test files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 16:25:05 +00:00
loks0nandClaude Opus 4.6 ac05c9e9ce Split TablesDB e2e tests into a separate CI job
Move all TablesDB* test files from tests/e2e/Services/Databases/ to a
new tests/e2e/Services/TablesDB/ directory, updating namespaces and
adding explicit imports for shared base traits. Add TablesDB as a
separate service in the CI matrix so /v1/databases and /v1/tables tests
run as independent parallel jobs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 16:22:01 +00:00
eldadfux ea2b1519d5 Updated deps 2026-03-15 09:26:37 +01:00
eldadfux e5385f7512 Removed old validator 2026-03-15 08:54:11 +01:00
eldadfux e13fe32f78 Update packages 2026-03-15 08:44:06 +01:00
eldadfux d8bf4b9f89 Implement email validation rules for disposable, canonical, and free emails in user account creation and project settings. Update error handling for invalid email types and adjust related configurations in the console and project models. 2026-03-14 09:21:22 +01:00
Jake BarnbyandClaude Opus 4.6 daaf1d7f77 fix: revert test polling timeouts to original values
The root cause (PostgreSQL duplicate index race condition) has been
fixed in the database library, so the timeout workarounds are no
longer needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 21:00:36 +13:00
Jake BarnbyandClaude Opus 4.6 1209a2e344 fix: increase Webhooks test polling timeouts to 240s for CI stability
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 13:27:15 +13:00
Jake BarnbyandClaude Opus 4.6 04bbc30e7e fix: move attribute setup outside Swoole coroutine to prevent fatal errors
The assertEventually call inside Coroutine\run() causes a fatal PHP
error (exit 255) when it times out, killing the entire test process.
Moving DB/collection/attribute setup and polling outside the coroutine
ensures PHPUnit can properly handle assertion failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 12:55:14 +13:00
Jake BarnbyandClaude Opus 4.6 48c9ecb53f fix: increase all Realtime test polling timeouts to 240s for CI stability
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 12:12:48 +13:00
Jake BarnbyandClaude Opus 4.6 eb082cdd86 fix: increase GraphQL Legacy attribute polling timeout to 240s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 12:03:01 +13:00
Jake BarnbyandClaude Opus 4.6 0c7a9290bd fix: increase Sites deployment timeouts from 120s to 200s for CI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 11:48:57 +13:00
Jake BarnbyandClaude Opus 4.6 341aabb942 fix: handle PostgreSQL race condition in shared mode project creation
PostgreSQL adapter throws non-Duplicate exceptions when a table/index
already exists during concurrent createCollection. Catch Throwable and
attempt metadata-only creation as fallback. Also increase Realtime
coroutine attribute polling timeout from 30s to 60s.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 11:29:53 +13:00
Jake BarnbyandClaude Opus 4.6 09c963abda fix: use $sequence for team lookup instead of $internalId
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 10:51:18 +13:00
Jake BarnbyandClaude Opus 4.6 eb2f0b1cea fix: restore team lookup by $internalId and remove duplicate composer key
The team resource query was changed from $internalId to $sequence,
breaking console session authentication (401 on project creation).
Also removed duplicate utopia-php/servers entry in composer.json
and updated composer.lock hash.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 10:40:29 +13:00
Jake BarnbyandGitHub 01a9340eaf Merge branch '1.8.x' into feat-installer 2026-03-14 10:30:29 +13:00
eldadfux 2d2c55e07c Merge remote-tracking branch 'origin/1.8.x' into feat-disposable-emails
Made-with: Cursor

# Conflicts:
#	app/controllers/api/account.php
#	app/controllers/api/messaging.php
#	app/controllers/api/projects.php
#	app/controllers/api/teams.php
#	app/controllers/api/users.php
#	composer.lock
#	src/Appwrite/GraphQL/Types/Mapper.php
#	src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Create.php
#	src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Email/Update.php
#	src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Email/Create.php
#	src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/Email/Update.php
#	src/Appwrite/SDK/Specification/Format/OpenAPI3.php
#	src/Appwrite/SDK/Specification/Format/Swagger2.php
2026-03-13 21:56:58 +01:00
eldadfux f6d38fe1ce Merge remote-tracking branch 'origin/1.8.x' into feat-user-impersonation
Made-with: Cursor

# Conflicts:
#	app/controllers/shared/api.php
2026-03-13 21:48:41 +01:00
Jake BarnbyandClaude Opus 4.6 8eb309e92c fix: update utopia-php/database to 5.3.12 for tenant type comparison fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 01:17:07 +13:00
Jake Barnby 6fee0cb73e Revert "fix: cast tenant to int for SQL adapters to fix strict type comparison"
This reverts commit 840b1fbef3.
2026-03-14 01:16:14 +13:00
Jake BarnbyandClaude Opus 4.6 840b1fbef3 fix: cast tenant to int for SQL adapters to fix strict type comparison
Document::getTenant() casts numeric tenants to (int), but
adapter->getTenant() held a string from getSequence(). The strict
!== comparison in Database::getCollection() then failed, returning
"Collection not found" for all shared-table MariaDB projects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 01:06:43 +13:00
Jake BarnbyandClaude Opus 4.6 03ebc4857a fix: pin utopia-php/di and servers to prevent breaking upgrades
The composer update pulled in utopia-php/di 0.3.1 which removed the
Dependency class, causing fatal errors. Pin to 0.1.0 and servers 0.2.5.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 00:49:31 +13:00
Jake BarnbyandClaude Opus 4.6 1db4c40c6a fix: update composer.lock to clean database 5.3.11
Picks up reverted Sequence validator changes that were breaking
MariaDB shared tables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 00:37:23 +13:00
Jake BarnbyandClaude Opus 4.6 99f08b9f95 fix: update utopia-php/database to 5.3.11 and utopia-php/mongo to 1.*
Use released versions instead of dev branches:
- database 5.3.11: $tenant type VAR_INTEGER -> VAR_ID for MongoDB compatibility
- mongo 1.0.1: collection exists exception uses error code 48

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 00:17:34 +13:00
eldadfux b85cf2fdb6 applied new logic for logs 2026-03-13 09:18:39 +01:00
eldadfux d8df5f1ea1 Updated comments and docs 2026-03-13 08:21:02 +01:00
Jake BarnbyandClaude Opus 4.6 cdbb8ff7e4 fix: use mongo library branch with collection exists error code fix
Adds utopia-php/mongo dev-fix-collection-exists-code which sets error
code 48 (NamespaceExists) when createCollection detects the collection
already exists. This allows the database adapter to handle it as a
DuplicateException and return true, fixing shared tables mode where
collections like _audit are shared across projects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 20:20:06 +13:00
eldadfux 10c587cbc2 Removed demo app 2026-03-13 08:08:34 +01:00
eldadfux e409524033 Fixed cors, added a test, fixed scope management 2026-03-13 08:06:07 +01:00
Jake BarnbyandClaude Opus 4.6 99f21b8af6 fix: use database library fix-mongo-perms branch for VAR_ID tenant type
Updates utopia-php/database to dev-fix-mongo-perms which changes the
$tenant internal attribute type from VAR_INTEGER to VAR_ID, fixing
MongoDB shared tables where tenant values are UUID7 strings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 20:04:42 +13:00
Jake BarnbyandClaude Opus 4.6 871795efa9 fix: remove conditional tenant int cast, use string tenant directly
The $tenant attribute type in the database library is being changed from
VAR_INTEGER to VAR_ID, which handles both SQL (integer) and MongoDB
(UUID7 string) adapters natively. This removes the now-unnecessary
conditional casting pattern throughout the codebase.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 19:53:42 +13:00
eldadfux 29d9c138c9 Added new tests 2026-03-13 07:25:36 +01:00
Jake Barnby dba8c28741 (fix): resolve permissions test collisions in parallel execution 2026-03-13 19:13:17 +13:00
Jake Barnby 526aa590e1 (fix): add _APP_DATABASE_SHARED_TABLES env to scheduler-executions container 2026-03-13 19:13:13 +13:00
Jake Barnby d91e3543c8 (fix): use adapter ID type to determine tenant value for MongoDB compatibility 2026-03-13 19:13:07 +13:00
Jake BarnbyandClaude Opus 4.6 f15f63c0c1 fix: update utopia-php/database to 5.3.9 for project permission fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 16:31:54 +13:00
Jake BarnbyandClaude Opus 4.6 fa406adab9 fix: update composer.lock to resolve paratest dependency conflict
symfony/console and symfony/string needed upgrading to satisfy
brianium/paratest v7.19.2's version constraints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:49:34 +13:00
Jake Barnby aa1012ffb6 Merge remote-tracking branch 'origin/1.8.x' into feat-installer
# Conflicts:
#	.github/workflows/tests.yml
#	composer.lock
2026-03-13 14:47:30 +13:00
Jake Barnby c192a154f5 (fix): Prevent auto-redirect on installer step errors 2026-03-13 14:36:45 +13:00
eldadfux 8304a8e0e4 Add impersonation feature for user management
- Introduced a new API endpoint to update user impersonator capability.
- Enhanced user model to include impersonator attributes.
- Updated database schema to support impersonation.
- Implemented impersonation logic in the request handling to allow users with impersonator capability to act as other users.
- Added relevant API documentation for impersonation headers.

This feature allows users with the appropriate permissions to impersonate other users, enhancing flexibility in user management.
2026-03-12 19:08:25 +01:00
Prem Palanisamy 065a0fddae Add backup policy migration support 2026-03-12 13:41:00 +00:00
Jake Barnby 6319d961eb (refactor): Simplify installer progress completion flow 2026-03-13 00:32:42 +13:00
Jake Barnby 5260577872 (fix): Make installer idempotent and fix session secret encoding 2026-03-13 00:32:40 +13:00
Jake Barnby 254c670c1d (feat): Add service healthchecks and health-based depends_on conditions 2026-03-13 00:32:37 +13:00
ArnabChatterjee20k 64dc730a3b refactor: remove debug output and enhance collection creation test with eventual assertion 2026-03-11 16:52:11 +05:30
ArnabChatterjee20k 3096404db5 added log vectordb colleciton creation 2026-03-11 16:23:13 +05:30
ArnabChatterjee20kandGitHub 6818f0b1c3 Merge branch '1.8.x' into sync-mongodb 2026-03-11 15:52:11 +05:30
ArnabChatterjee20k 3ddc073874 updated ollama 2026-03-11 15:51:27 +05:30
ArnabChatterjee20k 4f3468de7b Merge remote-tracking branch 'origin/1.8.x' into sync-mongodb 2026-03-11 14:36:30 +05:30
ArnabChatterjee20k 7a9d7202e5 updated docker file 2026-03-11 14:24:59 +05:30
ArnabChatterjee20k 09bfab03a7 updated ollama 2026-03-11 13:56:48 +05:30
ArnabChatterjee20k 52cd8b7992 updating ollama 2026-03-11 13:47:26 +05:30
Jake Barnby 2f01965014 Remove redundant images 2026-03-11 17:03:44 +13:00
Jake BarnbyandClaude Opus 4.6 36bd7a4667 (fix): Address code review security and correctness findings
- Remove var_dump debug calls leaking API keys to stdout
- Stop embedding secret keys in HTML data attributes on upgrades
- Strip sensitive fields from sessionStorage install lock
- Quote hostPath in Docker Compose YAML template
- Remove stack traces from client-facing error responses
- Strip sessionSecret and traces from Status endpoint response
- Fix undefined $input variable (should be $userInput) in CLI install
- Add backtick escaping in .env template to prevent shell injection
- Add 2-hour timeout to isInstallationComplete infinite loop
- Escape user-supplied startCommand in shell strings
- Add LOCK_EX to progress file writes
- Fix typo in Upgrade.php error message
- Remove unused variable in V21 response filter
- Remove dead code in applyLockPayload after sessionStorage sanitization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:58:57 +13:00
ArnabChatterjee20kandGitHub 1cb7ea49ec Merge branch '1.8.x' into sync-mongodb 2026-03-10 18:43:50 +05:30
ArnabChatterjee20k 9989820e17 Fix database type handling and update dependencies in composer files 2026-03-10 18:43:17 +05:30
ArnabChatterjee20k 798c605492 updated postgres 2026-03-10 17:43:34 +05:30
ArnabChatterjee20k ffa4645d25 Refactor database adapter initialization for attribute support
- Moved the `setSupportForAttributes` method call from the adapter initialization to the database resource creation in `getDatabasesDB`.
- Updated the logic to ensure attribute support is set correctly based on the database type.
2026-03-10 17:32:38 +05:30
ArnabChatterjee20k ca0656ce35 Refactor PostgreSQL setup in Docker configuration
- Updated docker-compose.yml to use a pre-built PostgreSQL image instead of a custom build.
- Removed obsolete PostgreSQL Dockerfiles from the project.
- Updated composer.lock with a new content hash.
2026-03-10 16:43:04 +05:30
ArnabChatterjee20k e4178d9ae6 Merge remote-tracking branch 'origin/sync-mongodb' into sync-mongodb 2026-03-10 16:34:10 +05:30
ArnabChatterjee20k 843dfa5c6c Update OLLAMA_KEEP_ALIVE setting in docker-compose.yml to disable keep-alive 2026-03-10 15:53:51 +05:30
ArnabChatterjee20k 52e2276445 Update dependencies and refactor code for clarity
- Changed version constraint for `spomky-labs/otphp` in composer.json to allow any 11.x version.
- Refactored `getDatabasesDBForProject` method in Migrations class to use the shorthand syntax for callable.
- Updated return values in Format class to use more descriptive types for index creation.
- Corrected parameter documentation in Attributes class for clarity and consistency.
2026-03-10 15:49:03 +05:30
ArnabChatterjee20k 9f78fc5341 updated env 2026-03-10 15:41:38 +05:30
ArnabChatterjee20k bf0d49cc76 Refactor embedding timeout and clean database logic
- Updated embedding agent timeout to be configurable via environment variable.
- Removed commented code in XList for clarity.
- Refactored database cleaning logic into separate methods for better readability and maintainability.
2026-03-10 15:41:32 +05:30
ArnabChatterjee20k b5b2338c82 pr comments addressed
* reverted install php
* deleted databasebase from tablesdb
2026-03-10 15:39:10 +05:30
ArnabChatterjee20k cef4d899e3 Add LEGACY constant and update database type references
- Introduced a new constant `LEGACY` in Constants.php.
- Updated `databaseType` in Action classes to use `LEGACY` instead of `TABLESDB`.
- Cleaned up duplicate parameter definition in Create.php for transactionId.
2026-03-10 14:38:14 +05:30
ArnabChatterjee20k f6655343c7 pr comments addressed
* Added new exception `MIGRATION_DATABASE_TYPE_UNSUPPORTED` with proper error metadata and HTTP 400 response.
* Replaced generic CSV database type errors with the new migration-specific exception for clearer error handling.
* Added support for `DOCUMENTSDB` in migration transfer resource service mapping.
* Fixed Appwrite report initialization by correctly injecting `getDatabasesDB`.
* Updated database adapter initialization to conditionally disable attribute support for `DOCUMENTSDB`.
* Moved `setSupportForAttributes` logic from pool initialization to database resource creation.
* Removed duplicate `getDatabasesDB` resource definition and redundant database event listener setup.
* Cleaned up unused variables and minor code inconsistencies.
* Fixed docblock formatting in `TransactionState`.
* Adjusted metrics handling in VectorDB embeddings text creation (removed unnecessary trigger/reset flow).
2026-03-10 14:30:57 +05:30
Jake Barnby fda8aee280 (style): Remove extra blank line in ConfigTest 2026-03-10 20:12:38 +13:00
Jake Barnby e2900d4349 Revert package 2026-03-10 19:49:02 +13:00
Jake Barnby 8b632eb428 (chore): Remove WEB_INSTALLER.md 2026-03-10 19:42:28 +13:00
Jake Barnby d19f14b60f (style): Remove section comment headers from installer tests 2026-03-10 19:42:19 +13:00
Jake Barnby c4c2534f9a (fix): Add CSRF validation to shutdown endpoint and quote .env values 2026-03-10 19:42:09 +13:00
Jake Barnby 91dffddf3b Format 2026-03-10 17:10:39 +13:00
Jake BarnbyandGitHub 4efababbb4 Merge pull request #11486 from appwrite/vectorsdb 2026-03-10 15:15:21 +13:00
Jake BarnbyandGitHub 5a258b9da1 Merge branch '1.8.x' into sync-mongodb 2026-03-10 15:11:54 +13:00
Jake BarnbyandGitHub 06e3e98cec Merge branch '1.8.x' into feat-installer 2026-03-10 15:07:42 +13:00
ArnabChatterjee20k ef248bd5fb fixed usage vectordb model issue on graphql 2026-03-09 17:30:42 +05:30
ArnabChatterjee20k 9c737d906b fixed vectordb db creation issue 2026-03-09 16:40:18 +05:30
ArnabChatterjee20k b8d2f7ece5 updated 2026-03-09 16:10:49 +05:30
ArnabChatterjee20k 5990422a2d updated lock 2026-03-09 15:55:33 +05:30
ArnabChatterjee20k ec500939df updated lock 2026-03-09 15:54:48 +05:30
ArnabChatterjee20k fddaebf254 Merge branch 'sync-mongodb' into vectorsdb 2026-03-09 15:51:30 +05:30
ArnabChatterjee20k d6bc9f120e Merge remote-tracking branch 'origin/1.8.x' into sync-mongodb 2026-03-09 15:50:48 +05:30
ArnabChatterjee20k 1705266779 remove debug logging from database creation process 2026-03-09 15:48:46 +05:30
ArnabChatterjee20k 97d64c3e01 fixing the dsn issue 2026-03-09 15:10:05 +05:30
ArnabChatterjee20k f839cf79d7 added logs for checking dsns on ci 2026-03-09 14:31:23 +05:30
ArnabChatterjee20k 844891c0ae updated 2026-03-09 14:29:39 +05:30
ArnabChatterjee20k c417206e7c updated migration 2026-03-09 14:22:26 +05:30
ArnabChatterjee20k 5326e6571b updated migration 2026-03-09 14:07:29 +05:30
ArnabChatterjee20k 681d930da3 renamed vectordb to vectordb 2026-03-09 13:51:48 +05:30
Chirag Aggarwal d80aacd706 add type safety 2026-03-07 14:20:36 +05:30
Chirag Aggarwal 4cd1dcc6f3 Merge branch '1.8.x' into update-python-sdk-readme 2026-03-07 12:03:54 +05:30
Chirag Aggarwal 576435c24b chore: update python sdk readme with pydantic models 2026-03-07 09:32:00 +05:30
ArnabChatterjee20k f102419d7c updated format 2026-03-05 14:03:17 +05:30
ArnabChatterjee20k a863ac773a updated index specs 2026-03-05 13:30:54 +05:30
ArnabChatterjee20k 26e92c6a8a Merge remote-tracking branch 'origin/1.8.x' into sync-mongodb 2026-03-05 12:32:19 +05:30
ArnabChatterjee20k 9f5172c1e9 updated migration 2026-03-05 12:30:30 +05:30
ArnabChatterjee20k 519ade1e51 updated realtime to include break 2026-03-05 12:22:45 +05:30
ArnabChatterjee20k 356cd0287c updated index types on vectordb
* merged all indexes together on the vectordb route so that the indexes on the sdk generator doesn't get overriden and fallback to the database adapter to know index is correct or not
2026-03-05 12:21:46 +05:30
ArnabChatterjee20k e84a26be76 Merge remote-tracking branch 'origin/1.8.x' into sync-mongodb 2026-03-05 12:11:44 +05:30
Jake Barnby 321528cac0 (refactor): Use Composer autoloading, Installer Platform class, and Utopia param validation 2026-03-04 22:58:55 +13:00
Jake Barnby 00d10e62b5 (feat): Add AppDomain validator for installer input 2026-03-04 22:58:48 +13:00
Jake Barnby cd5f3c3eb3 (chore): Bump base Docker image to 1.0.1 2026-03-04 22:58:43 +13:00
Jake BarnbyandClaude Opus 4.6 34dd5006bb fix: update installer ModuleTest for new Shutdown action
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 22:04:06 +13:00
Jake Barnby 3cc2375b15 Merge remote-tracking branch 'origin/1.8.x' into feat-installer 2026-03-03 20:57:13 +13:00
Jake Barnby 5ee58dcc81 (fix): Fix installer progress step transitions and redirect handling 2026-03-03 19:47:37 +13:00
Jake Barnby 1dfee199c0 (fix): Fix installer health check, network connect, and shutdown flow 2026-03-03 19:47:29 +13:00
Jake Barnby 4fda4d4ac3 Fix host access 2026-03-03 00:46:51 +13:00
Jake Barnby 9c7cef2971 Fix pg 18 path 2026-03-03 00:46:38 +13:00
Jake Barnby 029cd695dd Use custom image 2026-03-03 00:46:25 +13:00
Jake Barnby 349bc63618 Trap ctrl+c on start 2026-03-02 21:53:08 +13:00
Jake Barnby dcfc33c65d (chore): Update lock 2026-03-02 21:50:30 +13:00
Jake Barnby 4c76ddc2a3 (fix): Fix installer Swoole migration and database selection 2026-03-02 21:50:25 +13:00
ArnabChatterjee20k d6c8aeee2a Merge remote-tracking branch 'origin/1.8.x' into sync-mongodb 2026-03-02 12:49:01 +05:30
ArnabChatterjee20k 8c3f11b3d5 updated tests 2026-03-02 12:25:40 +05:30
Jake Barnby f540df50b1 Merge remote-tracking branch 'origin/1.8.x' into feat-installer 2026-03-02 18:40:37 +13:00
Jake Barnby adda327509 Update lock 2026-03-02 18:40:19 +13:00
ArnabChatterjee20k e37a9ad059 updated compose 2026-02-27 22:25:08 +05:30
ArnabChatterjee20k 9fc11ce475 updated tests and docker compose 2026-02-27 19:28:24 +05:30
ArnabChatterjee20k 9a32c59340 added vectordb tests 2026-02-27 19:27:24 +05:30
ArnabChatterjee20k c78d19de0d updated 2026-02-27 18:47:46 +05:30
ArnabChatterjee20k 1353f1f56c added status 2026-02-27 18:44:59 +05:30
ArnabChatterjee20k ee34d83cd8 added output of not starting 2026-02-27 18:40:36 +05:30
ArnabChatterjee20k d68449d25b reset 2026-02-27 18:34:18 +05:30
ArnabChatterjee20k d34c83a798 updated tests 2026-02-27 18:25:33 +05:30
ArnabChatterjee20k 6e485bcd5a updated tests file to see the logs of appwrite 2026-02-27 18:12:00 +05:30
ArnabChatterjee20k bab3a49b0b updated 2026-02-27 18:01:06 +05:30
ArnabChatterjee20k 55796eca5c updated lock 2026-02-27 17:21:03 +05:30
ArnabChatterjee20k abf497d509 Merge remote-tracking branch 'origin/1.8.x' into sync-mongodb 2026-02-27 17:19:31 +05:30
ArnabChatterjee20k a76a5cda38 Refactor code structure for improved readability and maintainability 2026-02-27 17:15:00 +05:30
ArnabChatterjee20k 377a9d915e updated env 2026-02-27 13:09:56 +05:30
Jake BarnbyandGitHub 1790f56d99 Merge branch '1.8.x' into feat-installer 2026-02-27 06:07:56 +00:00
Jake BarnbyandClaude Opus 4.6 18d7b12dff fix: capture readExistingCompose result and document shadowed param in Upgrade
- Store readExistingCompose() return value in $data before using it
  (previously $data was undefined on line 60)
- Add comment documenting that CLI database param is intentionally
  overridden by detection from existing configuration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 02:56:55 +13:00
Jake BarnbyandClaude Opus 4.6 118199fd4b fix: revert name/email hashing in analytics payload
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 02:54:06 +13:00
Jake BarnbyandClaude Opus 4.6 7a27cf4ac7 fix: address review feedback for installer PR
- Initialize $isUpgrade=false in Install.php action() to prevent undefined variable
- Assign $this->lockedDatabase in Upgrade.php before calling parent::action()
- Remove stack trace exposure from buildErrorDetails() in Http Install action
- Suppress raw exception messages for 500+ errors in Error handler
- Remove sessionSecret from progress details to prevent credential leak
- Hash name/email in analytics payload to avoid sending raw PII
- Validate and default dbService in compose.phtml to prevent invalid output
- Fix host normalization in progress.js redirect URL builder
- Release global lock on early return for existing installation conflict
- Consolidate duplicate database host/port assignment blocks
- Add @runInSeparateProcess to testRouteRegistration to prevent global state leak

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 01:44:46 +13:00
Jake Barnby f8d8997ae3 Update installer 2026-02-27 01:29:38 +13:00
ArnabChatterjee20k c7b3972fa0 fixed event 2026-02-26 17:12:44 +05:30
ArnabChatterjee20k fda53c0520 updated composer 2026-02-26 17:07:24 +05:30
ArnabChatterjee20k f2e789537c updated lock 2026-02-26 16:34:42 +05:30
ArnabChatterjee20k 3287ef6a57 Merge remote-tracking branch 'origin/1.8.x' into sync-mongodb 2026-02-26 16:33:56 +05:30
ArnabChatterjee20k 74b3d8f493 updated lock 2026-02-26 16:22:59 +05:30
Jake Barnby a7046dbe3c (fix): Replace tab characters with spaces in tests.yml workflow 2026-02-26 22:43:23 +13:00
Jake Barnby 5f58e1da90 (fix): Fix syntax error and lint issues in Upgrade.php 2026-02-26 22:13:46 +13:00
Jake Barnby 7f29baec77 (style): Fix indentation and import ordering lint errors 2026-02-26 22:02:49 +13:00
Jake Barnby bc992f15fc (chore): Update composer.lock to match composer.json 2026-02-26 21:56:38 +13:00
Jake Barnby 904f94659f (docs): Update web installer docs for Swoole architecture 2026-02-26 21:42:48 +13:00
Jake Barnby 8892e4f30e (test): Add unit tests for installer module, state, and config 2026-02-26 21:42:42 +13:00
Jake Barnby 41948cb337 (refactor): Replace HttpHandler with Utopia Platform module/action pattern and Swoole HTTP server 2026-02-26 21:42:37 +13:00
Jake Barnby 26dac3a410 Merge remote-tracking branch 'origin/1.8.x' into feat-installer
# Conflicts:
#	.gitignore
#	composer.lock
#	tests/e2e/Services/Databases/DatabasesBase.php
2026-02-26 18:51:23 +13:00
Jake Barnby 3ecb4ee4e2 Sync 1.8.x 2026-02-26 18:50:29 +13:00
ArnabChatterjee20k e0972f4636 updated lock 2026-02-25 16:50:13 +05:30
ArnabChatterjee20k 42f914f6aa fixed merge conflicts 2026-02-25 16:49:31 +05:30
ArnabChatterjee20k 58f4fff864 added 2026-02-25 16:20:44 +05:30
ArnabChatterjee20k 681c9743b1 added controllers 2026-02-25 16:09:28 +05:30
ArnabChatterjee20k cd2d2a1238 added configs 2026-02-25 16:09:16 +05:30
ArnabChatterjee20k 905763e755 added tests 2026-02-25 16:08:40 +05:30
ArnabChatterjee20k 2550482432 updated docs 2026-02-25 16:07:19 +05:30
ArnabChatterjee20k 8d028066ae update transaction not to start if there is no operation in the logs 2026-02-25 15:44:14 +05:30
ArnabChatterjee20k bd4adad545 refractored transaction update 2026-02-25 12:49:27 +05:30
ArnabChatterjee20k c40f9b1de0 addressed comments 2026-02-24 18:23:52 +05:30
ArnabChatterjee20k 8237250453 refactor: streamline database DSN handling and remove deprecated attributes 2026-02-24 17:40:50 +05:30
ArnabChatterjee20k c8a7ed2022 updated migration to remove the dependency on dsn at project level attribute 2026-02-24 10:43:10 +05:30
ArnabChatterjee20k 4847c5a308 changed deps so that create document depends on test create collection 2026-02-23 13:27:01 +05:30
ArnabChatterjee20k f6985bae41 updated lock 2026-02-23 13:15:52 +05:30
ArnabChatterjee20k cc9df4f5c5 Merge remote-tracking branch 'origin/1.8.x' into documents-db-api 2026-02-23 13:09:49 +05:30
ArnabChatterjee20k 83b7b4e599 updated 2026-02-23 13:05:12 +05:30
ArnabChatterjee20k fdc573009c updated tests 2026-02-20 16:53:46 +05:30
ArnabChatterjee20k 51d769b0a1 Merge remote-tracking branch 'origin/1.8.x' into documents-db-api 2026-02-20 13:58:56 +05:30
ArnabChatterjee20k f6b04d05b2 updated composer lock 2026-02-18 16:10:13 +05:30
ArnabChatterjee20k 2926412604 Merge remote-tracking branch 'origin/1.8.x' into documents-db-api 2026-02-18 16:07:44 +05:30
ArnabChatterjee20k ee21b1feda updated composer 2026-02-18 16:06:31 +05:30
ArnabChatterjee20k 2d0140f705 Merge remote-tracking branch 'origin/1.8.x' into documents-db-api 2026-02-18 16:01:55 +05:30
ArnabChatterjee20k 183bd4143c updated tests 2026-02-16 18:36:42 +05:30
ArnabChatterjee20k 57ac67a106 linting and updated timeouts 2026-02-16 16:23:38 +05:30
ArnabChatterjee20k 4b6d809a3b updated docker compose 2026-02-16 16:09:15 +05:30
ArnabChatterjee20k 0221556ae7 Merge remote-tracking branch 'origin/documents-db-api' into documents-db-api 2026-02-16 15:16:51 +05:30
ArnabChatterjee20k 6a1955d20d updated tests 2026-02-16 15:16:04 +05:30
ArnabChatterjee20kandGitHub 8c7d2c58f5 Merge branch '1.8.x' into documents-db-api 2026-02-16 14:30:53 +05:30
ArnabChatterjee20k 5b7746b464 empty 2026-02-16 14:12:46 +05:30
ArnabChatterjee20k e6e0cef91e linting 2026-02-16 13:55:34 +05:30
ArnabChatterjee20k bdc4c4a18d Merge remote-tracking branch 'origin/documents-db-api' into documents-db-api 2026-02-16 13:53:21 +05:30
ArnabChatterjee20k 49efa02e09 synced documents db and vectordb with 1.8.x 2026-02-16 13:52:16 +05:30
ArnabChatterjee20k fd96bcbd3f Merge remote-tracking branch 'origin/1.8.x' into documents-db-api 2026-02-16 12:20:53 +05:30
Jake Barnby b41678d57a Merge remote-tracking branch 'origin/feat-mongodb' into feat-installer
# Conflicts:
#	.github/workflows/tests.yml
#	Dockerfile
#	app/views/install/compose.phtml
#	composer.lock
#	mongo-entrypoint.sh
#	src/Appwrite/Platform/Tasks/Install.php
#	src/Appwrite/Platform/Tasks/Upgrade.php
#	tests/e2e/Client.php
#	tests/e2e/Services/Databases/DatabasesBase.php
#	tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php
#	tests/e2e/Services/Databases/Legacy/DatabasesCustomServerTest.php
#	tests/e2e/Services/Databases/TablesDB/DatabasesBase.php
#	tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php
#	tests/e2e/Services/Databases/TablesDB/DatabasesCustomServerTest.php
#	tests/e2e/Services/Databases/Transactions/TransactionsBase.php
#	tests/e2e/Services/GraphQL/Legacy/DatabaseServerTest.php
#	tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
#	tests/e2e/Services/Projects/ProjectsConsoleClientTest.php
#	tests/e2e/Services/Teams/TeamsCustomClientTest.php
2026-02-13 17:09:42 +13:00
Jake BarnbyandGitHub 9647c3fa7d Merge pull request #11310 from appwrite/database-type-filters 2026-02-12 09:38:45 +00:00
Darshan 1e411bbef5 update: lint. 2026-02-12 13:25:44 +05:30
Darshan 7c47a4db72 add: filtering. 2026-02-12 13:22:07 +05:30
ArnabChatterjee20k ae33104ecf added admin auth to the index creation 2026-02-11 18:19:46 +05:30
ArnabChatterjee20k b90a9e8cab added admin auth to the index creation 2026-02-11 16:49:33 +05:30
ArnabChatterjee20k 4024cf0492 Merge remote-tracking branch 'origin/1.8.x' into documents-db-api 2026-02-11 16:22:37 +05:30
ArnabChatterjee20k e07f9a7b49 added admin auth to vector db 2026-02-11 14:04:14 +05:30
ArnabChatterjee20k 61f778196b Merge remote-tracking branch 'origin/1.8.x' into documents-db-api 2026-02-11 13:58:43 +05:30
ArnabChatterjee20k 93897f86d0 Merge remote-tracking branch 'origin/1.8.x' into documents-db-api 2026-02-10 15:34:51 +05:30
ArnabChatterjee20k 095e08441f added get databases db in the document logs 2026-02-10 13:33:29 +05:30
ArnabChatterjee20k da63463a3c updated migration 2026-02-10 11:20:53 +05:30
ArnabChatterjee20k 8e140bae6c updated project and migraiton worker 2026-02-09 17:43:42 +05:30
ArnabChatterjee20k 3b2eb02f8a Merge branch '1.8.x' into documents-db-api 2026-02-09 17:27:09 +05:30
Jake Barnby 97be6e2cf4 Merge branch 'feat-installer' of github.com:appwrite/appwrite into feat-installer 2026-02-05 00:31:10 +13:00
Jake Barnby 1df76ed975 Merge remote-tracking branch 'origin/1.8.x' into feat-installer
# Conflicts:
#	Dockerfile
#	app/views/install/compose.phtml
#	composer.lock
#	src/Appwrite/Platform/Tasks/Install.php
2026-02-05 00:29:29 +13:00
Jake BarnbyandGitHub 2278a423f5 Merge pull request #11175 from appwrite/new-install-ui
Add web installer
2026-02-04 11:15:20 +00:00
Darshan 282b3c74ec fix: local install path. 2026-02-04 15:49:05 +05:30
Darshan 180276bd9d remove: mock stuff.
fix: layout regression.
2026-02-04 15:48:37 +05:30
Darshan abab48c1b9 remove: mock stuff. 2026-02-04 14:48:42 +05:30
Darshan 587f9965a2 misc. 2026-02-03 14:37:29 +05:30
Darshan bdad14e83a fix: gradient. 2026-02-03 14:32:24 +05:30
Darshan 92e94e365f update: send to existing analyics endpoint. 2026-02-03 13:54:20 +05:30
Darshan 35918465f8 update: address comments, remove name field and auto-derive. 2026-02-02 19:00:54 +05:30
Darshan 42e52fcaa6 revert: handle fallbacks on growth. 2026-01-30 12:11:39 +05:30
Darshan ba29e66292 add: fallbacks. 2026-01-30 12:09:09 +05:30
Darshan d8cc3202c6 add: tracking on installations. 2026-01-30 11:44:00 +05:30
Darshan 02ce669993 update: design changes on creds step. 2026-01-30 11:08:26 +05:30
ArnabChatterjee20k e53f5b4023 add admin permission to create 2026-01-28 19:06:19 +05:30
ArnabChatterjee20k 02cddb5c08 updated the format specs 2026-01-28 10:55:19 +05:30
ArnabChatterjee20k cb4d11a1d4 updated 2026-01-27 18:13:27 +05:30
ArnabChatterjee20k 139535e83c updated 2026-01-27 17:18:24 +05:30
ArnabChatterjee20k b9e95f6d82 updated 2026-01-27 17:17:23 +05:30
ArnabChatterjee20k 79cb668a50 updated composer lock 2026-01-27 16:34:34 +05:30
ArnabChatterjee20k 8078516e2f fixed composer lock 2026-01-27 16:32:59 +05:30
ArnabChatterjee20k 1729bd23b2 Merge remote-tracking branch 'origin/1.8.x' into documents-db-api 2026-01-27 16:29:25 +05:30
Darshan a2a24ee367 update: behavioural hotfix. 2026-01-26 19:27:22 +05:30
Darshan 5e0f163f1f fix: tests. 2026-01-26 19:25:08 +05:30
Darshan c9a6a0f0aa add: deps. 2026-01-26 18:57:50 +05:30
Darshan f5681f1167 fix: bun availability on CI. 2026-01-26 18:44:55 +05:30
Darshan 0e4d514007 update: docs. 2026-01-26 18:41:44 +05:30
Darshan c39d8ad591 fix: tests. 2026-01-26 18:31:16 +05:30
Darshan f59796e290 update: use helpers. 2026-01-26 18:06:36 +05:30
Darshan cff11cae5a update: use more constants. 2026-01-26 18:00:45 +05:30
Darshan aa3047f338 update: use constants for regexes. 2026-01-26 17:48:08 +05:30
Darshan e17b8cec38 update: consistent naming pattern. 2026-01-26 17:40:19 +05:30
Darshan fc2ee0c32a update: inline args. 2026-01-26 17:35:13 +05:30
Darshan 5690eb1dc5 update: filter checks. 2026-01-26 17:26:18 +05:30
Darshan fbba7166ae update: send mock/default values from backend.
update: update task callback.
2026-01-26 17:22:02 +05:30
Darshan 289c3e233e update: misc fixes. 2026-01-26 17:14:31 +05:30
Darshan 6345802b1a update: use helper method. 2026-01-26 17:06:52 +05:30
Darshan 7fb654e3a4 misc: updates, cleanups. 2026-01-26 16:10:51 +05:30
Darshan 8b9051751a update: password validation.
misc: updates, cleanups.
2026-01-26 15:26:56 +05:30
Darshan 335c3f363c fix: ci. 2026-01-25 18:07:42 +05:30
Darshan 6dac2ce253 improve: animations like imagine.
fix: password eye button not showing again.
2026-01-25 18:01:58 +05:30
Darshan f34e40e1e4 add: ci tests. 2026-01-25 17:12:55 +05:30
Darshan fe74760177 update: screenshot tests. 2026-01-25 17:06:10 +05:30
Darshan 2d5e64a21e update: screenshot tests. 2026-01-25 15:42:09 +05:30
Darshan b503965e8c update: tests. 2026-01-25 15:20:09 +05:30
Darshan a9da80687f remove: unneeded check. 2026-01-25 11:33:24 +05:30
Darshan d4dd6cf634 add: completion marker. 2026-01-25 11:33:05 +05:30
Darshan 85d2e08dc5 update: installer doc. 2026-01-25 10:51:09 +05:30
Darshan 0eda9e6315 add: account creation.
feat: mocking options.
2026-01-24 20:09:56 +05:30
Darshan eb8abcfe63 fix: empty input considered as invalid.
fix: referencing wrong file in upgrade task.
2026-01-24 16:35:14 +05:30
Darshan 80a5c24840 bump: gitignore. 2026-01-24 16:32:01 +05:30
Darshan 5144b1ce88 update: splits.
fix: broken SSE.
fix: upgrade flow not working.
feat: csrf tokens for better security.
fix: local testing mode with wrong paths.
feat: csrf validations pre-post hook plus proper permissions handling on files.
feat: js modules for better handling of js operations on web installer frontend ui.
2026-01-24 16:30:51 +05:30
Darshan 3d86f67acb update: move server handling to a new file.
update: tooltip things.
remove: unused var.
2026-01-22 19:52:46 +05:30
Darshan 58440e70ac misc. 2026-01-22 18:12:41 +05:30
Darshan dd16ead898 update: new installer ui. 2026-01-22 17:28:23 +05:30
ArnabChatterjee20k ac7fc7ae69 linting 2026-01-22 12:47:26 +05:30
ArnabChatterjee20k 4b570dc3f0 updetad pools 2026-01-22 12:41:18 +05:30
ArnabChatterjee20k aa3d66df7b Merge branch 'new-pool-adapter' into documents-db-api 2026-01-22 12:34:22 +05:30
ArnabChatterjee20k a16e05ed4d updated timeout 2026-01-21 13:36:35 +05:30
ArnabChatterjee20k 84dc5682d5 updated auth param 2026-01-21 12:31:42 +05:30
ArnabChatterjee20k a112de41c5 updated ollama timeout 2026-01-21 12:23:29 +05:30
ArnabChatterjee20k 02bba85fe7 updated migration tests for adding endpoint 2026-01-21 12:21:37 +05:30
ArnabChatterjee20k 56f68d33df updated migration 2026-01-21 11:44:13 +05:30
Jake Barnby bdb8bc081f Add doc 2026-01-21 18:09:46 +13:00
ArnabChatterjee20k d4edf9ed13 updated vectordb test 2026-01-20 20:10:57 +05:30
ArnabChatterjee20k 12b38e2a27 commenting mongodb out for now to check the issues on the ci 2026-01-20 19:45:19 +05:30
ArnabChatterjee20k 9f1807a128 linting and fixing resources timeout and validator 2026-01-20 19:41:52 +05:30
ArnabChatterjee20k 6bdfcc3fb5 fixed endpoint in migration 2026-01-20 18:44:54 +05:30
ArnabChatterjee20k d8dd1918c4 synced vectordb 2026-01-20 15:45:12 +05:30
ArnabChatterjee20k 376c5e47f5 added missing injections 2026-01-20 14:01:37 +05:30
ArnabChatterjee20k edfb9e0bd7 updated resources to use the listeners based on the databasetype 2026-01-20 13:56:52 +05:30
ArnabChatterjee20k eab4c7f28a Merge remote-tracking branch 'origin/1.8.x' into documents-db-api 2026-01-20 13:56:32 +05:30
ArnabChatterjee20k c361340252 linting 2026-01-19 18:45:05 +05:30
ArnabChatterjee20k 41e197003e fixed merge conflicts 2026-01-19 18:43:30 +05:30
ArnabChatterjee20k c7ba37ec49 fixed merge conflicts 2026-01-19 14:35:31 +05:30
ArnabChatterjee20k ba82aab563 Merge remote-tracking branch 'origin/1.8.x' into documents-db-api 2026-01-19 13:22:53 +05:30
ArnabChatterjee20k f580dc9145 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2026-01-13 14:32:46 +05:30
Jake BarnbyandGitHub 421dca2f99 Merge pull request #10653 from appwrite/vector-db-api
vectordb api endpoints
2025-12-10 10:59:58 +00:00
ArnabChatterjee20k 991b2b1c3b updated delete worker for vectordb and updated tests 2025-12-09 18:44:26 +05:30
ArnabChatterjee20k d22743a1cf Merge remote-tracking branch 'upstream/documents-db-api' into vector-db-api 2025-12-09 18:37:09 +05:30
ArnabChatterjee20k a2039c0b76 added deletion test for multidb 2025-12-09 18:34:25 +05:30
ArnabChatterjee20k c549ddd149 updated project deletion worker to delete per database 2025-12-09 18:27:10 +05:30
ArnabChatterjee20k f3721f712a fixed missing project usage fields 2025-12-09 15:38:43 +05:30
ArnabChatterjee20k 480757dcfe added usage test for the vectordb 2025-12-09 12:07:26 +05:30
ArnabChatterjee20k fbaeff8920 Merge remote-tracking branch 'upstream/documents-db-api' into vector-db-api 2025-12-08 20:31:07 +05:30
ArnabChatterjee20k de88ec4065 updated missing skip metrics and tablesdb tests 2025-12-08 20:30:37 +05:30
ArnabChatterjee20k f149ba33d4 updated stats for vectordb 2025-12-08 20:27:30 +05:30
ArnabChatterjee20k b15f2eed8f Merge remote-tracking branch 'upstream/documents-db-api' into vector-db-api 2025-12-08 19:32:21 +05:30
ArnabChatterjee20k ec63dc22d4 updated controllers 2025-12-08 19:09:54 +05:30
ArnabChatterjee20k 7c7b98399c linting 2025-12-08 17:51:01 +05:30
ArnabChatterjee20k 9d679e726e updated composer lock 2025-12-08 17:46:00 +05:30
ArnabChatterjee20k 070ec03c0c Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-12-08 17:31:04 +05:30
ArnabChatterjee20k be89224c36 updated console client test 2025-12-05 19:14:29 +05:30
ArnabChatterjee20k 8edd3c87cb added documentsdb total reads and writes in the project response 2025-12-05 19:09:35 +05:30
ArnabChatterjee20k 9ac3cd73a8 Refactor database metrics handling for DocumentsDB
- Introduced new methods to retrieve metrics for write and read operations based on database type.
- Updated bulk delete, update, upsert, and create actions to use the new metric methods.
- Enhanced document retrieval and update actions to utilize the new metric structure.
- Added support for DocumentsDB metrics in usage statistics and response models.
- Created new response models for DocumentsDB usage and updated existing models to accommodate new metrics.
- Adjusted migration and stats resources to include DocumentsDB metrics.
- Updated tests to validate the new metrics and ensure correct functionality for DocumentsDB usage.
2025-12-05 18:58:13 +05:30
ArnabChatterjee20k 203170c36d updated database and collection stats 2025-12-04 15:57:18 +05:30
ArnabChatterjee20k 6f5bfdb726 updated stats 2025-12-03 20:18:53 +05:30
b499857871 Update src/Appwrite/Utopia/Database/Validator/Queries/Base.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2025-12-03 19:12:56 +05:30
1388b0ca21 Update app/config/variables.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2025-12-03 19:12:38 +05:30
ArnabChatterjee20k af095bdf4a updated stats 2025-12-03 19:11:46 +05:30
ArnabChatterjee20k 7af361fa16 * added logger in the create text embedding
* aadded error text metric
2025-12-03 13:38:24 +05:30
ArnabChatterjee20k 069f231b3a updated composer lock 2025-12-02 18:40:28 +05:30
ArnabChatterjee20k def028e970 Merge remote-tracking branch 'upstream/documents-db-api' into vector-db-api 2025-12-02 18:38:21 +05:30
ArnabChatterjee20k 16f49d13c8 updated composer lock 2025-12-02 18:36:29 +05:30
ArnabChatterjee20k e72d7979e0 updated composer lock 2025-12-02 18:31:44 +05:30
ArnabChatterjee20k ca4c7b5361 added stats usage for text embeddings 2025-12-02 18:13:49 +05:30
ArnabChatterjee20k 7ad6bb048c merged 1.8.x 2025-12-02 16:35:40 +05:30
Jake Barnby cbf0cc842d Add web installer 2025-12-02 23:33:21 +13:00
ArnabChatterjee20k d2c9ac079a Refactor VectorDB Embeddings API and Update Dimension Handling
- Updated the Create.php file to streamline the embedding creation process by removing unnecessary authentication checks and simplifying the parameter structure.
- Changed the parameter from 'documents' to 'texts' for clarity and adjusted the embedding model handling.
- Modified the response model to include error handling for embedding generation.
- Updated the Embedding response model to reflect changes in dimension naming and added error message handling.
- Refactored VectorDBCollection model to replace 'dimensions' with 'dimension' for consistency across the codebase.
- Adjusted all relevant tests to accommodate the new parameter names and response structures, ensuring comprehensive coverage for the updated functionality.
2025-12-02 14:20:25 +05:30
ArnabChatterjee20k abe1257bd5 Merge remote-tracking branch 'upstream/documents-db-api' into vector-db-api 2025-11-21 20:17:59 +05:30
ArnabChatterjee20k 3db22fd2db fixed enum route 2025-11-21 20:03:44 +05:30
ArnabChatterjee20k a423067b36 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-11-21 19:53:52 +05:30
ArnabChatterjee20k 33aab0357f updated tests 2025-11-21 19:52:28 +05:30
ArnabChatterjee20k 34e9c69a62 updated tests condition 2025-11-21 19:11:26 +05:30
ArnabChatterjee20k fdf5ca0067 updated tests condition 2025-11-21 19:08:35 +05:30
ArnabChatterjee20k 6eb17b847f updated composer 2025-11-21 18:06:32 +05:30
ArnabChatterjee20k 918ebb8ecc Add VectorDB text embedding creation endpoint and update related tests 2025-11-21 18:00:51 +05:30
ArnabChatterjee20k c0ac52810e Merge remote-tracking branch 'upstream/documents-db-api' into vector-db-api 2025-11-21 17:25:18 +05:30
ArnabChatterjee20k b658cddc71 updated get project dsn to be of the project db type(dedicate, shared) so that databasesdb and projectdb are not in conflict 2025-11-21 17:22:19 +05:30
ArnabChatterjee20k fe753db78c Merge remote-tracking branch 'upstream/documents-db-api' into vector-db-api 2025-11-20 18:57:38 +05:30
ArnabChatterjee20k 2f1e47e35a updated migrations 2025-11-20 18:56:14 +05:30
ArnabChatterjee20k c3ad337398 updated migrations for vectordb importing/exporting in csv endpoints 2025-11-20 18:54:41 +05:30
ArnabChatterjee20k cc759868b8 Merge remote-tracking branch 'upstream/documents-db-api' into vector-db-api 2025-11-20 15:51:01 +05:30
ArnabChatterjee20k 1004435e9e skipped operator tests for mongodb 2025-11-20 14:07:05 +05:30
ArnabChatterjee20k a9e749a81f updated migration service extractor for csv import/export 2025-11-20 14:06:42 +05:30
ArnabChatterjee20k 553fe15ba9 resolved merge conflicts 2025-11-19 20:01:02 +05:30
ArnabChatterjee20k fec758d237 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-11-19 20:00:17 +05:30
ArnabChatterjee20k 70a1d5d0f7 updated composer lock 2025-11-14 21:33:17 +05:30
ArnabChatterjee20k c66308e4e9 Merge remote-tracking branch 'upstream/documents-db-api' into vector-db-api 2025-11-14 21:28:40 +05:30
ArnabChatterjee20k 21aae468f2 updated database type function 2025-11-14 19:35:53 +05:30
ArnabChatterjee20k f007b83ccc updated composer 2025-11-14 19:18:03 +05:30
ArnabChatterjee20k 28be12d827 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-11-14 19:16:08 +05:30
ArnabChatterjee20k 93bd6b39a4 updated migrations + index finding issue in vectordb 2025-11-13 16:47:00 +05:30
ArnabChatterjee20k 75045ffd58 added constants for the vector dimension 2025-11-12 16:28:58 +05:30
ArnabChatterjee20k ed8419986a linting 2025-11-12 16:23:59 +05:30
ArnabChatterjee20k 954011b2c4 added transactions for vectordb 2025-11-12 16:21:27 +05:30
ArnabChatterjee20k c3f221f1fb added timestamp mutation test 2025-11-07 13:13:13 +05:30
ArnabChatterjee20k de59f53de3 reverted error tracing 2025-11-06 18:25:01 +05:30
ArnabChatterjee20k 04b169fab8 updated composer packages 2025-11-06 18:01:01 +05:30
ArnabChatterjee20k 857ec3b099 reverted docker compose 2025-11-06 17:54:21 +05:30
ArnabChatterjee20k b1dd377b67 Merge branch 'documents-db-api' into vector-db-api 2025-11-06 17:52:14 +05:30
ArnabChatterjee20k 81128e8446 reverted changes 2025-11-06 17:51:33 +05:30
ArnabChatterjee20k 86371e2082 Merge branch 'documents-db-api' into vector-db-api 2025-11-06 17:50:14 +05:30
ArnabChatterjee20k bd6f62dd68 updated realtime, create endpoint , resources, registers 2025-11-06 17:48:39 +05:30
ArnabChatterjee20k d2275c05fb updated tests 2025-11-05 20:34:12 +05:30
ArnabChatterjee20k 4988881568 updated method names and sdk namespaces for vectordb 2025-11-03 11:17:57 +05:30
ArnabChatterjee20k f9be1ddfc5 added vector embedding creation + update endpoint 2025-10-31 19:07:34 +05:30
ArnabChatterjee20k 60e95458a7 vector db endpoints
* configs and default attributes + indexes
* separate collection creation setup
* fix -> database metadata fix on collection creation
2025-10-31 18:01:47 +05:30
ArnabChatterjee20k 2bb6b0a2a9 updated database dsn for create in the create database 2025-10-31 16:45:01 +05:30
ArnabChatterjee20k 3311bd27cb Merge branch 'documents-db-api' into vector-db-api 2025-10-31 16:35:00 +05:30
ArnabChatterjee20k 5d9ef0dcca added metadata create on database 2025-10-31 16:33:54 +05:30
ArnabChatterjee20k 7ddecdd9ef Merge branch 'documents-db-api' into vector-db-api 2025-10-31 10:45:58 +05:30
ArnabChatterjee20k c8f966b0e5 added the default database type for the transactions context 2025-10-30 13:44:26 +05:30
ArnabChatterjee20k 3d2f21be7c updated composer lock 2025-10-30 13:02:49 +05:30
ArnabChatterjee20k 3e876f5bd8 fixed merge conflicts 2025-10-30 12:57:41 +05:30
ArnabChatterjee20k a5e56438c7 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-30 12:35:57 +05:30
ArnabChatterjee20k 4e79259c4e Refactor database handling to streamline DSN retrieval for documents and tables databases 2025-10-30 12:27:52 +05:30
ArnabChatterjee20k fae79e5c3f updated tests from feat-mongodb branch 2025-10-29 13:49:18 +05:30
ArnabChatterjee20k b169be9dda updated migration merge conflicts 2025-10-29 11:41:46 +05:30
ArnabChatterjee20k 8f4496346d Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-28 18:41:03 +05:30
ArnabChatterjee20k f306c03030 updated tests and index creation 2025-10-28 18:32:17 +05:30
ArnabChatterjee20k a8469a9b92 updated composer lock 2025-10-27 18:10:41 +05:30
ArnabChatterjee20k b63510acb5 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-27 18:10:08 +05:30
ArnabChatterjee20k 3e612ec88d flask test fixing via retry 2025-10-27 18:08:44 +05:30
ArnabChatterjee20k 1be36ae552 updated deps 2025-10-27 16:19:04 +05:30
ArnabChatterjee20k 445b657207 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-27 16:15:10 +05:30
Eldad Fux da6dff2d17 Merge branch 'feat-disposable-emails' of github.com:appwrite/appwrite into feat-disposable-emails 2025-10-19 22:22:13 +01:00
Eldad Fux b4aab6134e Merge remote-tracking branch 'origin/1.8.x' into feat-disposable-emails 2025-10-19 22:20:31 +01:00
Eldad Fux 1c465a8cda refactor: standardize email validation across API controllers
- Reintroduced the Utopia Emails validator in multiple API controllers to ensure consistent email validation practices.
- Removed deprecated email validation references, streamlining the codebase and aligning with recent updates to the Utopia Emails library.
2025-10-19 22:16:03 +01:00
Eldad A. FuxandGitHub 8ab6c2d83b Merge branch '1.8.x' into feat-disposable-emails 2025-10-19 22:13:31 +01:00
Eldad Fux c89d075200 refactor: streamline disposable email validation in account API
- Replaced the previous disposable email validation logic with the EmailNotDisposable validator from the Utopia Emails library for consistency and improved maintainability.
- Removed unnecessary configuration loading related to disposable emails, aligning with recent updates to the email validation process.
2025-10-19 22:09:26 +01:00
Eldad Fux 6c6c71484d refactor: replace disposable email validation logic with Utopia Emails library
- Introduced the EmailNotDisposable validator from the Utopia Emails library for improved disposable email validation in the account API.
- Removed the deprecated disposable emails configuration loading from the config files to streamline the codebase.
2025-10-19 22:01:27 +01:00
Eldad Fux 18a66bae4e chore: update Utopia Emails dependency to version 0.5 and remove deprecated email validator
- Updated the Utopia Emails dependency version from 0.4.* to 0.5.* in composer.json and composer.lock.
- Removed the custom Email validator class and its associated tests as it is no longer needed with the new Utopia Emails library.
2025-10-19 21:55:52 +01:00
Eldad Fux 68b72f3162 feat: integrate Utopia Emails library for email validation
- Added Utopia Emails library as a dependency in composer.json.
- Updated email validation references across multiple API controllers to use the new Utopia Emails validator.
- Removed the deprecated disposable emails configuration file.
- Updated composer.lock to reflect the new library and version changes for existing dependencies.
2025-10-19 21:44:22 +01:00
ArnabChatterjee20k 5098973e0d reverted longtext resource file 2025-10-17 18:14:16 +05:30
ArnabChatterjee20k d0976ff429 updated registers config 2025-10-17 14:16:45 +05:30
ArnabChatterjee20k 157783f782 merged feat-mongodb 2025-10-17 13:58:27 +05:30
ArnabChatterjee20k e3a70373e1 updated docuemntsdb transaction path names 2025-10-16 22:19:53 +05:30
ArnabChatterjee20k a6e5563f8e reverted docker compose 2025-10-16 21:51:56 +05:30
ArnabChatterjee20k 470d84a843 * updated transactions
* updated tests
2025-10-16 21:45:30 +05:30
ArnabChatterjee20k 80037f7e8d updated http php for mongodb setup 2025-10-16 15:57:13 +05:30
ArnabChatterjee20k cc848c90a7 updated lock file 2025-10-16 14:25:22 +05:30
ArnabChatterjee20k e0c293c5b4 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-16 14:25:14 +05:30
ArnabChatterjee20k 2d82013261 updated docker compose setup for mongodb which was causing issue for mongo transactions 2025-10-16 14:22:28 +05:30
ArnabChatterjee20k 5a6561f158 vectordb (in progress)
* pools , env setup
* collections, db endpoints
2025-10-16 12:53:39 +05:30
ArnabChatterjee20k f28588ecb6 updated migration 2025-10-15 14:24:59 +05:30
ArnabChatterjee20k 6579ea5b56 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-15 14:16:49 +05:30
ArnabChatterjee20k 9373688be2 updated migration worker to pass the dsn resolver for the destination database 2025-10-15 10:33:46 +05:30
Eldad A. FuxandGitHub f6438065e3 Merge branch '1.8.x' into feat-disposable-emails 2025-10-15 00:14:06 +01:00
ArnabChatterjee20k 2cec06be18 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-14 21:18:38 +05:30
ArnabChatterjee20k 3a60bb306c * updated migration tests
* database dsn fetching
* removed dsn from response models
2025-10-14 21:13:26 +05:30
ArnabChatterjee20k e2ddb0d6d0 updated migration 2025-10-14 13:23:48 +05:30
Darshan 0049265c8a Merge remote-tracking branch 'origin/documents-db-api' into documents-db-api 2025-10-14 12:49:41 +05:30
Darshan 1f27867565 update: method name. 2025-10-14 12:49:34 +05:30
ArnabChatterjee20k faf0cdec35 changed default fallbackForDB to mariadb 2025-10-14 12:46:01 +05:30
4c6961f7d6 Update app/controllers/api/users.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2025-10-14 07:58:30 +01:00
0bd27c4182 Update app/controllers/api/users.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2025-10-14 07:58:24 +01:00
Eldad Fux ddcfac4f0f feat: enhance account handling with plan support for disposable email validation
- Added 'plan' injection to account-related API endpoints.
- Updated logic to check for disposable email validation support based on the plan.
- Improved handling of disposable email checks during account creation and email updates.
2025-10-14 00:03:11 +01:00
Eldad Fux 3e58014dbd fixes 2025-10-13 23:48:50 +01:00
Eldad Fux ff06920e24 feat: add support for blocking disposable email addresses
- Introduced a new exception for disposable email addresses.
- Updated user account creation and email handling to check against a list of disposable email domains.
- Added a new API endpoint to enable or disable disposable email checks for projects.
- Updated project model to include disposable email settings.
- Configured loading of disposable email domains from a separate configuration file.
2025-10-13 23:43:54 +01:00
ArnabChatterjee20k 6073eab496 * updated env to have mongodb support for the tablesdb/legacy
* setAttributeSupport to support context schema and schemaless in mongodb
* updated the connection registers
2025-10-13 15:47:40 +05:30
ArnabChatterjee20k 50ddc611a4 updated migrations 2025-10-12 20:49:02 +05:30
ArnabChatterjee20k 0ef2cfa753 reverted docker compose 2025-10-12 20:26:39 +05:30
ArnabChatterjee20k 790e503ad9 updated composer lock 2025-10-12 20:19:57 +05:30
ArnabChatterjee20k f6dbc0c882 Merge remote-tracking branch 'upstream/feat-mongodb' into documents-db-api 2025-10-12 20:17:53 +05:30
ArnabChatterjee20k a6c226aac9 removed the documentsdb changes from the migration tests 2025-10-10 20:38:54 +05:30
ArnabChatterjee20k 2d41a3353d * migration fix for tablesdb, legacy with multitype callback
* updated patch script
2025-10-10 20:29:05 +05:30
ArnabChatterjee20k 34e4208b3e addressed comments
* renamed getDatabaseDB to getDatabasesDB
* renamed dbForDatabase to dbForDatabases
* removed call_user_func and using newer callable syntax
2025-10-10 20:00:43 +05:30
ArnabChatterjee20k d51486c62a * fixed stats usage events(hacky fix)
* updated redundant routes(happened during merge conflicts)
2025-10-10 19:39:59 +05:30
ArnabChatterjee20k f62f00d4d9 Merge remote-tracking branch 'upstream/documents-db-api' into documents-db-api 2025-10-10 15:26:33 +05:30
ArnabChatterjee20k 44e6ef3146 event generation fix 2025-10-10 15:24:42 +05:30
Jake BarnbyandGitHub dea49d54c9 Merge pull request #10628 from appwrite/fix-enums
Update: enums
2025-10-10 22:16:45 +13:00
Darshan 8a9523fa48 update: enums. 2025-10-10 14:40:26 +05:30
Darshan 5dba0183c0 update: enums. 2025-10-10 14:40:01 +05:30
ArnabChatterjee20k 0108f86180 linting + merge conflicts resolving 2025-10-10 13:39:16 +05:30
ArnabChatterjee20k bc71d83697 empty commit 2025-10-10 10:58:11 +05:30
ArnabChatterjee20k 0754c6263e merged changes from 1.8.x 2025-10-10 10:55:48 +05:30
ArnabChatterjee20k 2c037803f0 updated sdk names and enums for documentsdb 2025-10-09 18:41:10 +05:30
ArnabChatterjee20k 48410d0cf6 * added getDatabaseDB in stats worker, migration worker
* added prefixed events for multitype db support with backward compat
* added realtime tests, migration tests
2025-10-09 18:26:05 +05:30
ArnabChatterjee20k 66b0f3c9df * added waiting time in usage test so that aggregator can work
* added documentsdb usage test
2025-10-09 18:24:04 +05:30
ArnabChatterjee20k d8910eea73 fix - updated documentsdb sdk namespace to documentsDB 2025-10-09 10:30:57 +05:30
ArnabChatterjee20k f7f965a812 * added prefixed channels for realtime channels along with backward compatibility
* added documentsdb tests
2025-10-09 00:35:38 +05:30
ArnabChatterjee20k d806bdbca7 added project region in the get database dsn 2025-10-08 11:17:12 +05:30
ArnabChatterjee20k 19b1196633 * new db pool implementation for connecting to db type
* fixed migration tests
2025-10-08 11:09:32 +05:30
ArnabChatterjee20k 88f2b1ad50 added patch script for adding database dsn in the databases collection 2025-10-07 13:18:11 +05:30
ArnabChatterjee20k f5ea19b15d updated tests 2025-10-07 12:45:21 +05:30
ArnabChatterjee20k d2645ecfb1 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-07 10:47:26 +05:30
ArnabChatterjee20k 44b75d50f4 revmoved databasse logs xlist action for documentsdb 2025-10-03 19:56:20 +05:30
ArnabChatterjee20k df23aacbc0 updated routes and endpoints 2025-10-03 19:51:00 +05:30
ArnabChatterjee20k 7d4dfe06e2 * removed dbForDocumentsRecords
* using callback based db resolution
* updated env
2025-10-03 19:50:29 +05:30
ArnabChatterjee20k e50af4c981 add references docs for the documentsdb 2025-10-02 19:24:35 +05:30
ArnabChatterjee20k 4fdab6fd52 updated database db resolution in the resources itself instead of switch in the worker action 2025-10-02 18:58:11 +05:30
ArnabChatterjee20k c54450f582 updated composer 2025-10-02 18:57:15 +05:30
ArnabChatterjee20k 6bf9693146 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-02 18:56:48 +05:30
ArnabChatterjee20k 422fd8b157 updated get endpoint 2025-09-30 20:02:30 +05:30
ArnabChatterjee20k bf061b460b added documentsdb in the github actions 2025-09-30 19:29:36 +05:30
ArnabChatterjee20k a035760b00 updated injections and reverted reduntant changes 2025-09-30 19:15:43 +05:30
ArnabChatterjee20k 85c0454c49 linting 2025-09-30 18:45:53 +05:30
ArnabChatterjee20k 7bfb0401c6 linting 2025-09-30 18:44:27 +05:30
ArnabChatterjee20k c097f4a450 updated composer lock 2025-09-30 18:43:02 +05:30
ArnabChatterjee20k a3faa108c5 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-09-30 18:42:18 +05:30
ArnabChatterjee20k c89b76ba80 *updated tests
*updated create documents to use the on next callback
2025-09-30 18:19:44 +05:30
ArnabChatterjee20k 80636e8a91 updated timeouts 2025-09-30 00:03:12 +05:30
ArnabChatterjee20k 206845e106 updated tests and delete delete documents 2025-09-28 14:43:44 +05:30
ArnabChatterjee20k 64d015065f * added new databasetype dsn
* updated worker to use the dsn to choose the database resource
2025-09-26 19:52:25 +05:30
ArnabChatterjee20k 56af56e341 updated endpoints 2025-09-26 18:01:39 +05:30
ArnabChatterjee20k 3086e1cbc1 updated increment, and decrements routes 2025-09-25 19:58:59 +05:30
ArnabChatterjee20k b032a55045 updated the endpoints 2025-09-25 00:48:09 +05:30
ArnabChatterjee20k d71c56b11f * Migrating endpoints
* New adapter resolution and injection
* Removed attribute creation for documentsdb
2025-09-23 23:15:46 +05:30
ArnabChatterjee20k 98ee242812 migrated tests 2025-09-20 18:21:00 +05:30
ArnabChatterjee20k 79267c8123 migrated endpoints 2025-09-20 18:20:50 +05:30
ArnabChatterjee20k d2ad0e7c74 documentsdb database creation endpoints 2025-09-19 19:44:46 +05:30
1333 changed files with 122417 additions and 23377 deletions
@@ -0,0 +1,174 @@
# Parallel Chunk Upload Support for utopia-php/storage
## Context
The Appwrite API now supports out-of-order chunked uploads (chunks can arrive in any sequence). The next step is **parallel uploads** — multiple chunks uploaded simultaneously via separate HTTP requests. The SDK guarantees the first chunk is sent before any parallel chunks, so the document creation race is handled at the API layer. However, the storage device layer has a race condition that must be fixed.
## Problem: `Local::joinChunks()` Race
When two requests upload the final missing chunks in parallel, both can observe `countChunks() == $chunks` and call `joinChunks()` simultaneously.
### Current behavior (loser throws)
```php
// Local::joinChunks()
$dest = \fopen($tmpAssemble, 'wb');
// ... stream all parts into $tmpAssemble ...
if (! \rename($tmpAssemble, $path)) {
\unlink($tmpAssemble);
throw new Exception('Failed to finalize assembled file '.$path);
}
```
The winner succeeds with `rename()`. The loser gets `false` from `rename()` (file already exists at `$path`) and throws a 500-error exception. The client that lost the race receives an error even though the file is fully assembled.
### Required behavior
If `$path` already exists, another request already assembled the file. The loser should **silently succeed** — the file is complete, nothing more to do.
## Proposed Changes
### 1. `Local::joinChunks()` — Handle assembly race
Before opening `$tmpAssemble`, check if the final file already exists. If it does, skip assembly entirely.
```php
private function joinChunks(string $path, int $chunks): void
{
// Race winner already assembled the file
if (\file_exists($path)) {
return;
}
$tmp = \dirname($path).DIRECTORY_SEPARATOR.'tmp_'.asename($path);
$tmpAssemble = \dirname($path).DIRECTORY_SEPARATOR.'tmp_assemble_'.asename($path);
// ... rest of assembly logic ...
if (! \rename($tmpAssemble, $path)) {
// Another request may have won the race between fclose and rename
if (\file_exists($path)) {
\unlink($tmpAssemble);
return;
}
\unlink($tmpAssemble);
throw new Exception('Failed to finalize assembled file '.$path);
}
// ... cleanup ...
}
```
### 2. `Local::countChunks()` — Reliability under concurrent writes
`countChunks()` uses `glob()` on the temp directory. Under heavy parallel load, `glob()` might miss files or return inconsistent counts. The current implementation is already fairly robust (it validates `.part.\d+` suffix), but we should document that the return value is a best-effort snapshot.
No code change needed here unless tests reveal issues.
### 3. Tests — Concurrent chunk uploads
Add a test that simulates two parallel requests completing a multi-chunk upload:
```php
public function testParallelChunkUpload(): void
{
$storage = $this->makeJoinTestStorage();
$dest = $storage->getRoot().DIRECTORY_SEPARATOR.'parallel.dat';
// Upload chunk 1 (creates temp directory)
$storage->uploadData('AAAA', $dest, 'application/octet-stream', 1, 2);
// Simulate two parallel requests uploading the last chunk
// In a real test, use pcntl_fork() or pthreads for true concurrency
// For the test suite, sequential calls are sufficient if we verify
// the second call doesn't throw after the first completed assembly
$storage->uploadData('BBBB', $dest, 'application/octet-stream', 2, 2);
// Verify file exists and is correct
$this->assertTrue(\file_exists($dest));
$this->assertSame('AAAABBBB', \file_get_contents($dest));
// Verify second assembly attempt doesn't throw
// (This simulates the race where another request already assembled)
try {
$storage->uploadData('BBBB', $dest, 'application/octet-stream', 2, 2);
} catch (\Exception $e) {
$this->fail('Duplicate assembly should not throw: '.$e->getMessage());
}
$storage->delete($storage->getRoot(), true);
}
```
A more realistic concurrent test using `pcntl_fork()`:
```php
public function testParallelChunkUploadWithFork(): void
{
if (!\function_exists('pcntl_fork')) {
$this->markTestSkipped('pcntl extension required for fork-based concurrency test');
}
$storage = $this->makeJoinTestStorage();
$dest = $storage->getRoot().DIRECTORY_SEPARATOR.'parallel-fork.dat';
// Pre-upload chunk 1
$storage->uploadData('AAAA', $dest, 'application/octet-stream', 1, 2);
$pid = pcntl_fork();
if ($pid === -1) {
$this->fail('Failed to fork');
} elseif ($pid === 0) {
// Child process: upload chunk 2
try {
$storage->uploadData('BBBB', $dest, 'application/octet-stream', 2, 2);
exit(0);
} catch (\Exception $e) {
exit(1);
}
}
// Parent process: also upload chunk 2 (race condition)
$parentSuccess = true;
try {
$storage->uploadData('BBBB', $dest, 'application/octet-stream', 2, 2);
} catch (\Exception $e) {
$parentSuccess = false;
}
pcntl_waitpid($pid, $status);
$childSuccess = pcntl_wexitstatus($status) === 0;
// At least one should succeed
$this->assertTrue($parentSuccess || $childSuccess, 'At least one parallel upload should succeed');
// File should be correctly assembled
$this->assertTrue(\file_exists($dest));
$this->assertSame('AAAABBBB', \file_get_contents($dest));
$storage->delete($storage->getRoot(), true);
}
```
## S3 Device
S3 already handles out-of-order multipart uploads natively. The `completeMultipartUpload` call with `ksort()` sorts parts by number regardless of upload order. However, parallel `completeMultipartUpload` calls for the same `uploadId` would still be problematic.
This is an **API-layer concern** — the Appwrite API should ensure only one request calls `completeMultipartUpload` per upload. The S3 device itself does not need changes.
## Files to Change
| File | Change |
|------|--------|
| `src/Storage/Device/Local.php` | Add `file_exists($path)` guard at start of `joinChunks()` and in `rename()` failure handler |
| `tests/Storage/Device/LocalTest.php` | Add `testParallelChunkUpload` and `testParallelChunkUploadWithFork` |
## Backwards Compatibility
Fully backwards compatible. The change only affects the error path when `rename()` fails due to an existing file. Previously it threw; now it returns silently. No public API signatures change.
## Related PRs
- Appwrite server PR: https://github.com/appwrite/appwrite/pull/12138 (out-of-order upload support)
- This storage PR is a prerequisite for the follow-up Appwrite PR that enables parallel chunk uploads at the API level.
@@ -0,0 +1,29 @@
# Patch Release Checklist for Appwrite
When bumping a patch version (e.g., `1.9.0` -> `1.9.1`), follow this checklist.
## Checklist
### Bump console image
Update the console Docker image tag in both files:
- [ ] `docker-compose.yml` -- update `image: appwrite/console:X.Y.Z`
- [ ] `app/views/install/compose.phtml` -- update `image: <?php echo $organization; ?>/console:X.Y.Z`
### Bump Appwrite version
- [ ] **`app/init/constants.php`** -- update `APP_VERSION_STABLE` to the new version (e.g., `'1.9.1'`). In same file, increment `APP_CACHE_BUSTER` by 1.
- [ ] **`README.md`** -- update the Docker image tag `appwrite/appwrite:X.Y.Z` in all 3 install code blocks (Unix, Windows CMD, PowerShell).
- [ ] **`README-CN.md`** -- same Docker image tag update in all 3 install code blocks.
- [ ] **`src/Appwrite/Migration/Migration.php`** -- add the new version to the `$versions` array, mapping it to a migration class. If new class exists, use that, otherwise use sle same class as previous version
### Update CHANGES.md
- [ ] Add a new `# Version X.Y.Z` section at the top of `CHANGES.md` with subsections: `### Notable changes`, `### Fixes`, `### Miscellaneous`
## Final review
- [ ] Ask user to review changes before commiting
- [ ] Ask user to update `CHANGES.md` with PRs
- [ ] Ask user to generate specs, if needed
- [ ] Ask user to add request and response filters, if needed
+14 -1
View File
@@ -39,7 +39,7 @@ _APP_REDIS_HOST=redis
_APP_REDIS_PORT=6379
_APP_REDIS_PASS=
_APP_REDIS_USER=
COMPOSE_PROFILES=mongodb
COMPOSE_PROFILES=mariadb,mongodb,postgresql
_APP_DB_ADAPTER=mongodb
_APP_DB_HOST=mongodb
_APP_DB_PORT=27017
@@ -47,6 +47,17 @@ _APP_DB_SCHEMA=appwrite
_APP_DB_USER=user
_APP_DB_PASS=password
_APP_DB_ROOT_PASS=rootsecretpassword
_APP_DATABASE_SHARED_TABLES=
_APP_DATABASE_SHARED_NAMESPACE=
_APP_DB_ADAPTER_DOCUMENTSDB=mongodb
_APP_DB_HOST_DOCUMENTSDB=mongodb
_APP_DB_PORT_DOCUMENTSDB=27017
_APP_DB_ADAPTER_VECTORSDB=postgresql
_APP_DB_HOST_VECTORSDB=postgresql
_APP_DB_PORT_VECTORSDB=5432
_APP_EMBEDDING_MODELS=embeddinggemma
_APP_EMBEDDING_ENDPOINT='http://ollama:11434/api/embed'
_APP_EMBEDDING_TIMEOUT=30000
_APP_STORAGE_DEVICE=Local
_APP_STORAGE_S3_ACCESS_KEY=
_APP_STORAGE_S3_SECRET=
@@ -137,3 +148,5 @@ _APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
_APP_TRUSTED_HEADERS=x-forwarded-for
_APP_POOL_ADAPTER=stack
_APP_WORKER_SCREENSHOTS_ROUTER=http://appwrite
_TESTS_OAUTH2_GITHUB_CLIENT_ID=
_TESTS_OAUTH2_GITHUB_CLIENT_SECRET=
+1 -1
View File
@@ -25,6 +25,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: AI Moderator
uses: github/ai-moderator@v1
uses: github/ai-moderator@81159c370785e295c97461ade67d7c33576e9319 # v1.1.4
with:
token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Issue Labeler
uses: github/issue-labeler@v3.4
uses: github/issue-labeler@c1b0f9f52a63158c4adc09425e858e87b32e9685 # v3.4
with:
configuration-path: .github/labeler.yml
enable-versioned-regex: false
+349
View File
@@ -0,0 +1,349 @@
const fs = require('fs');
const marker = '<!-- appwrite-benchmark-results -->';
const serviceLabels = ['Account', 'TablesDB', 'Storage', 'Functions'];
module.exports = async ({ github, context, core }) => {
const body = buildComment(core);
fs.writeFileSync('benchmark-comment.txt', body);
const pullRequest = context.payload.pull_request;
if (!pullRequest || pullRequest.head.repo.full_name !== `${context.repo.owner}/${context.repo.repo}`) {
return;
}
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
per_page: 100,
});
const existing = comments.find((comment) => {
return comment.user?.type === 'Bot' && comment.body?.includes(marker);
}) || comments.find((comment) => {
return comment.user?.type === 'Bot' && comment.body?.includes('Benchmark results');
});
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
return;
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
body,
});
};
function buildComment(core) {
const before = readSummary('benchmark-before-summary.json', core);
const after = readSummary('benchmark-after-summary.json', core);
const beforeSamples = readSamples('benchmark-before-samples.json', core);
const afterSamples = readSamples('benchmark-after-samples.json', core);
const baseRef = markdownText(process.env.BENCHMARK_BASE_REF || 'base');
const headRef = markdownText(process.env.BENCHMARK_HEAD_REF || 'head');
const rows = benchmarkRows(before, after, beforeSamples, afterSamples);
const topWaits = topSamples(afterSamples, 'appwrite_api_waiting', 3);
const lines = [
marker,
'## :sparkles: Benchmark results',
'',
`Comparing ${baseRef} (before) to ${headRef} (after).`,
'',
];
if (before === null) {
lines.push('> Before benchmark did not complete; showing current branch metrics only.', '');
}
if (after === null) {
lines.push('> Current branch benchmark did not complete; showing available metrics only.', '');
}
lines.push(
'**Before**',
'',
metricTable(rows, 'before'),
'',
'**After**',
'',
metricTable(rows, 'after'),
'',
'**Delta**',
'',
'| Scenario | P95 delta (ms) |',
'| --- | ---: |',
...rows.map(deltaRow),
'',
'<details>',
'<summary><strong>Top API waits</strong></summary>',
'',
'<br>',
'',
'| API request | Max wait (ms) |',
'| --- | ---: |',
...topWaitRows(topWaits),
'',
'</details>',
);
return `${lines.join('\n')}\n`;
}
function readSummary(path, core) {
if (!fs.existsSync(path)) {
return null;
}
try {
return JSON.parse(fs.readFileSync(path, 'utf8'));
} catch (error) {
core?.warning(`Invalid benchmark summary ${path}: ${error.message}`);
return null;
}
}
function readSamples(path, core) {
if (!fs.existsSync(path)) {
return [];
}
const contents = fs.readFileSync(path, 'utf8').trim();
if (contents === '') {
return [];
}
return contents
.split('\n')
.filter(Boolean)
.flatMap((line) => {
try {
return [JSON.parse(line)];
} catch (error) {
core?.warning(`Invalid benchmark sample in ${path}: ${error.message}`);
return [];
}
});
}
function benchmarkRows(before, after, beforeSamples, afterSamples) {
const beforeServices = serviceStats(beforeSamples);
const afterServices = serviceStats(afterSamples);
return [
{
label: 'API total',
before: apiSampleStats(beforeSamples) || summaryStats(before, 'appwrite_api_duration'),
after: apiSampleStats(afterSamples) || summaryStats(after, 'appwrite_api_duration'),
},
...serviceLabels.map((label) => ({
label,
before: beforeServices.get(label) || null,
after: afterServices.get(label) || null,
})),
];
}
function summaryStats(summary, durationMetric, iterationsMetric = null, rpsMetric = null) {
const values = metricValues(summary, durationMetric);
if (!values) {
return null;
}
return {
p50: values.med ?? null,
p95: values['p(95)'] ?? null,
iterations: iterationsMetric ? metricValue(summary, iterationsMetric, 'count') : values.count ?? null,
rps: rpsMetric ? metricValue(summary, rpsMetric, 'rate') : null,
};
}
function serviceStats(samples) {
const apiSamples = samples.filter((sample) => {
return sample.metric === 'appwrite_api_duration' && typeof sample.data?.value === 'number';
});
const groups = new Map();
for (const sample of apiSamples) {
const service = serviceFromName(sample.data.tags?.name || '');
if (!service) {
continue;
}
const serviceSamples = groups.get(service) || [];
serviceSamples.push(sample);
groups.set(service, serviceSamples);
}
return new Map([...groups.entries()].map(([service, serviceSamples]) => {
const values = serviceSamples.map((sample) => sample.data.value);
const durationSeconds = sampleWindowSeconds(serviceSamples);
return [service, {
p50: percentile(values, 50),
p95: percentile(values, 95),
iterations: values.length,
rps: durationSeconds ? values.length / durationSeconds : null,
}];
}));
}
function apiSampleStats(samples) {
const apiSamples = samples.filter((sample) => {
return sample.metric === 'appwrite_api_duration' && typeof sample.data?.value === 'number';
});
const values = apiSamples.map((sample) => sample.data.value);
if (values.length === 0) {
return null;
}
const durationSeconds = sampleWindowSeconds(apiSamples);
return {
p50: percentile(values, 50),
p95: percentile(values, 95),
iterations: values.length,
rps: durationSeconds ? values.length / durationSeconds : null,
};
}
function serviceFromName(name) {
if (name.startsWith('account.')) {
return 'Account';
}
if (name.startsWith('tablesdb.')) {
return 'TablesDB';
}
if (name.startsWith('storage.') || name.startsWith('tokens.')) {
return 'Storage';
}
if (name.startsWith('functions.')) {
return 'Functions';
}
return null;
}
function sampleWindowSeconds(samples) {
const times = samples
.map((sample) => Date.parse(sample.data?.time))
.filter((value) => !Number.isNaN(value));
if (times.length < 2) {
return null;
}
return Math.max((Math.max(...times) - Math.min(...times)) / 1000, 1);
}
function percentile(values, percentileValue) {
if (values.length === 0) {
return null;
}
const sorted = [...values].sort((left, right) => left - right);
const index = Math.ceil((percentileValue / 100) * sorted.length) - 1;
return sorted[Math.max(0, Math.min(index, sorted.length - 1))];
}
function metricValues(data, metric) {
return data?.metrics?.[metric]?.values ?? null;
}
function metricValue(data, metric, stat) {
return metricValues(data, metric)?.[stat] ?? null;
}
function metricTable(rows, side) {
return [
'| Scenario | P50 (ms) | P95 (ms) | Requests | RPS |',
'| --- | ---: | ---: | ---: | ---: |',
...rows.map((row) => metricRow(row, side)),
].join('\n');
}
function metricRow(row, side) {
const values = row[side];
return `| ${row.label} | ${formatMs(values?.p50)} | ${formatMs(values?.p95)} | ${formatCount(values?.iterations)} | ${formatRate(values?.rps)} |`;
}
function deltaRow(row) {
return `| ${row.label} | ${formatDelta(row.before?.p95, row.after?.p95)} |`;
}
function topSamples(samples, metric, limit) {
const byName = samples.reduce((result, sample) => {
if (sample.metric !== metric || typeof sample.data?.value !== 'number') {
return result;
}
const name = sample.data.tags?.name || 'unknown';
const current = result.get(name);
if (!current || sample.data.value > current.value) {
result.set(name, { name, value: sample.data.value });
}
return result;
}, new Map());
return [...byName.values()]
.sort((left, right) => right.value - left.value)
.slice(0, limit);
}
function topWaitRows(samples) {
if (samples.length === 0) {
return ['| n/a | n/a |'];
}
return samples.map((sample) => {
return `| ${markdownText(sample.name).replace(/\|/g, '\\|')} | ${formatMs(sample.value)} |`;
});
}
function markdownText(value) {
return String(value || '').replace(/[\r\n]/g, ' ').replace(/[&<>"']/g, (char) => {
return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' })[char];
});
}
function formatMs(value) {
return formatNumber(value, 2);
}
function formatRate(value) {
return formatNumber(value, 2);
}
function formatCount(value) {
if (value === null || value === undefined || Number.isNaN(value)) {
return 'n/a';
}
return `${Math.round(value)}`;
}
function formatDelta(before, after) {
if (before === null || before === undefined || after === null || after === undefined || Number.isNaN(before) || Number.isNaN(after)) {
return 'n/a';
}
const difference = Number((after - before).toFixed(2));
return `${difference > 0 ? '+' : ''}${trimNumber(difference)}`;
}
function formatNumber(value, decimals) {
if (value === null || value === undefined || Number.isNaN(value)) {
return 'n/a';
}
return trimNumber(Number(value).toFixed(decimals));
}
function trimNumber(value) {
const text = String(value);
const trimmed = text.includes('.') ? text.replace(/\.?0+$/, '') : text;
return trimmed === '' ? '0' : trimmed;
}
+390 -182
View File
@@ -7,7 +7,8 @@ concurrency:
env:
COMPOSE_FILE: docker-compose.yml
IMAGE: appwrite-dev
CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }}
REGISTRY_IMAGE: ghcr.io/${{ github.repository }}/appwrite-dev
K6_VERSION: '0.53.0'
on:
pull_request:
@@ -19,6 +20,10 @@ on:
type: string
default: ''
permissions:
contents: read
packages: write
jobs:
dependencies:
name: Checks / Dependencies
@@ -27,7 +32,7 @@ jobs:
actions: read
security-events: write
contents: read
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@v2.3.3"
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@c51854704019a247608d928f370c98740469d4b5" # v2.3.5
security:
name: Checks / Image
@@ -38,13 +43,13 @@ jobs:
security-events: write
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
submodules: 'recursive'
- name: Build the Docker image
uses: docker/build-push-action@v6
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
push: false
@@ -53,7 +58,7 @@ jobs:
target: production
- name: Run Trivy vulnerability scanner on image
uses: aquasecurity/trivy-action@0.35.0
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: 'pr_image:${{ github.sha }}'
format: 'sarif'
@@ -61,7 +66,7 @@ jobs:
severity: 'CRITICAL,HIGH'
- name: Run Trivy vulnerability scanner on source code
uses: aquasecurity/trivy-action@0.35.0
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: 'fs'
scan-ref: '.'
@@ -71,57 +76,132 @@ jobs:
skip-setup-trivy: true
- name: Upload image scan results
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
if: always() && hashFiles('trivy-image-results.sarif') != ''
with:
sarif_file: 'trivy-image-results.sarif'
category: 'trivy-image'
- name: Upload source code scan results
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
if: always() && hashFiles('trivy-fs-results.sarif') != ''
with:
sarif_file: 'trivy-fs-results.sarif'
category: 'trivy-source'
composer:
name: Checks / Composer
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0
with:
php-version: '8.3'
tools: composer:v2
coverage: none
- name: Validate
run: composer validate
- name: Install dependencies
run: composer install --prefer-dist --no-progress --ignore-platform-reqs
- name: Audit
env:
COMPOSER_NO_AUDIT: 0
run: composer audit
format:
name: Checks / Format
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 2
- run: git checkout HEAD^2
if: github.event_name == 'pull_request'
- name: Validate composer.json and composer.lock
run: |
docker run --rm -v $PWD:/app composer:2.8 sh -c \
"composer validate"
- name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0
with:
php-version: '8.3'
tools: composer:v2
coverage: none
- name: Install dependencies
run: composer install --prefer-dist --no-progress --ignore-platform-reqs
- name: Run Linter
run: |
docker run --rm -v $PWD:/app composer:2.8 sh -c \
"composer install --profile --ignore-platform-reqs && composer lint"
run: composer lint
analyze:
name: Checks / Analyze
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0
with:
php-version: '8.3'
tools: composer:v2
coverage: none
- name: Install dependencies
run: composer install --prefer-dist --no-progress --ignore-platform-reqs
- name: Cache PHPStan result cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: .phpstan-cache
key: phpstan-${{ github.sha }}
restore-keys: |
phpstan-
- name: Run PHPStan
run: |
docker run --rm -v $PWD:/app composer:2.8 sh -c \
"composer install --profile --ignore-platform-reqs && composer check"
run: composer analyze -- --no-progress
specs:
name: Checks / Specs
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0
with:
php-version: '8.3'
extensions: swoole
tools: composer:v2
coverage: none
- name: Install dependencies
run: composer install --prefer-dist --no-progress --ignore-platform-reqs
- name: Generate specs
run: _APP_STORAGE_LIMIT=5368709120 php app/cli.php specs --version=latest --git=no
locale:
name: Checks / Locale
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24'
- name: Run Locale check
run: |
docker run --rm -v $PWD:/app node:24-alpine sh -c \
"cd /app/.github/workflows/static-analysis/locale && node index.js"
run: node .github/workflows/static-analysis/locale/index.js
matrix:
name: Tests / Matrix
@@ -132,11 +212,11 @@ jobs:
steps:
- name: Generate matrix
id: generate
uses: actions/github-script@v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const allDatabases = ['MariaDB', 'PostgreSQL', 'MongoDB'];
const allModes = ['dedicated', 'shared_v1', 'shared_v2'];
const allModes = ['dedicated', 'shared'];
const defaultDatabases = ['MongoDB'];
const defaultModes = ['dedicated'];
@@ -173,41 +253,40 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- name: Login to Docker Hub
uses: docker/login-action@v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Appwrite
uses: docker/build-push-action@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build and push Appwrite
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
push: false
tags: ${{ env.IMAGE }}
load: true
push: true
tags: ${{ env.REGISTRY_IMAGE }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar
target: development
build-args: |
DEBUG=false
TESTING=true
VERSION=dev
- name: Cache Docker Image
uses: actions/cache@v5
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
unit:
name: Tests / Unit
runs-on: ubuntu-latest
@@ -215,27 +294,32 @@ jobs:
permissions:
contents: read
pull-requests: write
packages: read
steps:
- name: checkout
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v5
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Login to Docker Hub
uses: docker/login-action@v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker Image
run: |
docker pull ${{ env.REGISTRY_IMAGE }}:${{ github.sha }}
docker tag ${{ env.REGISTRY_IMAGE }}:${{ github.sha }} ${{ env.IMAGE }}
- name: Load and Start Appwrite
timeout-minutes: 5
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose pull --quiet --ignore-buildable
docker compose up -d --quiet-pull --wait
@@ -243,7 +327,7 @@ jobs:
run: docker compose exec -T appwrite vars
- name: Run Unit Tests
uses: itznotabug/php-retry@v3
uses: itznotabug/php-retry@d6bef45a8bff490babfb613e33b00d133e4062f0 # v3
with:
max_attempts: 2
retry_wait_seconds: 60
@@ -263,27 +347,32 @@ jobs:
permissions:
contents: read
pull-requests: write
packages: read
steps:
- name: checkout
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v5
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Login to Docker Hub
uses: docker/login-action@v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker Image
run: |
docker pull ${{ env.REGISTRY_IMAGE }}:${{ github.sha }}
docker tag ${{ env.REGISTRY_IMAGE }}:${{ github.sha }} ${{ env.IMAGE }}
- name: Load and Start Appwrite
timeout-minutes: 5
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose pull --quiet --ignore-buildable
docker compose up -d --quiet-pull --wait
@@ -296,7 +385,7 @@ jobs:
done
- name: Run General Tests
uses: itznotabug/php-retry@v3
uses: itznotabug/php-retry@d6bef45a8bff490babfb613e33b00d133e4062f0 # v3
with:
max_attempts: 2
retry_wait_seconds: 60
@@ -317,11 +406,12 @@ jobs:
e2e_service:
name: Tests / E2E / ${{ matrix.database }} (${{ matrix.mode }}) / ${{ matrix.service }}
runs-on: ubuntu-latest
runs-on: ${{ matrix.runner || format('runs-on={0}/runner=4cpu-linux-x64/volume=120g/spot=false', github.run_id) }}
needs: [build, matrix]
permissions:
contents: read
pull-requests: write
packages: read
strategy:
fail-fast: false
matrix:
@@ -332,10 +422,12 @@ jobs:
Avatars,
Console,
Databases,
TablesDB,
Functions,
FunctionsSchedule,
GraphQL,
Health,
Advisor,
Locale,
Projects,
Realtime,
@@ -345,24 +437,33 @@ jobs:
Tokens,
Teams,
Users,
ProjectWebhooks,
Webhooks,
VCS,
Messaging,
Migrations
Migrations,
Project,
Presences
]
include:
- service: Databases
runner: runs-on=${{ github.run_id }}/runner=8cpu-linux-x64/volume=120g/spot=false
paratest_processes: 3
timeout_minutes: 30
- service: TablesDB
runner: runs-on=${{ github.run_id }}/runner=8cpu-linux-x64/volume=120g/spot=false
paratest_processes: 3
timeout_minutes: 30
- service: Migrations
paratest_processes: 1
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Load Cache
uses: actions/cache@v5
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
- name: Set database environment
- name: Set environment
run: |
echo "_APP_OPTIONS_ROUTER_PROTECTION=enabled" >> $GITHUB_ENV
if [ "${{ matrix.database }}" = "MariaDB" ]; then
echo "COMPOSE_PROFILES=mariadb" >> $GITHUB_ENV
echo "_APP_DB_ADAPTER=mariadb" >> $GITHUB_ENV
@@ -381,19 +482,31 @@ jobs:
fi
- name: Login to Docker Hub
uses: docker/login-action@v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker Image
run: |
docker pull ${{ env.REGISTRY_IMAGE }}:${{ github.sha }}
docker tag ${{ env.REGISTRY_IMAGE }}:${{ github.sha }} ${{ env.IMAGE }}
- name: Load and Start Appwrite
timeout-minutes: 5
env:
_APP_BROWSER_HOST: http://invalid-browser/v1
_APP_DATABASE_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'database_db_main' || '' }}
_APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'database_db_main' || '' }}
_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'documentsdb_db_main' || '' }}
_APP_DATABASE_VECTORSDB_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'vectorsdb_db_main' || '' }}
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose pull --quiet --ignore-buildable
docker compose up -d --quiet-pull --wait
@@ -406,11 +519,11 @@ jobs:
done
- name: Run tests
uses: itznotabug/php-retry@v3
uses: itznotabug/php-retry@d6bef45a8bff490babfb613e33b00d133e4062f0 # v3
with:
max_attempts: 2
retry_wait_seconds: 60
timeout_minutes: 20
timeout_minutes: ${{ matrix.timeout_minutes || 20 }}
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/e2e/Services/${{ matrix.service }}
@@ -420,12 +533,19 @@ jobs:
# Services that rely on sequential test method execution (shared static state)
FUNCTIONAL_FLAG="--functional"
case "${{ matrix.service }}" in
Databases|Functions|Realtime) FUNCTIONAL_FLAG="" ;;
Databases|TablesDB|Functions|Realtime|GraphQL|ProjectWebhooks) FUNCTIONAL_FLAG="" ;;
esac
PARATEST_PROCESSES="${{ matrix.paratest_processes }}"
if [ -z "$PARATEST_PROCESSES" ]; then
PARATEST_PROCESSES="$(nproc)"
fi
docker compose exec -T \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite vendor/bin/paratest --processes $(nproc) $FUNCTIONAL_FLAG "$SERVICE_PATH" --exclude-group abuseEnabled --exclude-group screenshots --log-junit tests/e2e/Services/${{ matrix.service }}/junit.xml
-e _TESTS_OAUTH2_GITHUB_CLIENT_ID="${{ secrets.TESTS_OAUTH2_GITHUB_CLIENT_ID }}" \
-e _TESTS_OAUTH2_GITHUB_CLIENT_SECRET="${{ secrets.TESTS_OAUTH2_GITHUB_CLIENT_SECRET }}" \
appwrite vendor/bin/paratest --processes "$PARATEST_PROCESSES" $FUNCTIONAL_FLAG "$SERVICE_PATH" --exclude-group abuseEnabled --exclude-group screenshots --log-junit tests/e2e/Services/${{ matrix.service }}/junit.xml
- name: Failure Logs
if: failure()
@@ -440,40 +560,48 @@ jobs:
permissions:
contents: read
pull-requests: write
packages: read
strategy:
fail-fast: false
matrix:
mode: ${{ fromJSON(needs.matrix.outputs.modes) }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
fetch-depth: 1
- name: Login to Docker Hub
uses: docker/login-action@v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker Image
run: |
docker pull ${{ env.REGISTRY_IMAGE }}:${{ github.sha }}
docker tag ${{ env.REGISTRY_IMAGE }}:${{ github.sha }} ${{ env.IMAGE }}
- name: Load and Start Appwrite
timeout-minutes: 5
env:
_APP_OPTIONS_ABUSE: enabled
_APP_DATABASE_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'database_db_main' || '' }}
_APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'database_db_main' || '' }}
_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'documentsdb_db_main' || '' }}
_APP_DATABASE_VECTORSDB_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'vectorsdb_db_main' || '' }}
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose pull --quiet --ignore-buildable
docker compose up -d --quiet-pull --wait
- name: Run tests
uses: itznotabug/php-retry@v3
uses: itznotabug/php-retry@d6bef45a8bff490babfb613e33b00d133e4062f0 # v3
with:
max_attempts: 2
retry_wait_seconds: 60
@@ -499,34 +627,40 @@ jobs:
permissions:
contents: read
pull-requests: write
packages: read
strategy:
fail-fast: false
matrix:
mode: ${{ fromJSON(needs.matrix.outputs.modes) }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v5
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Login to Docker Hub
uses: docker/login-action@v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker Image
run: |
docker pull ${{ env.REGISTRY_IMAGE }}:${{ github.sha }}
docker tag ${{ env.REGISTRY_IMAGE }}:${{ github.sha }} ${{ env.IMAGE }}
- name: Load and Start Appwrite
timeout-minutes: 5
env:
_APP_DATABASE_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'database_db_main' || '' }}
_APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'database_db_main' || '' }}
_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'documentsdb_db_main' || '' }}
_APP_DATABASE_VECTORSDB_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'vectorsdb_db_main' || '' }}
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose pull --quiet --ignore-buildable
docker compose up -d --quiet-pull --wait
@@ -539,7 +673,7 @@ jobs:
done
- name: Run tests
uses: itznotabug/php-retry@v3
uses: itznotabug/php-retry@d6bef45a8bff490babfb613e33b00d133e4062f0 # v3
with:
max_attempts: 2
retry_wait_seconds: 60
@@ -560,100 +694,174 @@ jobs:
benchmark:
name: Benchmark
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
needs: build
permissions:
actions: read
contents: read
issues: write
pull-requests: write
packages: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
fetch-depth: 1
- name: Login to Docker Hub
uses: docker/login-action@v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Load and Start Appwrite
run: |
sed -i 's/traefik/localhost/g' .env
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose up -d
sleep 10
- name: Install Oha
run: |
echo "deb [signed-by=/usr/share/keyrings/azlux-archive-keyring.gpg] http://packages.azlux.fr/debian/ stable main" | sudo tee /etc/apt/sources.list.d/azlux.list
sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg
sudo apt update
sudo apt install oha
oha --version
- name: Benchmark PR
run: 'oha -z 180s http://localhost/v1/health/version --output-format json > benchmark.json'
- name: Cleaning
run: docker compose down -v
- name: Installing latest version
run: |
rm docker-compose.yml
rm .env
curl https://appwrite.io/install/compose -o docker-compose.yml
curl https://appwrite.io/install/env -o .env
sed -i 's/_APP_OPTIONS_ABUSE=enabled/_APP_OPTIONS_ABUSE=disabled/g' .env
docker compose up -d
sleep 10
- name: Benchmark Latest
run: oha -z 180s http://localhost/v1/health/version --output-format json > benchmark-latest.json
- name: Prepare comment
run: |
echo '## :sparkles: Benchmark results' > benchmark.txt
echo ' ' >> benchmark.txt
echo "- Requests per second: $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
echo "- Requests with 200 status code: $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
echo "- P99 latency: $(jq -r '.latencyPercentiles.p99' benchmark.json )" >> benchmark.txt
echo " " >> benchmark.txt
echo " " >> benchmark.txt
echo "## :zap: Benchmark Comparison" >> benchmark.txt
echo " " >> benchmark.txt
echo "| Metric | This PR | Latest version | " >> benchmark.txt
echo "| --- | --- | --- | " >> benchmark.txt
echo "| RPS | $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.summary.requestsPerSec|tonumber|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
echo "| 200 | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
echo "| P99 | $(jq -r '.latencyPercentiles.p99' benchmark.json ) | $(jq -r '.latencyPercentiles.p99' benchmark-latest.json ) | " >> benchmark.txt
- name: Save results
uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
name: benchmark.json
path: benchmark.json
retention-days: 7
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Find Comment
if: github.event.pull_request.head.repo.full_name == github.repository
uses: peter-evans/find-comment@v3
id: fc
- name: Pull Appwrite image
run: |
docker pull ${{ env.REGISTRY_IMAGE }}:${{ github.sha }}
docker tag ${{ env.REGISTRY_IMAGE }}:${{ github.sha }} ${{ env.IMAGE }}
docker tag ${{ env.REGISTRY_IMAGE }}:${{ github.sha }} ${{ env.IMAGE }}:after
- name: Setup k6
uses: grafana/setup-k6-action@db07bd9765aac508ef18982e52ab937fe633a065 # v1.2.1
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: Benchmark results
k6-version: ${{ env.K6_VERSION }}
- name: Prepare benchmark before
id: benchmark_before_prepare
continue-on-error: true
run: |
git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }}
git worktree add --detach /tmp/appwrite-benchmark-before ${{ github.event.pull_request.base.sha }}
docker build \
--cache-from ${{ env.IMAGE }}:after \
--target development \
--build-arg DEBUG=false \
--build-arg TESTING=true \
--build-arg VERSION=dev \
--tag ${{ env.IMAGE }}:before \
/tmp/appwrite-benchmark-before
- name: Start before Appwrite
id: benchmark_before_start
if: steps.benchmark_before_prepare.outcome == 'success'
continue-on-error: true
working-directory: /tmp/appwrite-benchmark-before
env:
_APP_DOMAIN: localhost
_APP_CONSOLE_DOMAIN: localhost
_APP_DOMAIN_FUNCTIONS: functions.localhost
_APP_OPTIONS_ABUSE: disabled
run: |
docker tag ${{ env.IMAGE }}:before ${{ env.IMAGE }}
docker compose up -d --wait --no-build
- name: Prepare benchmark files
run: rm -f benchmark-before-summary.json benchmark-after-summary.json benchmark-before-samples.json benchmark-after-samples.json
- name: Benchmark before
if: steps.benchmark_before_start.outcome == 'success'
continue-on-error: true
uses: grafana/run-k6-action@de51a7390bdf0ac85a3bef493691bd71d4c7c158 # v1.4.0
env:
APPWRITE_ENDPOINT: 'http://localhost/v1'
APPWRITE_BENCHMARK_ITERATIONS: '5'
APPWRITE_BENCHMARK_VUS: '1'
APPWRITE_WORKER_TIMEOUT_MS: '120000'
APPWRITE_BENCHMARK_SUMMARY_PATH: 'benchmark-before-summary.json'
with:
path: tests/benchmarks/http.js
flags: --quiet --out json=benchmark-before-samples.json
cloud-comment-on-pr: false
debug: true
- name: Stop before Appwrite
if: always()
run: |
if [ -d /tmp/appwrite-benchmark-before ]; then
cd /tmp/appwrite-benchmark-before
docker compose down -v || true
fi
- name: Wait for benchmark ports
if: always()
run: |
for port in 80 443 8080 9503; do
for attempt in $(seq 1 30); do
if ! ss -ltn | awk '{print $4}' | grep -Eq "[:.]${port}$"; then
break
fi
sleep 1
done
if ss -ltn | awk '{print $4}' | grep -Eq "[:.]${port}$"; then
echo "Port ${port} is still in use after stopping the before stack"
ss -ltn
exit 1
fi
done
- name: Start after Appwrite
env:
_APP_DOMAIN: localhost
_APP_CONSOLE_DOMAIN: localhost
_APP_DOMAIN_FUNCTIONS: functions.localhost
_APP_OPTIONS_ABUSE: disabled
run: |
docker tag ${{ env.IMAGE }}:after ${{ env.IMAGE }}
docker compose up -d --wait --no-build
- name: Benchmark after
id: benchmark_after
continue-on-error: true
uses: grafana/run-k6-action@de51a7390bdf0ac85a3bef493691bd71d4c7c158 # v1.4.0
env:
APPWRITE_ENDPOINT: 'http://localhost/v1'
APPWRITE_BENCHMARK_ITERATIONS: '5'
APPWRITE_BENCHMARK_VUS: '1'
APPWRITE_WORKER_TIMEOUT_MS: '120000'
APPWRITE_BENCHMARK_PREVIOUS_SUMMARY_PATH: '../../benchmark-before-summary.json'
APPWRITE_BENCHMARK_SUMMARY_PATH: 'benchmark-after-summary.json'
with:
path: tests/benchmarks/http.js
flags: --quiet --out json=benchmark-after-samples.json
cloud-comment-on-pr: false
debug: true
- name: Stop after Appwrite
if: always()
run: docker compose down -v || true
- name: Comment on PR
if: github.event.pull_request.head.repo.full_name == github.repository
uses: peter-evans/create-or-update-comment@v4
if: always()
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
BENCHMARK_BASE_REF: ${{ github.event.pull_request.base.ref }}
BENCHMARK_HEAD_REF: ${{ github.event.pull_request.head.ref }}
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body-path: benchmark.txt
edit-mode: replace
script: |
const comment = require('./.github/workflows/benchmark-comment.js');
await comment({ github, context, core });
- name: Save results
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: benchmark-results
path: |
benchmark-comment.txt
benchmark-before-summary.json
benchmark-after-summary.json
benchmark-before-samples.json
benchmark-after-samples.json
retention-days: 7
- name: Fail benchmark
if: always() && steps.benchmark_after.outcome != 'success'
run: exit 1
+32 -2
View File
@@ -5,12 +5,17 @@ on:
types:
- closed
permissions:
actions: write
contents: read
packages: write
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Cleanup
run: |
@@ -36,4 +41,29 @@ jobs:
done
done
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cleanup GHCR image
continue-on-error: true
run: |
package_path="${GITHUB_REPOSITORY#*/}/appwrite-dev"
encoded_path="$(printf '%s' "$package_path" | jq -Rr @uri)"
gh api --paginate "/repos/${GITHUB_REPOSITORY}/pulls/${{ github.event.pull_request.number }}/commits" --jq '.[].sha' | while read -r sha; do
version_ids=$(gh api --paginate -H "Accept: application/vnd.github+json" \
"/orgs/${GITHUB_REPOSITORY_OWNER}/packages/container/${encoded_path}/versions" \
--jq ".[] | select(.metadata.container.tags | index(\"${sha}\")) | .id")
if [ -z "$version_ids" ]; then
echo "No GHCR version found for SHA ${sha}"
continue
fi
echo "$version_ids" | while read -r version_id; do
gh api --method DELETE -H "Accept: application/vnd.github+json" \
"/orgs/${GITHUB_REPOSITORY_OWNER}/packages/container/${encoded_path}/versions/${version_id}"
echo "Deleted ${package_path}:${sha} (version ${version_id})"
done
done
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+4 -4
View File
@@ -34,7 +34,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -47,14 +47,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -68,4 +68,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
+10 -6
View File
@@ -10,13 +10,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- name: Build the Docker image
run: DOCKER_BUILDKIT=1 docker build . --target production -t appwrite_image:latest
- name: Run Trivy vulnerability scanner on image
uses: aquasecurity/trivy-action@0.20.0
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: 'appwrite_image:latest'
format: 'sarif'
@@ -24,24 +24,28 @@ jobs:
ignore-unfixed: 'false'
severity: 'CRITICAL,HIGH'
- name: Upload Docker Image Scan Results
uses: github/codeql-action/upload-sarif@v2
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
if: always() && hashFiles('trivy-image-results.sarif') != ''
with:
sarif_file: 'trivy-image-results.sarif'
category: 'trivy-image'
scan-code:
name: Scan Code
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run Trivy vulnerability scanner on filesystem
uses: aquasecurity/trivy-action@0.20.0
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: 'fs'
format: 'sarif'
output: 'trivy-fs-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Code Scan Results
uses: github/codeql-action/upload-sarif@v2
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
if: always() && hashFiles('trivy-fs-results.sarif') != ''
with:
sarif_file: 'trivy-fs-results.sarif'
category: 'trivy-source'
+6 -6
View File
@@ -12,33 +12,33 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 2
submodules: recursive
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
images: appwrite/cloud
tags: |
type=ref,event=tag
- name: Build & Publish to DockerHub
uses: docker/build-push-action@v6
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
platforms: linux/amd64,linux/arm64
+6 -6
View File
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -20,20 +20,20 @@ jobs:
submodules: recursive
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
images: appwrite/appwrite
tags: |
@@ -42,7 +42,7 @@ jobs:
type=semver,pattern={{major}}
- name: Build and push
uses: docker/build-push-action@v6
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
platforms: linux/amd64,linux/arm64
+2 -2
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set SDK type
id: set-sdk
@@ -49,7 +49,7 @@ jobs:
docker compose exec appwrite sdks --platform=${{ steps.set-sdk.outputs.platform }} --sdk=${{ steps.set-sdk.outputs.sdk_type }} --version=latest --git=no
sudo chown -R $USER:$USER ./app/sdks/${{ steps.set-sdk.outputs.platform }}-${{ steps.set-sdk.outputs.sdk_type }}
- uses: actions/setup-node@v4
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "This issue has been labeled as a 'question', indicating that it requires additional information from the requestor. It has been inactive for 7 days. If no further activity occurs, this issue will be closed in 14 days."
+7
View File
@@ -21,3 +21,10 @@ appwrite.config.json
/app/config/specs/
/docs/examples/
.phpunit.cache
.phpstan-cache
playwright-report
test-results
docker-compose.web-installer.yml
.env.web-installer
docker-compose.web-installer.yml.**.backup
tests/playwright/screenshots
+105 -80
View File
@@ -1,107 +1,132 @@
# AGENTS.md
# Appwrite
Appwrite is an end-to-end backend server for web, mobile, native, and backend apps. This guide provides context and instructions for AI coding agents working on the Appwrite codebase.
Self-hosted Backend-as-a-Service platform. Hybrid monolithic-microservice architecture built with PHP 8.3+ on Swoole, delivered as Docker containers.
## Project Overview
## Commands
Appwrite is a self-hosted Backend-as-a-Service (BaaS) platform that provides developers with a set of APIs and tools to build secure, scalable applications. The project uses a hybrid monolithic-microservice architecture built with PHP, running on Swoole for high performance.
| Command | Purpose |
|---------|---------|
| `docker compose up -d --force-recreate --build` | Build and start all services |
| `docker compose exec appwrite test tests/e2e/Services/[Service]` | Run E2E tests for a service |
| `docker compose exec appwrite test tests/e2e/Services/[Service] --filter=[Method]` | Run a single test method |
| `docker compose exec appwrite test tests/unit/` | Run unit tests |
| `composer format` | Auto-format code (Pint, PSR-12) |
| `composer format <file>` | Format a specific file |
| `composer lint <file>` | Check formatting of a file |
| `composer analyze` | Static analysis (PHPStan level 3) |
| `composer check` | Same as `analyze` |
**Key Technologies:**
- **Backend:** PHP 8.3+, Swoole
- **Libraries:** Utopia PHP
- **Database:** MariaDB, Redis
- **Cache:** Redis
- **Queue:** Redis
- **Containers:** Docker
## Stack
## Development Commands
- PHP 8.3+, Swoole 6.x (async runtime, replaces PHP-FPM)
- Utopia PHP framework (HTTP routing, CLI, DI, queue)
- MongoDB (default), MariaDB, MySQL, PostgreSQL (adapters via utopia-php/database)
- Redis (cache, queue, pub/sub)
- Docker + Traefik (reverse proxy)
- PHPUnit 12, Pint (PSR-12), PHPStan level 3
```bash
# Run Appwrite
docker compose up -d --force-recreate --build
## Project layout
# Run specific test
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName] --filter=[FunctionName]
- **src/Appwrite/Platform/Modules/** -- feature modules (Account, Avatars, Compute, Console, Databases, Functions, Health, Project, Projects, Proxy, Sites, Storage, Teams, Tokens, VCS, Webhooks)
- **src/Appwrite/Platform/Workers/** -- background job workers
- **src/Appwrite/Platform/Tasks/** -- CLI tasks
- **app/init.php** -- bootstrap (registers services, resources, listeners)
- **app/init/** -- configs, constants, locales, models, registers, resources, span, database filters/formats
- **bin/** -- CLI entry points: `worker-*` (14 workers), `schedule-*`, `queue-*`, plus `doctor`, `install`, `migrate`, `realtime`, `upgrade`, `ssl`, `vars`, `maintenance`, `interval`, `specs`, `sdks`, etc.
- **tests/e2e/** -- end-to-end tests per service
- **tests/unit/** -- unit tests
- **public/** -- static assets and generated SDKs
# Format code
composer format
## Module structure
Each module under `src/Appwrite/Platform/Modules/{Name}/` contains:
```
Module.php -- registers all services for the module
Services/Http.php -- registers HTTP endpoints
Services/Workers.php -- registers background workers
Services/Tasks.php -- registers CLI tasks
Http/{Service}/ -- endpoint actions (Create.php, Get.php, Update.php, Delete.php, XList.php)
Workers/ -- worker implementations
Tasks/ -- CLI task implementations
```
## Code Style Guidelines
HTTP endpoint nesting reflects the URL path. Sub-resources get subdirectories. For example, within the Functions module:
`Http/Deployments/Template/Create.php` -> `POST /v1/functions/:functionId/deployments/template`
- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) coding standard
- Use PSR-4 autoloading
- Strict type declarations where applicable
- Comprehensive PHPDoc comments
File names in Http directories must only be `Get.php`, `Create.php`, `Update.php`, `Delete.php`, or `XList.php`. For non-CRUD operations, model the endpoint as a property update. For example, updating a team membership status lives at `Teams/Http/Memberships/Status/Update.php` (`PATCH /v1/teams/:teamId/memberships/:membershipId/status`).
### Naming Conventions
Register new modules in `src/Appwrite/Platform/Appwrite.php`. Detailed module guide: `src/Appwrite/Platform/AGENTS.md`.
#### `resourceType` Naming Rule
## Action pattern (HTTP endpoints)
When a collection has a combination of `resourceType`, `resourceId`, and/or `resourceInternalId`, the value of `resourceType` MUST always be **plural** - for example: `functions`, `sites`, `deployments`.
Examples:
```php
'resourceType' => 'functions'
'resourceType' => 'sites'
'resourceType' => 'deployments'
class Create extends Action
{
public static function getName(): string { return 'createTeam'; }
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/teams')
->desc('Create team')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].create')
->label('scope', 'teams.write')
->param('teamId', '', new CustomId(), 'Team ID.')
->param('name', null, new Text(128), 'Team name.')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback($this->action(...));
}
public function action(
string $teamId,
string $name,
Response $response,
Database $dbForProject,
Event $queueForEvents,
): void {
// implementation
}
}
```
## Performance Patterns
Common injections: `$response`, `$request`, `$dbForProject`, `$dbForPlatform`, `$user`, `$project`, `$queueForEvents`, `$queueForMails`, `$queueForDeletes`.
### Document Update Optimization
## Conventions
When updating documents, always pass only the changed attributes as a sparse `Document` rather than the full document. This is more efficient because `updateDocument()` internally performs `array_merge($old, $new)`.
- PSR-12 formatting enforced by Pint. PSR-4 autoloading.
- `resourceType` values are always **plural**: `'functions'`, `'sites'`, `'deployments'`.
- When updating documents, pass only changed attributes as a sparse Document:
```php
// correct
$dbForProject->updateDocument('users', $user->getId(), new Document([
'name' => $name,
]));
// incorrect -- passing full document is inefficient
$user->setAttribute('name', $name);
$dbForProject->updateDocument('users', $user->getId(), $user);
```
Exceptions: migrations, `array_merge()` with `getArrayCopy()`, updates where nearly all attributes change, complex nested relationship logic requiring full document state.
- Avoid introducing dependencies outside the `utopia-php` ecosystem.
- Never hardcode credentials -- use environment variables.
- Code changes may require container restart. No central log location -- check relevant containers.
**Correct Pattern:**
```php
// Good: Pass only changed attributes directly
$user = $dbForProject->updateDocument('users', $user->getId(), new Document([
'name' => $name,
'email' => $email,
]));
```
## Tracing with Utopia Span
**Incorrect Pattern:**
```php
$user->setAttribute('name', $name);
$user->setAttribute('email', $email);
In handlers, only call `Span::add($key, $value)`. **Never** call `Span::init`, `Span::error`, or `Span::finish` -- lifecycle is owned by the entry-point harness (`app/http.php`, `app/worker.php`, `app/realtime.php`, `Bus::dispatch`). For selective export, filter in the sampler in `app/init/span.php`.
// Bad: Passing full document is inefficient
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
```
Keys are `snake_case` with dots only for child relationships: `project.id` (id of project), `storage.bucket.id`. No dot otherwise: `inbound_bytes`, not `inbound.bytes`. No camelCase, no bare top-level keys (`function.id`, not `functionId`).
**Exceptions:**
- Migration files (need full document updates by design)
- Cases already using `array_merge()` with `getArrayCopy()`
- Updates where almost all attributes of the document change at once (sparse update provides little benefit compared to passing the full document)
- Complex nested relationship logic where full document state is required
Cross-cutting identifiers (`project.id`, `function.id`, `user.id`) live at the top level, not under a subsystem (no `realtime.project.id`). The trace sampler and downstream filters look them up by the canonical key.
## Security Considerations
## Patch release process
### Critical Security Practices
For bumping patch versions (e.g., `1.9.0` -> `1.9.1`), follow the checklist in `.claude/skills/patch-release-checklist/SKILL.md`. It covers the 4 files that must be updated, console image bumps, CHANGES.md updates, and common pitfalls to avoid.
- **Never hardcode credentials** - Use environment variables
- **Rate limiting** - Respect abuse prevention mechanisms
## Cross-repo context
## Dependencies
Avoid introducing new dependencies other than utopia-php.
## Adding new endpoints
When adding new endpoints, make sure to use modules and follow its patterns. Find instruction in [Modules AGENTS.md](src/Appwrite/Platform/AGENTS.md) file.
## Pull Request Guidelines
### Before Submitting
- Run `composer format`
- Update documentation if adding features
- Add/update tests for your changes
- Check that Docker build succeeds
`docs/specs/authentication.drawio.svg`
## Known Issues and Gotchas
- **Hot Reload:** Code changes require container restart in some cases
- **Logging:** There is no central place for logs, so when debugging, ensure to check all possibly relevant containers
Appwrite is the base server for `appwrite/cloud`. Changes to the Action pattern, module structure, DI system, or response models affect cloud. The `feat-dedicated-db` feature spans cloud, edge, and console.
+120 -87
View File
@@ -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
@@ -859,7 +892,7 @@
* Unset index length by @fogelito in https://github.com/appwrite/appwrite/pull/8978
* Update base to 0.9.5 by @basert in https://github.com/appwrite/appwrite/pull/9005
* Sync main into 1.6.x by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/9011
* Improved shared tables V2 by @abnegate in https://github.com/appwrite/appwrite/pull/9013
* Improved shared tables by @abnegate in https://github.com/appwrite/appwrite/pull/9013
* Ensure backwards compatibility for 1.6.x by @christyjacob4 in https://github.com/appwrite/appwrite/pull/9018
# Version 1.6.0
+8 -2
View File
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
FROM appwrite/base:1.0.0 AS base
FROM appwrite/base:1.4.1 AS base
LABEL maintainer="team@appwrite.io"
@@ -24,6 +24,10 @@ ENV _APP_VERSION=$VERSION \
_APP_HOME=https://appwrite.io
RUN \
if [ "$DEBUG" != "true" ]; then \
rm -f /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
rm -f /usr/local/lib/php/extensions/no-debug-non-zts-*/xdebug.so; \
fi && \
if [ "$DEBUG" == "true" ]; then \
apk add boost boost-dev; \
fi
@@ -100,7 +104,8 @@ RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
FROM base AS production
RUN rm -rf /usr/src/code/app/config/specs && \
rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20240924/xdebug.so && \
rm -f /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini && \
rm -f /usr/local/lib/php/extensions/no-debug-non-zts-*/xdebug.so && \
find /usr -name '*.a' -delete 2>/dev/null || true && \
find /usr -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true && \
find /usr -name '*.pyc' -delete 2>/dev/null || true
@@ -121,5 +126,6 @@ RUN if [ "$DEBUG" = "true" ]; then \
fi
EXPOSE 80
EXPOSE 8080
CMD [ "php", "app/http.php" ]
+3 -3
View File
@@ -72,7 +72,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.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 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
+46 -66
View File
@@ -1,43 +1,28 @@
> We just announced DB operators for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-db-operators)
> Appwrite Cloud is now Generally Available - [Learn more](https://appwrite.io/cloud-ga)
> [Get started with Appwrite](https://apwr.dev/appcloud)
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/55a81268-4ecc-46cd-bdf5-73f7e8662fee" />
<br />
<p align="center">
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite banner, with logo and text saying "The Developer's Cloud"></a>
<br />
<br />
<b>Appwrite is a best-in-class, developer-first platform that gives builders everything they need to create scalable, stable, and production-ready software, fast.</b>
<h1>Appwrite</h1>
<b>Appwrite is an open-source, all-in-one development platform. Use built-in backend infrastructure and web hosting, all from a single place.</b>
<br />
<br />
</p>
<!-- [![Build Status](https://img.shields.io/travis/com/appwrite/appwrite?style=flat-square)](https://travis-ci.com/appwrite/appwrite) -->
[![We're Hiring label](https://img.shields.io/static/v1?label=We're&message=Hiring&color=blue&style=flat-square)](https://appwrite.io/company/careers)
[![Hacktoberfest label](https://img.shields.io/static/v1?label=hacktoberfest&message=ready&color=191120&style=flat-square)](https://hacktoberfest.appwrite.io)
[![Discord label](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord?r=Github)
[![Build Status label](https://img.shields.io/github/actions/workflow/status/appwrite/appwrite/tests.yml?branch=master&label=tests&style=flat-square)](https://github.com/appwrite/appwrite/actions)
[![X Account label](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
<!-- [![Docker Pulls](https://img.shields.io/docker/pulls/appwrite/appwrite?color=f02e65&style=flat-square)](https://hub.docker.com/r/appwrite/appwrite) -->
<!-- [![Translate](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md) -->
<!-- [![Swag Store](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io) -->
[![Discord](https://img.shields.io/badge/chat-5865F2?style=flat-square&logo=discord&logoColor=white)](https://appwrite.io/discord)
[![X](https://img.shields.io/badge/follow-000000?style=flat-square&logo=x&logoColor=white)](https://x.com/appwrite)
[![Appwrite Cloud](https://img.shields.io/badge/Cloud-F02E65?style=flat-square&logo=icloud&logoColor=white)](https://cloud.appwrite.io)
English | [简体中文](README-CN.md)
Appwrite is an end-to-end platform for building Web, Mobile, Native, or Backend apps, packaged as a set of Docker microservices. It includes both a backend server and a fully integrated hosting solution for deploying static and server-side rendered frontends. Appwrite abstracts the complexity and repetitiveness required to build modern apps from scratch and allows you to build secure, full-stack applications faster.
Appwrite is an open-source development platform for building web, mobile, and AI applications. It brings together backend infrastructure and web hosting in one place, so teams can build, ship, and scale without stitching together a fragmented stack. Appwrite is available as a managed cloud platform and can also be self-hosted on infrastructure you control.
Using Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, messaging, and [more services](https://appwrite.io/docs).
With Appwrite, you can add authentication, databases, storage, functions, messaging, realtime capabilities, and integrated web app hosting through Sites. It is designed to reduce the repetitive backend work required to launch modern products while giving developers secure primitives and flexible APIs to build production-ready applications faster.
![Appwrite project dashboard showing various Appwrite features](public/images/github.png)
Find out more at: [https://appwrite.io](https://appwrite.io).
Find out more at [https://appwrite.io](https://appwrite.io).
Table of Contents:
- [Products](#products)
- [Installation \& Setup](#installation--setup)
- [Self-Hosting](#self-hosting)
- [Unix](#unix)
@@ -47,17 +32,31 @@ Table of Contents:
- [Upgrade from an Older Version](#upgrade-from-an-older-version)
- [One-Click Setups](#one-click-setups)
- [Getting Started](#getting-started)
- [Products](#products)
- [SDKs](#sdks)
- [Client](#client)
- [Server](#server)
- [Community](#community)
- [Architecture](#architecture)
- [Contributing](#contributing)
- [Security](#security)
- [Follow Us](#follow-us)
- [License](#license)
## Products
- **[Appwrite Auth](https://appwrite.io/docs/products/auth)** - Secure user authentication with multiple login methods including email/password, SMS, OAuth, anonymous sessions, and magic links. Includes session management, multi-factor authentication, and user verification flows.
- **[Appwrite Databases](https://appwrite.io/docs/products/databases)** - Scalable structured data storage with support for databases, tables, and rows. Includes querying, pagination, indexing, and relationships to model complex application data.
- **[Appwrite Storage](https://appwrite.io/docs/products/storage)** - Secure file storage with support for uploads, downloads, encryption, compression, and file transformations for media and assets.
- **[Appwrite Functions](https://appwrite.io/docs/products/functions)** - Serverless compute platform to run custom backend logic in isolated runtimes, triggered by events or scheduled jobs.15 runtimes supported.
- **[Appwrite Messaging](https://appwrite.io/docs/products/messaging)** - Multi-channel messaging system for sending emails, SMS, and push notifications to users for engagement, alerts, and transactional workflows.
- **[Appwrite Sites](https://appwrite.io/docs/products/sites)** - Integrated hosting platform to deploy and scale web applications with support for custom domains, SSR, and seamless backend integration. Git integration and previews are supported.
## Installation & Setup
The easiest way to get started with Appwrite is by [signing up for Appwrite Cloud](https://cloud.appwrite.io/). While Appwrite Cloud is in public beta, you can build with Appwrite completely free, and we won't collect your credit card information.
@@ -72,10 +71,11 @@ Before running the installation command, make sure you have [Docker](https://www
```bash
docker run -it --rm \
--publish 20080:20080 \
--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,20 +84,22 @@ docker run -it --rm \
```cmd
docker run -it --rm ^
--publish 20080:20080 ^
--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
```powershell
docker run -it --rm `
--publish 20080:20080 `
--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.
@@ -165,51 +167,29 @@ Getting started with Appwrite is as easy as creating a new project, choosing you
| | [Quick start for Kotlin](https://appwrite.io/docs/quick-starts/kotlin) |
| | [Quick start for Swift](https://appwrite.io/docs/quick-starts/swift) |
### Products
- [**Account**](https://appwrite.io/docs/references/cloud/client-web/account) - Manage current user authentication and account. Track and manage the user sessions, devices, sign-in methods, and security logs.
- [**Users**](https://appwrite.io/docs/server/users) - Manage and list all project users when building backend integrations with Server SDKs.
- [**Teams**](https://appwrite.io/docs/references/cloud/client-web/teams) - Manage and group users in teams. Manage memberships, invites, and user roles within a team.
- [**Databases**](https://appwrite.io/docs/references/cloud/client-web/databases) - Manage databases, collections, and documents. Read, create, update, and delete documents and filter lists of document collections using advanced filters.
- [**Storage**](https://appwrite.io/docs/references/cloud/client-web/storage) - Manage storage files. Read, create, delete, and preview files. Manipulate the preview of your files to perfectly fit your app. All files are scanned by ClamAV and stored in a secure and encrypted way.
- [**Functions**](https://appwrite.io/docs/references/cloud/server-nodejs/functions) - Customize your Appwrite project by executing your custom code in a secure, isolated environment. You can trigger your code on any Appwrite system event either manually or using a CRON schedule.
- [**Messaging**](https://appwrite.io/docs/references/cloud/client-web/messaging) - Communicate with your users through push notifications, emails, and SMS text messages using Appwrite Messaging.
- [**Realtime**](https://appwrite.io/docs/realtime) - Listen to real-time events for any of your Appwrite services including users, storage, functions, databases, and more.
- [**Locale**](https://appwrite.io/docs/references/cloud/client-web/locale) - Track your user's location and manage your app locale-based data.
- [**Avatars**](https://appwrite.io/docs/references/cloud/client-web/avatars) - Manage your users' avatars, countries' flags, browser icons, and credit card symbols. Generate QR codes from links or plaintext strings.
- [**MCP**](https://appwrite.io/docs/tooling/mcp) - Use Appwrite's Model Context Protocol (MCP) server to allow LLMs and AI tools like Claude Desktop, Cursor, and Windsurf Editor to directly interact with your Appwrite project through natural language.
- [**Sites**](https://appwrite.io/docs/products/sites) - Develop, deploy, and scale your web applications directly from Appwrite, alongside your backend.
For the complete API documentation, visit [https://appwrite.io/docs](https://appwrite.io/docs). For more tutorials, news and announcements check out our [blog](https://medium.com/appwrite-io) and [Discord Server](https://discord.gg/GSeTUeA).
### SDKs
Below is a list of currently supported platforms and languages. If you would like to help us add support to your platform of choice, you can go over to our [SDK Generator](https://github.com/appwrite/sdk-generator) project and view our [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md).
#### Client
- :white_check_mark: &nbsp; [Web](https://github.com/appwrite/sdk-for-web) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Flutter](https://github.com/appwrite/sdk-for-flutter) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Apple](https://github.com/appwrite/sdk-for-apple) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Android](https://github.com/appwrite/sdk-for-android) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [React Native](https://github.com/appwrite/sdk-for-react-native) - **Beta** (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Web](https://github.com/appwrite/sdk-for-web)
- :white_check_mark: &nbsp; [Flutter](https://github.com/appwrite/sdk-for-flutter)
- :white_check_mark: &nbsp; [Apple](https://github.com/appwrite/sdk-for-apple)
- :white_check_mark: &nbsp; [Android](https://github.com/appwrite/sdk-for-android)
- :white_check_mark: &nbsp; [React Native](https://github.com/appwrite/sdk-for-react-native)
#### Server
- :white_check_mark: &nbsp; [NodeJS](https://github.com/appwrite/sdk-for-node) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [PHP](https://github.com/appwrite/sdk-for-php) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Dart](https://github.com/appwrite/sdk-for-dart) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Deno](https://github.com/appwrite/sdk-for-deno) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Ruby](https://github.com/appwrite/sdk-for-ruby) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Python](https://github.com/appwrite/sdk-for-python) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Kotlin](https://github.com/appwrite/sdk-for-kotlin) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [Swift](https://github.com/appwrite/sdk-for-swift) (Maintained by the Appwrite Team)
- :white_check_mark: &nbsp; [.NET](https://github.com/appwrite/sdk-for-dotnet) - **Beta** (Maintained by the Appwrite Team)
#### Community
- :white_check_mark: &nbsp; [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (Maintained by [Michael Gangolf](https://github.com/m1ga/))
- :white_check_mark: &nbsp; [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (Maintained by [fenix-hub @GodotNuts](https://github.com/fenix-hub))
- :white_check_mark: &nbsp; [NodeJS](https://github.com/appwrite/sdk-for-node)
- :white_check_mark: &nbsp; [PHP](https://github.com/appwrite/sdk-for-php)
- :white_check_mark: &nbsp; [Dart](https://github.com/appwrite/sdk-for-dart)
- :white_check_mark: &nbsp; [Deno](https://github.com/appwrite/sdk-for-deno)
- :white_check_mark: &nbsp; [Ruby](https://github.com/appwrite/sdk-for-ruby)
- :white_check_mark: &nbsp; [Python](https://github.com/appwrite/sdk-for-python)
- :white_check_mark: &nbsp; [Kotlin](https://github.com/appwrite/sdk-for-kotlin)
- :white_check_mark: &nbsp; [Swift](https://github.com/appwrite/sdk-for-swift)
- :white_check_mark: &nbsp; [.NET](https://github.com/appwrite/sdk-for-dotnet)
Looking for more SDKs? - Help us by contributing a pull request to our [SDK Generator](https://github.com/appwrite/sdk-generator)!
+51 -82
View File
@@ -2,39 +2,29 @@
require_once __DIR__ . '/init.php';
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Publisher\Usage as UsagePublisher;
use Appwrite\Event\StatsResources;
use Appwrite\Platform\Appwrite;
use Appwrite\Runtimes\Runtimes;
use Appwrite\Usage\Context as UsageContext;
use Appwrite\Utopia\Database\Documents\User;
use Executor\Executor;
use Swoole\Runtime;
use Swoole\Timer;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\Adapters\Generic;
use Utopia\CLI\CLI;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\DI\Dependency;
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Platform\Service;
use Utopia\Pools\Group;
use Utopia\Queue\Broker\Pool as BrokerPool;
use Utopia\Queue\Publisher;
use Utopia\Queue\Queue;
use Utopia\Registry\Registry;
use Utopia\System\System;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
use function Swoole\Coroutine\run;
@@ -45,9 +35,10 @@ Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
require_once __DIR__ . '/controllers/general.php';
global $register;
global $container;
$platform = new Appwrite();
$args = $platform->getEnv('argv');
$args = $_SERVER['argv'] ?? [];
\array_shift($args);
if (! isset($args[0])) {
@@ -56,21 +47,14 @@ if (! isset($args[0])) {
}
$taskName = $args[0];
$cli = new CLI(new Generic(), $_SERVER['argv'] ?? [], $container);
$platform->setCli($cli);
$platform->init(Service::TYPE_TASK);
$cli = $platform->getCli();
$setResource = function (string $name, callable $callback, array $injections = []) use ($cli) {
$dependency = new Dependency();
$dependency->setName($name)->setCallback($callback);
foreach ($injections as $injection) {
$dependency->inject($injection);
}
$cli->setResource($dependency);
};
$container->set('register', fn () => $register, []);
$setResource('register', fn () => $register, []);
$setResource('cache', function ($pools) {
$container->set('cache', function ($pools) {
$list = Config::getParam('pools-cache', []);
$adapters = [];
@@ -81,18 +65,18 @@ $setResource('cache', function ($pools) {
return new Cache(new Sharding($adapters));
}, ['pools']);
$setResource('pools', function (Registry $register) {
$container->set('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
$setResource('authorization', function () {
$container->set('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
$setResource('dbForPlatform', function ($pools, $cache, $authorization) {
$container->set('dbForPlatform', function ($pools, $cache, $authorization) {
$sleep = 3;
$maxAttempts = 5;
$attempts = 0;
@@ -135,17 +119,13 @@ $setResource('dbForPlatform', function ($pools, $cache, $authorization) {
return $dbForPlatform;
}, ['pools', 'cache', 'authorization']);
$setResource('console', function () {
return new Document(Config::getParam('console'));
}, []);
$setResource(
$container->set(
'isResourceBlocked',
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false,
[]
);
$setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
$container->set('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
@@ -161,13 +141,20 @@ $setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $c
}
if (isset($databases[$dsn->getHost()])) {
/** @var array $collections */
$collections = Config::getParam('collections', []);
$projectCollections = $collections['projects'] ?? [];
$projectsGlobalCollections = array_keys($projectCollections);
$projectsGlobalCollections[] = 'audit';
$database = $databases[$dsn->getHost()];
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant((int) $project->getSequence())
->setGlobalCollections($projectsGlobalCollections)
->setTenant($project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
@@ -186,9 +173,16 @@ $setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $c
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
/** @var array $collections */
$collections = Config::getParam('collections', []);
$projectCollections = $collections['projects'] ?? [];
$projectsGlobalCollections = array_keys($projectCollections);
$projectsGlobalCollections[] = 'audit';
$database
->setSharedTables(true)
->setTenant((int) $project->getSequence())
->setTenant($project->getSequence())
->setGlobalCollections($projectsGlobalCollections)
->setNamespace($dsn->getParam('namespace'));
} else {
$database
@@ -207,16 +201,20 @@ $setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $c
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
$setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
if ($database !== null && $project !== null && ! $project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int) $project->getSequence());
return function (?Document $project = null) use ($pools, $cache, &$database, $authorization) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
return $database;
}
/** @var array $collections */
$collections = Config::getParam('collections', []);
$logsCollections = $collections['logs'] ?? [];
$logsCollections = array_keys($logsCollections);
$adapter = new DatabasePool($pools->get('logs'));
$database = new Database($adapter, $cache);
@@ -225,52 +223,23 @@ $setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $a
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
->setGlobalCollections($logsCollections)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
// set tenant
if ($project !== null && ! $project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int) $project->getSequence());
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
}
return $database;
};
}, ['pools', 'cache', 'authorization']);
$setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
$setResource('publisherDatabases', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
$setResource('publisherFunctions', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
$setResource('publisherMigrations', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
$setResource('publisherMessaging', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
$setResource('usage', function () {
$container->set('usage', function () {
return new UsageContext();
}, []);
$setResource('publisherForUsage', fn (Publisher $publisher) => new UsagePublisher(
$publisher,
new Queue(System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME))
), ['publisher']);
$setResource('queueForStatsResources', function (Publisher $publisher) {
return new StatsResources($publisher);
}, ['publisher']);
$setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
$setResource('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
$setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
$setResource('logError', function (Registry $register) {
$container->set('logError', function (Registry $register) {
return function (Throwable $error, string $namespace, string $action) use ($register) {
Console::error('[Error] Timestamp: ' . date('c', time()));
Console::error('[Error] Type: ' . get_class($error));
@@ -322,25 +291,24 @@ $setResource('logError', function (Registry $register) {
};
}, ['register']);
$setResource('executor', fn () => new Executor(), []);
$setResource('bus', function (Registry $register) use ($cli) {
return $register->get('bus')->setResolver(fn (string $name) => $cli->getResource($name));
$container->set('bus', function (Registry $register) use ($container) {
return $register->get('bus')->setResolver(fn (string $name) => $container->get($name));
}, ['register']);
$setResource('telemetry', fn () => new NoTelemetry(), []);
$exitCode = 0;
$cli
->error()
->inject('error')
->inject('logError')
->action(function (Throwable $error, callable $logError) use ($taskName) {
->action(function (Throwable $error, callable $logError) use ($taskName, &$exitCode) {
call_user_func_array($logError, [
$error,
'Task',
$taskName,
]);
$exitCode = 1;
Timer::clearAll();
});
@@ -349,3 +317,4 @@ $cli->shutdown()->action(fn () => Timer::clearAll());
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
require_once __DIR__ . '/init/span.php';
run($cli->run(...));
Console::exit($exitCode);
+2
View File
@@ -4,6 +4,7 @@
$common = include __DIR__ . '/collections/common.php';
$projects = include __DIR__ . '/collections/projects.php';
$databases = include __DIR__ . '/collections/databases.php';
$vectorsdb = include __DIR__ . '/collections/vectorsdb.php';
$platform = include __DIR__ . '/collections/platform.php';
$logs = include __DIR__ . '/collections/logs.php';
@@ -26,6 +27,7 @@ unset($common['files']);
$collections = [
'buckets' => $buckets,
'databases' => $databases,
'vectorsdb' => $vectorsdb,
'projects' => array_merge_recursive($projects, $common),
'console' => array_merge_recursive($platform, $common),
'logs' => $logs,
+25
View File
@@ -419,6 +419,17 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('impersonator'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => false,
'default' => false,
'array' => false,
],
],
'indexes' => [
[
@@ -491,6 +502,13 @@ return [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('impersonator'),
'type' => Database::INDEX_KEY,
'attributes' => [ID::custom('impersonator')],
'lengths' => [],
'orders' => [],
],
],
],
@@ -1505,6 +1523,13 @@ return [
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_team_confirm'),
'type' => Database::INDEX_KEY,
'attributes' => ['teamInternalId', 'confirm'],
'lengths' => [],
'orders' => [],
],
],
],
+459 -4
View File
@@ -404,6 +404,13 @@ $platformCollections = [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_teamInternalId'),
'type' => Database::INDEX_KEY,
'attributes' => ['teamInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
],
],
@@ -594,7 +601,7 @@ $platformCollections = [
'filters' => [],
],
[
'$id' => ID::custom('key'),
'$id' => ID::custom('key'), // For app platforms
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -605,7 +612,7 @@ $platformCollections = [
'filters' => [],
],
[
'$id' => ID::custom('store'),
'$id' => ID::custom('store'), // Unused at the moment
'type' => Database::VAR_STRING,
'format' => '',
'size' => 256,
@@ -616,7 +623,7 @@ $platformCollections = [
'filters' => [],
],
[
'$id' => ID::custom('hostname'),
'$id' => ID::custom('hostname'), // For web platforms
'type' => Database::VAR_STRING,
'format' => '',
'size' => 256,
@@ -635,6 +642,13 @@ $platformCollections = [
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_project_id'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
],
],
@@ -1007,7 +1021,14 @@ $platformCollections = [
'attributes' => ['projectInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
]
],
[
'$id' => ID::custom('_key_project_id'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
],
],
@@ -1935,6 +1956,440 @@ $platformCollections = [
'attributes' => [],
'indexes' => []
],
'reports' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('reports'),
'name' => 'Reports',
'attributes' => [
[
'$id' => ID::custom('projectInternalId'),
'type' => Database::VAR_ID,
'format' => '',
'size' => 0,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('projectId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('appInternalId'),
'type' => Database::VAR_ID,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('appId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('type'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('title'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 256,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('summary'),
'type' => Database::VAR_TEXT,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
// Resource type the report is about. Plural noun, e.g. databases, sites, urls.
'$id' => ID::custom('targetType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
// Free-form target identifier (URL for lighthouse, resource ID for db).
// Indexed by `_key_project_target` with an explicit prefix length.
'$id' => ID::custom('target'),
'type' => Database::VAR_TEXT,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
// Category strings, e.g. 'performance', 'accessibility'. Native array
// column — we never query on individual entries (MySQL JSON-array
// indexes are weak), this is read+rewrite only.
'$id' => ID::custom('categories'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
[
// Virtual attribute — insights live in the `insights` collection
// back-referenced by `reportInternalId`. The subQuery filter joins
// them at read time.
'$id' => ID::custom('insights'),
'type' => Database::VAR_TEXT,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['subQueryReportInsights'],
],
[
'$id' => ID::custom('analyzedAt'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_project_app_type'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'appInternalId', 'type'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_project_target'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'appInternalId', 'targetType', 'target'],
'lengths' => [null, null, null, 700],
'orders' => [],
],
],
],
'insights' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('insights'),
'name' => 'Insights',
'attributes' => [
[
'$id' => ID::custom('projectInternalId'),
'type' => Database::VAR_ID,
'format' => '',
'size' => 0,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('projectId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('reportInternalId'),
'type' => Database::VAR_ID,
'format' => '',
'size' => 0,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('reportId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('type'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('severity'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('status'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16,
'signed' => true,
'required' => true,
'default' => 'active',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceInternalId'),
'type' => Database::VAR_ID,
'format' => '',
'size' => 0,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('parentResourceType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('parentResourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('parentResourceInternalId'),
'type' => Database::VAR_ID,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('title'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 256,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('summary'),
'type' => Database::VAR_TEXT,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('ctas'),
'type' => Database::VAR_TEXT,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['json'],
],
[
'$id' => ID::custom('analyzedAt'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('dismissedAt'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('dismissedBy'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_project_report'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'reportInternalId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_project_resource'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'resourceType', 'resourceId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_project_parent_resource'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'parentResourceType', 'parentResourceId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_project_type'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'type'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_project_severity'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'severity'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_project_status'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'status'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_project_dismissedAt'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'dismissedAt'],
'lengths' => [],
'orders' => [Database::ORDER_ASC, Database::ORDER_DESC],
],
],
],
];
// Organization API keys subquery
+195
View File
@@ -61,6 +61,15 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('database'),
'type' => Database::VAR_STRING,
'size' => 2000,
'required' => false,
'signed' => true,
'array' => false,
'filters' => [],
]
],
'indexes' => [
[
@@ -832,6 +841,28 @@ return [
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('providerBranches'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('providerPaths'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
@@ -1311,6 +1342,28 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('providerBranches'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('providerPaths'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
@@ -2745,4 +2798,146 @@ return [
],
],
],
// Naming it presenceLogs as later it might be only be used as a presence events table only and not for the actual presence
'presenceLogs' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('presenceLogs'),
'name' => 'Presence Logs',
'attributes' => [
[
'$id' => ID::custom('userInternalId'),
'type' => Database::VAR_ID,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('userId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('expiresAt'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('status'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('source'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('hostname'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('metadata'),
'type' => Database::VAR_TEXT,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => false,
'default' => new \stdClass(),
'array' => false,
'filters' => ['json'],
],
[
'$id' => ID::custom('permissionsHash'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 32,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => ID::custom('_unique_userId'),
'type' => Database::INDEX_UNIQUE,
'attributes' => ['userId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC]
],
[
'$id' => ID::custom('_key_userInternal'),
'type' => Database::INDEX_KEY,
'attributes' => ['userInternalId'],
'orders' => [Database::ORDER_ASC]
],
[
'$id' => ID::custom('_key_expiresAt'),
'type' => Database::INDEX_KEY,
'attributes' => ['expiresAt'],
'lengths' => [],
'orders' => [Database::ORDER_ASC]
],
[
'$id' => ID::custom('_key_status'),
'type' => Database::INDEX_KEY,
'attributes' => ['status'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC]
],
[
'$id' => ID::custom('_key_source'),
'type' => Database::INDEX_KEY,
'attributes' => ['source'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC]
],
[
'$id' => ID::custom('_key_source_status'),
'type' => Database::INDEX_KEY,
'attributes' => ['source', 'status']
],
[
'$id' => ID::custom('_key_permissionsHash'),
'type' => Database::INDEX_KEY,
'attributes' => ['permissionsHash']
]
]
]
];
+165
View File
@@ -0,0 +1,165 @@
<?php
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
return [
'collections' => [
'$collection' => ID::custom('databases'),
'$id' => ID::custom('collections'),
'name' => 'Collections',
'attributes' => [
[
'$id' => ID::custom('databaseInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('databaseId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => ID::custom('name'),
'type' => Database::VAR_STRING,
'size' => 256,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('dimension'),
'type' => Database::VAR_INTEGER,
'size' => 0,
'required' => true,
'signed' => false,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('enabled'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => ID::custom('documentSecurity'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => ID::custom('attributes'),
'type' => Database::VAR_STRING,
'size' => 1000000,
'required' => false,
'signed' => true,
'array' => false,
'filters' => ['subQueryAttributes'],
],
[
'$id' => ID::custom('indexes'),
'type' => Database::VAR_STRING,
'size' => 1000000,
'required' => false,
'signed' => true,
'array' => false,
'filters' => ['subQueryIndexes'],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'defaultAttributes' => [
[
'$id' => ID::custom('embeddings'),
'type' => Database::VAR_VECTOR,
'required' => true,
'signed' => false,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('metadata'),
'type' => Database::VAR_OBJECT,
'default' => [],
'required' => false,
'size' => 0,
'signed' => false,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => ID::custom('_fulltext_search'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
'lengths' => [256],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_enabled'),
'type' => Database::INDEX_KEY,
'attributes' => ['enabled'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_documentSecurity'),
'type' => Database::INDEX_KEY,
'attributes' => ['documentSecurity'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
],
'defaultIndexes' => [
// not creating default indexes on the embeddings as it depends on the type of query users using the most
[
'$id' => ID::custom('_key_metadata'),
'type' => Database::INDEX_OBJECT,
'attributes' => ['metadata'],
'lengths' => [],
'orders' => [],
],
]
]
];
+9
View File
@@ -34,11 +34,20 @@ $console = [
'legalAddress' => '',
'legalTaxId' => '',
'auths' => [
'membershipsUserName' => true,
'membershipsUserEmail' => true,
'membershipsMfa' => true,
'membershipsUserId' => true,
'membershipsUserPhone' => true,
'mockNumbers' => [],
'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
'duration' => TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled',
// For email configuration, false means feature is disabled; false means these emails are allowed during sign-ups
'disposableEmails' => false,
'canonicalEmails' => false,
'freeEmails' => false,
'invalidateSessions' => true
],
'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
+4
View File
@@ -22,12 +22,16 @@ return [
'X-Appwrite-Locale',
'X-Appwrite-Mode',
'X-Appwrite-JWT',
'X-Appwrite-Organization',
'X-Appwrite-Response-Format',
'X-Appwrite-Timeout',
'X-Appwrite-ID',
'X-Appwrite-Timestamp',
'X-Appwrite-Session',
'X-Appwrite-Platform',
'X-Appwrite-Impersonate-User-Id',
'X-Appwrite-Impersonate-User-Email',
'X-Appwrite-Impersonate-User-Phone',
// SDK generator
'X-SDK-Version',
'X-SDK-Name',
+117 -1
View File
@@ -226,6 +226,21 @@ return [
'description' => 'A user with the same email already exists in the current project.',
'code' => 409,
],
Exception::USER_EMAIL_DISPOSABLE => [
'name' => Exception::USER_EMAIL_DISPOSABLE,
'description' => 'Disposable email addresses are not allowed. Please use a permanent email address.',
'code' => 400,
],
Exception::USER_EMAIL_FREE => [
'name' => Exception::USER_EMAIL_FREE,
'description' => 'Free email addresses are not allowed. Please use a business or custom-domain email address.',
'code' => 400,
],
Exception::USER_EMAIL_NOT_CANONICAL => [
'name' => Exception::USER_EMAIL_NOT_CANONICAL,
'description' => 'This email address must already be in its canonical form. Please remove aliases, tags, or provider-specific variations and try again.',
'code' => 400,
],
Exception::USER_PASSWORD_MISMATCH => [
'name' => Exception::USER_PASSWORD_MISMATCH,
'description' => 'Passwords do not match. Please check the password and confirm password.',
@@ -369,7 +384,7 @@ return [
],
Exception::API_KEY_EXPIRED => [
'name' => Exception::API_KEY_EXPIRED,
'description' => 'The dynamic API key has expired. Please don\'t use dynamic API keys for more than duration of the execution.',
'description' => 'The ephemeral API key has expired. Please don\'t use ephemeral API keys for more than duration of the execution.',
'code' => 401,
],
@@ -608,6 +623,11 @@ return [
'description' => 'Synchronous function execution timed out. Use asynchronous execution instead, or ensure the execution duration doesn\'t exceed 30 seconds.',
'code' => 408,
],
Exception::FUNCTION_ASYNCHRONOUS_TIMEOUT => [
'name' => Exception::FUNCTION_ASYNCHRONOUS_TIMEOUT,
'description' => 'Asynchronous function execution timed out. Ensure the execution duration doesn\'t exceed the configured function timeout.',
'code' => 408,
],
Exception::FUNCTION_TEMPLATE_NOT_FOUND => [
'name' => Exception::FUNCTION_TEMPLATE_NOT_FOUND,
'description' => 'Function Template with the requested ID could not be found.',
@@ -672,6 +692,11 @@ return [
'description' => 'Build with the requested ID failed. Please check the logs for more information.',
'code' => 400,
],
Exception::BUILD_TIMEOUT => [
'name' => Exception::BUILD_TIMEOUT,
'description' => 'Build timed out. Increase the build timeout via the `_APP_COMPUTE_BUILD_TIMEOUT` environment variable, or simplify the build to complete within the limit.',
'code' => 408,
],
/** Deployments */
Exception::DEPLOYMENT_NOT_FOUND => [
@@ -700,6 +725,18 @@ return [
'code' => 404,
],
/** Presence */
Exception::PRESENCE_NOT_FOUND => [
'name' => Exception::PRESENCE_NOT_FOUND,
'description' => 'Presence with the requested ID could not be found.',
'code' => 404,
],
Exception::PRESENCE_ALREADY_EXISTS => [
'name' => Exception::PRESENCE_ALREADY_EXISTS,
'description' => 'Presence with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
/** Databases */
Exception::DATABASE_NOT_FOUND => [
'name' => Exception::DATABASE_NOT_FOUND,
@@ -1144,6 +1181,11 @@ return [
'description' => 'Webhook with the requested ID could not be found.',
'code' => 404,
],
Exception::WEBHOOK_ALREADY_EXISTS => [
'name' => Exception::WEBHOOK_ALREADY_EXISTS,
'description' => 'Webhook with the same ID already exists. Try again with a different ID.',
'code' => 409,
],
Exception::KEY_NOT_FOUND => [
'name' => Exception::KEY_NOT_FOUND,
'description' => 'Key with the requested ID could not be found.',
@@ -1159,6 +1201,16 @@ return [
'description' => 'Platform with the requested ID could not be found.',
'code' => 404,
],
Exception::PLATFORM_METHOD_UNSUPPORTED => [
'name' => Exception::PLATFORM_METHOD_UNSUPPORTED,
'description' => 'The requested platform has invalid type. Please use corresponding update method for the platform type.',
'code' => 400,
],
Exception::PLATFORM_ALREADY_EXISTS => [
'name' => Exception::PLATFORM_ALREADY_EXISTS,
'description' => 'Platform with the same ID already exists in this project. Try again with a different ID.',
'code' => 409,
],
Exception::VARIABLE_NOT_FOUND => [
'name' => Exception::VARIABLE_NOT_FOUND,
'description' => 'Variable with the requested ID could not be found.',
@@ -1201,6 +1253,31 @@ return [
'description' => 'Migration is already in progress. You can check the status of the migration in your Appwrite Console\'s "Settings" > "Migrations".',
'code' => 409,
],
Exception::MIGRATION_DATABASE_TYPE_UNSUPPORTED => [
'name' => Exception::MIGRATION_DATABASE_TYPE_UNSUPPORTED,
'description' => 'The specified database type is not supported for CSV import or export operations.',
'code' => 400,
],
Exception::MIGRATION_SOURCE_PROJECT_ID_REQUIRED => [
'name' => Exception::MIGRATION_SOURCE_PROJECT_ID_REQUIRED,
'description' => 'A source projectId is required for Appwrite migrations. Provide it in the migration credentials.',
'code' => 400,
],
Exception::MIGRATION_SOURCE_PROJECT_NOT_FOUND => [
'name' => Exception::MIGRATION_SOURCE_PROJECT_NOT_FOUND,
'description' => 'The source project for the provided projectId was not found. Verify the projectId and the API key has access to it.',
'code' => 404,
],
Exception::MIGRATION_SOURCE_TYPE_INVALID => [
'name' => Exception::MIGRATION_SOURCE_TYPE_INVALID,
'description' => 'The migration source type is invalid. Use one of the supported source types.',
'code' => 400,
],
Exception::MIGRATION_DESTINATION_TYPE_INVALID => [
'name' => Exception::MIGRATION_DESTINATION_TYPE_INVALID,
'description' => 'The migration destination type is invalid. Use one of the supported destination types.',
'code' => 400,
],
/** Realtime */
Exception::REALTIME_MESSAGE_FORMAT_INVALID => [
@@ -1373,4 +1450,43 @@ return [
'description' => 'When using project API key, make sure to pass x-appwrite-project header with your project ID.',
'code' => 403,
],
Exception::MOCK_NUMBER_ALREADY_EXISTS => [
'name' => Exception::MOCK_NUMBER_ALREADY_EXISTS,
'description' => 'Mock number with the requested number already exists. Try again with a different number. or update OTP of existing mock number.',
'code' => 409,
],
Exception::MOCK_NUMBER_NOT_FOUND => [
'name' => Exception::MOCK_NUMBER_NOT_FOUND,
'description' => 'Mock number with the requested number could not be found.',
'code' => 404,
],
Exception::MOCK_NUMBER_LIMIT_EXCEEDED => [
'name' => Exception::MOCK_NUMBER_LIMIT_EXCEEDED,
'description' => 'The maximum number of mock phones for this project has been reached.',
'code' => 400,
],
/** Advisor */
Exception::INSIGHT_NOT_FOUND => [
'name' => Exception::INSIGHT_NOT_FOUND,
'description' => 'Insight with the requested ID could not be found.',
'code' => 404,
],
Exception::INSIGHT_ALREADY_EXISTS => [
'name' => Exception::INSIGHT_ALREADY_EXISTS,
'description' => 'Insight with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
/** Reports */
Exception::REPORT_NOT_FOUND => [
'name' => Exception::REPORT_NOT_FOUND,
'description' => 'Report with the requested ID could not be found.',
'code' => 404,
],
Exception::REPORT_ALREADY_EXISTS => [
'name' => Exception::REPORT_ALREADY_EXISTS,
'description' => 'Report with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
];
+29 -1
View File
@@ -426,5 +426,33 @@ return [
'update' => [
'$description' => 'This event triggers when a proxy rule is updated.',
]
]
],
'reports' => [
'$model' => Response::MODEL_REPORT,
'$resource' => true,
'$description' => 'This event triggers on any report event.',
'create' => [
'$description' => 'This event triggers when a report is created.',
],
'update' => [
'$description' => 'This event triggers when a report is updated.',
],
'delete' => [
'$description' => 'This event triggers when a report is deleted.',
],
'insights' => [
'$model' => Response::MODEL_INSIGHT,
'$resource' => true,
'$description' => 'This event triggers on any insight event.',
'create' => [
'$description' => 'This event triggers when an insight is created.',
],
'update' => [
'$description' => 'This event triggers when an insight is updated.',
],
'delete' => [
'$description' => 'This event triggers when an insight is deleted.',
],
],
],
];
+70 -14
View File
@@ -14,7 +14,11 @@ return [
'name' => 'Analog',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'bundleCommand' => 'bash /usr/local/server/helpers/analog/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/analog/env.sh',
'adapters' => [
@@ -40,7 +44,11 @@ return [
'name' => 'Angular',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'bundleCommand' => 'bash /usr/local/server/helpers/angular/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/angular/env.sh',
'adapters' => [
@@ -66,7 +74,11 @@ return [
'name' => 'Next.js',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'bundleCommand' => 'bash /usr/local/server/helpers/next-js/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/next-js/env.sh',
'adapters' => [
@@ -91,7 +103,11 @@ return [
'name' => 'React',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'adapters' => [
'static' => [
'key' => 'static',
@@ -108,7 +124,11 @@ return [
'name' => 'Nuxt',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'bundleCommand' => 'bash /usr/local/server/helpers/nuxt/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/nuxt/env.sh',
'adapters' => [
@@ -133,7 +153,11 @@ return [
'name' => 'Vue.js',
'screenshotSleep' => 5000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'adapters' => [
'static' => [
'key' => 'static',
@@ -150,7 +174,11 @@ return [
'name' => 'SvelteKit',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'bundleCommand' => 'bash /usr/local/server/helpers/sveltekit/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/sveltekit/env.sh',
'adapters' => [
@@ -175,7 +203,11 @@ return [
'name' => 'Astro',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'bundleCommand' => 'bash /usr/local/server/helpers/astro/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/astro/env.sh',
'adapters' => [
@@ -200,7 +232,11 @@ return [
'name' => 'TanStack Start',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'bundleCommand' => 'bash /usr/local/server/helpers/tanstack-start/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/tanstack-start/env.sh',
'adapters' => [
@@ -225,7 +261,11 @@ return [
'name' => 'Remix',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'bundleCommand' => 'bash /usr/local/server/helpers/remix/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/remix/env.sh',
'adapters' => [
@@ -250,7 +290,11 @@ return [
'name' => 'Lynx',
'screenshotSleep' => 5000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'adapters' => [
'static' => [
'key' => 'static',
@@ -284,7 +328,11 @@ return [
'name' => 'React Native',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'adapters' => [
'static' => [
'key' => 'static',
@@ -301,7 +349,11 @@ return [
'name' => 'Vite',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'adapters' => [
'static' => [
'key' => 'static',
@@ -317,7 +369,11 @@ return [
'name' => 'Other',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => $templateRuntimes['NODE'],
'runtimes' => array_merge(
$templateRuntimes['NODE'],
$templateRuntimes['BUN'],
$templateRuntimes['DENO']
),
'adapters' => [
'static' => [
'key' => 'static',
-6
View File
@@ -9,11 +9,5 @@ return [
'mfaChallenge',
'sessionAlert',
'otpSession'
],
'sms' => [
'verification',
'login',
'invitation',
'mfaChallenge'
]
];
+15 -15
View File
@@ -57,21 +57,21 @@
"emails.recovery.thanks": "Thanks,",
"emails.recovery.buttonText": "Reset password",
"emails.recovery.signature": "{{project}} team",
"emails.csvExport.success.subject": "Your CSV export is ready",
"emails.csvExport.success.preview": "Your data export has been completed successfully.",
"emails.csvExport.success.hello": "Hello {{user}},",
"emails.csvExport.success.body": "Your CSV export is ready to download. Click the button below to download your data export.",
"emails.csvExport.success.footer": "This download link will expire in 1 hour.",
"emails.csvExport.success.thanks": "Thanks,",
"emails.csvExport.success.buttonText": "Download CSV",
"emails.csvExport.success.signature": "Appwrite team",
"emails.csvExport.failure.subject": "Your CSV export failed - file too large",
"emails.csvExport.failure.preview": "Your data export failed because the file size exceeds your plan limit.",
"emails.csvExport.failure.hello": "Hello {{user}},",
"emails.csvExport.failure.body": "Your CSV export could not be completed because the export file size ({{size}}MB) exceeds your plan limit. Please consider upgrading your plan or exporting a smaller dataset.",
"emails.csvExport.failure.footer": "If you have any questions, please contact our support team.",
"emails.csvExport.failure.thanks": "Thanks,",
"emails.csvExport.failure.signature": "{{project}} team",
"emails.dataExport.success.subject": "Your {{type}} export is ready",
"emails.dataExport.success.preview": "Your data export has been completed successfully.",
"emails.dataExport.success.hello": "Hello {{user}},",
"emails.dataExport.success.body": "Your {{type}} export is ready to download. Click the button below to download your data export.",
"emails.dataExport.success.footer": "This download link will expire in 1 hour.",
"emails.dataExport.success.thanks": "Thanks,",
"emails.dataExport.success.buttonText": "Download {{type}}",
"emails.dataExport.success.signature": "Appwrite team",
"emails.dataExport.failure.subject": "Your {{type}} export failed - file too large",
"emails.dataExport.failure.preview": "Your data export failed because the file size exceeds your plan limit.",
"emails.dataExport.failure.hello": "Hello {{user}},",
"emails.dataExport.failure.body": "Your {{type}} export could not be completed because the export file size ({{size}}MB) exceeds your plan limit. Please consider upgrading your plan or exporting a smaller dataset.",
"emails.dataExport.failure.footer": "If you have any questions, please contact our support team.",
"emails.dataExport.failure.thanks": "Thanks,",
"emails.dataExport.failure.signature": "{{project}} team",
"emails.invitation.subject": "Invitation to {{team}} Team at {{project}}",
"emails.invitation.preview": "{{owner}} invited you to join {{team}} at {{project}}",
"emails.invitation.hello": "Hello {{user}},",
+10
View File
@@ -28,6 +28,16 @@
"emails.invitation.thanks": "Gracias.,",
"emails.invitation.buttonText": "Aceptar invitación a {{team}}",
"emails.invitation.signature": "El equipo de {{project}}",
"emails.sessionAlert.subject": "Alerta de seguridad: nueva sesión en tu cuenta de {{project}}",
"emails.sessionAlert.preview": "Nuevo inicio de sesión detectado en {{project}} a las {{time}} UTC.",
"emails.sessionAlert.hello": "Hola {{user}},",
"emails.sessionAlert.body": "Se ha creado una nueva sesión en tu cuenta de {{b}}{{project}}{{/b}}, {{b}}el {{date}} de {{year}} a las {{time}} UTC{{/b}}.\nEstos son los detalles de la nueva sesión:",
"emails.sessionAlert.listDevice": "Dispositivo: {{b}}{{device}}{{/b}}",
"emails.sessionAlert.listIpAddress": "Dirección IP: {{b}}{{ipAddress}}{{/b}}",
"emails.sessionAlert.listCountry": "País: {{b}}{{country}}{{/b}}",
"emails.sessionAlert.footer": "Si has sido tú, no tienes que hacer nada más.\nSi no has iniciado esta sesión o sospechas actividad no autorizada, protege tu cuenta.",
"emails.sessionAlert.thanks": "Gracias,",
"emails.sessionAlert.signature": "El equipo de {{project}}",
"locale.country.unknown": "Desconocido",
"countries.af": "Afganistán",
"countries.ao": "Angola",
+44
View File
@@ -167,6 +167,17 @@ return [
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Figma',
],
'fusionauth' => [
'name' => 'FusionAuth',
'developers' => 'https://fusionauth.io/docs/',
'icon' => 'icon-fusionauth',
'enabled' => true,
'sandbox' => false,
'form' => 'fusionauth.phtml',
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\FusionAuth',
],
'github' => [
'name' => 'GitHub',
'developers' => 'https://developer.github.com/',
@@ -200,6 +211,28 @@ return [
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Google',
],
'keycloak' => [
'name' => 'Keycloak',
'developers' => 'https://www.keycloak.org/documentation',
'icon' => 'icon-keycloak',
'enabled' => true,
'sandbox' => false,
'form' => 'keycloak.phtml',
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Keycloak',
],
'kick' => [
'name' => 'Kick',
'developers' => 'https://docs.kick.com/',
'icon' => 'icon-kick',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Kick',
],
'linkedin' => [
'name' => 'LinkedIn',
'developers' => 'https://developer.linkedin.com/',
@@ -376,6 +409,17 @@ return [
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\Wordpress',
],
'x' => [
'name' => 'X',
'developers' => 'https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code',
'icon' => 'icon-twitter',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
'class' => 'Appwrite\\Auth\\OAuth2\\X',
],
'yahoo' => [
'name' => 'Yahoo',
'developers' => 'https://developer.yahoo.com/oauth2/guide/flows_authcode/',
@@ -9,8 +9,8 @@ return [
'key' => 'graphql',
'name' => 'GraphQL',
],
'realtime' => [
'key' => 'realtime',
'name' => 'Realtime',
'websocket' => [
'key' => 'websocket',
'name' => 'Websocket',
],
];
+24 -6
View File
@@ -12,6 +12,8 @@ $member = [
'account',
'teams.read',
'teams.write',
'presences.read',
'presences.write',
'documents.read',
'documents.write',
'rows.read',
@@ -21,8 +23,8 @@ $member = [
'projects.read',
'locale.read',
'avatars.read',
'execution.read',
'execution.write',
'executions.read',
'executions.write',
'targets.read',
'targets.write',
'subscribers.write',
@@ -47,6 +49,8 @@ $admins = [
'buckets.write',
'users.read',
'users.write',
'presences.read',
'presences.write',
'databases.read',
'databases.write',
'collections.read',
@@ -55,6 +59,14 @@ $admins = [
'tables.write',
'platforms.read',
'platforms.write',
'oauth2.read',
'oauth2.write',
'mocks.read',
'mocks.write',
'project.policies.read',
'project.policies.write',
'templates.read',
'templates.write',
'projects.write',
'keys.read',
'keys.write',
@@ -62,6 +74,8 @@ $admins = [
'devKeys.write',
'webhooks.read',
'webhooks.write',
'project.read',
'project.write',
'locale.read',
'avatars.read',
'health.read',
@@ -71,8 +85,8 @@ $admins = [
'sites.write',
'log.read',
'log.write',
'execution.read',
'execution.write',
'executions.read',
'executions.write',
'rules.read',
'rules.write',
'migrations.read',
@@ -93,6 +107,10 @@ $admins = [
'tokens.write',
'schedules.read',
'schedules.write',
'insights.read',
'insights.write',
'reports.read',
'reports.write',
];
return [
@@ -113,7 +131,7 @@ return [
'files.write',
'locale.read',
'avatars.read',
'execution.write',
'executions.write',
],
],
User::ROLE_USERS => [
@@ -132,7 +150,7 @@ return [
'label' => 'Owner',
'scopes' => \array_merge($member, $admins),
],
User::ROLE_APPS => [
User::ROLE_KEYS => [
'label' => 'Applications',
'scopes' => ['global', 'health.read', 'graphql'],
],
+8 -24
View File
@@ -3,40 +3,24 @@
// List of scopes for organization (teams) API keys
return [
"platforms.read" => [
"description" => 'Access to read project\'s platforms',
],
"platforms.write" => [
"description" =>
'Access to create, update, and delete project\'s platforms',
],
"projects.read" => [
"description" => 'Access to read organization\'s projects',
"description" => 'Access to read organization projects',
"category" => "Projects",
],
"projects.write" => [
"description" =>
"Access to create, update, and delete projects in organization",
],
"keys.read" => [
"description" => 'Access to read project\'s API keys',
],
"keys.write" => [
"description" =>
"Access to create, update, and delete project\'s API keys",
"Access to create, update, and delete organization projects",
"category" => "Projects",
],
"devKeys.read" => [
"description" => 'Access to read project\'s development keys',
"category" => "Other",
"deprecated" => true,
],
"devKeys.write" => [
"description" =>
"Access to create, update, and delete project\'s development keys",
],
"webhooks.read" => [
"description" =>
"Access to read project\'s webhooks",
],
"webhooks.write" => [
"description" =>
"Access to create, update, and delete project\'s webhooks",
"category" => "Other",
"deprecated" => true,
],
];
+346 -131
View File
@@ -1,175 +1,390 @@
<?php
return [ // List of publicly visible scopes
'sessions.write' => [
'description' => 'Access to create, update, and delete user sessions',
// List of publicly visible scopes
return [
// Project
"project.read" => [
"description" =>
"Access to read project\'s information",
"category" => "Project",
],
"project.write" => [
"description" =>
"Access to update project\'s information",
"category" => "Project",
],
"keys.read" => [
"description" =>
"Access to read project\'s keys",
"category" => "Project",
],
"keys.write" => [
"description" =>
"Access to create, update, and delete project\'s keys",
"category" => "Project",
],
"platforms.read" => [
"description" =>
"Access to read project\'s platforms",
"category" => "Project",
],
"platforms.write" => [
"description" =>
"Access to create, update, and delete project\'s platforms",
"category" => "Project",
],
"mocks.read" => [
"description" =>
"Access to read project\'s mocks",
"category" => "Project",
],
"mocks.write" => [
"description" =>
"Access to create, update, and delete project\'s mocks",
"category" => "Project",
],
"policies.read" => [
"description" =>
"Access to read project\'s policies. Replaced by \'project.policies.read\' for more granular control",
"category" => "Project",
'deprecated' => true,
],
"policies.write" => [
"description" =>
"Access to update project\'s policies. Replaces by \'project.policies.write\' for more granular control",
"category" => "Project",
'deprecated' => true,
],
"project.policies.read" => [
"description" =>
"Access to read project\'s policies",
"category" => "Project",
],
"project.policies.write" => [
"description" =>
"Access to update project\'s policies",
"category" => "Project",
],
"templates.read" => [
"description" =>
"Access to read project\'s templates",
"category" => "Project",
],
"templates.write" => [
"description" =>
"Access to create, update, and delete project\'s templates",
"category" => "Project",
],
"oauth2.read" => [
"description" =>
"Access to read project\'s OAuth2 configuration",
"category" => "Project",
],
"oauth2.write" => [
"description" =>
"Access to update project\'s OAuth2 configuration",
"category" => "Project",
],
// Auth
'users.read' => [
'description' => 'Access to read your project\'s users',
'description' => 'Access to read users',
'category' => 'Auth',
],
'users.write' => [
'description' => 'Access to create, update, and delete your project\'s users',
'description' => 'Access to create, update, and delete users',
'category' => 'Auth',
],
'sessions.read' => [
'description' => 'Access to read user sessions',
'category' => 'Auth',
],
'sessions.write' => [
'description' => 'Access to create, update, and delete user sessions',
'category' => 'Auth',
],
'teams.read' => [
'description' => 'Access to read your project\'s teams',
'description' => 'Access to read teams',
'category' => 'Auth',
],
'teams.write' => [
'description' => 'Access to create, update, and delete your project\'s teams',
'description' => 'Access to create, update, and delete teams',
'category' => 'Auth',
],
// Databases
'databases.read' => [
'description' => 'Access to read your project\'s databases',
'description' => 'Access to read databases',
'category' => 'Databases',
],
'databases.write' => [
'description' => 'Access to create, update, and delete your project\'s databases',
],
'collections.read' => [
'description' => 'Access to read your project\'s database collections',
],
'collections.write' => [
'description' => 'Access to create, update, and delete your project\'s database collections',
'description' => 'Access to create, update, and delete databases',
'category' => 'Databases',
],
'tables.read' => [
'description' => 'Access to read your project\'s database tables',
'description' => 'Access to read database tables',
'category' => 'Databases',
],
'tables.write' => [
'description' => 'Access to create, update, and delete your project\'s database tables',
],
'attributes.read' => [
'description' => 'Access to read your project\'s database collection\'s attributes',
],
'attributes.write' => [
'description' => 'Access to create, update, and delete your project\'s database collection\'s attributes',
'description' => 'Access to create, update, and delete database tables',
'category' => 'Databases',
],
'columns.read' => [
'description' => 'Access to read your project\'s database table\'s columns',
'description' => 'Access to read database table columns',
'category' => 'Databases',
],
'columns.write' => [
'description' => 'Access to create, update, and delete your project\'s database table\'s columns',
'description' => 'Access to create, update, and delete database table columns',
'category' => 'Databases',
],
'indexes.read' => [
'description' => 'Access to read your project\'s database table\'s indexes',
'description' => 'Access to read database table indexes',
'category' => 'Databases',
],
'indexes.write' => [
'description' => 'Access to create, update, and delete your project\'s database table\'s indexes',
],
'documents.read' => [
'description' => 'Access to read your project\'s database documents',
],
'documents.write' => [
'description' => 'Access to create, update, and delete your project\'s database documents',
'description' => 'Access to create, update, and delete database table indexes',
'category' => 'Databases',
],
'rows.read' => [
'description' => 'Access to read your project\'s database rows',
'description' => 'Access to read database table rows',
'category' => 'Databases',
],
'rows.write' => [
'description' => 'Access to create, update, and delete your project\'s database rows',
'description' => 'Access to create, update, and delete database table rows',
'category' => 'Databases',
],
'files.read' => [
'description' => 'Access to read your project\'s storage files and preview images',
'collections.read' => [
'description' => 'Access to read database collections',
'category' => 'Databases',
'deprecated' => true,
],
'files.write' => [
'description' => 'Access to create, update, and delete your project\'s storage files',
'collections.write' => [
'description' => 'Access to create, update, and delete database collections',
'category' => 'Databases',
'deprecated' => true,
],
'attributes.read' => [
'description' => 'Access to read database collection attributes',
'category' => 'Databases',
'deprecated' => true,
],
'attributes.write' => [
'description' => 'Access to create, update, and delete database collection attributes',
'category' => 'Databases',
'deprecated' => true,
],
'documents.read' => [
'description' => 'Access to read database collection documents',
'category' => 'Databases',
'deprecated' => true,
],
'documents.write' => [
'description' => 'Access to create, update, and delete database collection documents',
'category' => 'Databases',
'deprecated' => true,
],
// Storage
'buckets.read' => [
'description' => 'Access to read your project\'s storage buckets',
'description' => 'Access to read storage buckets',
'category' => 'Storage',
],
'buckets.write' => [
'description' => 'Access to create, update, and delete your project\'s storage buckets',
'description' => 'Access to create, update, and delete storage buckets',
'category' => 'Storage',
],
'functions.read' => [
'description' => 'Access to read your project\'s functions and code deployments',
'files.read' => [
'description' => 'Access to read storage files and preview images',
'category' => 'Storage',
],
'functions.write' => [
'description' => 'Access to create, update, and delete your project\'s functions and code deployments',
],
'sites.read' => [
'description' => 'Access to read your project\'s sites and deployments',
],
'sites.write' => [
'description' => 'Access to create, update, and delete your project\'s sites and deployments',
],
'log.read' => [
'description' => 'Access to read your site\'s logs',
],
'log.write' => [
'description' => 'Access to update, and delete your site\'s logs',
],
'execution.read' => [
'description' => 'Access to read your project\'s execution logs',
],
'execution.write' => [
'description' => 'Access to execute your project\'s functions',
],
'locale.read' => [
'description' => 'Access to access your project\'s Locale service',
],
'avatars.read' => [
'description' => 'Access to access your project\'s Avatars service',
],
'health.read' => [
'description' => 'Access to read your project\'s health status',
],
'providers.read' => [
'description' => 'Access to read your project\'s providers',
],
'providers.write' => [
'description' => 'Access to create, update, and delete your project\'s providers',
],
'messages.read' => [
'description' => 'Access to read your project\'s messages',
],
'messages.write' => [
'description' => 'Access to create, update, and delete your project\'s messages',
],
'topics.read' => [
'description' => 'Access to read your project\'s topics',
],
'topics.write' => [
'description' => 'Access to create, update, and delete your project\'s topics',
],
'subscribers.read' => [
'description' => 'Access to read your project\'s subscribers',
],
'subscribers.write' => [
'description' => 'Access to create, update, and delete your project\'s subscribers',
],
'targets.read' => [
'description' => 'Access to read your project\'s targets',
],
'targets.write' => [
'description' => 'Access to create, update, and delete your project\'s targets',
],
'rules.read' => [
'description' => 'Access to read your project\'s proxy rules',
],
'rules.write' => [
'description' => 'Access to create, update, and delete your project\'s proxy rules',
],
'schedules.read' => [
'description' => 'Access to read your project\'s schedules',
],
'schedules.write' => [
'description' => 'Access to create, update, and delete your project\'s schedules',
],
'migrations.read' => [
'description' => 'Access to read your project\'s migrations',
],
'migrations.write' => [
'description' => 'Access to create, update, and delete your project\'s migrations.',
],
'vcs.read' => [
'description' => 'Access to read your project\'s VCS repositories',
],
'vcs.write' => [
'description' => 'Access to create, update, and delete your project\'s VCS repositories',
],
'assistant.read' => [
'description' => 'Access to read the Assistant service',
'files.write' => [
'description' => 'Access to create, update, and delete storage files',
'category' => 'Storage',
],
'tokens.read' => [
'description' => 'Access to read your project\'s tokens',
'description' => 'Access to read storage file tokens',
'category' => 'Storage',
],
'tokens.write' => [
'description' => 'Access to create, update, and delete your project\'s tokens',
'description' => 'Access to create, update, and delete storage file tokens',
'category' => 'Storage',
],
// Functions
'functions.read' => [
'description' => 'Access to read functions and deployments',
'category' => 'Functions',
],
'functions.write' => [
'description' => 'Access to create, update, and delete functions and deployments',
'category' => 'Functions',
],
'executions.read' => [
'description' => 'Access to read function executions',
'category' => 'Functions',
],
'executions.write' => [
'description' => 'Access to create function executions',
'category' => 'Functions',
],
'execution.read' => [
'description' => 'Access to read function executions. This scope is deprecated for consistency purposes, and replaced by `executions.read`.',
'category' => 'Functions',
'deprecated' => true,
],
'execution.write' => [
'description' => 'Access to create function executions. This scope is deprecated for consistency purposes, and replaced by `executions.write`.',
'category' => 'Functions',
'deprecated' => true,
],
// Sites
'sites.read' => [
'description' => 'Access to read sites and deployments',
'category' => 'Sites',
],
'sites.write' => [
'description' => 'Access to create, update, and delete sites and deployments',
'category' => 'Sites',
],
'log.read' => [
'description' => 'Access to read site logs',
'category' => 'Sites',
],
'log.write' => [
'description' => 'Access to update, and delete site logs',
'category' => 'Sites',
],
// Messaging
'providers.read' => [
'description' => 'Access to read messaging providers',
'category' => 'Messaging',
],
'providers.write' => [
'description' => 'Access to create, update, and delete messaging providers',
'category' => 'Messaging',
],
'topics.read' => [
'description' => 'Access to read messaging topics',
'category' => 'Messaging',
],
'topics.write' => [
'description' => 'Access to create, update, and delete messaging topics',
'category' => 'Messaging',
],
'subscribers.read' => [
'description' => 'Access to read messaging subscribers',
'category' => 'Messaging',
],
'subscribers.write' => [
'description' => 'Access to create, update, and delete messaging subscribers',
'category' => 'Messaging',
],
'targets.read' => [
'description' => 'Access to read messaging targets',
'category' => 'Messaging',
],
'targets.write' => [
'description' => 'Access to create, update, and delete messaging targets',
'category' => 'Messaging',
],
'messages.read' => [
'description' => 'Access to read messaging messages',
'category' => 'Messaging',
],
'messages.write' => [
'description' => 'Access to create, update, and delete messaging messages',
'category' => 'Messaging',
],
// Proxy
'rules.read' => [
'description' => 'Access to read proxy rules.',
'category' => 'Proxy',
],
'rules.write' => [
'description' => 'Access to create, update, and delete proxy rules.',
'category' => 'Proxy',
],
// Other
"webhooks.read" => [
"description" =>
"Access to read webhooks",
'category' => 'Other',
],
"webhooks.write" => [
"description" =>
"Access to create, update, and delete webhooks",
'category' => 'Other',
],
'locale.read' => [
'description' => 'Access to use Locale service',
'category' => 'Other',
],
'avatars.read' => [
'description' => 'Access to use Avatars service',
'category' => 'Other',
],
'health.read' => [
'description' => 'Access to use Health service',
'category' => 'Other',
],
'assistant.read' => [
'description' => 'Access to use Assistant service',
'category' => 'Other',
],
'migrations.read' => [
'description' => 'Access to read migrations',
'category' => 'Other',
],
'migrations.write' => [
'description' => 'Access to create, update, and delete migrations.',
'category' => 'Other',
],
// TODO: Figure out where to move those
'schedules.read' => [
'description' => 'Access to read schedules.',
'category' => 'Other',
],
'schedules.write' => [
'description' => 'Access to create, update, and delete schedules.',
'category' => 'Other',
],
'vcs.read' => [
'description' => 'Access to read resources under VCS service.',
'category' => 'Other',
],
'vcs.write' => [
'description' => 'Access to create, update, and delete resources under VCS service.',
'category' => 'Other',
],
// Advisor
'insights.read' => [
'description' => 'Access to read insights under Advisor service.',
'category' => 'Advisor',
],
'insights.write' => [
'description' => 'Reserved for Advisor insight ingestion outside CE.',
'category' => 'Advisor',
],
'reports.read' => [
'description' => 'Access to read reports under Advisor service.',
'category' => 'Advisor',
],
'reports.write' => [
'description' => 'Access to delete reports under Advisor service.',
'category' => 'Advisor',
],
'presences.read' => [
'description' => 'Access to read your project\'s presences',
'category' => 'Presences',
],
'presences.write' => [
'description' => 'Access to create, update, and delete your project\'s presences',
'category' => 'Presences',
],
];
+124 -73
View File
@@ -8,55 +8,6 @@ return [
'enabled' => true,
'beta' => false,
'sdks' => [
[
'key' => 'web',
'name' => 'Web',
'version' => '22.4.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_SDK_PLATFORM_CLIENT,
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/client-web'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git',
'gitRepoName' => 'sdk-for-web',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/web/CHANGELOG.md'),
'demos' => [
[
'icon' => 'react.svg',
'name' => 'Todo App with React JS',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-react',
'url' => 'https://appwrite-todo-with-react.vercel.app/',
],
[
'icon' => 'vue.svg',
'name' => 'Todo App with Vue JS',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-vue',
'url' => 'https://appwrite-todo-with-vue.vercel.app/',
],
[
'icon' => 'angular.svg',
'name' => 'Todo App with Angular',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-angular',
'url' => 'https://appwrite-todo-with-angular.vercel.app/',
],
[
'icon' => 'svelte.svg',
'name' => 'Todo App with Svelte',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-svelte',
'url' => 'https://appwrite-todo-with-svelte.vercel.app/',
],
]
],
[
'key' => 'flutter',
'name' => 'Flutter',
@@ -250,26 +201,16 @@ return [
],
],
],
[
'key' => 'markdown',
'name' => 'Markdown',
'version' => '0.3.0',
'url' => 'https://github.com/appwrite/sdk-for-md.git',
'package' => 'https://www.npmjs.com/package/@appwrite.io/docs',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_SDK_PLATFORM_CONSOLE,
'prism' => 'markdown',
'source' => \realpath(__DIR__ . '/../sdks/console-md'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-md.git',
'gitRepoName' => 'sdk-for-md',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'repoBranch' => 'main',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/md/CHANGELOG.md'),
],
],
],
APP_SDK_PLATFORM_STATIC => [
'key' => APP_SDK_PLATFORM_STATIC,
'name' => 'Static',
'description' => 'SDK artifacts for Appwrite integrations that do not require a generated platform API specification.',
'enabled' => true,
'beta' => false,
'sdks' => [
[
'key' => 'agent-skills',
'name' => 'AgentSkills',
@@ -279,9 +220,10 @@ return [
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_SDK_PLATFORM_CONSOLE,
'spec' => 'static',
'family' => APP_SDK_PLATFORM_STATIC,
'prism' => 'agent-skills',
'source' => \realpath(__DIR__ . '/../sdks/console-agent-skills'),
'source' => \realpath(__DIR__ . '/../sdks/static-agent-skills'),
'gitUrl' => 'git@github.com:appwrite/agent-skills.git',
'gitRepoName' => 'agent-skills',
'gitUserName' => 'appwrite',
@@ -298,9 +240,10 @@ return [
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_SDK_PLATFORM_CONSOLE,
'spec' => 'static',
'family' => APP_SDK_PLATFORM_STATIC,
'prism' => 'cursor-plugin',
'source' => \realpath(__DIR__ . '/../sdks/console-cursor-plugin'),
'source' => \realpath(__DIR__ . '/../sdks/static-cursor-plugin'),
'gitUrl' => 'git@github.com:appwrite/cursor-plugin.git',
'gitRepoName' => 'cursor-plugin',
'gitUserName' => 'appwrite',
@@ -308,6 +251,46 @@ return [
'repoBranch' => 'main',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/cursor-plugin/CHANGELOG.md'),
],
[
'key' => 'claude-plugin',
'name' => 'ClaudePlugin',
'version' => '0.1.0',
'url' => 'https://github.com/appwrite/claude-plugin.git',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'spec' => 'static',
'family' => APP_SDK_PLATFORM_STATIC,
'prism' => 'claude-plugin',
'source' => \realpath(__DIR__ . '/../sdks/static-claude-plugin'),
'gitUrl' => 'git@github.com:appwrite/claude-plugin.git',
'gitRepoName' => 'claude-plugin',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'repoBranch' => 'main',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/claude-plugin/CHANGELOG.md'),
],
[
'key' => 'codex-plugin',
'name' => 'CodexPlugin',
'version' => '0.1.1',
'url' => 'https://github.com/appwrite/codex-plugin.git',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'spec' => 'static',
'family' => APP_SDK_PLATFORM_STATIC,
'prism' => 'codex-plugin',
'source' => \realpath(__DIR__ . '/../sdks/static-codex-plugin'),
'gitUrl' => 'git@github.com:appwrite/codex-plugin.git',
'gitRepoName' => 'codex-plugin',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'repoBranch' => 'main',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/codex-plugin/CHANGELOG.md'),
],
],
],
@@ -318,6 +301,55 @@ return [
'enabled' => true,
'beta' => false,
'sdks' => [
[
'key' => 'web',
'name' => 'Web',
'version' => '22.4.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/client-web'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git',
'gitRepoName' => 'sdk-for-web',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/web/CHANGELOG.md'),
'demos' => [
[
'icon' => 'react.svg',
'name' => 'Todo App with React JS',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-react',
'url' => 'https://appwrite-todo-with-react.vercel.app/',
],
[
'icon' => 'vue.svg',
'name' => 'Todo App with Vue JS',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-vue',
'url' => 'https://appwrite-todo-with-vue.vercel.app/',
],
[
'icon' => 'angular.svg',
'name' => 'Todo App with Angular',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-angular',
'url' => 'https://appwrite-todo-with-angular.vercel.app/',
],
[
'icon' => 'svelte.svg',
'name' => 'Todo App with Svelte',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-svelte',
'url' => 'https://appwrite-todo-with-svelte.vercel.app/',
],
]
],
[
'key' => 'nodejs',
'name' => 'Node.js',
@@ -494,6 +526,25 @@ return [
'gitBranch' => 'dev',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/swift/CHANGELOG.md'),
],
[
'key' => 'rust',
'name' => 'Rust',
'version' => '0.1.0',
'url' => 'https://github.com/appwrite/sdk-for-rust',
'package' => 'https://crates.io/crates/appwrite',
'enabled' => true,
'beta' => true,
'dev' => true,
'hidden' => false,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'rust',
'source' => \realpath(__DIR__ . '/../sdks/server-rust'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-rust.git',
'gitRepoName' => 'sdk-for-rust',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/rust/CHANGELOG.md'),
],
[
'key' => 'graphql',
'name' => 'GraphQL',
+20 -6
View File
@@ -137,7 +137,7 @@ return [
'docs' => true,
'docsUrl' => '',
'tests' => false,
'optional' => false,
'optional' => true,
'icon' => '',
'platforms' => ['client', 'server', 'console'],
],
@@ -193,7 +193,7 @@ return [
'docs' => false,
'docsUrl' => '',
'tests' => false,
'optional' => false,
'optional' => true,
'icon' => '',
'platforms' => ['client', 'server', 'console'],
],
@@ -235,7 +235,7 @@ return [
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/proxy',
'tests' => false,
'optional' => false,
'optional' => true,
'icon' => '/images/services/proxy.png',
'platforms' => ['client', 'server', 'console'],
],
@@ -286,12 +286,12 @@ return [
'name' => 'Migrations',
'subtitle' => 'The Migrations service allows you to migrate third-party data to your Appwrite project.',
'description' => '/docs/services/migrations.md',
'controller' => 'api/migrations.php',
'controller' => '', // Uses modules
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/migrations',
'tests' => true,
'optional' => false,
'optional' => true,
'icon' => '/images/services/migrations.png',
'platforms' => ['client', 'server', 'console'],
],
@@ -308,5 +308,19 @@ return [
'optional' => true,
'icon' => '/images/services/messaging.png',
'platforms' => ['client', 'server', 'console'],
]
],
'advisor' => [
'key' => 'advisor',
'name' => 'Advisor',
'subtitle' => 'The Advisor service surfaces actionable reports about your project resources, with CTA descriptors for one-click remediation in the console.',
'description' => '/docs/services/advisor.md',
'controller' => '', // Uses modules
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/server/advisor',
'tests' => true,
'optional' => true,
'icon' => '/images/services/insights.png',
'platforms' => ['server', 'console'],
],
];
+3 -5
View File
@@ -31,9 +31,6 @@ class FunctionUseCases
public const DEV_TOOLS = 'dev-tools';
public const AUTH = 'auth';
/**
* @var array<string>
*/
public static function getAll(): array
{
return [
@@ -82,12 +79,13 @@ return [
...getRuntimes($templateRuntimes['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter', $allowList),
...getRuntimes($templateRuntimes['BUN'], 'bun install', 'src/main.ts', 'bun/starter', $allowList),
...getRuntimes($templateRuntimes['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter', $allowList),
...getRuntimes($templateRuntimes['RUST'], '', 'main.rs', 'rust/starter', $allowList),
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/starter">file</a>.',
'instructions' => 'For documentation and instructions check out the <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates">templates repository</a>.',
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'providerVersion' => '0.3.*',
'variables' => [],
'scopes' => ['users.read']
],
+9 -12
View File
@@ -25,9 +25,6 @@ class SiteUseCases
public const FORMS = 'forms';
public const DASHBOARD = 'dashboard';
/**
* @var array<string>
*/
public static function getAll(): array
{
return [
@@ -252,7 +249,7 @@ return [
'frameworks' => [
getFramework('VITE', [
'providerRootDirectory' => './vite/vitepress',
'outputDirectory' => '404.html',
'fallbackFile' => '404.html',
'installCommand' => 'npm i vitepress && npm install',
'buildCommand' => 'npm run docs:build',
'outputDirectory' => './.vitepress/dist',
@@ -275,7 +272,7 @@ return [
'frameworks' => [
getFramework('VUE', [
'providerRootDirectory' => './vue/vuepress',
'outputDirectory' => '404.html',
'fallbackFile' => '404.html',
'installCommand' => 'npm install',
'buildCommand' => 'npm run build',
'outputDirectory' => './src/.vuepress/dist',
@@ -298,7 +295,7 @@ return [
'frameworks' => [
getFramework('REACT', [
'providerRootDirectory' => './react/docusaurus',
'outputDirectory' => '404.html',
'fallbackFile' => '404.html',
'installCommand' => 'npm install',
'buildCommand' => 'npm run build',
'outputDirectory' => './build',
@@ -1490,13 +1487,13 @@ return [
]
],
[
'key' => 'crm-dashboard-react-admin',
'name' => 'CRM dashboard with React Admin',
'tagline' => 'A React-based admin dashboard template with CRM features.',
'key' => 'dashboard-react-admin',
'name' => 'E-commerce dashboard with React Admin',
'tagline' => 'A React-based admin dashboard template with e-commerce features.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DASHBOARD],
'screenshotDark' => $url . '/images/sites/templates/crm-dashboard-react-admin-dark.png',
'screenshotLight' => $url . '/images/sites/templates/crm-dashboard-react-admin-light.png',
'useCases' => [SiteUseCases::DASHBOARD, SiteUseCases::ECOMMERCE],
'screenshotDark' => $url . '/images/sites/templates/dashboard-react-admin-dark.png',
'screenshotLight' => $url . '/images/sites/templates/dashboard-react-admin-light.png',
'frameworks' => [
getFramework('REACT', [
'providerRootDirectory' => './react/react-admin',
+13 -4
View File
@@ -872,18 +872,18 @@ return [
],
[
'name' => '_APP_FUNCTIONS_BUILD_TIMEOUT',
'description' => 'Deprecated since 1.7.0. The maximum number of seconds allowed as a timeout value when building a new function. The default value is 900 seconds.',
'description' => 'Deprecated since 1.7.0. The maximum number of seconds allowed as a timeout value when building a new function. The default value is 2700 seconds.',
'introduction' => '0.13.0',
'default' => '900',
'default' => '2700',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_COMPUTE_BUILD_TIMEOUT',
'description' => 'The maximum number of seconds allowed as a timeout value when building a new function or site. The default value is 900 seconds.',
'description' => 'The maximum number of seconds allowed as a timeout value when building a new function or site. The default value is 2700 seconds.',
'introduction' => '1.7.0',
'default' => '900',
'default' => '2700',
'required' => false,
'question' => '',
'filter' => ''
@@ -1336,6 +1336,15 @@ return [
'category' => 'Migrations',
'description' => '',
'variables' => [
[
'name' => '_APP_MIGRATION_HOST',
'description' => 'Internal hostname the migrations worker uses to reach this instance\'s API (for migrations and CSV/JSON imports & exports). Defaults to \'appwrite\', the API service name in the standard Docker Compose setup. Only change this for non-standard deployments.',
'introduction' => '1.9.0',
'default' => 'appwrite',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_MIGRATIONS_FIREBASE_CLIENT_ID',
'description' => 'Google OAuth client ID. You can find it in your GCP application settings.',
File diff suppressed because it is too large Load Diff
+9 -3
View File
@@ -28,12 +28,18 @@ use Utopia\Validator\Text;
Http::init()
->groups(['graphql'])
->inject('project')
->inject('user')
->inject('request')
->inject('response')
->inject('authorization')
->action(function (Document $project, Authorization $authorization) {
->action(function (Document $project, User $user, Request $request, Response $response, Authorization $authorization) {
$response->setUser($user);
$request->setUser($user);
if (
array_key_exists('graphql', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['graphql']
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
&& !($user->isPrivileged($authorization->getRoles()) || $user->isKey($authorization->getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
@@ -225,7 +231,7 @@ function execute(
$validations = GraphQL::getStandardValidationRules();
if (System::getEnv('_APP_GRAPHQL_INTROSPECTION', 'enabled') === 'disabled') {
$validations[] = new DisableIntrospection();
$validations[] = new DisableIntrospection(DisableIntrospection::ENABLED);
}
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
+1
View File
@@ -231,6 +231,7 @@ Http::get('/v1/locale/continents')
->inject('locale')
->action(function (Response $response, Locale $locale) {
$list = array_keys(Config::getParam('locale-continents'));
$output = [];
foreach ($list as $value) {
$output[] = new Document([
+60 -53
View File
@@ -3,12 +3,13 @@
use Ahc\Jwt\JWT;
use Appwrite\Auth\Validator\Phone;
use Appwrite\Detector\Detector;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Messaging;
use Appwrite\Event\Message\Delete as DeleteMessage;
use Appwrite\Event\Message\Messaging as MessagingMessage;
use Appwrite\Event\Publisher\Delete as DeletePublisher;
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Messaging\Status as MessageStatus;
use Appwrite\Network\Validator\Email;
use Appwrite\Permission;
use Appwrite\Role;
use Appwrite\SDK\AuthType;
@@ -43,6 +44,7 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\Roles;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Validator\Email;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
@@ -482,7 +484,6 @@ Http::post('/v1/messaging/providers/msg91')
$enabled === true
&& \array_key_exists('senderId', $credentials)
&& \array_key_exists('authKey', $credentials)
&& \array_key_exists('from', $options)
) {
$enabled = true;
} else {
@@ -1180,6 +1181,7 @@ Http::get('/v1/messaging/providers/:providerId/logs')
'userEmail' => $log['data']['userEmail'] ?? null,
'userName' => $log['data']['userName'] ?? null,
'mode' => $log['data']['mode'] ?? null,
'userType' => $log['data']['userType'] ?? null,
'ip' => $log['ip'],
'time' => $log['time'],
'osCode' => $os['osCode'],
@@ -2585,6 +2587,7 @@ Http::get('/v1/messaging/topics/:topicId/logs')
'userEmail' => $log['data']['userEmail'] ?? null,
'userName' => $log['data']['userName'] ?? null,
'mode' => $log['data']['mode'] ?? null,
'userType' => $log['data']['userType'] ?? null,
'ip' => $log['ip'],
'time' => $log['time'],
'osCode' => $os['osCode'],
@@ -2726,9 +2729,9 @@ Http::delete('/v1/messaging/topics/:topicId')
->param('topicId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Topic ID.', false, ['dbForProject'])
->inject('queueForEvents')
->inject('dbForProject')
->inject('queueForDeletes')
->inject('publisherForDeletes')
->inject('response')
->action(function (string $topicId, Event $queueForEvents, Database $dbForProject, Delete $queueForDeletes, Response $response) {
->action(function (string $topicId, Event $queueForEvents, Database $dbForProject, DeletePublisher $publisherForDeletes, Response $response) {
$topic = $dbForProject->getDocument('topics', $topicId);
if ($topic->isEmpty()) {
@@ -2737,9 +2740,11 @@ Http::delete('/v1/messaging/topics/:topicId')
$dbForProject->deleteDocument('topics', $topicId);
$queueForDeletes
->setType(DELETE_TYPE_TOPIC)
->setDocument($topic);
$publisherForDeletes->enqueue(new DeleteMessage(
project: $queueForEvents->getProject(),
type: DELETE_TYPE_TOPIC,
document: $topic,
));
$queueForEvents
->setParam('topicId', $topic->getId());
@@ -3000,6 +3005,7 @@ Http::get('/v1/messaging/subscribers/:subscriberId/logs')
'userEmail' => $log['data']['userEmail'] ?? null,
'userName' => $log['data']['userName'] ?? null,
'mode' => $log['data']['mode'] ?? null,
'userType' => $log['data']['userType'] ?? null,
'ip' => $log['ip'],
'time' => $log['time'],
'osCode' => $os['osCode'],
@@ -3185,9 +3191,9 @@ Http::post('/v1/messaging/messages/email')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('project')
->inject('queueForMessaging')
->inject('publisherForMessaging')
->inject('response')
->action(function (string $messageId, string $subject, string $content, ?array $topics, ?array $users, ?array $targets, ?array $cc, ?array $bcc, ?array $attachments, bool $draft, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
->action(function (string $messageId, string $subject, string $content, ?array $topics, ?array $users, ?array $targets, ?array $cc, ?array $bcc, ?array $attachments, bool $draft, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response) {
$messageId = $messageId == 'unique()'
? ID::unique()
: $messageId;
@@ -3204,10 +3210,6 @@ Http::post('/v1/messaging/messages/email')
throw new Exception(Exception::MESSAGE_MISSING_TARGET);
}
if ($status === MessageStatus::SCHEDULED && \is_null($scheduledAt)) {
throw new Exception(Exception::MESSAGE_MISSING_SCHEDULE);
}
$mergedTargets = \array_merge($targets, $cc, $bcc);
if (!empty($mergedTargets)) {
@@ -3276,9 +3278,11 @@ Http::post('/v1/messaging/messages/email')
switch ($status) {
case MessageStatus::PROCESSING:
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
->setMessageId($message->getId());
$publisherForMessaging->enqueue(new MessagingMessage(
type: MESSAGE_SEND_TYPE_EXTERNAL,
project: $project,
messageId: $message->getId(),
));
break;
case MessageStatus::SCHEDULED:
$schedule = $dbForPlatform->createDocument('schedules', new Document([
@@ -3364,9 +3368,9 @@ Http::post('/v1/messaging/messages/sms')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('project')
->inject('queueForMessaging')
->inject('publisherForMessaging')
->inject('response')
->action(function (string $messageId, string $content, ?array $topics, ?array $users, ?array $targets, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
->action(function (string $messageId, string $content, ?array $topics, ?array $users, ?array $targets, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response) {
$messageId = $messageId == 'unique()'
? ID::unique()
: $messageId;
@@ -3383,10 +3387,6 @@ Http::post('/v1/messaging/messages/sms')
throw new Exception(Exception::MESSAGE_MISSING_TARGET);
}
if ($status === MessageStatus::SCHEDULED && \is_null($scheduledAt)) {
throw new Exception(Exception::MESSAGE_MISSING_SCHEDULE);
}
if (!empty($targets)) {
$foundTargets = $dbForProject->find('targets', [
Query::equal('$id', $targets),
@@ -3424,9 +3424,11 @@ Http::post('/v1/messaging/messages/sms')
switch ($status) {
case MessageStatus::PROCESSING:
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
->setMessageId($message->getId());
$publisherForMessaging->enqueue(new MessagingMessage(
type: MESSAGE_SEND_TYPE_EXTERNAL,
project: $project,
messageId: $message->getId(),
));
break;
case MessageStatus::SCHEDULED:
$schedule = $dbForPlatform->createDocument('schedules', new Document([
@@ -3504,10 +3506,10 @@ Http::post('/v1/messaging/messages/push')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('project')
->inject('queueForMessaging')
->inject('publisherForMessaging')
->inject('response')
->inject('platform')
->action(function (string $messageId, string $title, string $body, ?array $topics, ?array $users, ?array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, int $badge, bool $draft, ?string $scheduledAt, bool $contentAvailable, bool $critical, string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response, array $platform) {
->action(function (string $messageId, string $title, string $body, ?array $topics, ?array $users, ?array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, int $badge, bool $draft, ?string $scheduledAt, bool $contentAvailable, bool $critical, string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response, array $platform) {
$messageId = $messageId == 'unique()'
? ID::unique()
: $messageId;
@@ -3524,10 +3526,6 @@ Http::post('/v1/messaging/messages/push')
throw new Exception(Exception::MESSAGE_MISSING_TARGET);
}
if ($status === MessageStatus::SCHEDULED && \is_null($scheduledAt)) {
throw new Exception(Exception::MESSAGE_MISSING_SCHEDULE);
}
if (!empty($targets)) {
$foundTargets = $dbForProject->find('targets', [
Query::equal('$id', $targets),
@@ -3566,7 +3564,7 @@ Http::post('/v1/messaging/messages/push')
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$endpoint = "$protocol://{$platform['apiHostname']}/v1";
$scheduleTime = $currentScheduledAt ?? $scheduledAt;
$scheduleTime = $scheduledAt;
if (!\is_null($scheduleTime)) {
$expiry = (new \DateTime($scheduleTime))->add(new \DateInterval('P15D'))->format('U');
} else {
@@ -3648,9 +3646,11 @@ Http::post('/v1/messaging/messages/push')
switch ($status) {
case MessageStatus::PROCESSING:
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
->setMessageId($message->getId());
$publisherForMessaging->enqueue(new MessagingMessage(
type: MESSAGE_SEND_TYPE_EXTERNAL,
project: $project,
messageId: $message->getId(),
));
break;
case MessageStatus::SCHEDULED:
$schedule = $dbForPlatform->createDocument('schedules', new Document([
@@ -3813,6 +3813,7 @@ Http::get('/v1/messaging/messages/:messageId/logs')
'userEmail' => $log['data']['userEmail'] ?? null,
'userName' => $log['data']['userName'] ?? null,
'mode' => $log['data']['mode'] ?? null,
'userType' => $log['data']['userType'] ?? null,
'ip' => $log['ip'],
'time' => $log['time'],
'osCode' => $os['osCode'],
@@ -3992,9 +3993,9 @@ Http::patch('/v1/messaging/messages/email/:messageId')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('project')
->inject('queueForMessaging')
->inject('publisherForMessaging')
->inject('response')
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $content, ?bool $draft, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, ?array $attachments, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $content, ?bool $draft, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, ?array $attachments, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response) {
$message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) {
@@ -4150,9 +4151,11 @@ Http::patch('/v1/messaging/messages/email/:messageId')
$message = $dbForProject->updateDocument('messages', $message->getId(), $message);
if ($status === MessageStatus::PROCESSING) {
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
->setMessageId($message->getId());
$publisherForMessaging->enqueue(new MessagingMessage(
type: MESSAGE_SEND_TYPE_EXTERNAL,
project: $project,
messageId: $message->getId(),
));
}
$queueForEvents
@@ -4214,9 +4217,9 @@ Http::patch('/v1/messaging/messages/sms/:messageId')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('project')
->inject('queueForMessaging')
->inject('publisherForMessaging')
->inject('response')
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $content, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $content, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response) {
$message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) {
@@ -4332,9 +4335,11 @@ Http::patch('/v1/messaging/messages/sms/:messageId')
$message = $dbForProject->updateDocument('messages', $message->getId(), $message);
if ($status === MessageStatus::PROCESSING) {
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
->setMessageId($message->getId());
$publisherForMessaging->enqueue(new MessagingMessage(
type: MESSAGE_SEND_TYPE_EXTERNAL,
project: $project,
messageId: $message->getId(),
));
}
$queueForEvents
@@ -4388,10 +4393,10 @@ Http::patch('/v1/messaging/messages/push/:messageId')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('project')
->inject('queueForMessaging')
->inject('publisherForMessaging')
->inject('response')
->inject('platform')
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, ?bool $contentAvailable, ?bool $critical, ?string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response, array $platform) {
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, ?bool $contentAvailable, ?bool $critical, ?string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response, array $platform) {
$message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) {
@@ -4593,9 +4598,11 @@ Http::patch('/v1/messaging/messages/push/:messageId')
$message = $dbForProject->updateDocument('messages', $message->getId(), $message);
if ($status === MessageStatus::PROCESSING) {
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
->setMessageId($message->getId());
$publisherForMessaging->enqueue(new MessagingMessage(
type: MESSAGE_SEND_TYPE_EXTERNAL,
project: $project,
messageId: $message->getId(),
));
}
$queueForEvents
@@ -4656,7 +4663,7 @@ Http::delete('/v1/messaging/messages/:messageId')
if (!empty($scheduleId)) {
try {
$dbForPlatform->deleteDocument('schedules', $scheduleId);
} catch (Exception) {
} catch (\Throwable) {
// Ignore
}
}
-957
View File
@@ -1,957 +0,0 @@
<?php
use Appwrite\Event\Event;
use Appwrite\Event\Migration;
use Appwrite\Extend\Exception;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CompoundUID;
use Appwrite\Utopia\Database\Validator\Queries\Migrations;
use Appwrite\Utopia\Response;
use Utopia\Compression\Algorithms\GZIP;
use Utopia\Compression\Algorithms\Zstd;
use Utopia\Compression\Compression;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Queries\Documents;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Http\Http;
use Utopia\Migration\Resource;
use Utopia\Migration\Sources\Appwrite;
use Utopia\Migration\Sources\CSV;
use Utopia\Migration\Sources\Firebase;
use Utopia\Migration\Sources\NHost;
use Utopia\Migration\Sources\Supabase;
use Utopia\Migration\Transfer;
use Utopia\Storage\Device;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\Text;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
include_once __DIR__ . '/../shared/api.php';
Http::post('/v1/migrations/appwrite')
->groups(['api', 'migrations'])
->desc('Create Appwrite migration')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createAppwriteMigration',
description: '/docs/references/migrations/migration-appwrite.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Appwrite::getSupportedResources())), 'List of resources to migrate')
->param('endpoint', '', new URL(), 'Source Appwrite endpoint')
->param('projectId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Source Project ID', false, ['dbForProject'])
->param('apiKey', '', new Text(512), 'Source API Key')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('user')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (array $resources, string $endpoint, string $projectId, string $apiKey, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
'stage' => 'init',
'source' => Appwrite::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'endpoint' => $endpoint,
'projectId' => $projectId,
'apiKey' => $apiKey,
],
'resources' => $resources,
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
]));
$queueForEvents->setParam('migrationId', $migration->getId());
// Trigger Transfer
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->setUser($user)
->trigger();
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::post('/v1/migrations/firebase')
->groups(['api', 'migrations'])
->desc('Create Firebase migration')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createFirebaseMigration',
description: '/docs/references/migrations/migration-firebase.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Firebase::getSupportedResources())), 'List of resources to migrate')
->param('serviceAccount', '', new Text(65536), 'JSON of the Firebase service account credentials')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('user')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$serviceAccountData = json_decode($serviceAccount, true);
if (empty($serviceAccountData)) {
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
}
if (!isset($serviceAccountData['project_id']) || !isset($serviceAccountData['client_email']) || !isset($serviceAccountData['private_key'])) {
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
}
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
'stage' => 'init',
'source' => Firebase::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'serviceAccount' => $serviceAccount,
],
'resources' => $resources,
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
]));
$queueForEvents->setParam('migrationId', $migration->getId());
// Trigger Transfer
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->setUser($user)
->trigger();
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::post('/v1/migrations/supabase')
->groups(['api', 'migrations'])
->desc('Create Supabase migration')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createSupabaseMigration',
description: '/docs/references/migrations/migration-supabase.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Supabase::getSupportedResources(), true)), 'List of resources to migrate')
->param('endpoint', '', new URL(), 'Source\'s Supabase Endpoint')
->param('apiKey', '', new Text(512), 'Source\'s API Key')
->param('databaseHost', '', new Text(512), 'Source\'s Database Host')
->param('username', '', new Text(512), 'Source\'s Database Username')
->param('password', '', new Text(512), 'Source\'s Database Password')
->param('port', 5432, new Integer(true), 'Source\'s Database Port', true)
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('user')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
'stage' => 'init',
'source' => Supabase::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'endpoint' => $endpoint,
'apiKey' => $apiKey,
'databaseHost' => $databaseHost,
'username' => $username,
'password' => $password,
'port' => $port,
],
'resources' => $resources,
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
]));
$queueForEvents->setParam('migrationId', $migration->getId());
// Trigger Transfer
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->setUser($user)
->trigger();
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::post('/v1/migrations/nhost')
->groups(['api', 'migrations'])
->desc('Create NHost migration')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createNHostMigration',
description: '/docs/references/migrations/migration-nhost.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(NHost::getSupportedResources())), 'List of resources to migrate')
->param('subdomain', '', new Text(512), 'Source\'s Subdomain')
->param('region', '', new Text(512), 'Source\'s Region')
->param('adminSecret', '', new Text(512), 'Source\'s Admin Secret')
->param('database', '', new Text(512), 'Source\'s Database Name')
->param('username', '', new Text(512), 'Source\'s Database Username')
->param('password', '', new Text(512), 'Source\'s Database Password')
->param('port', 5432, new Integer(true), 'Source\'s Database Port', true)
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('user')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
'stage' => 'init',
'source' => NHost::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'subdomain' => $subdomain,
'region' => $region,
'adminSecret' => $adminSecret,
'database' => $database,
'username' => $username,
'password' => $password,
'port' => $port,
],
'resources' => $resources,
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
]));
$queueForEvents->setParam('migrationId', $migration->getId());
// Trigger Transfer
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->setUser($user)
->trigger();
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::post('/v1/migrations/csv/imports')
->alias('/v1/migrations/csv')
->groups(['api', 'migrations'])
->desc('Import documents from a CSV')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createCSVImport',
description: '/docs/references/migrations/migration-csv-import.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('bucketId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).', false, ['dbForProject'])
->param('fileId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'File ID.', false, ['dbForProject'])
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.')
->param('internalFile', false, new Boolean(), 'Is the file stored in an internal bucket?', true)
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
->inject('project')
->inject('platform')
->inject('deviceForFiles')
->inject('deviceForMigrations')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (
string $bucketId,
string $fileId,
string $resourceId,
bool $internalFile,
Response $response,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
Document $project,
array $platform,
Device $deviceForFiles,
Device $deviceForMigrations,
Event $queueForEvents,
Migration $queueForMigrations
) {
$bucket = $authorization->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
if ($internalFile) {
return $dbForPlatform->getDocument('buckets', 'default');
}
return $dbForProject->getDocument('buckets', $bucketId);
});
if ($bucket->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = $authorization->skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
$path = $file->getAttribute('path', '');
if (!$deviceForFiles->exists($path)) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
}
// No encryption or compression on files above 20MB.
$hasEncryption = !empty($file->getAttribute('openSSLCipher'));
$compression = $file->getAttribute('algorithm', Compression::NONE);
$hasCompression = $compression !== Compression::NONE;
$migrationId = ID::unique();
$newPath = $deviceForMigrations->getPath($migrationId . '_' . $fileId . '.csv');
if ($hasEncryption || $hasCompression) {
$source = $deviceForFiles->read($path);
if ($hasEncryption) {
$source = OpenSSL::decrypt(
$source,
$file->getAttribute('openSSLCipher'),
System::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
0,
hex2bin($file->getAttribute('openSSLIV')),
hex2bin($file->getAttribute('openSSLTag'))
);
}
if ($hasCompression) {
switch ($compression) {
case Compression::ZSTD:
$source = (new Zstd())->decompress($source);
break;
case Compression::GZIP:
$source = (new GZIP())->decompress($source);
break;
}
}
// Manual write after decryption and/or decompression
if (!$deviceForMigrations->write($newPath, $source, 'text/csv')) {
throw new \Exception('Unable to copy file');
}
} elseif (!$deviceForFiles->transfer($path, $newPath, $deviceForMigrations)) {
throw new \Exception('Unable to copy file');
}
$fileSize = $deviceForMigrations->getFileSize($newPath);
$resources = Transfer::extractServices([Transfer::GROUP_DATABASES]);
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => $migrationId,
'status' => 'pending',
'stage' => 'init',
'source' => CSV::getName(),
'destination' => Appwrite::getName(),
'resources' => $resources,
'resourceId' => $resourceId,
'resourceType' => Resource::TYPE_DATABASE,
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
'options' => [
'path' => $newPath,
'size' => $fileSize,
],
]));
$queueForEvents->setParam('migrationId', $migration->getId());
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setProject($project)
->trigger();
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::post('/v1/migrations/csv/exports')
->groups(['api', 'migrations'])
->desc('Export documents to CSV')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createCSVExport',
description: '/docs/references/migrations/migration-csv-export.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database to export.')
->param('filename', '', new Text(255), 'The name of the file to be created for the export, excluding the .csv extension.')
->param('columns', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of attributes to export. If empty, all attributes will be exported. You can use the `*` wildcard to export all attributes from the collection.', true)
->param('queries', [], new ArrayList(new Text(0)), 'Array of query strings generated using the Query class provided by the SDK to filter documents to export. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->param('delimiter', ',', new Text(1), 'The character that separates each column value. Default is comma.', true)
->param('enclosure', '"', new Text(1), 'The character that encloses each column value. Default is double quotes.', true)
->param('escape', '"', new Text(1), 'The escape character for the enclosure character. Default is double quotes.', true)
->param('header', true, new Boolean(), 'Whether to include the header row with column names. Default is true.', true)
->param('notify', true, new Boolean(), 'Set to true to receive an email when the export is complete. Default is true.', true)
->inject('user')
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
->inject('project')
->inject('platform')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (
string $resourceId,
string $filename,
array $columns,
array $queries,
string $delimiter,
string $enclosure,
string $escape,
bool $header,
bool $notify,
Document $user,
Response $response,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
Document $project,
array $platform,
Event $queueForEvents,
Migration $queueForMigrations
) {
try {
$parsedQueries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
if ($bucket->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
[$databaseId, $collectionId] = \explode(':', $resourceId, 2);
if (empty($databaseId)) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
if (empty($collectionId)) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
if ($collection->isEmpty()) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$validator = new Documents(
attributes: $collection->getAttribute('attributes', []),
indexes: $collection->getAttribute('indexes', []),
idAttributeType: $dbForProject->getAdapter()->getIdAttributeType(),
);
if (!$validator->isValid($parsedQueries)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
'stage' => 'init',
'source' => Appwrite::getName(),
'destination' => CSV::getName(),
'resources' => Transfer::extractServices([Transfer::GROUP_DATABASES]),
'resourceId' => $resourceId,
'resourceType' => Resource::TYPE_DATABASE,
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
'options' => [
'bucketId' => 'default', // Always use internal bucket
'filename' => $filename,
'columns' => $columns,
'queries' => $queries,
'delimiter' => $delimiter,
'enclosure' => $enclosure,
'escape' => $escape,
'header' => $header,
'notify' => $notify,
'userInternalId' => $user->getSequence(),
],
]));
$queueForEvents->setParam('migrationId', $migration->getId());
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->trigger();
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::get('/v1/migrations')
->groups(['api', 'migrations'])
->desc('List migrations')
->label('scope', 'migrations.read')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'list',
description: '/docs/references/migrations/list-migrations.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MIGRATION_LIST,
)
]
))
->param('queries', [], new Migrations(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Migrations::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
->action(function (array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$migrationId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('migrations', $migrationId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Migration '{$migrationId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$migrations = $dbForProject->find('migrations', $queries);
$total = $includeTotal ? $dbForProject->count('migrations', $filterQueries, APP_LIMIT_COUNT) : 0;
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'migrations' => $migrations,
'total' => $total,
]), Response::MODEL_MIGRATION_LIST);
});
Http::get('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Get migration')
->label('scope', 'migrations.read')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'get',
description: '/docs/references/migrations/get-migration.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MIGRATION,
)
]
))
->param('migrationId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Migration unique ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $migrationId, Response $response, Database $dbForProject) {
$migration = $dbForProject->getDocument('migrations', $migrationId);
if ($migration->isEmpty()) {
throw new Exception(Exception::MIGRATION_NOT_FOUND);
}
$response->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::get('/v1/migrations/appwrite/report')
->groups(['api', 'migrations'])
->desc('Get Appwrite migration report')
->label('scope', 'migrations.write')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'getAppwriteReport',
description: '/docs/references/migrations/migration-appwrite-report.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MIGRATION_REPORT,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Appwrite::getSupportedResources())), 'List of resources to migrate')
->param('endpoint', '', new URL(), "Source's Appwrite Endpoint")
->param('projectID', '', new Text(512), "Source's Project ID")
->param('key', '', new Text(512), "Source's API Key")
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('user')
->action(function (array $resources, string $endpoint, string $projectID, string $key, Response $response) {
try {
$appwrite = new Appwrite($projectID, $endpoint, $key);
$report = $appwrite->report($resources);
} catch (\Throwable $e) {
throw new Exception(
Exception::MIGRATION_PROVIDER_ERROR,
'Unable to connect to the migration source. Please verify your credentials and ensure the source is reachable from this server. Check for network restrictions such as firewalls, IP allowlists, or outbound connectivity limits.'
);
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
Http::get('/v1/migrations/firebase/report')
->groups(['api', 'migrations'])
->desc('Get Firebase migration report')
->label('scope', 'migrations.write')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'getFirebaseReport',
description: '/docs/references/migrations/migration-firebase-report.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MIGRATION_REPORT,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Firebase::getSupportedResources())), 'List of resources to migrate')
->param('serviceAccount', '', new Text(65536), 'JSON of the Firebase service account credentials')
->inject('response')
->action(function (array $resources, string $serviceAccount, Response $response) {
$serviceAccount = json_decode($serviceAccount, true);
if (empty($serviceAccount)) {
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
}
if (!isset($serviceAccount['project_id']) || !isset($serviceAccount['client_email']) || !isset($serviceAccount['private_key'])) {
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
}
try {
$firebase = new Firebase($serviceAccount);
$report = $firebase->report($resources);
} catch (\Throwable $e) {
throw new Exception(
Exception::MIGRATION_PROVIDER_ERROR,
'Unable to connect to the migration source. Please verify your credentials and ensure the source is reachable from this server. Check for network restrictions such as firewalls, IP allowlists, or outbound connectivity limits.'
);
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
Http::get('/v1/migrations/supabase/report')
->groups(['api', 'migrations'])
->desc('Get Supabase migration report')
->label('scope', 'migrations.write')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'getSupabaseReport',
description: '/docs/references/migrations/migration-supabase-report.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MIGRATION_REPORT,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Supabase::getSupportedResources(), true)), 'List of resources to migrate')
->param('endpoint', '', new URL(), 'Source\'s Supabase Endpoint.')
->param('apiKey', '', new Text(512), 'Source\'s API Key.')
->param('databaseHost', '', new Text(512), 'Source\'s Database Host.')
->param('username', '', new Text(512), 'Source\'s Database Username.')
->param('password', '', new Text(512), 'Source\'s Database Password.')
->param('port', 5432, new Integer(true), 'Source\'s Database Port.', true)
->inject('response')
->inject('dbForProject')
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response) {
try {
$supabase = new Supabase($endpoint, $apiKey, $databaseHost, 'postgres', $username, $password, $port);
$report = $supabase->report($resources);
} catch (\Throwable $e) {
throw new Exception(
Exception::MIGRATION_PROVIDER_ERROR,
'Unable to connect to the migration source. Please verify your credentials and ensure the source is reachable from this server. Check for network restrictions such as firewalls, IP allowlists, or outbound connectivity limits.'
);
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
Http::get('/v1/migrations/nhost/report')
->groups(['api', 'migrations'])
->desc('Get NHost migration report')
->label('scope', 'migrations.write')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'getNHostReport',
description: '/docs/references/migrations/migration-nhost-report.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MIGRATION_REPORT,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(NHost::getSupportedResources())), 'List of resources to migrate.')
->param('subdomain', '', new Text(512), 'Source\'s Subdomain.')
->param('region', '', new Text(512), 'Source\'s Region.')
->param('adminSecret', '', new Text(512), 'Source\'s Admin Secret.')
->param('database', '', new Text(512), 'Source\'s Database Name.')
->param('username', '', new Text(512), 'Source\'s Database Username.')
->param('password', '', new Text(512), 'Source\'s Database Password.')
->param('port', 5432, new Integer(true), 'Source\'s Database Port.', true)
->inject('response')
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response) {
try {
$nhost = new NHost($subdomain, $region, $adminSecret, $database, $username, $password, $port);
$report = $nhost->report($resources);
} catch (\Throwable $e) {
throw new Exception(
Exception::MIGRATION_PROVIDER_ERROR,
'Unable to connect to the migration source. Please verify your credentials and ensure the source is reachable from this server. Check for network restrictions such as firewalls, IP allowlists, or outbound connectivity limits.'
);
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
Http::patch('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Update retry migration')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].retry')
->label('audits.event', 'migration.retry')
->label('audits.resource', 'migrations/{request.migrationId}')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'retry',
description: '/docs/references/migrations/retry-migration.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('migrationId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Migration unique ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('user')
->inject('queueForMigrations')
->action(function (string $migrationId, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Migration $queueForMigrations) {
$migration = $dbForProject->getDocument('migrations', $migrationId);
if ($migration->isEmpty()) {
throw new Exception(Exception::MIGRATION_NOT_FOUND);
}
if ($migration->getAttribute('status') !== 'failed') {
throw new Exception(Exception::MIGRATION_IN_PROGRESS, 'Migration not failed yet');
}
$migration
->setAttribute('status', 'pending')
->setAttribute('dateUpdated', \time());
// Trigger Migration
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->setUser($user)
->trigger();
$response->noContent();
});
Http::delete('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Delete migration')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].delete')
->label('audits.event', 'migrationId.delete')
->label('audits.resource', 'migrations/{request.migrationId}')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'delete',
description: '/docs/references/migrations/delete-migration.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('migrationId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Migration ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $migrationId, Response $response, Database $dbForProject, Event $queueForEvents) {
$migration = $dbForProject->getDocument('migrations', $migrationId);
if ($migration->isEmpty()) {
throw new Exception(Exception::MIGRATION_NOT_FOUND);
}
if (!$dbForProject->deleteDocument('migrations', $migration->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove migration from DB');
}
$queueForEvents->setParam('migrationId', $migration->getId());
$response->noContent();
});
+62 -236
View File
@@ -1,25 +1,15 @@
<?php
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DateTimeValidator;
use Utopia\Database\Validator\UID;
use Utopia\Http\Http;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
Http::get('/v1/project/usage')
@@ -62,16 +52,33 @@ Http::get('/v1/project/usage')
METRIC_EXECUTIONS_MB_SECONDS,
METRIC_BUILDS_MB_SECONDS,
METRIC_DOCUMENTS,
METRIC_DOCUMENTS_DOCUMENTSDB,
METRIC_DATABASES,
METRIC_DATABASES_DOCUMENTSDB,
METRIC_USERS,
METRIC_BUCKETS,
METRIC_FILES_STORAGE,
METRIC_DATABASES_STORAGE,
METRIC_DATABASES_STORAGE_DOCUMENTSDB,
METRIC_DEPLOYMENTS_STORAGE,
METRIC_BUILDS_STORAGE,
METRIC_DATABASES_OPERATIONS_READS,
METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB,
METRIC_DATABASES_OPERATIONS_WRITES,
METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB,
METRIC_FILES_IMAGES_TRANSFORMED,
// VectorsDB totals
METRIC_DATABASES_VECTORSDB,
METRIC_COLLECTIONS_VECTORSDB,
METRIC_DOCUMENTS_VECTORSDB,
METRIC_DATABASES_STORAGE_VECTORSDB,
METRIC_DATABASES_OPERATIONS_READS_VECTORSDB,
METRIC_DATABASES_OPERATIONS_WRITES_VECTORSDB,
// Embeddings totals
METRIC_EMBEDDINGS_TEXT,
METRIC_EMBEDDINGS_TEXT_TOTAL_TOKENS,
METRIC_EMBEDDINGS_TEXT_TOTAL_DURATION,
METRIC_EMBEDDINGS_TEXT_TOTAL_ERROR
],
'period' => [
METRIC_NETWORK_REQUESTS,
@@ -80,22 +87,38 @@ Http::get('/v1/project/usage')
METRIC_USERS,
METRIC_EXECUTIONS,
METRIC_DATABASES_STORAGE,
METRIC_DATABASES_STORAGE_DOCUMENTSDB,
METRIC_EXECUTIONS_MB_SECONDS,
METRIC_BUILDS_MB_SECONDS,
METRIC_DATABASES_OPERATIONS_READS,
METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB,
METRIC_DATABASES_OPERATIONS_WRITES,
METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB,
METRIC_FILES_IMAGES_TRANSFORMED,
// VectorsDB time series
METRIC_DATABASES_VECTORSDB,
METRIC_COLLECTIONS_VECTORSDB,
METRIC_DOCUMENTS_VECTORSDB,
METRIC_DATABASES_STORAGE_VECTORSDB,
METRIC_DATABASES_OPERATIONS_READS_VECTORSDB,
METRIC_DATABASES_OPERATIONS_WRITES_VECTORSDB,
// Embeddings time series
METRIC_EMBEDDINGS_TEXT,
METRIC_EMBEDDINGS_TEXT_TOTAL_TOKENS,
METRIC_EMBEDDINGS_TEXT_TOTAL_DURATION,
METRIC_EMBEDDINGS_TEXT_TOTAL_ERROR
]
];
$factor = match ($period) {
'1h' => 3600,
'1d' => 86400,
default => throw new \LogicException('Unsupported period: ' . $period),
};
$limit = match ($period) {
'1h' => (new DateTime($startDate))->diff(new DateTime($endDate))->days * 24,
'1d' => (new DateTime($startDate))->diff(new DateTime($endDate))->days
'1d' => (new DateTime($startDate))->diff(new DateTime($endDate))->days,
};
$format = match ($period) {
@@ -357,8 +380,11 @@ Http::get('/v1/project/usage')
'buildsMbSecondsTotal' => $total[METRIC_BUILDS_MB_SECONDS],
'documentsTotal' => $total[METRIC_DOCUMENTS],
'rowsTotal' => $total[METRIC_DOCUMENTS],
'documentsdbDocumentsTotal' => $total[METRIC_DOCUMENTS_DOCUMENTSDB],
'databasesTotal' => $total[METRIC_DATABASES],
'documentsdbTotal' => $total[METRIC_DATABASES_DOCUMENTSDB],
'databasesStorageTotal' => $total[METRIC_DATABASES_STORAGE],
'documentsdbDatabasesStorageTotal' => $total[METRIC_DATABASES_STORAGE_DOCUMENTSDB],
'usersTotal' => $total[METRIC_USERS],
'bucketsTotal' => $total[METRIC_BUCKETS],
'filesStorageTotal' => $total[METRIC_FILES_STORAGE],
@@ -367,10 +393,27 @@ Http::get('/v1/project/usage')
'deploymentsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE],
'databasesReadsTotal' => $total[METRIC_DATABASES_OPERATIONS_READS],
'databasesWritesTotal' => $total[METRIC_DATABASES_OPERATIONS_WRITES],
'documentsdbDatabasesReadsTotal' => $total[METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB],
'documentsdbDatabasesWritesTotal' => $total[METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB],
'vectorsdbDatabasesTotal' => $total[METRIC_DATABASES_VECTORSDB] ?? 0,
'vectorsdbCollectionsTotal' => $total[METRIC_COLLECTIONS_VECTORSDB] ?? 0,
'vectorsdbDocumentsTotal' => $total[METRIC_DOCUMENTS_VECTORSDB] ?? 0,
'vectorsdbDatabasesStorageTotal' => $total[METRIC_DATABASES_STORAGE_VECTORSDB] ?? 0,
'vectorsdbDatabasesReadsTotal' => $total[METRIC_DATABASES_OPERATIONS_READS_VECTORSDB] ?? 0,
'vectorsdbDatabasesWritesTotal' => $total[METRIC_DATABASES_OPERATIONS_WRITES_VECTORSDB] ?? 0,
'executionsBreakdown' => $executionsBreakdown,
'bucketsBreakdown' => $bucketsBreakdown,
'databasesReads' => $usage[METRIC_DATABASES_OPERATIONS_READS],
'databasesWrites' => $usage[METRIC_DATABASES_OPERATIONS_WRITES],
'documentsdbDatabasesReads' => $usage[METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB],
'documentsdbDatabasesWrites' => $usage[METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB],
'documentsdbDatabasesStorage' => $usage[METRIC_DATABASES_STORAGE_DOCUMENTSDB],
'vectorsdbDatabases' => $usage[METRIC_DATABASES_VECTORSDB] ?? [],
'vectorsdbCollections' => $usage[METRIC_COLLECTIONS_VECTORSDB] ?? [],
'vectorsdbDocuments' => $usage[METRIC_DOCUMENTS_VECTORSDB] ?? [],
'vectorsdbDatabasesStorage' => $usage[METRIC_DATABASES_STORAGE_VECTORSDB] ?? [],
'vectorsdbDatabasesReads' => $usage[METRIC_DATABASES_OPERATIONS_READS_VECTORSDB] ?? [],
'vectorsdbDatabasesWrites' => $usage[METRIC_DATABASES_OPERATIONS_WRITES_VECTORSDB] ?? [],
'databasesStorageBreakdown' => $databasesStorageBreakdown,
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
@@ -380,230 +423,13 @@ Http::get('/v1/project/usage')
'authPhoneCountryBreakdown' => $authPhoneCountryBreakdown,
'imageTransformations' => $usage[METRIC_FILES_IMAGES_TRANSFORMED],
'imageTransformationsTotal' => $total[METRIC_FILES_IMAGES_TRANSFORMED],
'embeddingsText' => $usage[METRIC_EMBEDDINGS_TEXT] ?? [],
'embeddingsTextTokens' => $usage[METRIC_EMBEDDINGS_TEXT_TOTAL_TOKENS] ?? [],
'embeddingsTextDuration' => $usage[METRIC_EMBEDDINGS_TEXT_TOTAL_DURATION] ?? [],
'embeddingsTextErrors' => $usage[METRIC_EMBEDDINGS_TEXT_TOTAL_ERROR] ?? [],
'embeddingsTextTotal' => $total[METRIC_EMBEDDINGS_TEXT] ?? 0,
'embeddingsTextTokensTotal' => $total[METRIC_EMBEDDINGS_TEXT_TOTAL_TOKENS] ?? 0,
'embeddingsTextDurationTotal' => $total[METRIC_EMBEDDINGS_TEXT_TOTAL_DURATION] ?? 0,
'embeddingsTextErrorsTotal' => $total[METRIC_EMBEDDINGS_TEXT_TOTAL_ERROR] ?? 0,
]), Response::MODEL_USAGE_PROJECT);
});
// Variables
Http::post('/v1/project/variables')
->desc('Create variable')
->groups(['api'])
->label('scope', 'projects.write')
->label('audits.event', 'variable.create')
->label('sdk', new Method(
namespace: 'project',
group: null,
name: 'createVariable',
description: '/docs/references/project/create-variable.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_VARIABLE,
)
]
))
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->action(function (string $key, string $value, bool $secret, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) {
$variableId = ID::unique();
$variable = new Document([
'$id' => $variableId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceInternalId' => '',
'resourceId' => '',
'resourceType' => 'project',
'key' => $key,
'value' => $value,
'secret' => $secret,
'search' => implode(' ', [$variableId, $key, 'project']),
]);
try {
$variable = $dbForProject->createDocument('variables', $variable);
} catch (DuplicateException $th) {
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
}
$functions = $dbForProject->find('functions', [
Query::limit(APP_LIMIT_SUBQUERY)
]);
foreach ($functions as $function) {
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($variable, Response::MODEL_VARIABLE);
});
Http::get('/v1/project/variables')
->desc('List variables')
->groups(['api'])
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'project',
group: null,
name: 'listVariables',
description: '/docs/references/project/list-variables.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_VARIABLE_LIST,
)
]
))
->inject('response')
->inject('dbForProject')
->action(function (Response $response, Database $dbForProject) {
$variables = $dbForProject->find('variables', [
Query::equal('resourceType', ['project']),
Query::limit(APP_LIMIT_SUBQUERY)
]);
$response->dynamic(new Document([
'variables' => $variables,
'total' => \count($variables),
]), Response::MODEL_VARIABLE_LIST);
});
Http::get('/v1/project/variables/:variableId')
->desc('Get variable')
->groups(['api'])
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'project',
group: null,
name: 'getVariable',
description: '/docs/references/project/get-variable.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_VARIABLE,
)
]
))
->param('variableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Variable unique ID.', false, ['dbForProject'])
->inject('response')
->inject('project')
->inject('dbForProject')
->action(function (string $variableId, Response $response, Document $project, Database $dbForProject) {
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceType') !== 'project') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
Http::put('/v1/project/variables/:variableId')
->desc('Update variable')
->groups(['api'])
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'project',
group: null,
name: 'updateVariable',
description: '/docs/references/project/update-variable.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_VARIABLE,
)
]
))
->param('variableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Variable unique ID.', false, ['dbForProject'])
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
->param('value', null, new Nullable(new Text(8192, 0)), 'Variable value. Max length: 8192 chars.', true)
->param('secret', null, new Nullable(new Boolean()), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->action(function (string $variableId, string $key, ?string $value, ?bool $secret, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) {
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceType') !== 'project') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
if ($variable->getAttribute('secret') === true && $secret === false) {
throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET);
}
$variable
->setAttribute('key', $key)
->setAttribute('value', $value ?? $variable->getAttribute('value'))
->setAttribute('secret', $secret ?? $variable->getAttribute('secret'))
->setAttribute('search', implode(' ', [$variableId, $key, 'project']));
try {
$dbForProject->updateDocument('variables', $variable->getId(), $variable);
} catch (DuplicateException $th) {
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
}
$functions = $dbForProject->find('functions', [
Query::limit(APP_LIMIT_SUBQUERY)
]);
foreach ($functions as $function) {
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
}
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
Http::delete('/v1/project/variables/:variableId')
->desc('Delete variable')
->groups(['api'])
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'project',
group: null,
name: 'deleteVariable',
description: '/docs/references/project/delete-variable.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('variableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Variable unique ID.', false, ['dbForProject'])
->inject('project')
->inject('response')
->inject('dbForProject')
->action(function (string $variableId, Document $project, Response $response, Database $dbForProject) {
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceType') !== 'project') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
$dbForProject->deleteDocument('variables', $variable->getId());
$functions = $dbForProject->find('functions', [
Query::limit(APP_LIMIT_SUBQUERY)
]);
foreach ($functions as $function) {
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
}
$response->noContent();
});
File diff suppressed because it is too large Load Diff
+206 -68
View File
@@ -11,11 +11,11 @@ use Appwrite\Auth\Validator\Phone;
use Appwrite\Deletes\Identities as DeleteIdentities;
use Appwrite\Deletes\Targets as DeleteTargets;
use Appwrite\Detector\Detector;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Message\Delete as DeleteMessage;
use Appwrite\Event\Publisher\Delete as DeletePublisher;
use Appwrite\Extend\Exception;
use Appwrite\Hooks\Hooks;
use Appwrite\Network\Validator\Email as EmailValidator;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
@@ -60,6 +60,7 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Emails\Validator\Email as EmailValidator;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
@@ -73,7 +74,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
/** TODO: Remove function when we move to using utopia/platform */
function createUser(Hash $hash, string $userId, ?string $email, ?string $password, ?string $phone, ?string $name, Document $project, Database $dbForProject, Hooks $hooks): Document
function createUser(Hash $hash, string $userId, ?string $email, ?string $password, ?string $phone, ?string $name, Document $project, Database $dbForProject, Hooks $hooks, array $plan): Document
{
$name = $name ?? '';
$plaintextPassword = $password;
@@ -110,11 +111,39 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
}
}
$emailMetadata = [
'emailCanonical' => null,
'emailIsCanonical' => null,
'emailIsCorporate' => null,
'emailIsDisposable' => null,
'emailIsFree' => null,
];
try {
$emailCanonical = new Email($email);
} catch (Throwable) {
$emailCanonical = null;
$parsedEmail = new Email($email ?? '');
$canonical = $parsedEmail->getCanonical();
$emailMetadata = [
'emailCanonical' => $canonical,
'emailIsCanonical' => $parsedEmail->get() === $canonical,
'emailIsCorporate' => $parsedEmail->isCorporate(),
'emailIsDisposable' => $parsedEmail->isDisposable(),
'emailIsFree' => $parsedEmail->isFree(),
];
} catch (\Throwable) {
}
if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
}
if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
$hashedPassword = null;
$isHashed = !$hash instanceof Plaintext;
@@ -159,11 +188,11 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $phone, $name]),
'emailCanonical' => $emailCanonical?->getCanonical(),
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
'emailIsCorporate' => $emailCanonical?->isCorporate(),
'emailIsDisposable' => $emailCanonical?->isDisposable(),
'emailIsFree' => $emailCanonical?->isFree(),
'emailCanonical' => $emailMetadata['emailCanonical'],
'emailIsCanonical' => $emailMetadata['emailIsCanonical'],
'emailIsCorporate' => $emailMetadata['emailIsCorporate'],
'emailIsDisposable' => $emailMetadata['emailIsDisposable'],
'emailIsFree' => $emailMetadata['emailIsFree'],
]);
if (!$isHashed && !empty($password)) {
@@ -256,10 +285,11 @@ Http::post('/v1/users')
->inject('project')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
->inject('plan')
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, array $plan) {
$plaintext = new Plaintext();
$user = createUser($plaintext, $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks);
$user = createUser($plaintext, $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks, $plan);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
@@ -292,11 +322,12 @@ Http::post('/v1/users/bcrypt')
->inject('project')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
->inject('plan')
->action(function (string $userId, string $email, string $password, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, array $plan) {
$bcrypt = new Bcrypt();
$bcrypt->setCost(8); // Default cost
$user = createUser($bcrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser($bcrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks, $plan);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -330,10 +361,11 @@ Http::post('/v1/users/md5')
->inject('project')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
->inject('plan')
->action(function (string $userId, string $email, string $password, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, array $plan) {
$md5 = new MD5();
$user = createUser($md5, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser($md5, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks, $plan);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -367,10 +399,11 @@ Http::post('/v1/users/argon2')
->inject('project')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
->inject('plan')
->action(function (string $userId, string $email, string $password, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, array $plan) {
$argon2 = new Argon2();
$user = createUser($argon2, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser($argon2, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks, $plan);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -405,13 +438,14 @@ Http::post('/v1/users/sha')
->inject('project')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordVersion, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
->inject('plan')
->action(function (string $userId, string $email, string $password, string $passwordVersion, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, array $plan) {
$sha = new Sha();
if (!empty($passwordVersion)) {
$sha->setVersion($passwordVersion);
}
$user = createUser($sha, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser($sha, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks, $plan);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -445,10 +479,11 @@ Http::post('/v1/users/phpass')
->inject('project')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
->inject('plan')
->action(function (string $userId, string $email, string $password, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, array $plan) {
$phpass = new PHPass();
$user = createUser($phpass, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser($phpass, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks, $plan);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -487,7 +522,8 @@ Http::post('/v1/users/scrypt')
->inject('project')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
->inject('plan')
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, array $plan) {
$scrypt = new Scrypt();
$scrypt
->setSalt($passwordSalt)
@@ -496,7 +532,7 @@ Http::post('/v1/users/scrypt')
->setParallelCost($passwordParallel)
->setLength($passwordLength);
$user = createUser($scrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser($scrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks, $plan);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -533,14 +569,15 @@ Http::post('/v1/users/scrypt-modified')
->inject('project')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
->inject('plan')
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, ?string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, array $plan) {
$scryptModified = new ScryptModified();
$scryptModified
->setSalt($passwordSalt)
->setSaltSeparator($passwordSaltSeparator)
->setSignerKey($passwordSignerKey);
$user = createUser($scryptModified, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser($scryptModified, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks, $plan);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -695,6 +732,13 @@ Http::get('/v1/users')
$cursor->setValue($cursorDocument);
}
$skipFilters = ['subQueryAuthenticators', 'subQuerySessions', 'subQueryTokens', 'subQueryChallenges', 'subQueryMemberships'];
$selects = Query::getByType($queries, [Query::TYPE_SELECT]);
if (empty($selects)) {
$skipFilters[] = 'subQueryTargets';
}
$users = [];
$total = 0;
@@ -707,7 +751,32 @@ Http::get('/v1/users')
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
}, ['subQueryAuthenticators', 'subQuerySessions', 'subQueryTokens', 'subQueryChallenges', 'subQueryMemberships']);
}, $skipFilters);
if (empty($selects) && !empty($users)) {
$sequences = [];
foreach ($users as $user) {
$sequences[] = $user->getSequence();
}
try {
$targets = $dbForProject->getAuthorization()->skip(fn () => $dbForProject->find('targets', [
Query::equal('userInternalId', $sequences),
Query::limit(PHP_INT_MAX),
]));
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$targetsByUser = [];
foreach ($targets as $target) {
$targetsByUser[$target->getAttribute('userInternalId')][] = $target;
}
foreach ($users as $user) {
$user->setAttribute('targets', $targetsByUser[$user->getSequence()] ?? []);
}
}
$response->dynamic(new Document([
'users' => $users,
@@ -820,7 +889,7 @@ Http::get('/v1/users/:userId/targets/:targetId')
Http::get('/v1/users/:userId/sessions')
->desc('List user sessions')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('scope', ['users.read', 'sessions.read'])
->label('sdk', new Method(
namespace: 'users',
group: 'sessions',
@@ -972,6 +1041,8 @@ Http::get('/v1/users/:userId/logs')
'userId' => ID::custom($log['data']['userId']),
'userEmail' => $log['data']['userEmail'] ?? null,
'userName' => $log['data']['userName'] ?? null,
'mode' => $log['data']['mode'] ?? null,
'userType' => $log['data']['userType'] ?? null,
'ip' => $log['ip'],
'time' => $log['time'],
'osCode' => $os['osCode'],
@@ -1212,6 +1283,47 @@ Http::put('/v1/users/:userId/labels')
$response->dynamic($user, Response::MODEL_USER);
});
Http::patch('/v1/users/:userId/impersonator')
->desc('Update user impersonator capability')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.impersonator')
->label('scope', 'users.write')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('sdk', new Method(
namespace: 'users',
group: 'users',
name: 'updateImpersonator',
description: '/docs/references/users/update-user-impersonator.md',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_USER,
)
]
))
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('impersonator', false, new Boolean(true), 'Whether the user can impersonate other users. When true, the user can browse project users to choose a target and can pass impersonation headers to act as that user. Internal audit logs still attribute impersonated actions to the original impersonator and store the target user details only in internal audit payload data.')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, bool $impersonator, Response $response, Database $dbForProject, Event $queueForEvents) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$user = $dbForProject->updateDocument('users', $user->getId(), new Document(['impersonator' => $impersonator]));
$queueForEvents
->setParam('userId', $user->getId());
$response->dynamic($user, Response::MODEL_USER);
});
Http::patch('/v1/users/:userId/verification/phone')
->desc('Update phone verification')
->groups(['api', 'users'])
@@ -1429,8 +1541,10 @@ Http::patch('/v1/users/:userId/email')
->param('email', '', new EmailValidator(allowEmpty: true), 'User email.')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('plan')
->inject('queueForEvents')
->action(function (string $userId, string $email, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $userId, string $email, Response $response, Database $dbForProject, Document $project, array $plan, Event $queueForEvents) {
$user = $dbForProject->getDocument('users', $userId);
@@ -1454,27 +1568,54 @@ Http::patch('/v1/users/:userId/email')
Query::equal('identifier', [$email]),
]);
if ($target instanceof Document && !$target->isEmpty()) {
if (!$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
}
}
$oldEmail = $user->getAttribute('email');
$emailMetadata = [
'emailCanonical' => null,
'emailIsCanonical' => null,
'emailIsCorporate' => null,
'emailIsDisposable' => null,
'emailIsFree' => null,
];
try {
$emailCanonical = new Email($email);
} catch (Throwable) {
$emailCanonical = null;
$parsedEmail = new Email($email);
$canonical = $parsedEmail->getCanonical();
$emailMetadata = [
'emailCanonical' => $canonical,
'emailIsCanonical' => $parsedEmail->get() === $canonical,
'emailIsCorporate' => $parsedEmail->isCorporate(),
'emailIsDisposable' => $parsedEmail->isDisposable(),
'emailIsFree' => $parsedEmail->isFree(),
];
} catch (\Throwable) {
}
if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
}
if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
$user
->setAttribute('email', $email)
->setAttribute('emailVerification', false)
->setAttribute('emailCanonical', $emailCanonical?->getCanonical())
->setAttribute('emailIsCanonical', $emailCanonical?->isCanonicalSupported())
->setAttribute('emailIsCorporate', $emailCanonical?->isCorporate())
->setAttribute('emailIsDisposable', $emailCanonical?->isDisposable())
->setAttribute('emailIsFree', $emailCanonical?->isFree())
->setAttribute('emailCanonical', $emailMetadata['emailCanonical'])
->setAttribute('emailIsCanonical', $emailMetadata['emailIsCanonical'])
->setAttribute('emailIsCorporate', $emailMetadata['emailIsCorporate'])
->setAttribute('emailIsDisposable', $emailMetadata['emailIsDisposable'])
->setAttribute('emailIsFree', $emailMetadata['emailIsFree'])
;
try {
@@ -1487,9 +1628,6 @@ Http::patch('/v1/users/:userId/email')
'emailIsDisposable' => $user->getAttribute('emailIsDisposable'),
'emailIsFree' => $user->getAttribute('emailIsFree'),
]));
/**
* @var Document $oldTarget
*/
$oldTarget = $user->find('identifier', $oldEmail, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
@@ -1573,7 +1711,7 @@ Http::patch('/v1/users/:userId/phone')
Query::equal('identifier', [$number]),
]);
if ($target instanceof Document && !$target->isEmpty()) {
if (!$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
}
}
@@ -1583,9 +1721,6 @@ Http::patch('/v1/users/:userId/phone')
'phone' => $phoneValue,
'phoneVerification' => $user->getAttribute('phoneVerification'),
]));
/**
* @var Document $oldTarget
*/
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
@@ -2144,8 +2279,8 @@ Http::delete('/v1/users/:userId/mfa/authenticators/:type')
->label('event', 'users.[userId].delete.mfa')
->label('scope', 'users.write')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('audits.resource', 'user/{request.userId}')
->label('audits.userId', '{request.userId}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk', [
new Method(
@@ -2212,7 +2347,7 @@ Http::post('/v1/users/:userId/sessions')
->desc('Create session')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.[sessionId].create')
->label('scope', 'users.write')
->label('scope', ['users.write', 'sessions.write'])
->label('audits.event', 'session.create')
->label('audits.resource', 'user/{request.userId}')
->label('usage.metric', 'sessions.{scope}.requests.create')
@@ -2296,9 +2431,8 @@ Http::post('/v1/users/:userId/sessions')
->setParam('sessionId', $session->getId())
->setPayload($response->output($session, Response::MODEL_SESSION));
return $response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($session, Response::MODEL_SESSION);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($session, Response::MODEL_SESSION);
});
Http::post('/v1/users/:userId/tokens')
@@ -2361,16 +2495,15 @@ Http::post('/v1/users/:userId/tokens')
->setParam('tokenId', $token->getId())
->setPayload($response->output($token, Response::MODEL_TOKEN));
return $response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($token, Response::MODEL_TOKEN);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($token, Response::MODEL_TOKEN);
});
Http::delete('/v1/users/:userId/sessions/:sessionId')
->desc('Delete user session')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.[sessionId].delete')
->label('scope', 'users.write')
->label('scope', ['users.write', 'sessions.write'])
->label('audits.event', 'session.delete')
->label('audits.resource', 'user/{request.userId}')
->label('sdk', new Method(
@@ -2421,7 +2554,7 @@ Http::delete('/v1/users/:userId/sessions')
->desc('Delete user sessions')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.delete')
->label('scope', 'users.write')
->label('scope', ['users.write', 'sessions.write'])
->label('audits.event', 'session.delete')
->label('audits.resource', 'user/{user.$id}')
->label('sdk', new Method(
@@ -2492,8 +2625,8 @@ Http::delete('/v1/users/:userId')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->inject('queueForDeletes')
->action(function (string $userId, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
->inject('publisherForDeletes')
->action(function (string $userId, Response $response, Database $dbForProject, Event $queueForEvents, DeletePublisher $publisherForDeletes) {
$user = $dbForProject->getDocument('users', $userId);
@@ -2508,9 +2641,11 @@ Http::delete('/v1/users/:userId')
DeleteIdentities::delete($dbForProject, Query::equal('userInternalId', [$user->getSequence()]));
DeleteTargets::delete($dbForProject, Query::equal('userInternalId', [$user->getSequence()]));
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($clone);
$publisherForDeletes->enqueue(new DeleteMessage(
project: $queueForEvents->getProject(),
type: DELETE_TYPE_DOCUMENT,
document: $clone,
));
$queueForEvents
->setParam('userId', $user->getId())
@@ -2543,10 +2678,10 @@ Http::delete('/v1/users/:userId/targets/:targetId')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID.', false, ['dbForProject'])
->inject('queueForEvents')
->inject('queueForDeletes')
->inject('publisherForDeletes')
->inject('response')
->inject('dbForProject')
->action(function (string $userId, string $targetId, Event $queueForEvents, Delete $queueForDeletes, Response $response, Database $dbForProject) {
->action(function (string $userId, string $targetId, Event $queueForEvents, DeletePublisher $publisherForDeletes, Response $response, Database $dbForProject) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
@@ -2566,9 +2701,11 @@ Http::delete('/v1/users/:userId/targets/:targetId')
$dbForProject->deleteDocument('targets', $target->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$queueForDeletes
->setType(DELETE_TYPE_TARGET)
->setDocument($target);
$publisherForDeletes->enqueue(new DeleteMessage(
project: $queueForEvents->getProject(),
type: DELETE_TYPE_TARGET,
document: $target,
));
$queueForEvents
->setParam('userId', $user->getId())
@@ -2617,7 +2754,7 @@ Http::delete('/v1/users/identities/:identityId')
->setParam('identityId', $identity->getId())
->setPayload($response->output($identity, Response::MODEL_IDENTITY));
return $response->noContent();
$response->noContent();
});
Http::post('/v1/users/:userId/jwts')
@@ -2736,6 +2873,7 @@ Http::get('/v1/users/usage')
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
default => throw new \LogicException('Unsupported period: ' . $days['period']),
};
foreach ($metrics as $metric) {
+187 -165
View File
@@ -7,9 +7,10 @@ use Ahc\Jwt\JWTException;
use Appwrite\Auth\Key;
use Appwrite\Bus\Events\ExecutionCompleted;
use Appwrite\Bus\Events\RequestCompleted;
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete as DeleteEvent;
use Appwrite\Event\Event;
use Appwrite\Event\Message\Delete as DeleteMessage;
use Appwrite\Event\Publisher\Certificate;
use Appwrite\Event\Publisher\Delete as DeletePublisher;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Network\Cors;
use Appwrite\Platform\Appwrite;
@@ -25,6 +26,11 @@ use Appwrite\Utopia\Request\Filters\V18 as RequestV18;
use Appwrite\Utopia\Request\Filters\V19 as RequestV19;
use Appwrite\Utopia\Request\Filters\V20 as RequestV20;
use Appwrite\Utopia\Request\Filters\V21 as RequestV21;
use Appwrite\Utopia\Request\Filters\V22 as RequestV22;
use Appwrite\Utopia\Request\Filters\V23 as RequestV23;
use Appwrite\Utopia\Request\Filters\V24 as RequestV24;
use Appwrite\Utopia\Request\Filters\V25 as RequestV25;
use Appwrite\Utopia\Request\Filters\V26 as RequestV26;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Filters\V16 as ResponseV16;
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17;
@@ -32,7 +38,13 @@ use Appwrite\Utopia\Response\Filters\V18 as ResponseV18;
use Appwrite\Utopia\Response\Filters\V19 as ResponseV19;
use Appwrite\Utopia\Response\Filters\V20 as ResponseV20;
use Appwrite\Utopia\Response\Filters\V21 as ResponseV21;
use Appwrite\Utopia\Response\Filters\V22 as ResponseV22;
use Appwrite\Utopia\Response\Filters\V23 as ResponseV23;
use Appwrite\Utopia\Response\Filters\V24 as ResponseV24;
use Appwrite\Utopia\Response\Filters\V25 as ResponseV25;
use Appwrite\Utopia\Response\Filters\V26 as ResponseV26;
use Appwrite\Utopia\View;
use Executor\Exception\Timeout as ExecutorTimeout;
use Executor\Executor;
use MaxMind\Db\Reader;
use Swoole\Http\Request as SwooleRequest;
@@ -61,13 +73,11 @@ use Utopia\System\System;
use Utopia\Validator;
use Utopia\Validator\Text;
Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeletePublisher $publisherForDeletes, int $executionsRetentionCount)
{
$host = $request->getHostname() ?? '';
$host = $request->getHostname();
if (!empty($previewHostname)) {
$host = $previewHostname;
}
@@ -118,7 +128,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
}
}
if (!in_array($host, $platformHostnames)) {
if (!in_array($host, $platformHostnames) && System::getEnv('_APP_OPTIONS_ROUTER_PROTECTION', 'enabled') === 'enabled') {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.', view: $errorView);
}
@@ -166,45 +176,15 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
if ($request->getMethod() !== Request::METHOD_GET) {
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.', view: $errorView);
}
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
$response->redirect('https://' . $request->getHostname() . $request->getURI());
return false;
}
}
/** @var Database $dbForProject */
$dbForProject = $getProjectDB($project);
/** @var Document $deployment */
if (!empty($rule->getAttribute('deploymentId', ''))) {
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
} else {
// 1.6.x DB schema compatibility
// TODO: Make sure deploymentId is never empty, and remove this code
// Check if site or function; should never be site, but better safe than sorry
// Attempts to use attribute from both schemas (1.6 and 1.7)
$resourceType = $rule->getAttribute('deploymentResourceType', $rule->getAttribute('resourceType', ''));
// ID of site or function
$resourceId = $rule->getAttribute('deploymentResourceId', '');
// Document of site or function
$resource = $resourceType === 'function' ?
$authorization->skip(fn () => $dbForProject->getDocument('functions', $resourceId)) :
$authorization->skip(fn () => $dbForProject->getDocument('sites', $resourceId));
// ID of active deployments
// Attempts to use attribute from both schemas (1.6 and 1.7)
$activeDeploymentId = $resource->getAttribute('deploymentId', $resource->getAttribute('deployment', ''));
// Get deployment document, as intended originally
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
}
if ($deployment->getAttribute('resourceType', '') === 'functions') {
$type = 'function';
} elseif ($deployment->getAttribute('resourceType', '') === 'sites') {
$type = 'site';
}
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
if ($deployment->isEmpty()) {
$resourceType = $rule->getAttribute('deploymentResourceType', '');
@@ -215,6 +195,14 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
throw $exception;
}
if ($deployment->getAttribute('resourceType', '') === 'functions') {
$type = 'function';
} elseif ($deployment->getAttribute('resourceType', '') === 'sites') {
$type = 'site';
} else {
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown deployment resource type', view: $errorView);
}
$resource = $type === 'function' ?
$authorization->skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
$authorization->skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
@@ -244,6 +232,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
if ($isPreview && $requirePreview) {
$cookie = $request->getCookie(COOKIE_NAME_PREVIEW, '');
$authorized = false;
$user = new Document();
// Security checks to mark authorized true
if (!empty($cookie)) {
@@ -273,7 +262,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
$membershipExists = false;
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
if (!$project->isEmpty() && isset($user)) {
if (!$project->isEmpty() && !$user->isEmpty()) {
$teamId = $project->getAttribute('teamId', '');
$membership = $user->find('teamId', $teamId, 'memberships');
if (!empty($membership)) {
@@ -301,13 +290,13 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
}
}
$body = $swooleRequest->getContent() ?? '';
$body = $swooleRequest->getContent() ?: '';
$method = $swooleRequest->server['request_method'];
$requestHeaders = $request->getHeaders();
if ($resource->isEmpty() || !$resource->getAttribute('enabled')) {
if ($type === 'functions') {
if ($type === 'function') {
throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND, view: $errorView);
} else {
throw new AppwriteException(AppwriteException::SITE_NOT_FOUND, view: $errorView);
@@ -329,7 +318,6 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
$runtime = match ($type) {
'function' => $runtimes[$resource->getAttribute('runtime')] ?? null,
'site' => $runtimes[$resource->getAttribute('buildRuntime')] ?? null,
default => null
};
// Static site enforced runtime
@@ -379,7 +367,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
$executionId = ID::unique();
$headers = \array_merge([], $requestHeaders);
$headers['x-appwrite-execution-id'] = $executionId ?? '';
$headers['x-appwrite-execution-id'] = $executionId;
$headers['x-appwrite-user-id'] = '';
$headers['x-appwrite-country-code'] = '';
$headers['x-appwrite-continent-code'] = '';
@@ -393,7 +381,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
'projectId' => $project->getId(),
'scopes' => $resource->getAttribute('scopes', [])
]);
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $jwtKey;
$headers['x-appwrite-key'] = API_KEY_EPHEMERAL . '_' . $jwtKey;
$headers['x-appwrite-trigger'] = 'http';
$headers['x-appwrite-user-jwt'] = '';
@@ -458,10 +446,10 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
// V2 vars
if ($version === 'v2') {
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_DATA' => $body ?? '',
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt'] ?? ''
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'],
'APPWRITE_FUNCTION_DATA' => $body,
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'],
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt']
]);
}
@@ -529,6 +517,11 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
}
/** Execute function */
$executionResponse = [
'headers' => [],
'body' => '',
];
try {
$version = match ($type) {
'function' => $resource->getAttribute('version', 'v2'),
@@ -555,7 +548,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
}
if (!empty($deployment->getAttribute('startCommand', ''))) {
$startCommand = 'cd /usr/local/server/src/function/ && ' . $deployment->getAttribute('startCommand', '');
$startCommand = 'cd /usr/local/server/src/function/ && ' . str_replace(['"', '`', '$'], ['\\"', '\\`', '\\$'], $deployment->getAttribute('startCommand', ''));
}
$runtimeEntrypoint = match ($version) {
@@ -568,26 +561,30 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
'site' => '',
};
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
body: \strlen($body) > 0 ? $body : null,
variables: $vars,
timeout: $resource->getAttribute('timeout', 30),
image: $runtime['image'],
source: $source,
entrypoint: $entrypoint,
version: $version,
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $runtimeEntrypoint,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
logging: $resource->getAttribute('logging', true),
requestTimeout: 30,
responseFormat: Executor::RESPONSE_FORMAT_ARRAY_HEADERS
);
try {
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
body: \strlen($body) > 0 ? $body : null,
variables: $vars,
timeout: $resource->getAttribute('timeout', 30),
image: $runtime['image'],
source: $source,
entrypoint: $entrypoint,
version: $version,
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $runtimeEntrypoint,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
logging: $resource->getAttribute('logging', true),
requestTimeout: 30,
responseFormat: Executor::RESPONSE_FORMAT_ARRAY_HEADERS
);
} catch (ExecutorTimeout $th) {
throw new AppwriteException(AppwriteException::FUNCTION_SYNCHRONOUS_TIMEOUT, previous: $th);
}
$headerOverrides = [];
@@ -672,9 +669,8 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
if (\is_string($logs) && \strlen($logs) > $maxLogLength) {
$warningMessage = "[WARNING] Logs truncated. The output exceeded {$maxLogLength} characters.\n";
$warningLength = \strlen($warningMessage);
$maxContentLength = max(0, $maxLogLength - $warningLength);
$logs = $warningMessage . ($maxContentLength > 0 ? \substr($logs, -$maxContentLength) : '');
$maxContentLength = $maxLogLength - \strlen($warningMessage);
$logs = $warningMessage . \substr($logs, -$maxContentLength);
}
// Truncate errors if they exceed the limit
@@ -683,9 +679,8 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
if (\is_string($errors) && \strlen($errors) > $maxErrorLength) {
$warningMessage = "[WARNING] Errors truncated. The output exceeded {$maxErrorLength} characters.\n";
$warningLength = \strlen($warningMessage);
$maxContentLength = max(0, $maxErrorLength - $warningLength);
$errors = $warningMessage . ($maxContentLength > 0 ? \substr($errors, -$maxContentLength) : '');
$maxContentLength = $maxErrorLength - \strlen($warningMessage);
$errors = $warningMessage . \substr($errors, -$maxContentLength);
}
/** Update execution status */
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
@@ -713,14 +708,12 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
throw $th;
}
} finally {
if ($type === 'function' || $type === 'site') {
$bus->dispatch(new ExecutionCompleted(
execution: $execution->getArrayCopy(),
project: $project->getArrayCopy(),
spec: $spec,
resource: $resource->getArrayCopy(),
));
}
$bus->dispatch(new ExecutionCompleted(
execution: $execution->getArrayCopy(),
project: $project->getArrayCopy(),
spec: $spec,
resource: $resource->getArrayCopy(),
));
}
$execution->setAttribute('logs', '');
@@ -734,7 +727,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
$execution->setAttribute('responseBody', $executionResponse['body'] ?? '');
$execution->setAttribute('responseHeaders', $headers);
$body = $execution['responseBody'] ?? '';
$body = $execution['responseBody'];
$contentType = 'text/plain';
foreach ($executionResponse['headers'] as $name => $values) {
@@ -748,11 +741,8 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
}
if (\is_array($values)) {
$count = 0;
foreach ($values as $value) {
$override = $count === 0;
$response->addHeader($name, $value, override: $override);
$count++;
$response->addHeader($name, $value);
}
} else {
$response->addHeader($name, $values);
@@ -777,12 +767,12 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
? RESOURCE_TYPE_FUNCTIONS
: RESOURCE_TYPE_SITES;
$queueForDeletes
->setProject($project)
->setResourceType($resourceType)
->setResource($resource->getSequence())
->setType(DELETE_TYPE_EXECUTIONS_LIMIT)
->trigger();
$publisherForDeletes->enqueue(new DeleteMessage(
project: $project,
type: DELETE_TYPE_EXECUTIONS_LIMIT,
resource: (string) $resource->getSequence(),
resourceType: $resourceType,
));
}
return true;
@@ -843,31 +833,32 @@ Http::init()
->inject('apiKey')
->inject('cors')
->inject('authorization')
->inject('queueForDeletes')
->inject('publisherForDeletes')
->inject('executionsRetentionCount')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, Event $queueForEvents, Bus $bus, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->inject('params')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, Event $queueForEvents, Bus $bus, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeletePublisher $publisherForDeletes, int $executionsRetentionCount, array $params) {
/*
* Appwrite Router
*/
$hostname = $request->getHostname() ?? '';
$hostname = $request->getHostname();
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!\in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $publisherForDeletes, $executionsRetentionCount)) {
$utopia->match($request)?->route->label('router', true);
}
}
/*
* Request format
*/
$route = $utopia->getRoute();
Request::setRoute($route);
$route = $utopia->match($request)?->route;
$request->setRoute($route);
if ($route === null) {
return $response
->setStatusCode(404)
->send('Not Found');
$response->setStatusCode(404);
$response->send('Not Found');
return;
}
$requestFormat = $request->getHeader('x-appwrite-response-format', System::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
@@ -886,11 +877,26 @@ Http::init()
}
if (version_compare($requestFormat, '1.8.0', '<')) {
$dbForProject = $getProjectDB($project);
$request->addFilter(new RequestV20($dbForProject, $route->getPathValues($request)));
$request->addFilter(new RequestV20($dbForProject, $params));
}
if (version_compare($requestFormat, '1.9.0', '<')) {
$request->addFilter(new RequestV21());
}
if (version_compare($requestFormat, '1.9.1', '<')) {
$request->addFilter(new RequestV22());
}
if (version_compare($requestFormat, '1.9.2', '<')) {
$request->addFilter(new RequestV23());
}
if (version_compare($requestFormat, '1.9.3', '<')) {
$request->addFilter(new RequestV24());
}
if (version_compare($requestFormat, '1.9.4', '<')) {
$request->addFilter(new RequestV25());
}
if (version_compare($requestFormat, '1.9.5', '<')) {
$request->addFilter(new RequestV26());
}
}
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
@@ -898,40 +904,16 @@ Http::init()
$locale->setDefault($localeParam);
}
$origin = \parse_url($request->getOrigin($request->getReferer('')), PHP_URL_HOST);
$selfDomain = new Domain($request->getHostname());
$endDomain = new Domain((string)$origin);
Config::setParam(
'domainVerification',
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
$endDomain->getRegisterable() !== ''
);
$localHosts = ['localhost','localhost:'.$request->getPort()];
$migrationHost = System::getEnv('_APP_MIGRATION_HOST');
if (!empty($migrationHost)) {
// Treat the migration host like localhost because internal migration and
// CI traffic may use it before a public domain is configured.
$localHosts[] = $migrationHost;
$localHosts[] = $migrationHost.':'.$request->getPort();
}
$isLocalHost = in_array($request->getHostname(), $localHosts);
$isIpAddress = filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false;
$isConsoleProject = $project->getAttribute('$id', '') === 'console';
$isConsoleRootSession = System::getEnv('_APP_CONSOLE_ROOT_SESSION', 'disabled') === 'enabled';
Config::setParam(
'cookieDomain',
$isLocalHost || $isIpAddress
? null
: (
$isConsoleProject && $isConsoleRootSession
? '.' . $selfDomain->getRegisterable()
: '.' . $request->getHostname()
)
);
$warnings = [];
/*
@@ -939,6 +921,21 @@ Http::init()
*/
$responseFormat = $request->getHeader('x-appwrite-response-format', System::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
if ($responseFormat) {
if (version_compare($responseFormat, '1.9.5', '<')) {
$response->addFilter(new ResponseV26());
}
if (version_compare($responseFormat, '1.9.4', '<')) {
$response->addFilter(new ResponseV25());
}
if (version_compare($responseFormat, '1.9.3', '<')) {
$response->addFilter(new ResponseV24());
}
if (version_compare($responseFormat, '1.9.2', '<')) {
$response->addFilter(new ResponseV23());
}
if (version_compare($responseFormat, '1.9.1', '<')) {
$response->addFilter(new ResponseV22());
}
if (version_compare($responseFormat, '1.9.0', '<')) {
$response->addFilter(new ResponseV21());
}
@@ -973,7 +970,8 @@ Http::init()
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.');
}
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
$response->redirect('https://' . $request->getHostname() . $request->getURI());
return;
}
}
});
@@ -1012,7 +1010,7 @@ Http::init()
return;
}
$route = $request->getRoute();
if ($route->getLabel('origin', false) === '*') {
if ($route?->getLabel('origin', false) === '*') {
return;
}
if (!$originValidator->isValid($origin)) {
@@ -1028,11 +1026,11 @@ Http::init()
->inject('request')
->inject('console')
->inject('dbForPlatform')
->inject('queueForCertificates')
->inject('publisherForCertificates')
->inject('platform')
->inject('authorization')
->inject('certifiedDomains')
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization, Table $certifiedDomains) {
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $publisherForCertificates, array $platform, Authorization $authorization, Table $certifiedDomains) {
$hostname = $request->getHostname();
$platformHostnames = $platform['hostnames'] ?? [];
@@ -1058,7 +1056,7 @@ Http::init()
}
// 4. Check/create rule (requires DB access)
$authorization->skip(function () use ($dbForPlatform, $domain, $console, $queueForCertificates, $certifiedDomains) {
$authorization->skip(function () use ($dbForPlatform, $domain, $console, $publisherForCertificates, $certifiedDomains) {
try {
// TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
@@ -1114,10 +1112,11 @@ Http::init()
$dbForPlatform->createDocument('rules', $document);
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
$queueForCertificates
->setDomain($document)
->setSkipRenewCheck(true)
->trigger();
$publisherForCertificates->enqueue(new \Appwrite\Event\Message\Certificate(
project: $console,
domain: $document,
skipRenewCheck: true,
));
} catch (Duplicate $e) {
Console::info('Certificate already exists');
} finally {
@@ -1146,17 +1145,17 @@ Http::options()
->inject('apiKey')
->inject('cors')
->inject('authorization')
->inject('queueForDeletes')
->inject('publisherForDeletes')
->inject('executionsRetentionCount')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeletePublisher $publisherForDeletes, int $executionsRetentionCount) {
/*
* Appwrite Router
*/
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $publisherForDeletes, $executionsRetentionCount)) {
$utopia->match($request)?->route->label('router', true);
}
}
@@ -1191,7 +1190,7 @@ Http::error()
->inject('authorization')
->action(function (Throwable $error, Http $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Bus $bus, Document $devKey, Authorization $authorization) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->getRoute();
$route = $utopia->match($request)?->route;
$class = \get_class($error);
$code = $error->getCode();
$message = $error->getMessage();
@@ -1199,9 +1198,7 @@ Http::error()
$line = $error->getLine();
$trace = $error->getTrace();
if (php_sapi_name() === 'cli') {
Span::error($error);
}
Span::error($error);
switch ($class) {
case Utopia\Http\Exception::class:
@@ -1261,7 +1258,16 @@ Http::error()
* If not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php
*/
if (!$publish && $project->getId() !== 'console') {
if (!DBUser::isPrivileged($authorization->getRoles())) {
$errorUser = new DBUser();
try {
$resolvedUser = $utopia->context()->get('user');
if ($resolvedUser instanceof DBUser) {
$errorUser = $resolvedUser;
}
} catch (\Throwable) {
// User resource may not be available in error context
}
if (!$errorUser->isPrivileged($authorization->getRoles())) {
$bus->dispatch(new RequestCompleted(
project: $project->getArrayCopy(),
request: $request,
@@ -1273,7 +1279,7 @@ Http::error()
if ($logger && $publish) {
try {
/** @var Utopia\Database\Document $user */
$user = $utopia->getResource('user');
$user = $utopia->context()->get('user');
} catch (\Throwable) {
// All good, user is optional information for logger
}
@@ -1434,6 +1440,7 @@ Http::error()
case 402: // Error allowed publicly
case 403: // Error allowed publicly
case 404: // Error allowed publicly
case 405: // Error allowed publicly
case 408: // Error allowed publicly
case 409: // Error allowed publicly
case 412: // Error allowed publicly
@@ -1468,6 +1475,21 @@ Http::error()
'type' => $type,
];
// Add CORS headers to error responses so browsers can read the error.
// Wrapped in try-catch: if the error itself is a DB failure, resolving
// the cors resource (which depends on rule -> DB) would cascade.
// Uses override:true to avoid duplicate headers if init() already set them.
try {
$cors = $utopia->context()->get('cors');
foreach ($cors->headers($request->getOrigin()) as $name => $value) {
$response
->removeHeader($name)
->addHeader($name, $value);
}
} catch (Throwable) {
// Degrade gracefully - error response without CORS is no worse than before.
}
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
@@ -1489,9 +1511,9 @@ Http::error()
->setParam('development', Http::isDevelopment())
->setParam('projectName', $project->getAttribute('name'))
->setParam('projectURL', $project->getAttribute('url'))
->setParam('message', $output['message'] ?? '')
->setParam('type', $output['type'] ?? '')
->setParam('code', $output['code'] ?? '')
->setParam('message', $output['message'])
->setParam('type', $output['type'])
->setParam('code', $output['code'])
->setParam('trace', $output['trace'] ?? [])
->setParam('exception', $error);
@@ -1525,16 +1547,16 @@ Http::get('/robots.txt')
->inject('previewHostname')
->inject('apiKey')
->inject('authorization')
->inject('queueForDeletes')
->inject('publisherForDeletes')
->inject('executionsRetentionCount')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeletePublisher $publisherForDeletes, int $executionsRetentionCount) {
$platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $publisherForDeletes, $executionsRetentionCount)) {
$utopia->match($request)?->route->label('router', true);
}
}
});
@@ -1559,16 +1581,16 @@ Http::get('/humans.txt')
->inject('previewHostname')
->inject('apiKey')
->inject('authorization')
->inject('queueForDeletes')
->inject('publisherForDeletes')
->inject('executionsRetentionCount')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeletePublisher $publisherForDeletes, int $executionsRetentionCount) {
$platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $publisherForDeletes, $executionsRetentionCount)) {
$utopia->match($request)?->route->label('router', true);
}
}
});
@@ -1606,7 +1628,7 @@ Http::get('/.well-known/acme-challenge/*')
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND, 'Unknown path');
}
if (!\substr($absolute, 0, \strlen($base)) === $base) {
if (\substr($absolute, 0, \strlen($base)) !== $base) {
throw new AppwriteException(AppwriteException::GENERAL_UNAUTHORIZED_SCOPE, 'Invalid path');
}
@@ -1685,7 +1707,7 @@ Http::get('/_appwrite/authorize')
->inject('previewHostname')
->action(function (Request $request, Response $response, string $previewHostname) {
$host = $request->getHostname() ?? '';
$host = $request->getHostname();
if (!empty($previewHostname)) {
$host = $previewHostname;
}
+33 -32
View File
@@ -13,6 +13,7 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\UID;
use Utopia\Http\Http;
use Utopia\Http\Route;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\Text;
@@ -244,36 +245,38 @@ Http::get('/v1/mock/github/callback')
throw new Exception(Exception::PROJECT_NOT_FOUND, $error);
}
if (!empty($providerInstallationId)) {
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$projectInternalId = $project->getSequence();
$teamId = $project->getAttribute('teamId', '');
$installation = new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'providerInstallationId' => $providerInstallationId,
'projectId' => $projectId,
'projectInternalId' => $projectInternalId,
'provider' => 'github',
'organization' => $owner,
'personal' => false
]);
$installation = $dbForPlatform->createDocument('installations', $installation);
if (empty($providerInstallationId)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Missing provider installation ID');
}
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId);
$projectInternalId = $project->getSequence();
$teamId = $project->getAttribute('teamId', '');
$installation = new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'providerInstallationId' => $providerInstallationId,
'projectId' => $projectId,
'projectInternalId' => $projectInternalId,
'provider' => 'github',
'organization' => $owner,
'personal' => false
]);
$installation = $dbForPlatform->createDocument('installations', $installation);
$response->json([
'installationId' => $installation->getId(),
]);
@@ -281,13 +284,11 @@ Http::get('/v1/mock/github/callback')
Http::shutdown()
->groups(['mock'])
->inject('utopia')
->inject('response')
->inject('request')
->action(function (Http $utopia, Response $response, Request $request) {
->inject('route')
->action(function (Response $response, Route $route) {
$result = [];
$route = $utopia->getRoute();
$path = APP_STORAGE_CACHE . '/tests.json';
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
+235 -190
View File
@@ -3,21 +3,21 @@
use Appwrite\Auth\Key;
use Appwrite\Auth\MFA\Type\TOTP;
use Appwrite\Bus\Events\RequestCompleted;
use Appwrite\Event\Audit;
use Appwrite\Event\Build;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Context\Audit as AuditContext;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Message\Audit as AuditMessage;
use Appwrite\Event\Message\Func as FunctionMessage;
use Appwrite\Event\Message\Usage as UsageMessage;
use Appwrite\Event\Messaging;
use Appwrite\Event\Publisher\Audit;
use Appwrite\Event\Publisher\Func as FunctionPublisher;
use Appwrite\Event\Publisher\Usage as UsagePublisher;
use Appwrite\Event\Realtime;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Functions\EventProcessor;
use Appwrite\Platform\Modules\Storage\Config\CacheControl;
use Appwrite\Platform\Modules\Storage\Config\StorageCacheControl;
use Appwrite\SDK\Method;
use Appwrite\Usage\Context;
use Appwrite\Utopia\Database\Documents\User;
@@ -37,13 +37,15 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Database\Validator\Roles;
use Utopia\Http\Http;
use Utopia\Http\Route;
use Utopia\Span\Span;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Validator\WhiteList;
$parseLabel = function (string $label, array $responsePayload, array $requestParams, User $user) {
$parseLabel = function (string $label, array $responsePayload, array $requestParams, User $user, Document $project) {
preg_match_all('/{(.*?)}/', $label, $matches);
foreach ($matches[1] ?? [] as $pos => $match) {
foreach ($matches[1] as $pos => $match) {
$find = $matches[0][$pos];
$parts = explode('.', $match);
@@ -51,11 +53,12 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
throw new Exception(Exception::GENERAL_SERVER_ERROR, "The server encountered an error while parsing the label: $label. Please create an issue on GitHub to allow us to investigate further https://github.com/appwrite/appwrite/issues/new/choose");
}
$namespace = $parts[0] ?? '';
$replace = $parts[1] ?? '';
$namespace = $parts[0];
$replace = $parts[1];
$params = match ($namespace) {
'user' => (array) $user,
'project' => $project->getArrayCopy(),
'request' => $requestParams,
default => $responsePayload,
};
@@ -83,11 +86,11 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
Http::init()
->groups(['api'])
->inject('utopia')
->inject('route')
->inject('request')
->inject('dbForPlatform')
->inject('dbForProject')
->inject('queueForAudits')
->inject('auditContext')
->inject('project')
->inject('user')
->inject('session')
@@ -96,8 +99,7 @@ Http::init()
->inject('team')
->inject('apiKey')
->inject('authorization')
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
$route = $utopia->getRoute();
->action(function (Route $route, Request $request, Database $dbForPlatform, Database $dbForProject, AuditContext $auditContext, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
/**
* Handle user authentication and session validation.
@@ -173,23 +175,24 @@ Http::init()
$role = $apiKey->getRole();
$scopes = $apiKey->getScopes();
// Handle special app role case
if ($apiKey->getRole() === User::ROLE_APPS) {
// Handle special key role case
if ($apiKey->getRole() === User::ROLE_KEYS) {
// Disable authorization checks for project API keys
if (($apiKey->getType() === API_KEY_STANDARD || $apiKey->getType() === API_KEY_DYNAMIC) && $apiKey->getProjectId() === $project->getId()) {
// Dynamic supported for backwards compatibility
if (($apiKey->getType() === API_KEY_STANDARD || $apiKey->getType() === API_KEY_EPHEMERAL || $apiKey->getType() === 'dynamic') && $apiKey->getProjectId() === $project->getId()) {
$authorization->setDefaultStatus(false);
}
$user = new User([
'$id' => '',
'status' => true,
'type' => ACTIVITY_TYPE_APP,
'type' => ACTOR_TYPE_KEY_PROJECT,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $apiKey->getName(),
]);
$queueForAudits->setUser($user);
$auditContext->user = $user;
}
// For standard keys, update last accessed time
@@ -215,7 +218,7 @@ Http::init()
);
}
if (! $dbKey) {
if (!$dbKey) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -253,7 +256,13 @@ Http::init()
}
}
$queueForAudits->setUser($user);
$userClone = clone $user;
$userClone->setAttribute('type', match ($apiKey->getType()) {
API_KEY_STANDARD => ACTOR_TYPE_KEY_PROJECT,
API_KEY_ACCOUNT => ACTOR_TYPE_KEY_ACCOUNT,
default => ACTOR_TYPE_KEY_ORGANIZATION,
});
$auditContext->user = $userClone;
}
// Apply permission
@@ -338,6 +347,19 @@ Http::init()
$scopes = \array_unique($scopes);
// Intentional: impersonators get users.read so they can discover a target user
// before impersonation starts, and keep that access while impersonating.
if (
!$user->isEmpty()
&& (
$user->getAttribute('impersonator', false)
|| $user->getAttribute('impersonatorUserId')
)
) {
$scopes[] = 'users.read';
$scopes = \array_unique($scopes);
}
$authorization->addRole($role);
foreach ($user->getRoles($authorization) as $authRole) {
$authorization->addRole($authRole);
@@ -359,7 +381,7 @@ Http::init()
}
// Step 6: Update project and user last activity
if (! $project->isEmpty() && $project->getId() !== 'console') {
if ($project->getId() !== 'console') {
$accessedAt = $project->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
@@ -369,8 +391,11 @@ Http::init()
}
if (! empty($user->getId())) {
$impersonatorUserId = $user->getAttribute('impersonatorUserId');
$accessedAt = $user->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
// Skip updating accessedAt for impersonated requests so we don't attribute activity to the target user.
if (! $impersonatorUserId && DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
$user->setAttribute('accessedAt', DateTime::now());
if ($project->getId() !== 'console' && $mode !== APP_MODE_ADMIN) {
@@ -386,9 +411,6 @@ Http::init()
}
// Steps 7-9: Access Control - Method, Namespace and Scope Validation
/**
* @var ?Method $method
*/
$method = $route->getLabel('sdk', false);
// Take the first method if there's more than one,
@@ -398,17 +420,26 @@ Http::init()
}
if (! empty($method)) {
$namespace = $method->getNamespace();
$namespace = \strtolower($method->getNamespace());
if (
array_key_exists($namespace, $project->getAttribute('services', []))
&& ! $project->getAttribute('services', [])[$namespace]
&& ! (User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
&& ! ($user->isPrivileged($authorization->getRoles()) || $user->isKey($authorization->getRoles()))
) {
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
}
}
// Step 8b: Check REST protocol status
if (
array_key_exists('rest', $project->getAttribute('apis', []))
&& ! $project->getAttribute('apis', [])['rest']
&& ! ($user->isPrivileged($authorization->getRoles()) || $user->isKey($authorization->getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
// Step 9: Validate scope permissions
$allowed = (array) $route->getLabel('scope', 'none');
if (empty(\array_intersect($allowed, $scopes))) {
@@ -443,105 +474,109 @@ Http::init()
Http::init()
->groups(['api'])
->inject('utopia')
->inject('route')
->inject('request')
->inject('response')
->inject('project')
->inject('user')
->inject('timelimit')
->inject('devKey')
->inject('authorization')
->action(function (Route $route, Request $request, Response $response, Document $project, User $user, callable $timelimit, Document $devKey, Authorization $authorization) {
$response->setUser($user);
$request->setUser($user);
$roles = $authorization->getRoles();
$shouldCheckAbuse = System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled'
&& ! $user->isKey($roles)
&& ! $user->isPrivileged($roles)
&& $devKey->isEmpty();
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$abuseKeyLabel = (! is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
$closestLimit = null;
foreach ($abuseKeyLabel as $abuseKey) {
$isRateLimited = false;
try {
$start = $request->getContentRangeStart();
$end = $request->getContentRangeEnd();
$timeLimit = $timelimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600));
$timeLimit
->setParam('{projectId}', $project->getId())
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname() . $route->getPath())
->setParam('{method}', $request->getMethod())
->setParam('{chunkId}', (int) ($start / ($end + 1 - $start)));
foreach ($request->getParams() as $key => $value) {
if (! empty($value)) {
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
}
}
$abuse = new Abuse($timeLimit);
$remaining = $timeLimit->remaining();
$limit = $timeLimit->limit();
$time = $timeLimit->time() + $route->getLabel('abuse-time', 3600);
if ($limit && ($remaining < $closestLimit || is_null($closestLimit))) {
$closestLimit = $remaining;
$response
->addHeader('X-RateLimit-Limit', $limit)
->addHeader('X-RateLimit-Remaining', $remaining)
->addHeader('X-RateLimit-Reset', $time);
}
if ($shouldCheckAbuse) {
$isRateLimited = $abuse->check();
}
} catch (\Throwable $th) {
\error_log((string) $th);
continue;
}
if ($isRateLimited) {
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED);
}
}
});
Http::init()
->groups(['api'])
->inject('route')
->inject('request')
->inject('response')
->inject('project')
->inject('user')
->inject('queueForEvents')
->inject('queueForMessaging')
->inject('queueForAudits')
->inject('queueForDeletes')
->inject('queueForDatabase')
->inject('queueForBuilds')
->inject('auditContext')
->inject('usage')
->inject('queueForFunctions')
->inject('queueForMails')
->inject('publisherForFunctions')
->inject('dbForProject')
->inject('timelimit')
->inject('resourceToken')
->inject('mode')
->inject('apiKey')
->inject('plan')
->inject('devKey')
->inject('telemetry')
->inject('platform')
->inject('authorization')
->action(function (Http $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Context $usage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) {
->inject('cacheControlForStorage')
->action(function (Route $route, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, AuditContext $auditContext, Context $usage, FunctionPublisher $publisherForFunctions, Database $dbForProject, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Telemetry $telemetry, array $platform, Authorization $authorization, callable $cacheControlForStorage) {
$route = $utopia->getRoute();
$response->setUser($user);
$request->setUser($user);
if (
array_key_exists('rest', $project->getAttribute('apis', []))
&& ! $project->getAttribute('apis', [])['rest']
&& ! (User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
/*
* Abuse Check
*/
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$timeLimitArray = [];
$abuseKeyLabel = (! is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
foreach ($abuseKeyLabel as $abuseKey) {
$start = $request->getContentRangeStart();
$end = $request->getContentRangeEnd();
$timeLimit = $timelimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600));
$timeLimit
->setParam('{projectId}', $project->getId())
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname() . $route->getPath())
->setParam('{method}', $request->getMethod())
->setParam('{chunkId}', (int) ($start / ($end + 1 - $start)));
$timeLimitArray[] = $timeLimit;
}
$closestLimit = null;
$roles = $authorization->getRoles();
$isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles);
foreach ($timeLimitArray as $timeLimit) {
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
if (! empty($value)) {
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
}
}
$abuse = new Abuse($timeLimit);
$remaining = $timeLimit->remaining();
$limit = $timeLimit->limit();
$time = $timeLimit->time() + $route->getLabel('abuse-time', 3600);
if ($limit && ($remaining < $closestLimit || is_null($closestLimit))) {
$closestLimit = $remaining;
$response
->addHeader('X-RateLimit-Limit', $limit)
->addHeader('X-RateLimit-Remaining', $remaining)
->addHeader('X-RateLimit-Reset', $time);
}
$enabled = System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled';
if (
$enabled // Abuse is enabled
&& ! $isAppUser // User is not API key
&& ! $isPrivilegedUser // User is not an admin
&& $devKey->isEmpty() // request doesn't not contain development key
&& $abuse->check() // Route is rate-limited
) {
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED);
}
}
$path = $route->getPath();
$databaseType = match (true) {
str_contains($path, '/documentsdb') => DATABASE_TYPE_DOCUMENTSDB,
str_contains($path, '/vectorsdb') => DATABASE_TYPE_VECTORSDB,
default => '',
};
/**
* TODO: (@loks0n)
@@ -555,43 +590,33 @@ Http::init()
->setProject($project)
->setUser($user);
$queueForAudits
->setMode($mode)
->setUserAgent($request->getUserAgent(''))
->setIP($request->getIP())
->setHostname($request->getHostname())
->setEvent($route->getLabel('audits.event', ''))
->setProject($project);
$auditContext->mode = $mode;
$auditContext->userAgent = $request->getUserAgent('');
$auditContext->ip = $request->getIP();
$auditContext->hostname = $request->getHostname();
$auditContext->event = $route->getLabel('audits.event', '');
$auditContext->project = $project;
/* If a session exists, use the user associated with the session */
if (! $user->isEmpty()) {
$userClone = clone $user;
// $user doesn't support `type` and can cause unintended effects.
$userClone->setAttribute('type', ACTIVITY_TYPE_USER);
$queueForAudits->setUser($userClone);
if (empty($user->getAttribute('type'))) {
$userClone->setAttribute('type', $mode === APP_MODE_ADMIN ? ACTOR_TYPE_ADMIN : ACTOR_TYPE_USER);
}
$auditContext->user = $userClone;
}
/* Auto-set projects */
$queueForDeletes->setProject($project);
$queueForDatabase->setProject($project);
$queueForMessaging->setProject($project);
$queueForFunctions->setProject($project);
$queueForBuilds->setProject($project);
$queueForMails->setProject($project);
/* Auto-set platforms */
$queueForFunctions->setPlatform($platform);
$queueForBuilds->setPlatform($platform);
$queueForMails->setPlatform($platform);
$useCache = $route->getLabel('cache', false);
$storageCacheOperationsCounter = $telemetry->createCounter('storage.cache.operations.load');
if ($useCache) {
$route = $utopia->match($request);
$roles = $authorization->getRoles();
$isAppUser = $user->isKey($roles);
$isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview';
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && ! User::isPrivileged($authorization->getRoles());
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && ! $user->isPrivileged($roles);
$key = $request->cacheIdentifier();
Span::add('storage.cache.key', $key);
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
@@ -600,15 +625,16 @@ Http::init()
$data = $cache->load($key, $timestamp);
if (! empty($data) && ! $cacheLog->isEmpty()) {
$cacheControl = \sprintf('private, max-age=%d', $timestamp);
$parts = explode('/', $cacheLog->getAttribute('resourceType', ''));
$type = $parts[0] ?? null;
$type = $parts[0];
if ($type === 'bucket' && (! $isImageTransformation || ! $isDisabled)) {
$bucketId = $parts[1] ?? null;
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isToken = ! $resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isPrivilegedUser = $user->isPrivileged($roles);
if ($bucket->isEmpty() || (! $bucket->getAttribute('enabled') && ! $isAppUser && ! $isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -640,8 +666,10 @@ Http::init()
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
Span::add('storage.bucket.id', $bucketId);
Span::add('storage.file.id', $fileId);
// Do not update transformedAt if it's a console user
if (! User::isPrivileged($authorization->getRoles())) {
if (! $user->isPrivileged($authorization->getRoles())) {
$transformedAt = $file->getAttribute('transformedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
$file->setAttribute('transformedAt', DateTime::now());
@@ -650,18 +678,44 @@ Http::init()
])));
}
}
if ($isImageTransformation) {
$cacheControl = $cacheControlForStorage(new StorageCacheControl(
source: CacheControl::SOURCE_CACHE,
user: $user,
maxAge: $timestamp,
project: $project,
bucket: $bucket,
file: $file,
resourceToken: $resourceToken,
fileSecurity: $fileSecurity,
cacheLog: $cacheLog,
route: $route,
));
}
}
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
$authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), new Document([
'accessedAt' => DateTime::now(),
])));
// Refresh the filesystem file's mtime so TTL-based expiry in cache->load() stays valid
$cache->save($key, $data);
}
$response
->addHeader('Cache-Control', sprintf('private, max-age=%d', $timestamp))
->addHeader('Cache-Control', $cacheControl)
->addHeader('X-Appwrite-Cache', 'hit')
->setContentType($cacheLog->getAttribute('mimeType'));
$storageCacheOperationsCounter->add(1, ['result' => 'hit']);
if (! $isImageTransformation || ! $isDisabled) {
Span::add('storage.cache.hit', true);
$response->send($data);
}
} else {
$storageCacheOperationsCounter->add(1, ['result' => 'miss']);
Span::add('storage.cache.hit', false);
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Pragma', 'no-cache')
@@ -675,7 +729,7 @@ Http::init()
->groups(['session'])
->inject('user')
->inject('request')
->action(function (Document $user, Request $request) {
->action(function (User $user, Request $request) {
if (\str_contains($request->getURI(), 'oauth2')) {
return;
}
@@ -693,13 +747,17 @@ Http::init()
*/
Http::shutdown()
->groups(['session'])
->inject('utopia')
->inject('request')
->inject('response')
->inject('project')
->inject('dbForProject')
->action(function (Http $utopia, Request $request, Response $response, Document $project, Database $dbForProject) {
$sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT;
->action(function (Request $request, Response $response, Document $project, Database $dbForProject) {
$sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? 0;
if ($sessionLimit === 0) {
return;
}
$session = $response->getPayload();
$userId = $session['userId'] ?? '';
if (empty($userId)) {
@@ -727,20 +785,17 @@ Http::shutdown()
Http::shutdown()
->groups(['api'])
->inject('utopia')
->inject('route')
->inject('request')
->inject('response')
->inject('project')
->inject('user')
->inject('queueForEvents')
->inject('queueForAudits')
->inject('auditContext')
->inject('publisherForAudits')
->inject('usage')
->inject('publisherForUsage')
->inject('queueForDeletes')
->inject('queueForDatabase')
->inject('queueForBuilds')
->inject('queueForMessaging')
->inject('queueForFunctions')
->inject('publisherForFunctions')
->inject('queueForWebhooks')
->inject('queueForRealtime')
->inject('dbForProject')
@@ -749,7 +804,8 @@ Http::shutdown()
->inject('eventProcessor')
->inject('bus')
->inject('apiKey')
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Audit $queueForAudits, Context $usage, UsagePublisher $publisherForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor, Bus $bus, ?Key $apiKey) use ($parseLabel) {
->inject('mode')
->action(function (Route $route, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, AuditContext $auditContext, Audit $publisherForAudits, Context $usage, UsagePublisher $publisherForUsage, FunctionPublisher $publisherForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor, Bus $bus, ?Key $apiKey, string $mode) use ($parseLabel) {
$responsePayload = $response->getPayload();
@@ -778,9 +834,15 @@ Http::shutdown()
if (! empty($functionsEvents)) {
foreach ($generatedEvents as $event) {
if (isset($functionsEvents[$event])) {
$queueForFunctions
->from($queueForEvents)
->trigger();
$publisherForFunctions->enqueue(FunctionMessage::fromEvent(
event: $queueForEvents->getEvent(),
params: $queueForEvents->getParams(),
project: $queueForEvents->getProject(),
user: $queueForEvents->getUser(),
userId: $queueForEvents->getUserId(),
payload: $queueForEvents->getPayload(),
platform: $queueForEvents->getPlatform(),
));
break;
}
}
@@ -799,7 +861,6 @@ Http::shutdown()
}
}
$route = $utopia->getRoute();
$requestParams = $route->getParamsValues();
/**
@@ -842,18 +903,20 @@ Http::shutdown()
*/
$pattern = $route->getLabel('audits.resource', null);
if (! empty($pattern)) {
$resource = $parseLabel($pattern, $responsePayload, $requestParams, $user);
$resource = $parseLabel($pattern, $responsePayload, $requestParams, $user, $project);
if (! empty($resource) && $resource !== $pattern) {
$queueForAudits->setResource($resource);
$auditContext->resource = $resource;
}
}
if (! $user->isEmpty()) {
$userClone = clone $user;
// $user doesn't support `type` and can cause unintended effects.
$userClone->setAttribute('type', ACTIVITY_TYPE_USER);
$queueForAudits->setUser($userClone);
} elseif ($queueForAudits->getUser() === null || $queueForAudits->getUser()->isEmpty()) {
if (empty($user->getAttribute('type'))) {
$userClone->setAttribute('type', $mode === APP_MODE_ADMIN ? ACTOR_TYPE_ADMIN : ACTOR_TYPE_USER);
}
$auditContext->user = $userClone;
} elseif ($auditContext->user === null || $auditContext->user->isEmpty()) {
/**
* User in the request is empty, and no user was set for auditing previously.
* This indicates:
@@ -865,46 +928,27 @@ Http::shutdown()
$user = new User([
'$id' => '',
'status' => true,
'type' => ACTIVITY_TYPE_GUEST,
'type' => ACTOR_TYPE_GUEST,
'email' => 'guest.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => 'Guest',
]);
$queueForAudits->setUser($user);
$auditContext->user = $user;
}
if (! empty($queueForAudits->getResource()) && ! $queueForAudits->getUser()->isEmpty()) {
$auditUser = $auditContext->user;
if (! empty($auditContext->resource) && ! $auditUser->isEmpty()) {
/**
* audits.payload is switched to default true
* in order to auto audit payload for all endpoints
*/
$pattern = $route->getLabel('audits.payload', true);
if (! empty($pattern)) {
$queueForAudits->setPayload($responsePayload);
$auditContext->payload = $responsePayload;
}
foreach ($queueForEvents->getParams() as $key => $value) {
$queueForAudits->setParam($key, $value);
}
$queueForAudits->trigger();
}
if (! empty($queueForDeletes->getType())) {
$queueForDeletes->trigger();
}
if (! empty($queueForDatabase->getType())) {
$queueForDatabase->trigger();
}
if (! empty($queueForBuilds->getType())) {
$queueForBuilds->trigger();
}
if (! empty($queueForMessaging->getType())) {
$queueForMessaging->trigger();
$publisherForAudits->enqueue(AuditMessage::fromContext($auditContext));
}
// Cache label
@@ -912,15 +956,16 @@ Http::shutdown()
if ($useCache) {
$resource = $resourceType = null;
$data = $response->getPayload();
if (! empty($data['payload'])) {
$statusCode = $response->getStatusCode();
if (! empty($data['payload']) && $statusCode >= 200 && $statusCode < 300) {
$pattern = $route->getLabel('cache.resource', null);
if (! empty($pattern)) {
$resource = $parseLabel($pattern, $responsePayload, $requestParams, $user);
$resource = $parseLabel($pattern, $responsePayload, $requestParams, $user, $project);
}
$pattern = $route->getLabel('cache.resourceType', null);
if (! empty($pattern)) {
$resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user);
$resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user, $project);
}
$cache = new Cache(
@@ -962,7 +1007,7 @@ Http::shutdown()
}
if ($project->getId() !== 'console') {
if (! User::isPrivileged($authorization->getRoles())) {
if (! $user->isPrivileged($authorization->getRoles())) {
$bus->dispatch(new RequestCompleted(
project: $project->getArrayCopy(),
request: $request,
+6 -6
View File
@@ -9,6 +9,7 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Http\Http;
use Utopia\Http\Route;
use Utopia\System\System;
Http::init()
@@ -32,12 +33,13 @@ Http::init()
Http::init()
->groups(['auth'])
->inject('utopia')
->inject('route')
->inject('request')
->inject('project')
->inject('geodb')
->inject('user')
->inject('authorization')
->action(function (Http $utopia, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
->action(function (Route $route, Request $request, Document $project, Reader $geodb, User $user, Authorization $authorization) {
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
if (!empty($denylist && $project->getId() === 'console')) {
$countries = explode(',', $denylist);
@@ -48,10 +50,8 @@ Http::init()
}
}
$route = $utopia->match($request);
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp($authorization->getRoles());
$isPrivilegedUser = $user->isPrivileged($authorization->getRoles());
$isAppUser = $user->isKey($authorization->getRoles());
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return;
+78 -73
View File
@@ -1,14 +1,13 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/init.php';
require_once __DIR__ . '/init/span.php';
$setRequestContext = require __DIR__ . '/init/resources/request.php';
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Swoole\Constant;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
use Swoole\Http\Server;
use Swoole\Process;
use Swoole\Table;
use Swoole\Timer;
@@ -27,11 +26,12 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\DI\Container;
use Utopia\Http\Adapter\Swoole\Server;
use Utopia\Http\Files;
use Utopia\Http\Http;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Utopia\Pools\Group;
use Utopia\Span\Span;
use Utopia\System\System;
@@ -48,18 +48,33 @@ $certifiedDomains = new Table(100_000);
$certifiedDomains->column('value', Table::TYPE_INT, 1);
$certifiedDomains->create();
Http::setResource('riskyDomains', fn () => $riskyDomains);
Http::setResource('certifiedDomains', fn () => $certifiedDomains);
$http = new Server(
host: "0.0.0.0",
port: System::getEnv('PORT', 80),
mode: SWOOLE_PROCESS,
);
global $container;
$container->set('riskyDomains', fn () => $riskyDomains);
$container->set('certifiedDomains', fn () => $certifiedDomains);
$container->set('pools', function ($register) {
return $register->get('pools');
}, ['register']);
$payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
$totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
$swoole = new Server(
host: "0.0.0.0",
port: System::getEnv('PORT', 80),
settings: [
Constant::OPTION_WORKER_NUM => $totalWorkers,
Constant::OPTION_DISPATCH_FUNC => dispatch(...),
Constant::OPTION_DISPATCH_MODE => SWOOLE_DISPATCH_UIDMOD,
Constant::OPTION_HTTP_COMPRESSION => false,
Constant::OPTION_PACKAGE_MAX_LENGTH => $payloadSize,
Constant::OPTION_OUTPUT_BUFFER_SIZE => $payloadSize,
Constant::OPTION_TASK_WORKER_NUM => 1, // required for the task to fetch domains background
],
resources: $container,
);
$http = $swoole->getServer();
/**
* Assigns HTTP requests to worker threads by analyzing its payload/content.
*
@@ -68,16 +83,16 @@ $totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intva
* riskier tasks to a dedicated worker subset. Prefers idle workers, with fallback to random selection if necessary.
* doc: https://openswoole.com/docs/modules/swoole-server/configuration#dispatch_func
*
* @param Server $server Swoole server instance.
* @param \Swoole\Http\Server $server Swoole server instance.
* @param int $fd client ID
* @param int $type the type of data and its current state
* @param string|null $data Request content for categorization.
* @global int $totalThreads Total number of workers.
* @return int Chosen worker ID for the request.
*/
function dispatch(Server $server, int $fd, int $type, $data = null): int
function dispatch(\Swoole\Http\Server $server, int $fd, int $type, $data = null): int
{
$resolveWorkerId = function (Server $server, $data = null) {
$resolveWorkerId = function (\Swoole\Http\Server $server, $data = null) {
global $totalWorkers, $riskyDomains;
// If data is not set we can send request to any worker
@@ -103,7 +118,7 @@ function dispatch(Server $server, int $fd, int $type, $data = null): int
$lines = explode("\n", $data, 3);
$request = $lines[0];
if (count($lines) > 1) {
$domain = trim(explode('Host: ', $lines[1])[1]);
$domain = trim(explode('Host: ', $lines[1])[1] ?? '');
}
// Sync executions are considered risky
@@ -160,18 +175,6 @@ function dispatch(Server $server, int $fd, int $type, $data = null): int
return $workerId;
}
$http
->set([
Constant::OPTION_WORKER_NUM => $totalWorkers,
Constant::OPTION_DISPATCH_FUNC => dispatch(...),
Constant::OPTION_DISPATCH_MODE => SWOOLE_DISPATCH_UIDMOD,
Constant::OPTION_HTTP_COMPRESSION => false,
Constant::OPTION_PACKAGE_MAX_LENGTH => $payloadSize,
Constant::OPTION_OUTPUT_BUFFER_SIZE => $payloadSize,
Constant::OPTION_TASK_WORKER_NUM => 1, // required for the task to fetch domains background
]);
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
});
@@ -188,13 +191,11 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server) {
Console::success('Reload completed...');
});
Http::setResource('bus', function ($register, $utopia) {
return $register->get('bus')->setResolver(fn (string $name) => $utopia->getResource($name));
}, ['register', 'utopia']);
$container->set('bus', fn ($register) => $register->get('bus')->setResolver(fn (string $name) => $swoole->context()->get($name)), ['register']);
include __DIR__ . '/controllers/general.php';
function createDatabase(Http $app, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void
function createDatabase(Container $resources, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void
{
$max = 15;
$sleep = 2;
@@ -203,7 +204,7 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
while (true) {
try {
$attempts++;
$resource = $app->getResource($resourceKey);
$resource = $resources->get($resourceKey);
/* @var $database Database */
$database = is_callable($resource) ? $resource() : $resource;
break; // exit loop on success
@@ -286,23 +287,21 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
Span::current()?->finish();
}
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $totalWorkers, $register) {
$app = new Http('UTC');
$http->on(Constant::EVENT_START, function ($http) use ($payloadSize, $totalWorkers, $container) {
/** @var \Utopia\Pools\Group $pools */
$pools = $container->get('pools');
go(function () use ($register, $app) {
$pools = $register->get('pools');
/** @var Group $pools */
Http::setResource('pools', fn () => $pools);
go(function () use ($container, $pools) {
/** @var array $collections */
$collections = Config::getParam('collections', []);
// create logs database first, `getLogsDB` is a callable.
createDatabase($app, 'getLogsDB', 'logs', $collections['logs'], $pools);
createDatabase($container, 'getLogsDB', 'logs', $collections['logs'], $pools);
// create appwrite database, `dbForPlatform` is a direct access call.
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $app) {
$authorization = $app->getResource('authorization');
createDatabase($container, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $container) {
$authorization = $container->get('authorization');
if ($dbForPlatform->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
$adapter = new AdapterDatabase($dbForPlatform);
@@ -409,13 +408,21 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $tot
});
$projectCollections = $collections['projects'];
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
$sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', ''));
$sharedTablesV2 = \array_diff($sharedTables, $sharedTablesV1);
$documentsSharedTables = \explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES', ''));
$vectorSharedTables = \explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES', ''));
$cache = $app->getResource('cache');
$cache = $container->get('cache');
foreach ($sharedTablesV2 as $hostname) {
// All shared tables pools that need project metadata collections
$allSharedTables = \array_values(\array_unique(\array_filter([
...$sharedTables,
...$documentsSharedTables,
...$vectorSharedTables,
])));
foreach ($allSharedTables as $hostname) {
Span::init('database.setup');
Span::add('database.hostname', $hostname);
@@ -492,14 +499,11 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $tot
});
});
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register, $files) {
$swoole->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files, $swoole, $setRequestContext) {
Span::init('http.request');
Http::setResource('swooleRequest', fn () => $swooleRequest);
Http::setResource('swooleResponse', fn () => $swooleResponse);
$request = new Request($swooleRequest);
$response = new Response($swooleResponse);
$request = new Request($utopiaRequest->getSwooleRequest());
$response = new Response($utopiaResponse->getSwooleResponse());
Span::add('http.method', $request->getMethod());
@@ -515,15 +519,18 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
return;
}
$app = new Http('UTC');
$app = new Http($swoole, 'UTC');
$app->context()->set('request', fn () => $request);
$app->context()->set('response', fn () => $response);
$app->context()->set('utopia', fn () => $app);
$setRequestContext($app->context());
$app->setCompression(System::getEnv('_APP_COMPRESSION_ENABLED', 'enabled') === 'enabled');
$app->setCompressionMinSize(intval(System::getEnv('_APP_COMPRESSION_MIN_SIZE_BYTES', '1024'))); // 1KB
$pools = $register->get('pools');
Http::setResource('pools', fn () => $pools);
try {
$authorization = $app->getResource('authorization');
$authorization = $app->context()->get('authorization');
$request->setAuthorization($authorization);
$response->setAuthorization($authorization);
@@ -532,25 +539,25 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
$app->run($request, $response);
$route = $app->getRoute();
$route = $app->match($request)?->route;
Span::add('http.path', $route?->getPath() ?? 'unknown');
} catch (\Throwable $th) {
Span::error($th);
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$logger = $app->getResource("logger");
$logger = $app->context()->get("logger");
if ($logger) {
try {
/** @var Utopia\Database\Document $user */
$user = $app->getResource('user');
$user = $app->context()->get('user');
} catch (\Throwable $_th) {
// All good, user is optional information for logger
}
$route = $app->getRoute();
$route = $app->match($request)?->route;
$log = $app->getResource("log");
$log = $app->context()->get("log");
if (isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
@@ -605,6 +612,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
}
}
$swooleResponse = $utopiaResponse->getSwooleResponse();
$swooleResponse->setStatusCode(500);
$output = ((Http::isDevelopment())) ? [
@@ -628,19 +636,16 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
});
// Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory
$http->on(Constant::EVENT_TASK, function () use ($register) {
$http->on(Constant::EVENT_TASK, function () use ($container) {
$lastSyncUpdate = null;
$pools = $register->get('pools');
Http::setResource('pools', fn () => $pools);
$app = new Http('UTC');
/** @var Utopia\Database\Database $dbForPlatform */
$dbForPlatform = $app->getResource('dbForPlatform');
$dbForPlatform = $container->get('dbForPlatform');
/** @var Table $riskyDomains */
$riskyDomains = $app->getResource('riskyDomains');
/** @var \Swoole\Table $riskyDomains */
$riskyDomains = $container->get('riskyDomains');
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $riskyDomains, &$lastSyncUpdate, $app) {
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $riskyDomains, &$lastSyncUpdate, $container) {
try {
$time = DateTime::now();
$limit = 1000;
@@ -657,7 +662,7 @@ $http->on(Constant::EVENT_TASK, function () use ($register) {
}
$results = [];
try {
$authorization = $app->getResource('authorization');
$authorization = $container->get('authorization');
$results = $authorization->skip(fn () => $dbForPlatform->find('rules', $queries));
} catch (Throwable $th) {
Console::error('rules ' . $th->getMessage());
@@ -707,4 +712,4 @@ $http->on(Constant::EVENT_TASK, function () use ($register) {
});
});
$http->start();
$swoole->start();
+1 -1
View File
@@ -12,7 +12,7 @@ Config::load('runtimes-v2', __DIR__ . '/../config/runtimes-v2.php', $configAdapt
Config::load('template-runtimes', __DIR__ . '/../config/template-runtimes.php', $configAdapter);
Config::load('events', __DIR__ . '/../config/events.php', $configAdapter);
Config::load('auth', __DIR__ . '/../config/auth.php', $configAdapter);
Config::load('apis', __DIR__ . '/../config/apis.php', $configAdapter); // List of APIs
Config::load('protocols', __DIR__ . '/../config/protocols.php', $configAdapter);
Config::load('errors', __DIR__ . '/../config/errors.php', $configAdapter);
Config::load('oAuthProviders', __DIR__ . '/../config/oAuthProviders.php', $configAdapter);
Config::load('sdks', __DIR__ . '/../config/sdks.php', $configAdapter);
+130 -12
View File
@@ -1,6 +1,13 @@
<?php
use Appwrite\Platform\Modules\Advisor\Enums\InsightCTAMethod;
use Appwrite\Platform\Modules\Advisor\Enums\InsightCTAService;
use Appwrite\Platform\Modules\Advisor\Enums\InsightSeverity;
use Appwrite\Platform\Modules\Advisor\Enums\InsightStatus;
use Appwrite\Platform\Modules\Advisor\Enums\InsightType;
use Appwrite\Platform\Modules\Advisor\Enums\ReportType;
use Appwrite\Platform\Modules\Compute\Specification;
use Utopia\System\System;
const APP_NAME = 'Appwrite';
const APP_DOMAIN = 'appwrite.io';
@@ -24,9 +31,6 @@ const APP_MODE_ADMIN = 'admin';
const APP_PAGING_LIMIT = 12;
const APP_LIMIT_COUNT = 5000;
const APP_LIMIT_USERS = 10_000;
const APP_LIMIT_USER_PASSWORD_HISTORY = 20;
const APP_LIMIT_USER_SESSIONS_MAX = 100;
const APP_LIMIT_USER_SESSIONS_DEFAULT = 10;
const APP_LIMIT_ANTIVIRUS = 20_000_000; //20MB
const APP_LIMIT_ENCRYPTION = 20_000_000; //20MB
const APP_LIMIT_COMPRESSION = 20_000_000; //20MB
@@ -46,14 +50,15 @@ const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
const APP_RESOURCE_TOKEN_ACCESS = 24 * 60 * 60; // 24 hours
const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 4321;
const APP_VERSION_STABLE = '1.8.1';
const APP_CACHE_BUSTER = 4326;
const APP_VERSION_STABLE = '1.9.5';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
const APP_DATABASE_ATTRIBUTE_DATETIME = 'datetime';
const APP_DATABASE_ATTRIBUTE_URL = 'url';
const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange';
const APP_DATABASE_ATTRIBUTE_BIGINT_RANGE = 'bigintRange';
const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange';
const APP_DATABASE_ATTRIBUTE_POINT = 'point';
const APP_DATABASE_ATTRIBUTE_LINE = 'line';
@@ -97,6 +102,7 @@ const APP_COMPUTE_DEPLOYMENT_MAX_RETENTION = 100 * 365; // 100 years
const APP_SDK_PLATFORM_SERVER = 'server';
const APP_SDK_PLATFORM_CLIENT = 'client';
const APP_SDK_PLATFORM_CONSOLE = 'console';
const APP_SDK_PLATFORM_STATIC = 'static';
const APP_VCS_GITHUB_USERNAME = 'Appwrite';
const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io';
const APP_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite';
@@ -153,11 +159,14 @@ const SESSION_PROVIDER_TOKEN = 'token';
const SESSION_PROVIDER_SERVER = 'server';
/**
* Activity associated with user or the app.
* Actor that performed the request (user, admin, guest, or API key).
*/
const ACTIVITY_TYPE_APP = 'app';
const ACTIVITY_TYPE_USER = 'user';
const ACTIVITY_TYPE_GUEST = 'guest';
const ACTOR_TYPE_USER = 'user';
const ACTOR_TYPE_ADMIN = 'admin';
const ACTOR_TYPE_GUEST = 'guest';
const ACTOR_TYPE_KEY_PROJECT = 'keyProject';
const ACTOR_TYPE_KEY_ACCOUNT = 'keyAccount';
const ACTOR_TYPE_KEY_ORGANIZATION = 'keyOrganization';
/**
* MFA
@@ -186,12 +195,12 @@ const BUILD_TYPE_RETRY = 'retry';
// Deletion Types
const ENABLE_EXECUTIONS_LIMIT_ON_ROUTE = false;
\define('ENABLE_EXECUTIONS_LIMIT_ON_ROUTE', System::getEnv('_APP_EXECUTIONS_LIMIT_ON_ROUTE', 'disabled') === 'enabled');
const DELETE_TYPE_DATABASES = 'databases';
const DELETE_TYPE_DOCUMENT = 'document';
const DELETE_TYPE_COLLECTIONS = 'collections';
const DELETE_TYPE_TRANSACTION = 'transaction';
const DELETE_TYPE_TRANSACTIONS = 'transactions';
const DELETE_TYPE_EXPIRED_TRANSACTIONS = 'expired_transactions';
const DELETE_TYPE_PROJECTS = 'projects';
const DELETE_TYPE_SITES = 'sites';
@@ -219,6 +228,7 @@ const DELETE_TYPE_EXPIRED_TARGETS = 'invalid_targets';
const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
const DELETE_TYPE_CSV_EXPORTS = 'csv_exports';
const DELETE_TYPE_MAINTENANCE = 'maintenance';
const DELETE_TYPE_REPORT = 'report';
// Rule statuses
const RULE_STATUS_CREATED = 'created'; // This is also the status when domain DNS verification fails.
@@ -242,6 +252,7 @@ const APP_AUTH_TYPE_KEY = 'Key';
const APP_AUTH_TYPE_ADMIN = 'Admin';
// Response related
const MAX_OUTPUT_CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
const APP_LIMIT_UPLOAD_CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
const APP_FUNCTION_LOG_LENGTH_LIMIT = 1000000;
const APP_FUNCTION_ERROR_LENGTH_LIMIT = 1000000;
// Function headers
@@ -253,7 +264,7 @@ const MESSAGE_TYPE_SMS = 'sms';
const MESSAGE_TYPE_PUSH = 'push';
// API key types
const API_KEY_STANDARD = 'standard';
const API_KEY_DYNAMIC = 'dynamic';
const API_KEY_EPHEMERAL = 'ephemeral';
const API_KEY_ORGANIZATION = 'organization';
const API_KEY_ACCOUNT = 'account';
// Usage metrics
@@ -288,6 +299,45 @@ const METRIC_DATABASES_OPERATIONS_READS = 'databases.operations.reads';
const METRIC_DATABASE_ID_OPERATIONS_READS = '{databaseInternalId}.databases.operations.reads';
const METRIC_DATABASES_OPERATIONS_WRITES = 'databases.operations.writes';
const METRIC_DATABASE_ID_OPERATIONS_WRITES = '{databaseInternalId}.databases.operations.writes';
// documentsdb
const METRIC_DATABASES_DOCUMENTSDB = 'documentsdb.databases';
const METRIC_COLLECTIONS_DOCUMENTSDB = 'documentsdb.collections';
const METRIC_DATABASES_STORAGE_DOCUMENTSDB = 'documentsdb.databases.storage';
const METRIC_DATABASE_ID_COLLECTIONS_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.collections';
const METRIC_DATABASE_ID_STORAGE_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.databases.storage';
const METRIC_DOCUMENTS_DOCUMENTSDB = 'documentsdb.documents';
const METRIC_DATABASE_ID_DOCUMENTS_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.documents';
const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.{collectionInternalId}.documents';
const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.{collectionInternalId}.databases.storage';
const METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB = 'documentsdb.databases.operations.reads';
const METRIC_DATABASE_ID_OPERATIONS_READS_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.databases.operations.reads';
const METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB = 'documentsdb.databases.operations.writes';
const METRIC_DATABASE_ID_OPERATIONS_WRITES_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.databases.operations.writes';
// vectorsdb
const METRIC_DATABASES_VECTORSDB = 'vectorsdb.databases';
const METRIC_COLLECTIONS_VECTORSDB = 'vectorsdb.collections';
const METRIC_DATABASES_STORAGE_VECTORSDB = 'vectorsdb.databases.storage';
const METRIC_DATABASE_ID_COLLECTIONS_VECTORSDB = 'vectorsdb.{databaseInternalId}.collections';
const METRIC_DATABASE_ID_STORAGE_VECTORSDB = 'vectorsdb.{databaseInternalId}.databases.storage';
const METRIC_DOCUMENTS_VECTORSDB = 'vectorsdb.documents';
const METRIC_DATABASE_ID_DOCUMENTS_VECTORSDB = 'vectorsdb.{databaseInternalId}.documents';
const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS_VECTORSDB = 'vectorsdb.{databaseInternalId}.{collectionInternalId}.documents';
const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE_VECTORSDB = 'vectorsdb.{databaseInternalId}.{collectionInternalId}.databases.storage';
const METRIC_DATABASES_OPERATIONS_READS_VECTORSDB = 'vectorsdb.databases.operations.reads';
const METRIC_DATABASE_ID_OPERATIONS_READS_VECTORSDB = 'vectorsdb.{databaseInternalId}.databases.operations.reads';
const METRIC_DATABASES_OPERATIONS_WRITES_VECTORSDB = 'vectorsdb.databases.operations.writes';
const METRIC_DATABASE_ID_OPERATIONS_WRITES_VECTORSDB = 'vectorsdb.{databaseInternalId}.databases.operations.writes';
const METRIC_EMBEDDINGS_TEXT = 'embeddings.text';
const METRIC_EMBEDDINGS_MODEL_TEXT = 'embeddings.text.{embeddingModel}';
const METRIC_EMBEDDINGS_TEXT_TOTAL_ERROR = 'embeddings.text.totalErrors';
const METRIC_EMBEDDINGS_MODEL_TEXT_TOTAL_ERROR = 'embeddings.text.{embeddingModel}.totalErrors';
const METRIC_EMBEDDINGS_TEXT_TOTAL_DURATION = 'embeddings.text.totalDuration';
const METRIC_EMBEDDINGS_MODEL_TEXT_TOTAL_DURATION = 'embeddings.text.{embeddingModel}.totalDuration';
const METRIC_EMBEDDINGS_TEXT_TOTAL_TOKENS = 'embeddings.text.totalTokens';
const METRIC_EMBEDDINGS_MODEL_TEXT_TOTAL_TOKENS = 'embeddings.text.{embeddingModel}.totalTokens';
const METRIC_BUCKETS = 'buckets';
const METRIC_FILES = 'files';
const METRIC_FILES_STORAGE = 'files.storage';
@@ -344,6 +394,7 @@ const METRIC_NETWORK_OUTBOUND = 'network.outbound';
const METRIC_MAU = 'users.mau';
const METRIC_DAU = 'users.dau';
const METRIC_WAU = 'users.wau';
const METRIC_USERS_PRESENCE = 'users.presence';
const METRIC_WEBHOOKS = 'webhooks';
const METRIC_PLATFORMS = 'platforms';
const METRIC_PROVIDERS = 'providers';
@@ -380,6 +431,56 @@ const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers';
const RESOURCE_TYPE_MESSAGES = 'messages';
const RESOURCE_TYPE_EXECUTIONS = 'executions';
const RESOURCE_TYPE_VCS = 'vcs';
const RESOURCE_TYPE_EMBEDDINGS_TEXT = 'embeddingsText';
const RESOURCE_TYPE_INSIGHTS = 'insights';
const RESOURCE_TYPE_REPORTS = 'reports';
// Insight types — engine-specific so the CTA action can reference the right public API.
const ADVISOR_INSIGHT_TYPES = [
InsightType::DATABASE_INDEX->value, // legacy databases.createIndex
InsightType::TABLES_DB_INDEX->value, // tablesDB.createIndex
InsightType::DOCUMENTS_DB_INDEX->value, // documentsDB.createIndex
InsightType::VECTORS_DB_INDEX->value, // vectorsDB.createIndex
InsightType::DATABASE_PERFORMANCE->value,
InsightType::SITE_PERFORMANCE->value,
InsightType::SITE_ACCESSIBILITY->value,
InsightType::SITE_SEO->value,
InsightType::FUNCTION_PERFORMANCE->value,
];
// Public API services (SDK namespaces) that an insight CTA's `service` can reference.
// Analyzers must pick the one matching the engine the resource lives in.
const ADVISOR_CTA_SERVICES = [
InsightCTAService::DATABASES->value, // legacy
InsightCTAService::TABLES_DB->value,
InsightCTAService::DOCUMENTS_DB->value,
InsightCTAService::VECTORS_DB->value,
];
// Public API method names that an insight CTA's `method` can reference for index suggestions.
const ADVISOR_CTA_METHODS = [
InsightCTAMethod::CREATE_INDEX->value,
];
// Insight severities
const ADVISOR_SEVERITIES = [
InsightSeverity::INFO->value,
InsightSeverity::WARNING->value,
InsightSeverity::CRITICAL->value,
];
// Insight statuses
const ADVISOR_STATUSES = [
InsightStatus::ACTIVE->value,
InsightStatus::DISMISSED->value,
];
// Report types
const ADVISOR_REPORT_TYPES = [
ReportType::LIGHTHOUSE->value,
ReportType::AUDIT->value,
ReportType::DATABASE_ANALYZER->value,
];
// Resource types for Tokens
const TOKENS_RESOURCE_TYPE_FILES = 'files';
@@ -401,3 +502,20 @@ const CACHE_RECONNECT_RETRY_DELAY = 1000;
// Project status
const PROJECT_STATUS_ACTIVE = 'active';
// Database types
const DATABASE_TYPE_LEGACY = 'legacy';
const DATABASE_TYPE_TABLESDB = 'tablesdb';
const DATABASE_TYPE_DOCUMENTSDB = 'documentsdb';
const DATABASE_TYPE_VECTORSDB = 'vectorsdb';
// CSV import/export allowed database types
const CSV_ALLOWED_DATABASE_TYPES = [
DATABASE_TYPE_LEGACY,
DATABASE_TYPE_TABLESDB,
DATABASE_TYPE_VECTORSDB
];
const VCS_DEPLOYMENT_SKIP_PATTERNS = [
'[skip ci]',
];
+22 -1
View File
@@ -1,5 +1,6 @@
<?php
use Appwrite\Network\Platform;
use Appwrite\OpenSSL\OpenSSL;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -123,11 +124,17 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
$platforms = $database->getAuthorization()->skip(fn () => $database
->find('platforms', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
foreach ($platforms as $platform) {
$platform->setAttribute('type', Platform::mapDeprecatedType($platform->getAttribute('type')));
}
return $platforms;
}
);
@@ -468,3 +475,17 @@ Database::addFilter(
]));
}
);
Database::addFilter(
'subQueryReportInsights',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database->find('insights', [
Query::equal('projectInternalId', [$document->getAttribute('projectInternalId')]),
Query::equal('reportInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
+8 -1
View File
@@ -1,9 +1,9 @@
<?php
use Appwrite\Network\Validator\Email;
use Utopia\Database\Database;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Structure;
use Utopia\Emails\Validator\Email;
use Utopia\Validator\IP;
use Utopia\Validator\Range;
use Utopia\Validator\URL;
@@ -36,6 +36,13 @@ Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) {
return new Range($min, $max, Range::TYPE_INTEGER);
}, Database::VAR_INTEGER);
// BigInt uses a dedicated bigintRange format name to avoid clobbering `intRange`.
Structure::addFormat(APP_DATABASE_ATTRIBUTE_BIGINT_RANGE, function ($attribute) {
$min = $attribute['formatOptions']['min'] ?? -INF;
$max = $attribute['formatOptions']['max'] ?? INF;
return new Range($min, $max, Range::TYPE_INTEGER);
}, Database::VAR_BIGINT);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) {
$min = $attribute['formatOptions']['min'] ?? -INF;
$max = $attribute['formatOptions']['max'] ?? INF;
+179 -9
View File
@@ -11,6 +11,7 @@ use Appwrite\Utopia\Response\Model\AlgoScryptModified;
use Appwrite\Utopia\Response\Model\AlgoSha;
use Appwrite\Utopia\Response\Model\Any;
use Appwrite\Utopia\Response\Model\Attribute;
use Appwrite\Utopia\Response\Model\AttributeBigInt;
use Appwrite\Utopia\Response\Model\AttributeBoolean;
use Appwrite\Utopia\Response\Model\AttributeDatetime;
use Appwrite\Utopia\Response\Model\AttributeEmail;
@@ -22,6 +23,7 @@ use Appwrite\Utopia\Response\Model\AttributeLine;
use Appwrite\Utopia\Response\Model\AttributeList;
use Appwrite\Utopia\Response\Model\AttributeLongtext;
use Appwrite\Utopia\Response\Model\AttributeMediumtext;
use Appwrite\Utopia\Response\Model\AttributeObject;
use Appwrite\Utopia\Response\Model\AttributePoint;
use Appwrite\Utopia\Response\Model\AttributePolygon;
use Appwrite\Utopia\Response\Model\AttributeRelationship;
@@ -29,12 +31,14 @@ use Appwrite\Utopia\Response\Model\AttributeString;
use Appwrite\Utopia\Response\Model\AttributeText;
use Appwrite\Utopia\Response\Model\AttributeURL;
use Appwrite\Utopia\Response\Model\AttributeVarchar;
use Appwrite\Utopia\Response\Model\AttributeVector;
use Appwrite\Utopia\Response\Model\AuthProvider;
use Appwrite\Utopia\Response\Model\BaseList;
use Appwrite\Utopia\Response\Model\Branch;
use Appwrite\Utopia\Response\Model\Bucket;
use Appwrite\Utopia\Response\Model\Collection;
use Appwrite\Utopia\Response\Model\Column;
use Appwrite\Utopia\Response\Model\ColumnBigInt;
use Appwrite\Utopia\Response\Model\ColumnBoolean;
use Appwrite\Utopia\Response\Model\ColumnDatetime;
use Appwrite\Utopia\Response\Model\ColumnEmail;
@@ -54,6 +58,11 @@ use Appwrite\Utopia\Response\Model\ColumnString;
use Appwrite\Utopia\Response\Model\ColumnText;
use Appwrite\Utopia\Response\Model\ColumnURL;
use Appwrite\Utopia\Response\Model\ColumnVarchar;
use Appwrite\Utopia\Response\Model\ConsoleKeyScope;
use Appwrite\Utopia\Response\Model\ConsoleKeyScopeList;
use Appwrite\Utopia\Response\Model\ConsoleOAuth2Provider;
use Appwrite\Utopia\Response\Model\ConsoleOAuth2ProviderList;
use Appwrite\Utopia\Response\Model\ConsoleOAuth2ProviderParameter;
use Appwrite\Utopia\Response\Model\ConsoleVariables;
use Appwrite\Utopia\Response\Model\Continent;
use Appwrite\Utopia\Response\Model\Country;
@@ -65,6 +74,8 @@ use Appwrite\Utopia\Response\Model\DetectionRuntime;
use Appwrite\Utopia\Response\Model\DetectionVariable;
use Appwrite\Utopia\Response\Model\DevKey;
use Appwrite\Utopia\Response\Model\Document as ModelDocument;
use Appwrite\Utopia\Response\Model\Embedding;
use Appwrite\Utopia\Response\Model\EphemeralKey;
use Appwrite\Utopia\Response\Model\Error;
use Appwrite\Utopia\Response\Model\ErrorDev;
use Appwrite\Utopia\Response\Model\Execution;
@@ -81,6 +92,8 @@ use Appwrite\Utopia\Response\Model\HealthTime;
use Appwrite\Utopia\Response\Model\HealthVersion;
use Appwrite\Utopia\Response\Model\Identity;
use Appwrite\Utopia\Response\Model\Index;
use Appwrite\Utopia\Response\Model\Insight;
use Appwrite\Utopia\Response\Model\InsightCTA;
use Appwrite\Utopia\Response\Model\Installation;
use Appwrite\Utopia\Response\Model\JWT;
use Appwrite\Utopia\Response\Model\Key;
@@ -102,14 +115,77 @@ use Appwrite\Utopia\Response\Model\MigrationReport;
use Appwrite\Utopia\Response\Model\Mock;
use Appwrite\Utopia\Response\Model\MockNumber;
use Appwrite\Utopia\Response\Model\None;
use Appwrite\Utopia\Response\Model\OAuth2Amazon;
use Appwrite\Utopia\Response\Model\OAuth2Apple;
use Appwrite\Utopia\Response\Model\OAuth2Auth0;
use Appwrite\Utopia\Response\Model\OAuth2Authentik;
use Appwrite\Utopia\Response\Model\OAuth2Autodesk;
use Appwrite\Utopia\Response\Model\OAuth2Bitbucket;
use Appwrite\Utopia\Response\Model\OAuth2Bitly;
use Appwrite\Utopia\Response\Model\OAuth2Box;
use Appwrite\Utopia\Response\Model\OAuth2Dailymotion;
use Appwrite\Utopia\Response\Model\OAuth2Discord;
use Appwrite\Utopia\Response\Model\OAuth2Disqus;
use Appwrite\Utopia\Response\Model\OAuth2Dropbox;
use Appwrite\Utopia\Response\Model\OAuth2Etsy;
use Appwrite\Utopia\Response\Model\OAuth2Facebook;
use Appwrite\Utopia\Response\Model\OAuth2Figma;
use Appwrite\Utopia\Response\Model\OAuth2FusionAuth;
use Appwrite\Utopia\Response\Model\OAuth2GitHub;
use Appwrite\Utopia\Response\Model\OAuth2Gitlab;
use Appwrite\Utopia\Response\Model\OAuth2Google;
use Appwrite\Utopia\Response\Model\OAuth2Keycloak;
use Appwrite\Utopia\Response\Model\OAuth2Kick;
use Appwrite\Utopia\Response\Model\OAuth2Linkedin;
use Appwrite\Utopia\Response\Model\OAuth2Microsoft;
use Appwrite\Utopia\Response\Model\OAuth2Notion;
use Appwrite\Utopia\Response\Model\OAuth2Oidc;
use Appwrite\Utopia\Response\Model\OAuth2Okta;
use Appwrite\Utopia\Response\Model\OAuth2Paypal;
use Appwrite\Utopia\Response\Model\OAuth2Podio;
use Appwrite\Utopia\Response\Model\OAuth2ProviderList;
use Appwrite\Utopia\Response\Model\OAuth2Salesforce;
use Appwrite\Utopia\Response\Model\OAuth2Slack;
use Appwrite\Utopia\Response\Model\OAuth2Spotify;
use Appwrite\Utopia\Response\Model\OAuth2Stripe;
use Appwrite\Utopia\Response\Model\OAuth2Tradeshift;
use Appwrite\Utopia\Response\Model\OAuth2Twitch;
use Appwrite\Utopia\Response\Model\OAuth2WordPress;
use Appwrite\Utopia\Response\Model\OAuth2X;
use Appwrite\Utopia\Response\Model\OAuth2Yahoo;
use Appwrite\Utopia\Response\Model\OAuth2Yandex;
use Appwrite\Utopia\Response\Model\OAuth2Zoho;
use Appwrite\Utopia\Response\Model\OAuth2Zoom;
use Appwrite\Utopia\Response\Model\Phone;
use Appwrite\Utopia\Response\Model\Platform;
use Appwrite\Utopia\Response\Model\PlatformAndroid;
use Appwrite\Utopia\Response\Model\PlatformApple;
use Appwrite\Utopia\Response\Model\PlatformLinux;
use Appwrite\Utopia\Response\Model\PlatformList;
use Appwrite\Utopia\Response\Model\PlatformWeb;
use Appwrite\Utopia\Response\Model\PlatformWindows;
use Appwrite\Utopia\Response\Model\PolicyList;
use Appwrite\Utopia\Response\Model\PolicyMembershipPrivacy;
use Appwrite\Utopia\Response\Model\PolicyPasswordDictionary;
use Appwrite\Utopia\Response\Model\PolicyPasswordHistory;
use Appwrite\Utopia\Response\Model\PolicyPasswordPersonalData;
use Appwrite\Utopia\Response\Model\PolicySessionAlert;
use Appwrite\Utopia\Response\Model\PolicySessionDuration;
use Appwrite\Utopia\Response\Model\PolicySessionInvalidation;
use Appwrite\Utopia\Response\Model\PolicySessionLimit;
use Appwrite\Utopia\Response\Model\PolicyUserLimit;
use Appwrite\Utopia\Response\Model\Preferences;
use Appwrite\Utopia\Response\Model\Presence;
use Appwrite\Utopia\Response\Model\Project;
use Appwrite\Utopia\Response\Model\ProjectAuthMethod;
use Appwrite\Utopia\Response\Model\ProjectProtocol;
use Appwrite\Utopia\Response\Model\ProjectService;
use Appwrite\Utopia\Response\Model\Provider;
use Appwrite\Utopia\Response\Model\ProviderRepository;
use Appwrite\Utopia\Response\Model\ProviderRepositoryFramework;
use Appwrite\Utopia\Response\Model\ProviderRepositoryFrameworkList;
use Appwrite\Utopia\Response\Model\ProviderRepositoryRuntime;
use Appwrite\Utopia\Response\Model\ProviderRepositoryRuntimeList;
use Appwrite\Utopia\Response\Model\Report;
use Appwrite\Utopia\Response\Model\ResourceToken;
use Appwrite\Utopia\Response\Model\Row;
use Appwrite\Utopia\Response\Model\Rule;
@@ -127,7 +203,6 @@ use Appwrite\Utopia\Response\Model\TemplateFramework;
use Appwrite\Utopia\Response\Model\TemplateFunction;
use Appwrite\Utopia\Response\Model\TemplateRuntime;
use Appwrite\Utopia\Response\Model\TemplateSite;
use Appwrite\Utopia\Response\Model\TemplateSMS;
use Appwrite\Utopia\Response\Model\TemplateVariable;
use Appwrite\Utopia\Response\Model\Token;
use Appwrite\Utopia\Response\Model\Topic;
@@ -136,17 +211,23 @@ use Appwrite\Utopia\Response\Model\UsageBuckets;
use Appwrite\Utopia\Response\Model\UsageCollection;
use Appwrite\Utopia\Response\Model\UsageDatabase;
use Appwrite\Utopia\Response\Model\UsageDatabases;
use Appwrite\Utopia\Response\Model\UsageDocumentsDB;
use Appwrite\Utopia\Response\Model\UsageDocumentsDBs;
use Appwrite\Utopia\Response\Model\UsageFunction;
use Appwrite\Utopia\Response\Model\UsageFunctions;
use Appwrite\Utopia\Response\Model\UsagePresence;
use Appwrite\Utopia\Response\Model\UsageProject;
use Appwrite\Utopia\Response\Model\UsageSite;
use Appwrite\Utopia\Response\Model\UsageSites;
use Appwrite\Utopia\Response\Model\UsageStorage;
use Appwrite\Utopia\Response\Model\UsageTable;
use Appwrite\Utopia\Response\Model\UsageUsers;
use Appwrite\Utopia\Response\Model\UsageVectorsDB;
use Appwrite\Utopia\Response\Model\UsageVectorsDBs;
use Appwrite\Utopia\Response\Model\User;
use Appwrite\Utopia\Response\Model\Variable;
use Appwrite\Utopia\Response\Model\VcsContent;
use Appwrite\Utopia\Response\Model\VectorsDBCollection;
use Appwrite\Utopia\Response\Model\Webhook;
// General
@@ -158,6 +239,7 @@ Response::setModel(new ErrorDev());
// Lists
Response::setModel(new BaseList('Rows List', Response::MODEL_ROW_LIST, 'rows', Response::MODEL_ROW));
Response::setModel(new BaseList('Documents List', Response::MODEL_DOCUMENT_LIST, 'documents', Response::MODEL_DOCUMENT));
Response::setModel(new BaseList('Presences List', Response::MODEL_PRESENCE_LIST, 'presences', Response::MODEL_PRESENCE));
Response::setModel(new BaseList('Tables List', Response::MODEL_TABLE_LIST, 'tables', Response::MODEL_TABLE));
Response::setModel(new BaseList('Collections List', Response::MODEL_COLLECTION_LIST, 'collections', Response::MODEL_COLLECTION));
Response::setModel(new BaseList('Databases List', Response::MODEL_DATABASE_LIST, 'databases', Response::MODEL_DATABASE));
@@ -177,19 +259,18 @@ Response::setModel(new BaseList('Site Templates List', Response::MODEL_TEMPLATE_
Response::setModel(new BaseList('Functions List', Response::MODEL_FUNCTION_LIST, 'functions', Response::MODEL_FUNCTION));
Response::setModel(new BaseList('Function Templates List', Response::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', Response::MODEL_TEMPLATE_FUNCTION));
Response::setModel(new BaseList('Installations List', Response::MODEL_INSTALLATION_LIST, 'installations', Response::MODEL_INSTALLATION));
Response::setModel(new BaseList('Framework Provider Repositories List', Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST, 'frameworkProviderRepositories', Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK));
Response::setModel(new BaseList('Runtime Provider Repositories List', Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST, 'runtimeProviderRepositories', Response::MODEL_PROVIDER_REPOSITORY_RUNTIME));
Response::setModel(new ProviderRepositoryFrameworkList());
Response::setModel(new ProviderRepositoryRuntimeList());
Response::setModel(new BaseList('Branches List', Response::MODEL_BRANCH_LIST, 'branches', Response::MODEL_BRANCH));
Response::setModel(new BaseList('Frameworks List', Response::MODEL_FRAMEWORK_LIST, 'frameworks', Response::MODEL_FRAMEWORK));
Response::setModel(new BaseList('Runtimes List', Response::MODEL_RUNTIME_LIST, 'runtimes', Response::MODEL_RUNTIME));
Response::setModel(new BaseList('Deployments List', Response::MODEL_DEPLOYMENT_LIST, 'deployments', Response::MODEL_DEPLOYMENT));
Response::setModel(new BaseList('Executions List', Response::MODEL_EXECUTION_LIST, 'executions', Response::MODEL_EXECUTION));
Response::setModel(new BaseList('Projects List', Response::MODEL_PROJECT_LIST, 'projects', Response::MODEL_PROJECT, true, false));
Response::setModel(new BaseList('Webhooks List', Response::MODEL_WEBHOOK_LIST, 'webhooks', Response::MODEL_WEBHOOK, true, false));
Response::setModel(new BaseList('Projects List', Response::MODEL_PROJECT_LIST, 'projects', Response::MODEL_PROJECT, true, true));
Response::setModel(new BaseList('Webhooks List', Response::MODEL_WEBHOOK_LIST, 'webhooks', Response::MODEL_WEBHOOK, true, true));
Response::setModel(new BaseList('API Keys List', Response::MODEL_KEY_LIST, 'keys', Response::MODEL_KEY, true, true));
Response::setModel(new BaseList('Dev Keys List', Response::MODEL_DEV_KEY_LIST, 'devKeys', Response::MODEL_DEV_KEY, true, false));
Response::setModel(new BaseList('Auth Providers List', Response::MODEL_AUTH_PROVIDER_LIST, 'platforms', Response::MODEL_AUTH_PROVIDER, true, false));
Response::setModel(new BaseList('Platforms List', Response::MODEL_PLATFORM_LIST, 'platforms', Response::MODEL_PLATFORM, true, false));
Response::setModel(new BaseList('Countries List', Response::MODEL_COUNTRY_LIST, 'countries', Response::MODEL_COUNTRY));
Response::setModel(new BaseList('Continents List', Response::MODEL_CONTINENT_LIST, 'continents', Response::MODEL_CONTINENT));
Response::setModel(new BaseList('Languages List', Response::MODEL_LANGUAGE_LIST, 'languages', Response::MODEL_LANGUAGE));
@@ -197,6 +278,9 @@ Response::setModel(new BaseList('Currencies List', Response::MODEL_CURRENCY_LIST
Response::setModel(new BaseList('Phones List', Response::MODEL_PHONE_LIST, 'phones', Response::MODEL_PHONE));
Response::setModel(new BaseList('Metric List', Response::MODEL_METRIC_LIST, 'metrics', Response::MODEL_METRIC, true, false));
Response::setModel(new BaseList('Variables List', Response::MODEL_VARIABLE_LIST, 'variables', Response::MODEL_VARIABLE));
Response::setModel(new BaseList('Mock Numbers List', Response::MODEL_MOCK_NUMBER_LIST, 'mockNumbers', Response::MODEL_MOCK_NUMBER));
Response::setModel(new PolicyList());
Response::setModel(new BaseList('Email Templates List', Response::MODEL_EMAIL_TEMPLATE_LIST, 'templates', Response::MODEL_EMAIL_TEMPLATE));
Response::setModel(new BaseList('Status List', Response::MODEL_HEALTH_STATUS_LIST, 'statuses', Response::MODEL_HEALTH_STATUS));
Response::setModel(new BaseList('Rule List', Response::MODEL_PROXY_RULE_LIST, 'rules', Response::MODEL_PROXY_RULE));
Response::setModel(new BaseList('Schedules List', Response::MODEL_SCHEDULE_LIST, 'schedules', Response::MODEL_SCHEDULE));
@@ -211,9 +295,14 @@ Response::setModel(new BaseList('Migrations List', Response::MODEL_MIGRATION_LIS
Response::setModel(new BaseList('Migrations Firebase Projects List', Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', Response::MODEL_MIGRATION_FIREBASE_PROJECT));
Response::setModel(new BaseList('Specifications List', Response::MODEL_SPECIFICATION_LIST, 'specifications', Response::MODEL_SPECIFICATION));
Response::setModel(new BaseList('VCS Content List', Response::MODEL_VCS_CONTENT_LIST, 'contents', Response::MODEL_VCS_CONTENT));
Response::setModel(new BaseList('VectorsDB Collections List', Response::MODEL_VECTORSDB_COLLECTION_LIST, 'collections', Response::MODEL_VECTORSDB_COLLECTION));
Response::setModel(new BaseList('Embedding list', Response::MODEL_EMBEDDING_LIST, 'embeddings', Response::MODEL_EMBEDDING));
Response::setModel(new BaseList('Insights List', Response::MODEL_INSIGHT_LIST, 'insights', Response::MODEL_INSIGHT));
Response::setModel(new BaseList('Reports List', Response::MODEL_REPORT_LIST, 'reports', Response::MODEL_REPORT));
// Entities
Response::setModel(new Database());
Response::setModel(new Embedding());
// Collection API Models
Response::setModel(new Collection());
@@ -221,6 +310,7 @@ Response::setModel(new Attribute());
Response::setModel(new AttributeList());
Response::setModel(new AttributeString());
Response::setModel(new AttributeInteger());
Response::setModel(new AttributeBigInt());
Response::setModel(new AttributeFloat());
Response::setModel(new AttributeBoolean());
Response::setModel(new AttributeEmail());
@@ -237,12 +327,24 @@ Response::setModel(new AttributeText());
Response::setModel(new AttributeMediumtext());
Response::setModel(new AttributeLongtext());
// DocumentsDB API Models
Response::setModel(new UsageDocumentsDBs());
Response::setModel(new UsageDocumentsDB());
// VectorsDB API Models
Response::setModel(new VectorsDBCollection());
Response::setModel(new AttributeObject());
Response::setModel(new AttributeVector());
Response::setModel(new UsageVectorsDBs());
Response::setModel(new UsageVectorsDB());
// Table API Models
Response::setModel(new Table());
Response::setModel(new Column());
Response::setModel(new ColumnList());
Response::setModel(new ColumnString());
Response::setModel(new ColumnInteger());
Response::setModel(new ColumnBigInt());
Response::setModel(new ColumnFloat());
Response::setModel(new ColumnBoolean());
Response::setModel(new ColumnEmail());
@@ -262,6 +364,7 @@ Response::setModel(new Index());
Response::setModel(new ColumnIndex());
Response::setModel(new Row());
Response::setModel(new ModelDocument());
Response::setModel(new Presence());
Response::setModel(new Log());
Response::setModel(new User());
Response::setModel(new AlgoMd5());
@@ -306,12 +409,71 @@ Response::setModel(new FrameworkAdapter());
Response::setModel(new Deployment());
Response::setModel(new Execution());
Response::setModel(new Project());
Response::setModel(new ProjectAuthMethod());
Response::setModel(new ProjectService());
Response::setModel(new ProjectProtocol());
Response::setModel(new Webhook());
Response::setModel(new Key());
Response::setModel(new EphemeralKey());
Response::setModel(new DevKey());
Response::setModel(new MockNumber());
Response::setModel(new OAuth2GitHub());
Response::setModel(new OAuth2Discord());
Response::setModel(new OAuth2Figma());
Response::setModel(new OAuth2Dropbox());
Response::setModel(new OAuth2Dailymotion());
Response::setModel(new OAuth2Bitbucket());
Response::setModel(new OAuth2Bitly());
Response::setModel(new OAuth2Box());
Response::setModel(new OAuth2Autodesk());
Response::setModel(new OAuth2Google());
Response::setModel(new OAuth2Zoom());
Response::setModel(new OAuth2Zoho());
Response::setModel(new OAuth2Yandex());
Response::setModel(new OAuth2X());
Response::setModel(new OAuth2WordPress());
Response::setModel(new OAuth2Twitch());
Response::setModel(new OAuth2Stripe());
Response::setModel(new OAuth2Spotify());
Response::setModel(new OAuth2Slack());
Response::setModel(new OAuth2Podio());
Response::setModel(new OAuth2Notion());
Response::setModel(new OAuth2Salesforce());
Response::setModel(new OAuth2Yahoo());
Response::setModel(new OAuth2Linkedin());
Response::setModel(new OAuth2Disqus());
Response::setModel(new OAuth2Amazon());
Response::setModel(new OAuth2Etsy());
Response::setModel(new OAuth2Facebook());
Response::setModel(new OAuth2Tradeshift());
Response::setModel(new OAuth2Paypal());
Response::setModel(new OAuth2Gitlab());
Response::setModel(new OAuth2Authentik());
Response::setModel(new OAuth2Auth0());
Response::setModel(new OAuth2FusionAuth());
Response::setModel(new OAuth2Keycloak());
Response::setModel(new OAuth2Oidc());
Response::setModel(new OAuth2Okta());
Response::setModel(new OAuth2Kick());
Response::setModel(new OAuth2Apple());
Response::setModel(new OAuth2Microsoft());
Response::setModel(new OAuth2ProviderList());
Response::setModel(new PolicyPasswordDictionary());
Response::setModel(new PolicyPasswordHistory());
Response::setModel(new PolicyPasswordPersonalData());
Response::setModel(new PolicySessionAlert());
Response::setModel(new PolicySessionDuration());
Response::setModel(new PolicySessionInvalidation());
Response::setModel(new PolicySessionLimit());
Response::setModel(new PolicyUserLimit());
Response::setModel(new PolicyMembershipPrivacy());
Response::setModel(new AuthProvider());
Response::setModel(new Platform());
Response::setModel(new PlatformWeb());
Response::setModel(new PlatformApple());
Response::setModel(new PlatformAndroid());
Response::setModel(new PlatformWindows());
Response::setModel(new PlatformLinux());
Response::setModel(new PlatformList());
Response::setModel(new Variable());
Response::setModel(new Country());
Response::setModel(new Continent());
@@ -331,6 +493,7 @@ Response::setModel(new UsageDatabase());
Response::setModel(new UsageTable());
Response::setModel(new UsageCollection());
Response::setModel(new UsageUsers());
Response::setModel(new UsagePresence());
Response::setModel(new UsageStorage());
Response::setModel(new UsageBuckets());
Response::setModel(new UsageFunctions());
@@ -342,9 +505,13 @@ Response::setModel(new Headers());
Response::setModel(new Specification());
Response::setModel(new Rule());
Response::setModel(new Schedule());
Response::setModel(new TemplateSMS());
Response::setModel(new TemplateEmail());
Response::setModel(new ConsoleVariables());
Response::setModel(new ConsoleOAuth2ProviderParameter());
Response::setModel(new ConsoleOAuth2Provider());
Response::setModel(new ConsoleOAuth2ProviderList());
Response::setModel(new ConsoleKeyScope());
Response::setModel(new ConsoleKeyScopeList());
Response::setModel(new MFAChallenge());
Response::setModel(new MFARecoveryCodes());
Response::setModel(new MFAType());
@@ -358,6 +525,9 @@ Response::setModel(new Target());
Response::setModel(new Migration());
Response::setModel(new MigrationReport());
Response::setModel(new MigrationFirebaseProject());
Response::setModel(new Insight());
Response::setModel(new InsightCTA());
Response::setModel(new Report());
// Tests (keep last)
Response::setModel(new Mock());
+368
View File
@@ -0,0 +1,368 @@
<?php
use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Extend\Exception;
use Appwrite\Network\Platform;
use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Request;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Database\DateTime as DatabaseDateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\DI\Container;
use Utopia\System\System;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
/**
* Register the minimal per-connection resources required by realtime.
*/
return function (Container $container): void {
$getProjectId = static function (Request $request): string {
$projectId = $request->getHeader('x-appwrite-project', '');
if (!empty($projectId)) {
return $projectId;
}
$projectId = $request->getParam('project', '');
return \is_string($projectId) ? $projectId : '';
};
$getMode = static function (Request $request, Document $project) use ($getProjectId): string {
$mode = $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
$projectId = $getProjectId($request);
if (!empty($projectId) && $project->getId() !== $projectId) {
$mode = APP_MODE_ADMIN;
}
return $mode;
};
$getDbForPlatform = static function (Authorization $authorization) {
$database = getConsoleDB();
$database->setAuthorization($authorization);
return $database;
};
$getDbForProject = static function (Document $project, Authorization $authorization) use ($getDbForPlatform) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $getDbForPlatform($authorization);
}
$database = getProjectDB($project);
$database->setAuthorization($authorization);
return $database;
};
$findRule = static function (Request $request, Document $project, Authorization $authorization) use ($getDbForPlatform): Document {
$domain = \parse_url($request->getOrigin(), PHP_URL_HOST);
if (empty($domain)) {
$domain = \parse_url($request->getReferer(), PHP_URL_HOST);
}
if (empty($domain)) {
return new Document();
}
$dbForPlatform = $getDbForPlatform($authorization);
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
$rule = $authorization->skip(function () use ($dbForPlatform, $domain, $isMd5) {
if ($isMd5) {
return $dbForPlatform->getDocument('rules', md5($domain));
}
return $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain]),
]);
});
$permitsCurrentProject = $rule->getAttribute('projectInternalId', '') === $project->getSequence();
if (!$permitsCurrentProject && !$rule->isEmpty() && !empty($rule->getAttribute('projectId', ''))) {
$trustedProjects = [];
foreach (\explode(',', System::getEnv('_APP_CONSOLE_TRUSTED_PROJECTS', '')) as $trustedProject) {
if (empty($trustedProject)) {
continue;
}
$trustedProjects[] = $trustedProject;
}
if (\in_array($rule->getAttribute('projectId', ''), $trustedProjects, true)) {
$permitsCurrentProject = true;
}
}
if (!$permitsCurrentProject) {
return new Document();
}
return $rule;
};
$findDevKey = static function (Request $request, Document $project, array $servers, Authorization $authorization) use ($getDbForPlatform): Document {
$devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', ''));
$key = $project->find('secret', $devKey, 'devKeys');
if (!$key) {
return new Document([]);
}
$expire = $key->getAttribute('expire');
if (!empty($expire) && $expire < DatabaseDateTime::formatTz(DatabaseDateTime::now())) {
return new Document([]);
}
$dbForPlatform = $getDbForPlatform($authorization);
$accessedAt = $key->getAttribute('accessedAt', 0);
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
$key->setAttribute('accessedAt', DatabaseDateTime::now());
$authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), new Document([
'accessedAt' => $key->getAttribute('accessedAt'),
])));
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
$sdkValidator = new WhiteList($servers, true);
$sdk = \strtolower($request->getHeader('x-sdk-name', 'UNKNOWN'));
if ($sdk !== 'unknown' && $sdkValidator->isValid($sdk)) {
$sdks = $key->getAttribute('sdks', []);
if (!\in_array($sdk, $sdks, true)) {
$sdks[] = $sdk;
$key->setAttribute('sdks', $sdks);
$key->setAttribute('accessedAt', DatabaseDateTime::now());
$key = $authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), new Document([
'sdks' => $key->getAttribute('sdks'),
'accessedAt' => $key->getAttribute('accessedAt'),
])));
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
}
return $key;
};
$container->set('authorization', function () {
return new Authorization();
}, []);
$container->set('project', function (Request $request, Document $console, Authorization $authorization) use ($getProjectId, $getDbForPlatform) {
$projectId = $getProjectId($request);
if (empty($projectId) || $projectId === 'console') {
return $console;
}
$dbForPlatform = $getDbForPlatform($authorization);
return $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
}, ['request', 'console', 'authorization']);
$container->set('originValidator', function (array $platform, Request $request, Document $project, array $servers, Authorization $authorization) use ($findDevKey, $findRule) {
$devKey = $findDevKey($request, $project, $servers, $authorization);
if (!$devKey->isEmpty()) {
return new URL();
}
$allowedHostnames = [...($platform['hostnames'] ?? [])];
if (!$project->isEmpty() && $project->getId() !== 'console') {
$allowedHostnames = [...$allowedHostnames, ...Platform::getHostnames($project->getAttribute('platforms', []))];
}
$rule = $findRule($request, $project, $authorization);
if (!$rule->isEmpty() && !empty($rule->getAttribute('domain', ''))) {
$allowedHostnames[] = $rule->getAttribute('domain', '');
}
$originHostname = \parse_url($request->getOrigin(), PHP_URL_HOST);
$refererHostname = \parse_url($request->getReferer(), PHP_URL_HOST);
$hostname = $originHostname ?: $refererHostname;
if ($request->getMethod() === 'OPTIONS' && !empty($hostname)) {
$allowedHostnames[] = $hostname;
}
$allowedSchemes = [...($platform['schemas'] ?? [])];
if (!$project->isEmpty() && $project->getId() !== 'console') {
$allowedSchemes[] = 'exp';
$allowedSchemes[] = 'appwrite-callback-' . $project->getId();
$allowedSchemes = [...$allowedSchemes, ...Platform::getSchemes($project->getAttribute('platforms', []))];
}
return new Origin(\array_unique($allowedHostnames), \array_unique($allowedSchemes));
}, ['platform', 'request', 'project', 'servers', 'authorization']);
$container->set('user', function (Request $request, Document $project, Document $console, Authorization $authorization) use ($getMode, $getDbForPlatform, $getDbForProject) {
$mode = $getMode($request, $project);
$store = new Store();
$proofForToken = new Token();
$proofForToken->setHash(new Sha());
$authorization->setDefaultStatus(true);
$dbForPlatform = $getDbForPlatform($authorization);
$dbForProject = $getDbForProject($project, $authorization);
$store->setKey('a_session_' . $project->getId());
if ($mode === APP_MODE_ADMIN) {
$store->setKey('a_session_' . $console->getId());
}
$store->decode(
$request->getCookie(
$store->getKey(),
$request->getCookie($store->getKey() . '_legacy', '')
)
);
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
$sessionHeader = $request->getHeader('x-appwrite-session', '');
if (!empty($sessionHeader)) {
$store->decode($sessionHeader);
}
}
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
$fallback = \json_decode($request->getHeader('x-fallback-cookies', ''), true);
$store->decode((\is_array($fallback) && isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : '');
}
$user = null;
if ($mode === APP_MODE_ADMIN) {
/** @var User $user */
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
} else {
if ($project->isEmpty()) {
$user = new User([]);
} elseif (!empty($store->getProperty('id', ''))) {
if ($project->getId() === 'console') {
/** @var User $user */
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
} else {
/** @var User $user */
$user = $dbForProject->getDocument('users', $store->getProperty('id', ''));
}
}
}
if (
!$user
|| $user->isEmpty()
|| !$user->sessionVerify($store->getProperty('secret', ''), $proofForToken)
) {
$user = new User([]);
}
$authJWT = $request->getHeader('x-appwrite-jwt', '');
if (!empty($authJWT) && !$project->isEmpty()) {
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_JWT_AND_COOKIE_SET);
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
try {
$payload = $jwt->decode($authJWT);
} catch (JWTException $error) {
throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
}
$jwtUserId = $payload['userId'] ?? '';
if (!empty($jwtUserId)) {
if ($mode === APP_MODE_ADMIN) {
$user = $dbForPlatform->getDocument('users', $jwtUserId);
} else {
$user = $dbForProject->getDocument('users', $jwtUserId);
}
}
$jwtSessionId = $payload['sessionId'] ?? '';
if (!empty($jwtSessionId) && empty($user->find('$id', $jwtSessionId, 'sessions'))) {
$user = new User([]);
}
}
$accountKey = $request->getHeader('x-appwrite-key', '');
$accountKeyUserId = $request->getHeader('x-appwrite-user', '');
if (!empty($accountKeyUserId) && !empty($accountKey)) {
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
$accountKeyUser = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $accountKeyUserId));
if (!$accountKeyUser->isEmpty()) {
$key = $accountKeyUser->find(
key: 'secret',
find: $accountKey,
subject: 'keys'
);
if (!empty($key)) {
$expire = $key->getAttribute('expire');
if (!empty($expire) && $expire < DatabaseDateTime::formatTz(DatabaseDateTime::now())) {
throw new Exception(Exception::ACCOUNT_KEY_EXPIRED);
}
$user = $accountKeyUser;
}
}
}
// Query params mirror the header fallback pattern used by ?project= and ?devKey=,
// allowing Console to embed impersonation in direct file/image URLs where headers cannot be set.
$impersonateUserId = $request->getHeader('x-appwrite-impersonate-user-id', (string)$request->getParam('impersonateUserId', ''));
$impersonateEmail = $request->getHeader('x-appwrite-impersonate-user-email', (string)$request->getParam('impersonateEmail', ''));
$impersonatePhone = $request->getHeader('x-appwrite-impersonate-user-phone', (string)$request->getParam('impersonatePhone', ''));
if (!$user->isEmpty() && $user->getAttribute('impersonator', false)) {
$userDb = ($mode === APP_MODE_ADMIN || $project->getId() === 'console') ? $dbForPlatform : $dbForProject;
$targetUser = null;
if (!empty($impersonateUserId)) {
$targetUser = $authorization->skip(fn () => $userDb->getDocument('users', $impersonateUserId));
} elseif (!empty($impersonateEmail)) {
$targetUser = $authorization->skip(fn () => $userDb->findOne('users', [
Query::equal('email', [\strtolower($impersonateEmail)]),
]));
} elseif (!empty($impersonatePhone)) {
$targetUser = $authorization->skip(fn () => $userDb->findOne('users', [
Query::equal('phone', [$impersonatePhone]),
]));
}
if ($targetUser !== null && !$targetUser->isEmpty()) {
$impersonator = clone $user;
$user = clone $targetUser;
$user->setAttribute('impersonatorUserId', $impersonator->getId());
$user->setAttribute('impersonatorUserInternalId', $impersonator->getSequence());
$user->setAttribute('impersonatorUserName', $impersonator->getAttribute('name', ''));
$user->setAttribute('impersonatorUserEmail', $impersonator->getAttribute('email', ''));
$user->setAttribute('impersonatorAccessedAt', $impersonator->getAttribute('accessedAt', 0));
}
}
$dbForPlatform->setMetadata('user', $user->getId());
$dbForProject->setMetadata('user', $user->getId());
return $user;
}, ['request', 'project', 'console', 'authorization']);
};
+75 -60
View File
@@ -6,7 +6,6 @@ use Appwrite\Hooks\Hooks;
use Appwrite\PubSub\Adapter\Redis as PubSub;
use Appwrite\URL\URL as AppwriteURL;
use MaxMind\Db\Reader;
use PHPMailer\PHPMailer\PHPMailer;
use Swoole\Database\PDOProxy;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\Config\Config;
@@ -25,6 +24,7 @@ use Utopia\Logger\Adapter\LogOwl;
use Utopia\Logger\Adapter\Raygun;
use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Logger;
use Utopia\Messaging\Adapter\Email\SMTP;
use Utopia\Mongo\Client as MongoClient;
use Utopia\Pools\Adapter\Stack as StackPool;
use Utopia\Pools\Adapter\Swoole as SwoolePool;
@@ -56,7 +56,7 @@ $register->set('logger', function () {
}
try {
$loggingProvider = new DSN($providerConfig ?? '');
$loggingProvider = new DSN($providerConfig);
$providerName = $loggingProvider->getScheme();
$providerConfig = match ($providerName) {
@@ -71,12 +71,12 @@ $register->set('logger', function () {
$providerConfig = match ($providerName) {
'sentry' => [ 'key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',],
'logowl' => ['ticket' => $configChunks[0] ?? '', 'host' => ''],
'logowl' => ['ticket' => $configChunks[0], 'host' => ''],
default => ['key' => $providerConfig],
};
}
if (empty($providerName) || empty($providerConfig)) {
if (empty($providerName)) {
return;
}
@@ -121,7 +121,7 @@ $register->set('realtimeLogger', function () {
default => ['key' => $loggingProvider->getHost()],
};
if (empty($providerName) || empty($providerConfig)) {
if (empty($providerName)) {
return;
}
@@ -160,7 +160,6 @@ $register->set('pools', function () {
'pass' => System::getEnv('_APP_DB_PASS', ''),
'path' => System::getEnv('_APP_DB_SCHEMA', ''),
]);
$fallbackForRedis = 'redis_main=' . AppwriteURL::unparse([
'scheme' => 'redis',
'host' => System::getEnv('_APP_REDIS_HOST', 'redis'),
@@ -169,6 +168,23 @@ $register->set('pools', function () {
'pass' => System::getEnv('_APP_REDIS_PASS', ''),
]);
$fallbackForDocumentsDB = 'db_main=' . AppwriteURL::unparse([
'scheme' => System::getEnv('_APP_DB_ADAPTER_DOCUMENTSDB', 'mongodb'),
'host' => System::getEnv('_APP_DB_HOST_DOCUMENTSDB', 'mongodb'),
'port' => System::getEnv('_APP_DB_PORT_DOCUMENTSDB', '27017'),
'user' => System::getEnv('_APP_DB_USER', ''),
'pass' => System::getEnv('_APP_DB_PASS', ''),
'path' => System::getEnv('_APP_DB_SCHEMA', ''),
]);
$fallbackForVectorsDB = 'db_main=' . AppwriteURL::unparse([
'scheme' => System::getEnv('_APP_DB_ADAPTER_VECTORSDB', 'postgresql'),
'host' => System::getEnv('_APP_DB_HOST_VECTORSDB', 'postgresql'),
'port' => System::getEnv('_APP_DB_PORT_VECTORSDB', '5432'),
'user' => System::getEnv('_APP_DB_USER', ''),
'pass' => System::getEnv('_APP_DB_PASS', ''),
'path' => System::getEnv('_APP_DB_SCHEMA', ''),
]);
$connections = [
'console' => [
'type' => 'database',
@@ -180,13 +196,25 @@ $register->set('pools', function () {
'type' => 'database',
'dsns' => $fallbackForDB,
'multiple' => true,
'schemes' => ['mariadb', 'mongodb', 'mysql', 'postgresql'],
'schemes' => ['mongodb','mariadb', 'mysql','postgresql'],
],
'documentsdb' => [
'type' => 'database',
'dsns' => System::getEnv('_APP_CONNECTIONS_DATABASE_DOCUMENTSDB', $fallbackForDocumentsDB),
'multiple' => true,
'schemes' => ['mongodb'],
],
'vectorsdb' => [
'type' => 'database',
'dsns' => System::getEnv('_APP_CONNECTIONS_DATABASE_VECTORSDB', $fallbackForVectorsDB),
'multiple' => true,
'schemes' => ['postgresql'],
],
'logs' => [
'type' => 'database',
'dsns' => System::getEnv('_APP_CONNECTIONS_DB_LOGS', $fallbackForDB),
'multiple' => false,
'schemes' => ['mariadb', 'mongodb', 'mysql', 'postgresql'],
'schemes' => ['mongodb','mariadb', 'mysql','postgresql'],
],
'publisher' => [
'type' => 'publisher',
@@ -212,31 +240,26 @@ $register->set('pools', function () {
'multiple' => true,
'schemes' => ['redis'],
],
'lock' => [
'type' => 'lock',
'dsns' => $fallbackForRedis,
'multiple' => false,
'schemes' => ['redis'],
],
];
$maxConnections = System::getEnv('_APP_CONNECTIONS_MAX', 151);
$instanceConnections = $maxConnections / System::getEnv('_APP_POOL_CLIENTS', 14);
$maxConnections = (int) System::getEnv('_APP_CONNECTIONS_MAX', 151);
$instanceConnections = $maxConnections / (int) System::getEnv('_APP_POOL_CLIENTS', 14);
$multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled';
if ($multiprocessing) {
$workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
} else {
$workerCount = 1;
}
if ($workerCount > $instanceConnections) {
throw new \Exception('Pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500);
}
$poolSize = (int)($instanceConnections / $workerCount);
$workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
$poolSize = max(1, (int)($instanceConnections / $workerCount));
foreach ($connections as $key => $connection) {
$type = $connection['type'] ?? '';
$multiple = $connection['multiple'] ?? false;
$schemes = $connection['schemes'] ?? [];
$type = $connection['type'];
$multiple = $connection['multiple'];
$schemes = $connection['schemes'];
$config = [];
$dsns = explode(',', $connection['dsns'] ?? '');
$dsns = explode(',', $connection['dsns']);
foreach ($dsns as &$dsn) {
$dsn = explode('=', $dsn);
$name = ($multiple) ? $key . '_' . $dsn[0] : $key;
@@ -280,7 +303,7 @@ $register->set('pools', function () {
]);
});
},
'mongodb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase, $dsn) {
'mongodb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
try {
$mongo = new MongoClient($dsnDatabase, $dsnHost, (int)$dsnPort, $dsnUser, $dsnPass, false);
@$mongo->connect();
@@ -301,7 +324,7 @@ $register->set('pools', function () {
));
});
},
'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
default => function () use ($dsnHost, $dsnPort, $dsnPass) {
$redis = new \Redis();
@$redis->pconnect($dsnHost, (int)$dsnPort);
if ($dsnPass) {
@@ -311,12 +334,17 @@ $register->set('pools', function () {
return $redis;
},
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'),
};
$poolAdapter = System::getEnv('_APP_POOL_ADAPTER', default: 'stack') === 'swoole' ? new SwoolePool() : new StackPool();
$pool = new Pool($poolAdapter, $name, $poolSize, function () use ($type, $resource, $dsn) {
// PubSub workers hold one long-lived subscribed connection and also need
// spare capacity for publishes from the same process.
$connectionPoolSize = $type === 'pubsub'
? max(2, $poolSize)
: $poolSize;
$pool = new Pool($poolAdapter, $name, $connectionPoolSize, function () use ($type, $resource, $dsn) {
// Get Adapter
switch ($type) {
case 'database':
@@ -353,6 +381,8 @@ $register->set('pools', function () {
}
return $adapter;
case 'lock':
return $resource();
default:
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
}
@@ -405,35 +435,20 @@ $register->set('db', function () {
});
$register->set('smtp', function () {
$mail = new PHPMailer(true);
$mail->isSMTP();
$username = System::getEnv('_APP_SMTP_USERNAME');
$password = System::getEnv('_APP_SMTP_PASSWORD');
$mail->XMailer = 'Appwrite Mailer';
$mail->Host = System::getEnv('_APP_SMTP_HOST', 'smtp');
$mail->Port = System::getEnv('_APP_SMTP_PORT', 25);
$mail->SMTPAuth = !empty($username) && !empty($password);
$mail->Username = $username;
$mail->Password = $password;
$mail->SMTPSecure = System::getEnv('_APP_SMTP_SECURE', '');
$mail->SMTPAutoTLS = false;
$mail->SMTPKeepAlive = true;
$mail->CharSet = 'UTF-8';
$mail->Timeout = 10; /* Connection timeout */
$mail->getSMTPInstance()->Timelimit = 30; /* Timeout for each individual SMTP command (e.g. HELO, EHLO, etc.) */
$from = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
$email = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
$mail->setFrom($email, $from);
$mail->addReplyTo($email, $from);
$mail->isHTML(true);
return $mail;
$username = System::getEnv('_APP_SMTP_USERNAME', '');
$password = System::getEnv('_APP_SMTP_PASSWORD', '');
return new SMTP(
host: System::getEnv('_APP_SMTP_HOST', 'smtp'),
port: (int) System::getEnv('_APP_SMTP_PORT', 25),
username: $username,
password: $password,
smtpSecure: System::getEnv('_APP_SMTP_SECURE', ''),
smtpAutoTLS: false,
xMailer: 'Appwrite Mailer',
timeout: 10,
keepAlive: true,
timelimit: 30,
);
});
$register->set('geodb', function () {
return new Reader(__DIR__ . '/../assets/dbip/dbip-country-lite-2025-12.mmdb');
+154 -1187
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+20 -1
View File
@@ -3,11 +3,30 @@
use Utopia\Span\Exporter;
use Utopia\Span\Span;
use Utopia\Span\Storage;
use Utopia\System\System;
Span::setStorage(new Storage\Coroutine());
Span::addExporter(new Exporter\Pretty(), function (Span $span): bool {
// Resolve trace filters once at boot to avoid repeated env lookups per span.
$traceProjectId = System::getEnv('_APP_TRACE_PROJECT_ID', '');
$traceFunctionId = System::getEnv('_APP_TRACE_FUNCTION_ID', '');
$traceEnabled = $traceProjectId !== '' || $traceFunctionId !== '';
Span::addExporter(new Exporter\Pretty(), function (Span $span) use ($traceEnabled, $traceProjectId, $traceFunctionId): bool {
if (\str_starts_with($span->getAction(), 'listener.')) {
return $span->getError() !== null;
}
// Selective tracing: when _APP_TRACE_PROJECT_ID / _APP_TRACE_FUNCTION_ID are set,
// only export spans tagged with matching project.id / function.id.
if ($traceEnabled) {
if ($traceProjectId !== '' && $span->get('project.id') !== $traceProjectId) {
return false;
}
if ($traceFunctionId !== '' && $span->get('function.id') !== $traceFunctionId) {
return false;
}
}
return true;
});
+444
View File
@@ -0,0 +1,444 @@
<?php
use Appwrite\Event\Event;
use Appwrite\Event\Realtime;
use Appwrite\Event\Webhook;
use Appwrite\Usage\Context;
use Appwrite\Utopia\Database\Documents\User;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit as UtopiaAudit;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\DI\Container;
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
use Utopia\Queue\Publisher;
use Utopia\Registry\Registry;
use Utopia\Storage\Device\Telemetry as TelemetryDevice;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
/**
* Register per-job resources on the given container.
* These resources depend on the queue message or keep mutable state and
* must be fresh for each worker job.
*/
return function (Container $container): void {
$container->set('log', fn () => new Log(), []);
$container->set('usage', fn () => new Context(), []);
$container->set('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
$container->set('dbForPlatform', function (Cache $cache, Group $pools, Authorization $authorization) {
$adapter = new DatabasePool($pools->get('console'));
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setNamespace('_console')
->setDocumentType('users', User::class);
return $dbForPlatform;
}, ['cache', 'pools', 'authorization']);
$container->set('project', function ($message, Database $dbForPlatform) {
$payload = $message->getPayload() ?? [];
$project = new Document($payload['project'] ?? []);
if ($project->isEmpty() || $project->getId() === 'console') {
return $project;
}
return $dbForPlatform->getDocument('projects', $project->getId());
}, ['message', 'dbForPlatform']);
$container->set('dbForProject', function (Cache $cache, Group $pools, Document $project, Database $dbForPlatform, Authorization $authorization) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$database->setDocumentType('users', User::class);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
/** @var array $collections */
$collections = Config::getParam('collections', []);
$projectCollections = $collections['projects'] ?? [];
$projectsGlobalCollections = array_keys($projectCollections);
$projectsGlobalCollections[] = 'audit';
$database
->setSharedTables(true)
->setGlobalCollections($projectsGlobalCollections)
->setTenant($project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
}, ['cache', 'pools', 'project', 'dbForPlatform', 'authorization']);
$container->set('getProjectDB', function (Group $pools, Database $dbForPlatform, Cache $cache, Authorization $authorization) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases): Database {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
if (isset($databases[$dsn->getHost()])) {
$database = $databases[$dsn->getHost()];
$database->setAuthorization($authorization);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
/** @var array $collections */
$collections = Config::getParam('collections', []);
$projectCollections = $collections['projects'] ?? [];
$projectsGlobalCollections = array_keys($projectCollections);
$projectsGlobalCollections[] = 'audit';
$database
->setSharedTables(true)
->setGlobalCollections($projectsGlobalCollections)
->setTenant($project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
}
return $database;
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$databases[$dsn->getHost()] = $database;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
/** @var array $collections */
$collections = Config::getParam('collections', []);
$projectCollections = $collections['projects'] ?? [];
$projectsGlobalCollections = array_keys($projectCollections);
$projectsGlobalCollections[] = 'audit';
$database
->setSharedTables(true)
->setGlobalCollections($projectsGlobalCollections)
->setTenant($project->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
$container->set('getDatabasesDB', function (Cache $cache, Registry $register, Document $project, Authorization $authorization) {
return function (Document $database, ?Document $projectDocument = null) use ($cache, $register, $project, $authorization): Database {
$projectDocument ??= $project;
$databaseDSN = $database->getAttribute('database', $project->getAttribute('database', ''));
$databaseType = $database->getAttribute('type', '');
// Backwards-compatibility: older or seeded legacy databases may not have a DSN stored
// in the "database" attribute. In that case, fall back to the project's database DSN.
if ($databaseDSN === '') {
$databaseDSN = $projectDocument->getAttribute('database', '');
}
try {
$databaseDSN = new DSN($databaseDSN);
} catch (\InvalidArgumentException) {
$databaseDSN = new DSN('mysql://' . $databaseDSN);
}
try {
$dsn = new DSN($projectDocument->getAttribute('database'));
} catch (\InvalidArgumentException) {
// Temporary fallback until all projects use shared tables
$dsn = new DSN('mysql://' . $projectDocument->getAttribute('database'));
}
$pools = $register->get('pools');
$databaseHost = $databaseDSN->getHost();
$pool = $pools->get($databaseHost);
$adapter = new DatabasePool($pool);
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization);
$database->getAdapter()->setSupportForAttributes($databaseType !== DOCUMENTSDB);
$sharedTables = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')));
/** @var array $collections */
$collections = Config::getParam('collections', []);
$projectCollections = $collections['projects'] ?? [];
$projectsGlobalCollections = array_keys($projectCollections);
$projectsGlobalCollections[] = 'audit';
$database->setGlobalCollections($projectsGlobalCollections);
// For separate pools (documentsdb/vectorsdb), check their own shared tables config.
// If not configured, use dedicated mode to avoid cross-engine tenant type mismatches.
if ($databaseHost !== $dsn->getHost()) {
$dbTypeSharedTables = match ($databaseType) {
DOCUMENTSDB => \array_filter(\explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES', ''))),
VECTORSDB => \array_filter(\explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES', ''))),
default => [],
};
if (\in_array($databaseHost, $dbTypeSharedTables)) {
$database
->setSharedTables(true)
->setGlobalCollections($projectsGlobalCollections)
->setTenant($projectDocument->getSequence())
->setNamespace($databaseDSN->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $projectDocument->getSequence());
}
} elseif (\in_array($dsn->getHost(), $sharedTables, true)) {
$database
->setSharedTables(true)
->setGlobalCollections($projectsGlobalCollections)
->setTenant($projectDocument->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $projectDocument->getSequence());
}
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
};
}, ['cache', 'register', 'project', 'authorization']);
$container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $authorization, &$database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
return $database;
}
/** @var array $collections */
$collections = Config::getParam('collections', []);
$logsCollections = $collections['logs'] ?? [];
$logsCollections = array_keys($logsCollections);
$adapter = new DatabasePool($pools->get('logs'));
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setSharedTables(true)
->setGlobalCollections($logsCollections)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER);
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
}
return $database;
};
}, ['pools', 'cache', 'authorization']);
$container->set('abuseRetention', function () {
return \time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
}, []);
$container->set('auditRetention', function (Document $project) {
if ($project->getId() === 'console') {
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)); // 6 months
}
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); // 14 days
}, ['project']);
$container->set('executionRetention', function () {
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); // 14 days
}, []);
$container->set('queueForEvents', function (Publisher $publisher) {
return new Event($publisher);
}, ['publisher']);
$container->set('queueForWebhooks', function (Publisher $publisher) {
return new Webhook($publisher);
}, ['publisher']);
$container->set('queueForRealtime', function () {
return new Realtime();
}, []);
$container->set('deviceForSites', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
$container->set('deviceForMigrations', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
$container->set('deviceForFunctions', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
$container->set('deviceForFiles', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
$container->set('deviceForBuilds', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
$container->set('deviceForCache', function (Document $project, Telemetry $telemetry) {
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
$container->set('logError', function (Registry $register, Document $project) {
return function (Throwable $error, string $namespace, string $action, ?array $extras = null) use ($register, $project) {
$logger = $register->get('logger');
if ($logger) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace($namespace);
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('code', $error->getCode());
$log->addTag('verboseType', \get_class($error));
$log->addTag('projectId', $project->getId());
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
if ($error->getPrevious() !== null) {
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
}
$log->addExtra('previousFile', $error->getPrevious()->getFile());
$log->addExtra('previousLine', $error->getPrevious()->getLine());
}
foreach (($extras ?? []) as $key => $value) {
$log->addExtra($key, $value);
}
$log->setAction($action);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
$responseCode = $logger->addLog($log);
Console::info('Error log pushed with status code: ' . $responseCode);
} catch (Throwable $th) {
Console::error('Error pushing log: ' . $th->getMessage());
}
}
Console::warning("Failed: {$error->getMessage()}");
Console::warning($error->getTraceAsString());
if ($error->getPrevious() !== null) {
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
Console::warning("Previous Failed: {$error->getPrevious()->getMessage()}");
}
Console::warning("Previous File: {$error->getPrevious()->getFile()} Line: {$error->getPrevious()->getLine()}");
}
};
}, ['register', 'project']);
$container->set('getAudit', function (Database $dbForPlatform, callable $getProjectDB) {
return function (Document $project) use ($dbForPlatform, $getProjectDB) {
if ($project->isEmpty() || $project->getId() === 'console') {
$adapter = new AdapterDatabase($dbForPlatform);
return new UtopiaAudit($adapter);
}
$dbForProject = $getProjectDB($project);
$adapter = new AdapterDatabase($dbForProject);
return new UtopiaAudit($adapter);
};
}, ['dbForPlatform', 'getProjectDB']);
$container->set('executionsRetentionCount', function (Document $project, array $plan) {
if ($project->getId() === 'console' || empty($plan)) {
return 0;
}
return (int) ($plan['executionsRetentionCount'] ?? 100);
}, ['project', 'plan']);
};
+2
View File
@@ -1,9 +1,11 @@
<?php
use Appwrite\Bus\Listeners\Log;
use Appwrite\Bus\Listeners\Mails;
use Appwrite\Bus\Listeners\Usage;
return [
new Log(),
new Mails(),
new Usage(),
];
+562 -171
View File
File diff suppressed because it is too large Load Diff
+150 -65
View File
@@ -12,8 +12,12 @@ $version = $this->getParam('version', '');
$organization = $this->getParam('organization', '');
$image = $this->getParam('image', '');
$enableAssistant = $this->getParam('enableAssistant', false);
$dbService = $this->getParam('database');
$dbService = $this->getParam('database', 'mongodb');
$allowedDbServices = ['mariadb', 'mongodb'];
if (!\in_array($dbService, $allowedDbServices, true)) {
$dbService = 'mongodb';
}
$hostPath = rtrim($this->getParam('hostPath', ''), '/');
?>services:
traefik:
image: traefik:3.6
@@ -63,6 +67,9 @@ $dbService = $this->getParam('database');
- traefik.http.routers.appwrite_api_https.service=appwrite_api
- traefik.http.routers.appwrite_api_https.tls=true
volumes:
<?php if ($version === 'local' && !empty($hostPath)): ?>
- "<?php echo $hostPath; ?>:/usr/src/code:rw"
<?php endif; ?>
- appwrite-uploads:/storage/uploads:rw
- appwrite-imports:/storage/imports:rw
- appwrite-cache:/storage/cache:rw
@@ -72,8 +79,10 @@ $dbService = $this->getParam('database');
- appwrite-sites:/storage/sites:rw
- appwrite-builds:/storage/builds:rw
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
# - clamav
environment:
- _APP_ENV
@@ -111,7 +120,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
@@ -185,7 +193,7 @@ $dbService = $this->getParam('database');
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: <?php echo $organization; ?>/console:7.6.4
image: <?php echo $organization; ?>/console:7.8.26
restart: unless-stopped
networks:
- appwrite
@@ -227,8 +235,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -245,7 +255,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_USAGE_STATS
- _APP_LOGGING_CONFIG
@@ -258,8 +267,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -274,7 +285,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
appwrite-worker-webhooks:
@@ -286,8 +296,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -300,7 +312,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -316,8 +327,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
@@ -339,7 +352,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
@@ -381,8 +393,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -397,7 +411,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
appwrite-worker-builds:
@@ -409,8 +422,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
volumes:
- appwrite-functions:/storage/functions:rw
- appwrite-sites:/storage/sites:rw
@@ -432,7 +447,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
- _APP_VCS_GITHUB_APP_NAME
- _APP_VCS_GITHUB_PRIVATE_KEY
@@ -479,8 +493,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
volumes:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
@@ -506,7 +522,35 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_CONFIG
appwrite-worker-executions:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: worker-executions
<<: *x-logging
container_name: appwrite-worker-executions
restart: unless-stopped
networks:
- appwrite
depends_on:
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_ENV
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_CONFIG
appwrite-worker-functions:
@@ -518,9 +562,12 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
- openruntimes-executor
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
openruntimes-executor:
condition: service_started
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -537,7 +584,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_FUNCTIONS_TIMEOUT
- _APP_SITES_TIMEOUT
- _APP_COMPUTE_BUILD_TIMEOUT
@@ -559,8 +605,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -573,7 +621,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -598,8 +645,10 @@ $dbService = $this->getParam('database');
volumes:
- appwrite-uploads:/storage/uploads:rw
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -614,7 +663,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
- _APP_SMS_FROM
- _APP_SMS_PROVIDER
@@ -652,7 +700,8 @@ $dbService = $this->getParam('database');
volumes:
- appwrite-imports:/storage/imports:rw
depends_on:
- <?= $dbService . "\n" ?>
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -674,7 +723,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
@@ -688,8 +736,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -711,7 +761,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
@@ -730,8 +779,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -742,7 +793,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -761,8 +811,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -773,7 +825,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -791,8 +842,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -803,7 +856,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -821,12 +873,21 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- <?= $dbService . "\n" ?>
- redis
<?= $dbService ?>:
condition: service_healthy
redis:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_OPTIONS_FORCE_HTTPS
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_DOMAIN_FUNCTIONS
- _APP_DOMAIN_SITES
- _APP_MIGRATION_HOST
- _APP_CONSOLE_SCHEMA
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -837,7 +898,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
appwrite-task-scheduler-executions:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
@@ -848,12 +908,21 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- <?= $dbService . "\n" ?>
- redis
<?= $dbService ?>:
condition: service_healthy
redis:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_OPTIONS_FORCE_HTTPS
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_DOMAIN_FUNCTIONS
- _APP_DOMAIN_SITES
- _APP_MIGRATION_HOST
- _APP_CONSOLE_SCHEMA
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -864,7 +933,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
appwrite-task-scheduler-messages:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
@@ -875,8 +943,10 @@ $dbService = $this->getParam('database');
networks:
- appwrite
depends_on:
- <?= $dbService . "\n" ?>
- redis
<?= $dbService ?>:
condition: service_healthy
redis:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -891,7 +961,6 @@ $dbService = $this->getParam('database');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
<?php if ($enableAssistant): ?>
appwrite-assistant:
@@ -919,7 +988,7 @@ $dbService = $this->getParam('database');
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
image: openruntimes/executor:0.7.22
image: openruntimes/executor:0.11.4
networks:
- appwrite
- runtimes
@@ -966,7 +1035,6 @@ $dbService = $this->getParam('database');
- OPR_EXECUTOR_STORAGE_WASABI_BUCKET=$_APP_STORAGE_WASABI_BUCKET
<?php if ($dbService === 'mariadb'): ?>
mariadb:
image: mariadb:10.11
container_name: appwrite-mariadb
@@ -982,6 +1050,12 @@ $dbService = $this->getParam('database');
- MYSQL_PASSWORD=${_APP_DB_PASS}
- MARIADB_AUTO_UPGRADE=1
command: 'mysqld --innodb-flush-method=fsync'
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 10s
retries: 10
start_period: 30s
<?php elseif ($dbService === 'mongodb'): ?>
@@ -989,13 +1063,12 @@ $dbService = $this->getParam('database');
image: mongo:8.2.5
container_name: appwrite-mongodb
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-mongodb:/data/db
- appwrite-mongodb-keyfile:/data/keyfile
ports:
- "27017:27017"
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
@@ -1059,18 +1132,24 @@ $dbService = $this->getParam('database');
<?php elseif ($dbService === 'postgresql'): ?>
postgresql:
image: postgres:18
image: appwrite/postgres:0.1.0
container_name: appwrite-postgresql
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-postgresql:/var/lib/postgresql/data:rw
- appwrite-postgresql:/var/lib/postgresql:rw
environment:
- POSTGRES_DB=${_APP_DB_SCHEMA}
- POSTGRES_USER=${_APP_DB_USER}
- POSTGRES_PASSWORD=${_APP_DB_PASS}
command: "postgres"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${_APP_DB_USER} -d ${_APP_DB_SCHEMA}"]
interval: 10s
timeout: 10s
retries: 10
start_period: 30s
<?php endif; ?>
@@ -1088,6 +1167,12 @@ $dbService = $this->getParam('database');
- appwrite
volumes:
- appwrite-redis:/data:rw
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
# clamav:
# image: appwrite/clamav:1.2.0
+5 -1
View File
@@ -3,6 +3,10 @@
$vars = $this->getParam('vars');
foreach ($vars as $key => $value) {
echo $key.'='.$value."\n";
if ($value === null || $value === '') {
echo $key . "=\n";
} else {
echo $key . '="' . addcslashes((string) $value, '"\\$`') . '"' . "\n";
}
}
?>
+147
View File
@@ -0,0 +1,147 @@
<?php
$isUpgrade = $isUpgrade ?? false;
$lockedDatabase = $lockedDatabase ?? null;
$vars = $vars ?? [];
$defaultHttpPort = $defaultHttpPort ?? '80';
$defaultHttpsPort = $defaultHttpsPort ?? '443';
$defaultAppDomain = $vars['_APP_DOMAIN']['default'] ?? 'localhost';
$defaultAppDomain = ($defaultAppDomain === 'traefik') ? 'localhost' : $defaultAppDomain;
$defaultEmailCertificates = $defaultEmailCertificates ?? '';
$defaultDatabase = $vars['_APP_DB_ADAPTER']['default'] ?? 'mongodb';
$lockedDatabase = $isUpgrade && empty($lockedDatabase) ? $defaultDatabase : $lockedDatabase;
$enabledDatabases = $enabledDatabases ?? ['mongodb', 'mariadb', 'postgresql'];
$isLocalInstall = $isLocalInstall ?? false;
$cardStep = ($step === 5) ? 4 : $step;
$stepFile = __DIR__ . "/installer/templates/steps/step-{$cardStep}.phtml";
if (!is_file($stepFile)) {
$stepFile = __DIR__ . "/installer/templates/steps/step-1.phtml";
}
$cssVersion = @filemtime(__DIR__ . '/installer/css/styles.css') ?: time();
$tooltipsVersion = @filemtime(__DIR__ . '/installer/js/tooltips.js') ?: time();
$constantsVersion = @filemtime(__DIR__ . '/installer/js/constants.js') ?: time();
$stepsContextVersion = @filemtime(__DIR__ . '/installer/js/modules/context.js') ?: time();
$stepsStateVersion = @filemtime(__DIR__ . '/installer/js/modules/state.js') ?: time();
$stepsValidationVersion = @filemtime(__DIR__ . '/installer/js/modules/validation.js') ?: time();
$stepsUiVersion = @filemtime(__DIR__ . '/installer/js/modules/ui.js') ?: time();
$stepsToastVersion = @filemtime(__DIR__ . '/installer/js/modules/toast.js') ?: time();
$stepsProgressVersion = @filemtime(__DIR__ . '/installer/js/modules/progress.js') ?: time();
$stepsVersion = @filemtime(__DIR__ . '/installer/js/steps.js') ?: time();
$installerVersion = @filemtime(__DIR__ . '/installer/js/installer.js') ?: time();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $isUpgrade ? 'Appwrite Update' : 'Appwrite Installation'; ?></title>
<meta name="appwrite-installer-csrf" content="<?php echo htmlspecialchars($csrfToken ?? '', ENT_QUOTES); ?>">
<link rel="icon" type="image/svg+xml" href="installer/icons/appwrite-mark.svg">
<link rel="stylesheet" href="installer/css/styles.css?v=<?php echo $cssVersion; ?>">
<script src="installer/js/tooltips.js?v=<?php echo $tooltipsVersion; ?>" defer></script>
<script src="installer/js/constants.js?v=<?php echo $constantsVersion; ?>" defer></script>
<!-- Load context first; other modules depend on InstallerStepsContext. -->
<script src="installer/js/modules/context.js?v=<?php echo $stepsContextVersion; ?>" defer></script>
<script src="installer/js/modules/state.js?v=<?php echo $stepsStateVersion; ?>" defer></script>
<script src="installer/js/modules/validation.js?v=<?php echo $stepsValidationVersion; ?>" defer></script>
<script src="installer/js/modules/ui.js?v=<?php echo $stepsUiVersion; ?>" defer></script>
<script src="installer/js/modules/toast.js?v=<?php echo $stepsToastVersion; ?>" defer></script>
<script src="installer/js/modules/progress.js?v=<?php echo $stepsProgressVersion; ?>" defer></script>
<script src="installer/js/steps.js?v=<?php echo $stepsVersion; ?>" defer></script>
<script src="installer/js/installer.js?v=<?php echo $installerVersion; ?>" defer></script>
</head>
<body
class="installer-page"
data-step="<?php echo $step; ?>"
data-upgrade="<?php echo $isUpgrade ? 'true' : 'false'; ?>"
<?php if (!empty($lockedDatabase)) { ?>
data-locked-database="<?php echo htmlspecialchars((string) $lockedDatabase, ENT_QUOTES, 'UTF-8'); ?>"
<?php } ?>
data-default-http-port="<?php echo htmlspecialchars((string) $defaultHttpPort, ENT_QUOTES, 'UTF-8'); ?>"
data-default-https-port="<?php echo htmlspecialchars((string) $defaultHttpsPort, ENT_QUOTES, 'UTF-8'); ?>"
data-default-app-domain="<?php echo htmlspecialchars((string) $defaultAppDomain, ENT_QUOTES, 'UTF-8'); ?>"
data-default-email-certificates="<?php echo htmlspecialchars((string) $defaultEmailCertificates, ENT_QUOTES, 'UTF-8'); ?>"
data-default-secret-key=""
data-default-assistant-openai-key=""
data-default-database="<?php echo htmlspecialchars((string) $defaultDatabase, ENT_QUOTES, 'UTF-8'); ?>"
data-enabled-databases="<?php echo htmlspecialchars(json_encode(array_values($enabledDatabases)), ENT_QUOTES, 'UTF-8'); ?>"
<?php if ($isLocalInstall) { ?>
data-dev-mode="true"
<?php } ?>
>
<div class="installer-backdrop" aria-hidden="true">
<span class="installer-gradients">
<span class="installer-blob blob-one">
<?php include __DIR__ . '/installer/icons/install-bg-1.svg'; ?>
</span>
<span class="installer-blob blob-two">
<?php include __DIR__ . '/installer/icons/install-bg-2.svg'; ?>
</span>
</span>
</div>
<main class="installer-main">
<div class="installer-card" data-step="<?php echo $step; ?>">
<div class="installer-step">
<div class="step-panel is-active">
<?php include $stepFile; ?>
</div>
</div>
<div class="action-shell">
<div class="divider"></div>
<div class="action-bar">
<button class="button secondary" type="button" data-action="back">
<span class="button-text typography-text-m-500">Back</span>
</button>
<div class="step-indicators" aria-hidden="true">
<span class="step-indicator" data-step="1">
<span class="indicator-active"><?php include __DIR__ . '/installer/icons/indicator-active.svg'; ?></span>
<span class="indicator-inactive"><?php include __DIR__ . '/installer/icons/indicator-inactive.svg'; ?></span>
</span>
<span class="step-indicator" data-step="2">
<span class="indicator-active"><?php include __DIR__ . '/installer/icons/indicator-active.svg'; ?></span>
<span class="indicator-inactive"><?php include __DIR__ . '/installer/icons/indicator-inactive.svg'; ?></span>
</span>
<span class="step-indicator" data-step="3">
<span class="indicator-active"><?php include __DIR__ . '/installer/icons/indicator-active.svg'; ?></span>
<span class="indicator-inactive"><?php include __DIR__ . '/installer/icons/indicator-inactive.svg'; ?></span>
</span>
<span class="step-indicator" data-step="4">
<span class="indicator-active"><?php include __DIR__ . '/installer/icons/indicator-active.svg'; ?></span>
<span class="indicator-inactive"><?php include __DIR__ . '/installer/icons/indicator-inactive.svg'; ?></span>
</span>
</div>
<button class="button primary" type="button" data-action="next">
<span class="button-text typography-text-m-500">Next</span>
</button>
</div>
</div>
</div>
<div class="install-screen" aria-live="polite">
<div class="install-screen-content">
<?php if ($step === 5) { include __DIR__ . '/installer/templates/steps/step-5.phtml'; } ?>
</div>
</div>
</main>
<footer class="installer-footer">
<div class="appwrite-logo">
<?php include __DIR__ . '/installer/icons/appwrite-logo.svg'; ?>
</div>
</footer>
<template id="field-error-template">
<div class="field-error typography-caption-400 text-error">
<span class="field-error-icon">
<?php include __DIR__ . '/installer/icons/exclamation-circle.svg'; ?>
</span>
<span class="field-error-text"></span>
</div>
</template>
<div id="installer-toast-stack" class="installer-toast-stack" aria-live="polite" aria-atomic="true"></div>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
<svg width="131" height="25" viewBox="0 0 131 25" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M38.2643 19.5087C40.4069 19.5087 41.49 18.3947 41.9609 17.6362H42.1728C42.267 18.4421 42.832 19.2006 43.9386 19.2006H46.0341V16.8304H45.4926C45.1159 16.8304 44.9275 16.617 44.9275 16.2852V6.78055H42.1492V8.29751H41.9373C41.3958 7.53903 40.2656 6.47242 38.1937 6.47242C34.8974 6.47242 32.4487 9.2219 32.4487 12.9906C32.4487 16.7593 34.9445 19.5087 38.2643 19.5087ZM38.7588 16.8067C36.8045 16.8067 35.2741 15.3608 35.2741 13.0143C35.2741 10.7151 36.7574 9.15079 38.7352 9.15079C40.6188 9.15079 42.1963 10.5492 42.1963 13.0143C42.1963 15.1238 40.8543 16.8067 38.7588 16.8067Z" fill="currentColor"/>
<path d="M47.6745 24.0166H50.4528V17.6362H50.6647C51.1826 18.3947 52.2893 19.5087 54.4789 19.5087C57.7752 19.5087 60.1768 16.7118 60.1768 12.9906C60.1768 9.2456 57.6104 6.47242 54.2906 6.47242C52.1715 6.47242 51.1356 7.63384 50.6411 8.2738H50.4292V6.78055H47.6745V24.0166ZM53.8903 16.8778C51.9832 16.8778 50.4057 15.4556 50.4057 12.9906C50.4057 10.8811 51.7477 9.10339 53.8432 9.10339C55.7974 9.10339 57.3279 10.644 57.3279 12.9906C57.3279 15.2897 55.8445 16.8778 53.8903 16.8778Z" fill="currentColor"/>
<path d="M61.6104 24.0166H64.3887V17.6362H64.6006C65.1186 18.3947 66.2252 19.5087 68.4149 19.5087C71.7112 19.5087 73.8839 16.7118 73.8839 12.9906C73.8839 9.2456 71.5464 6.47242 68.2265 6.47242C66.1075 6.47242 65.0715 7.63384 64.5771 8.2738H64.3652V6.78055H61.6104V24.0166ZM67.8263 16.8778C65.9191 16.8778 64.3416 15.4556 64.3416 12.9906C64.3416 10.8811 65.6837 9.10339 67.7792 9.10339C69.7334 9.10339 71.2638 10.644 71.2638 12.9906C71.2638 15.2897 69.7805 16.8778 67.8263 16.8778Z" fill="currentColor"/>
<path d="M77.5565 19.489H81.4885L83.7252 9.74733H83.8665L86.1033 19.489H90.0117L93.1415 7.06896H90.3414L88.1046 16.8343H87.8927L85.6559 7.06896H81.9594L79.6991 16.8343H79.4872L77.2739 7.06896H74.3073L77.5565 19.489Z" fill="currentColor"/>
<path d="M94.549 19.489H97.3273V13.3501C97.3273 11.0036 98.4104 9.55771 100.435 9.55771H101.66V6.76083H100.741C99.1638 6.76083 97.963 7.85114 97.4921 8.89405H97.3038V7.06896H94.549V19.489Z" fill="currentColor"/>
<path d="M115.447 19.489H117.613V17.0003H115.47C114.623 17.0003 114.27 16.621 114.27 15.744V9.53401H117.754V7.06896H114.27V3.58472H111.633V7.06896H109.325V9.53401H111.468V15.7677C111.468 18.3987 113.045 19.489 115.447 19.489Z" fill="currentColor"/>
<path d="M125.067 19.5087C127.633 19.5087 129.893 18.2288 130.694 15.6452L128.151 15.029C127.704 16.4037 126.409 17.1148 125.043 17.1148C123.018 17.1148 121.676 15.7875 121.653 13.7016H131V12.9195C131 9.2219 128.716 6.47242 124.949 6.47242C121.629 6.47242 118.78 9.10339 118.78 13.0143C118.78 16.8067 121.3 19.5087 125.067 19.5087ZM121.676 11.6632C121.841 10.17 123.183 8.91377 124.949 8.91377C126.644 8.91377 128.033 9.98037 128.175 11.6632H121.676Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M108.09 19.489H105.312V9.53401H103.145V7.06896H108.09V19.489Z" fill="currentColor"/>
<path d="M106.494 5.34533C107.507 5.34533 108.26 4.58686 108.26 3.59136C108.26 2.61956 107.507 1.86108 106.494 1.86108C105.482 1.86108 104.729 2.61956 104.729 3.59136C104.729 4.58686 105.482 5.34533 106.494 5.34533Z" fill="currentColor"/>
<path d="M24.2577 16.4436V21.9248H10.6705C6.71194 21.9248 3.25559 19.7204 1.40636 16.4436C1.13754 15.9672 0.90225 15.4674 0.704883 14.9487C0.31744 13.9322 0.0738912 12.8415 0 11.7034V10.2214C0.0160422 9.96781 0.0413207 9.71617 0.0743773 9.46752C0.141949 8.95727 0.244035 8.45799 0.378206 7.97265C1.64748 3.37143 5.77469 0 10.6705 0C15.5662 0 19.693 3.37143 20.9622 7.97265H15.1526C14.1988 6.47279 12.5479 5.4812 10.6705 5.4812C8.79305 5.4812 7.14216 6.47279 6.18839 7.97265C5.89768 8.42859 5.67212 8.93136 5.52434 9.46752C5.39308 9.94289 5.32308 10.4442 5.32308 10.9624C5.32308 12.5335 5.96768 13.9497 7.00119 14.9487C7.95886 15.876 9.25 16.4436 10.6705 16.4436H24.2577Z" fill="#FD366E"/>
<path d="M24.2578 9.46753V14.9487H14.3398C15.3733 13.9497 16.018 12.5335 16.018 10.9624C16.018 10.4442 15.9479 9.9429 15.8167 9.46753H24.2578Z" fill="#FD366E"/>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

@@ -0,0 +1,4 @@
<svg width="25" height="22" viewBox="0 0 25 22" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M24.2577 16.4436V21.9248H10.6705C6.71194 21.9248 3.25559 19.7204 1.40636 16.4436C1.13754 15.9672 0.90225 15.4674 0.704883 14.9487C0.31744 13.9322 0.0738912 12.8415 0 11.7034V10.2214C0.0160422 9.96781 0.0413207 9.71617 0.0743773 9.46752C0.141949 8.95727 0.244035 8.45799 0.378206 7.97265C1.64748 3.37143 5.77469 0 10.6705 0C15.5662 0 19.693 3.37143 20.9622 7.97265H15.1526C14.1988 6.47279 12.5479 5.4812 10.6705 5.4812C8.79305 5.4812 7.14216 6.47279 6.18839 7.97265C5.89768 8.42859 5.67212 8.93136 5.52434 9.46752C5.39308 9.94289 5.32308 10.4442 5.32308 10.9624C5.32308 12.5335 5.96768 13.9497 7.00119 14.9487C7.95886 15.876 9.25 16.4436 10.6705 16.4436H24.2577Z" fill="#FD366E"/>
<path d="M24.2578 9.46753V14.9487H14.3398C15.3733 13.9497 16.018 12.5335 16.018 10.9624C16.018 10.4442 15.9479 9.9429 15.8167 9.46753H24.2578Z" fill="#FD366E"/>
</svg>

After

Width:  |  Height:  |  Size: 980 B

@@ -0,0 +1,3 @@
<svg class="accordion-chevron" data-open="false" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.29289 7.29289C5.68342 6.90237 6.31658 6.90237 6.70711 7.29289L10 10.5858L13.2929 7.29289C13.6834 6.90237 14.3166 6.90237 14.7071 7.29289C15.0976 7.68342 15.0976 8.31658 14.7071 8.70711L10.7071 12.7071C10.3166 13.0976 9.68342 13.0976 9.29289 12.7071L5.29289 8.70711C4.90237 8.31658 4.90237 7.68342 5.29289 7.29289Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 536 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20" aria-hidden="true">
<path fill="currentColor" d="M7 9a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2z"/>
<path fill="currentColor" d="M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2V5h8a2 2 0 0 0-2-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 293 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20" aria-hidden="true">
<path fill="currentColor" fill-rule="evenodd" d="M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0m-7 4a1 1 0 1 1-2 0 1 1 0 0 1 2 0m-1-9a1 1 0 0 0-1 1v4a1 1 0 1 0 2 0V6a1 1 0 0 0-1-1" clip-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 296 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path fill="currentColor" fill-rule="evenodd" d="M3.707 2.293a1 1 0 1 0-1.414 1.414l14 14a1 1 0 1 0 1.414-1.414l-1.473-1.473A10 10 0 0 0 19.543 10C18.268 5.943 14.478 3 10 3a9.96 9.96 0 0 0-4.512 1.074zm4.261 4.26 1.514 1.515a2.003 2.003 0 0 1 2.45 2.45l1.515 1.514a4 4 0 0 0-5.478-5.478" clip-rule="evenodd"/>
<path fill="currentColor" d="M12.454 16.697 9.75 13.992a4 4 0 0 1-3.742-3.742L2.335 6.578A10 10 0 0 0 .458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.67-.105 2.454-.303"/>
</svg>

After

Width:  |  Height:  |  Size: 568 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path fill="currentColor" d="M10 12a2 2 0 1 0 0-4 2 2 0 0 0 0 4"/>
<path fill="currentColor" fill-rule="evenodd" d="M.457 10C1.732 5.943 5.522 3 10 3s8.267 2.943 9.542 7c-1.275 4.057-5.065 7-9.542 7-4.478 0-8.268-2.943-9.543-7M14 10a4 4 0 1 1-8 0 4 4 0 0 1 8 0" clip-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 372 B

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M5 8C5 6.34315 6.34315 5 8 5C9.65685 5 11 6.34315 11 8C11 9.65685 9.65685 11 8 11C6.34315 11 5 9.65685 5 8Z" fill="#FD366E"/>
</svg>

After

Width:  |  Height:  |  Size: 261 B

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M5 8C5 6.34315 6.34315 5 8 5C9.65685 5 11 6.34315 11 8C11 9.65685 9.65685 11 8 11C6.34315 11 5 9.65685 5 8Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 266 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20" aria-hidden="true">
<path fill="currentColor" fill-rule="evenodd" d="M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0m-7-4a1 1 0 1 1-2 0 1 1 0 0 1 2 0M9 9a1 1 0 1 0 0 2v3a1 1 0 0 0 1 1h1a1 1 0 1 0 0-2v-3a1 1 0 0 0-1-1z" clip-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 313 B

@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="408" height="353" viewBox="0 0 408 353" fill="none" aria-hidden="true">
<g filter="url(#filter0_f_3091_1065)">
<path d="M216.4 88.698C162.554 64.5014 104.764 118.126 82.6001 147.963C90.6227 156.819 114.663 172.323 146.645 163.495C186.622 152.459 191.109 162.269 229.454 181.887C267.799 201.506 268.207 261.18 291.051 268.537C313.895 275.894 322.054 244.422 324.501 193.74C326.949 143.058 283.709 118.944 216.4 88.698Z" fill="url(#paint0_radial_3091_1065)"/>
</g>
<defs>
<filter id="filter0_f_3091_1065" x="9.91821e-05" y="-2.28882e-05" width="407.2" height="352.2" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="41.3" result="effect1_foregroundBlur_3091_1065"/>
</filter>
<radialGradient id="paint0_radial_3091_1065" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(203.6 176.1) rotate(-90) scale(93.5 121)">
<stop stop-color="#FE9567"/>
<stop offset="1" stop-color="#FE9567" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,16 @@
<svg width="569" height="367" viewBox="0 0 569 367" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<g filter="url(#install-bg-2-filter)">
<path d="M136.265 227.973C89.0703 207.713 99.6672 144.647 110.865 115.646C130.119 111.289 176.904 102.33 210.005 101.35C251.382 100.125 288.253 121.773 333.317 135.252C378.381 148.731 466.051 125.449 467.28 160.577C468.509 195.704 412.793 254.522 402.142 242.269C391.49 230.015 319.798 227.973 246.057 254.522C172.316 281.072 195.257 253.297 136.265 227.973Z" fill="url(#install-bg-2-paint)"/>
</g>
<defs>
<filter id="install-bg-2-filter" x="-0.000198364" y="0.0000457764" width="568.6" height="366.6" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="50.65" result="effect1_foregroundBlur_install_bg_2"/>
</filter>
<radialGradient id="install-bg-2-paint" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(284.3 183.3) rotate(-90) scale(82 183)">
<stop stop-color="#FD366E"/>
<stop offset="1" stop-color="#FD366E" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<circle class="check-bg" cx="8" cy="8" r="7" fill="var(--fgcolor-neutral-primary)"/>
<path class="check-mark" d="M5 8.2L7.2 10.2L11.2 6.2" fill="none" stroke="var(--fgcolor-on-invert)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 383 B

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path class="spinner-ring" opacity="0.2" d="M14.4001 7.99998C14.4001 11.5346 11.5347 14.4 8.0001 14.4C4.46548 14.4 1.6001 11.5346 1.6001 7.99998C1.6001 4.46535 4.46548 1.59998 8.0001 1.59998C11.5347 1.59998 14.4001 4.46535 14.4001 7.99998ZM2.8801 7.99998C2.8801 10.8277 5.1724 13.12 8.0001 13.12C10.8278 13.12 13.1201 10.8277 13.1201 7.99998C13.1201 5.17228 10.8278 2.87998 8.0001 2.87998C5.1724 2.87998 2.8801 5.17228 2.8801 7.99998Z" fill="var(--fgcolor-neutral-secondary)"/>
<path class="spinner-arc" d="M4.32921 2.7574C5.15654 2.1781 6.10923 1.80282 7.10939 1.66226C8.10955 1.5217 9.12877 1.61984 10.0837 1.94866C11.0387 2.27748 11.9023 2.82764 12.6039 3.55416C13.3055 4.28069 13.8252 5.16294 14.1204 6.1288C14.4157 7.09465 14.4782 8.11668 14.3029 9.11133C14.1275 10.106 13.7192 11.045 13.1114 11.8516C12.5035 12.6582 11.7134 13.3095 10.8057 13.7523C9.8979 14.195 8.89823 14.4166 7.8884 14.399L7.91074 13.1192C8.71861 13.1333 9.51834 12.956 10.2446 12.6018C10.9708 12.2476 11.6029 11.7266 12.0891 11.0813C12.5754 10.436 12.902 9.68477 13.0423 8.88906C13.1826 8.09334 13.1326 7.27572 12.8964 6.50303C12.6601 5.73035 12.2444 5.02454 11.6831 4.44333C11.1218 3.86211 10.431 3.42198 9.66701 3.15892C8.90304 2.89586 8.08766 2.81735 7.28753 2.9298C6.48741 3.04225 5.72525 3.34247 5.06339 3.80592L4.32921 2.7574Z" fill="var(--fgcolor-neutral-secondary)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path fill="currentColor" fill-rule="evenodd" d="M5 9V7a5 5 0 1 1 10 0v2a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m8-2v2H7V7a3 3 0 1 1 6 0" clip-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 309 B

@@ -0,0 +1,10 @@
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="33" height="33" rx="8.5" fill="white" stroke="#EDEDF0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.3208 9.43304C27.9548 9.4451 28.0607 9.61 27.2684 9.80574C26.468 10.0028 25.5 9.88216 24.6393 10.2441C22.387 11.188 22.0224 14.8372 19.3397 16.249C17.5807 17.2424 15.7869 17.4703 14.1835 17.973C12.8965 18.4892 12.0961 18.8311 11.1523 19.6448C10.4203 20.2763 10.242 20.889 9.47647 21.6813C8.70022 22.7364 5.75747 21.7993 5 23.1225C5.39952 23.3813 5.6301 23.4523 6.33127 23.3598C6.18647 23.6346 5.26411 23.9966 5.43169 24.4015C5.43169 24.4015 7.66121 24.8077 9.54081 23.6735C10.4176 23.3169 11.2394 22.5621 12.6015 22.3811C14.3658 22.1465 16.354 22.7512 18.487 22.9214C18.046 23.7928 17.5915 24.3077 17.1048 25.0263C16.9547 25.1885 17.2335 25.3319 17.751 25.2341C18.6814 25.0035 19.3558 24.7541 20.0248 24.2916C20.8975 23.6896 21.2729 23.1386 22.009 22.2658C22.6484 23.2914 24.9034 23.5167 25.366 22.6305C24.5053 22.2658 24.3216 20.3688 24.6165 19.5497C24.9651 18.7694 25.2158 17.666 25.4974 16.6404C25.7507 15.7167 25.9076 14.3077 26.2106 13.5864C26.5726 12.6895 27.2764 12.4093 27.8046 11.9334C28.3328 11.4574 28.8584 11.0606 28.8423 9.97198C28.8369 9.61805 28.6546 9.42231 28.3208 9.43304Z" fill="#1F305F"/>
<path d="M5.673 24.2137C7.05522 24.2553 7.43061 24.2191 8.52324 23.7445C9.45232 23.341 10.6951 22.2537 11.7931 21.9024C13.4046 21.3849 15.1327 21.4627 16.8474 21.6651C17.4212 21.7335 17.9977 21.8287 18.4093 21.7844C19.0514 21.3903 19.0823 20.3204 19.4805 20.2346C19.3719 22.3046 18.4843 23.6292 17.5968 24.8304C19.467 24.5006 20.7165 23.3329 21.4727 21.8877C21.7019 21.4493 22.0827 20.732 22.257 20.2387C22.395 20.5658 22.0773 20.7736 22.2261 21.1302C23.43 20.1368 23.9985 19.0053 24.4865 17.3482C25.0522 15.4311 25.6327 13.7834 25.9974 13.2163C26.3527 12.6626 26.9064 12.3207 27.4118 11.9668C27.9856 11.5633 28.4991 11.1436 28.5875 10.3754C27.9816 10.3191 27.8421 10.1797 27.7523 9.87402C27.4493 10.0443 27.1705 10.0818 26.8554 10.0912C26.5819 10.0993 26.2816 10.0872 25.9143 10.1247C22.8804 10.4358 22.7396 13.4013 20.5342 15.5785C20.3921 15.7166 20.1494 15.9204 19.9886 16.0411C19.3075 16.5492 18.5782 16.828 17.81 17.1216C16.5658 17.5962 15.3861 17.7343 14.2197 18.1446C13.363 18.4449 12.5667 18.7894 11.8628 19.2707C11.6872 19.3914 11.4486 19.6059 11.2957 19.7306C10.8828 20.0684 10.612 20.4424 10.3492 20.8286C10.0784 21.2254 9.81831 21.6343 9.42014 22.0244C8.77528 22.6572 6.36746 22.2094 5.51882 22.7966C5.42497 22.8623 5.34855 22.9401 5.29761 23.0339C5.76014 23.2444 6.06982 23.1157 6.60207 23.1734C6.66776 23.6815 5.50005 23.9818 5.673 24.2137Z" fill="#C0765A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.9033 11.3797C25.3323 11.7524 26.2333 11.4534 26.0724 10.712C25.4047 10.6557 25.0186 10.8823 24.9033 11.3797Z" fill="#1F305F"/>
<path d="M27.8971 10.5123C27.7831 10.7523 27.5646 11.0606 27.5646 11.6706C27.5632 11.7752 27.4855 11.8476 27.4841 11.6854C27.4895 11.0888 27.6477 10.8314 27.8153 10.4935C27.8917 10.3554 27.9386 10.4117 27.8971 10.5123Z" fill="#1F305F"/>
<path d="M27.7818 10.4225C27.6464 10.6504 27.322 11.0674 27.2697 11.676C27.2603 11.7806 27.1745 11.845 27.1879 11.6841C27.2469 11.0915 27.5057 10.7202 27.7028 10.3971C27.7912 10.2657 27.8328 10.326 27.7818 10.4225Z" fill="#1F305F"/>
<path d="M27.6772 10.303C27.523 10.5189 27.0243 11.0203 26.9197 11.6209C26.901 11.7241 26.8112 11.7804 26.838 11.6222C26.9466 11.0364 27.3769 10.5752 27.5995 10.2708C27.6987 10.1462 27.7362 10.2105 27.6772 10.303Z" fill="#1F305F"/>
<path d="M27.5835 10.1688C27.4012 10.3619 26.8059 11 26.6182 11.5805C26.5847 11.6797 26.4882 11.724 26.5365 11.5698C26.7255 11.0041 27.247 10.3954 27.5111 10.1259C27.6278 10.0173 27.6546 10.087 27.5835 10.1688Z" fill="#1F305F"/>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -0,0 +1,6 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="8" fill="#081E2B"/>
<g transform="translate(11.4, 6.1)">
<path d="M6.03701 2.19958C5.24603 1.21687 4.56489 0.218806 4.42574 0.0115161C4.41109 -0.00383871 4.38911 -0.00383871 4.37447 0.0115161C4.23531 0.218806 3.55418 1.21687 2.76319 2.19958C-4.02614 11.2666 3.8325 17.3856 3.8325 17.3856L3.89841 17.4316C3.95699 18.3759 4.10348 19.7348 4.10348 19.7348H4.39644H4.6894C4.6894 19.7348 4.83588 18.3836 4.89447 17.4316L4.96038 17.3779C4.9677 17.3779 12.8264 11.2666 6.03701 2.19958ZM4.39644 17.2473C4.39644 17.2473 4.04489 16.9325 3.94968 16.7713V16.756L4.37447 6.88281C4.37447 6.8521 4.41842 6.8521 4.41842 6.88281L4.8432 16.756V16.7713C4.74799 16.9325 4.39644 17.2473 4.39644 17.2473Z" fill="#00ED64"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 861 B

@@ -0,0 +1,27 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="31" height="31" rx="7.5" fill="white" stroke="#EDEDF0"/>
<g transform="translate(3.2, 3.2) scale(1)">
<svg viewBox="0 0 25.6 25.6" width="25.6" height="25.6">
<style><![CDATA[.B{stroke-linecap:round}.C{stroke-linejoin:round}.D{stroke-linejoin:miter}.E{stroke-width:.716}]]></style>
<g fill="none" stroke="#fff">
<path d="M18.983 18.636c.163-1.357.114-1.555 1.124-1.336l.257.023c.777.035 1.793-.125 2.4-.402 1.285-.596 2.047-1.592.78-1.33-2.89.596-3.1-.383-3.1-.383 3.053-4.53 4.33-10.28 3.227-11.687-3.004-3.84-8.205-2.024-8.292-1.976l-.028.005c-.57-.12-1.2-.19-1.93-.2-1.308-.02-2.3.343-3.054.914 0 0-9.277-3.822-8.846 4.807.092 1.836 2.63 13.9 5.66 10.25C8.29 15.987 9.36 14.86 9.36 14.86c.53.353 1.167.533 1.834.468l.052-.044a2.01 2.01 0 0 0 .021.518c-.78.872-.55 1.025-2.11 1.346-1.578.325-.65.904-.046 1.056.734.184 2.432.444 3.58-1.162l-.046.183c.306.245.285 1.76.33 2.842s.116 2.093.337 2.688.48 2.13 2.53 1.7c1.713-.367 3.023-.896 3.143-5.81" fill="#000" stroke="#000" stroke-linecap="butt" stroke-width="2.149" class="D"/>
<path d="M23.535 15.6c-2.89.596-3.1-.383-3.1-.383 3.053-4.53 4.33-10.28 3.228-11.687-3.004-3.84-8.205-2.023-8.292-1.976l-.028.005a10.31 10.31 0 0 0-1.929-.201c-1.308-.02-2.3.343-3.054.914 0 0-9.278-3.822-8.846 4.807.092 1.836 2.63 13.9 5.66 10.25C8.29 15.987 9.36 14.86 9.36 14.86c.53.353 1.167.533 1.834.468l.052-.044a2.02 2.02 0 0 0 .021.518c-.78.872-.55 1.025-2.11 1.346-1.578.325-.65.904-.046 1.056.734.184 2.432.444 3.58-1.162l-.046.183c.306.245.52 1.593.484 2.815s-.06 2.06.18 2.716.48 2.13 2.53 1.7c1.713-.367 2.6-1.32 2.725-2.906.088-1.128.286-.962.3-1.97l.16-.478c.183-1.53.03-2.023 1.085-1.793l.257.023c.777.035 1.794-.125 2.39-.402 1.285-.596 2.047-1.592.78-1.33z" fill="#336791" stroke="none"/>
<g class="E">
<g class="B">
<path d="M12.814 16.467c-.08 2.846.02 5.712.298 6.4s.875 2.05 2.926 1.612c1.713-.367 2.337-1.078 2.607-2.647l.633-5.017M10.356 2.2S1.072-1.596 1.504 7.033c.092 1.836 2.63 13.9 5.66 10.25C8.27 15.95 9.27 14.907 9.27 14.907m6.1-13.4c-.32.1 5.164-2.005 8.282 1.978 1.1 1.407-.175 7.157-3.228 11.687" class="C"/>
<path d="M20.425 15.17s.2.98 3.1.382c1.267-.262.504.734-.78 1.33-1.054.49-3.418.615-3.457-.06-.1-1.745 1.244-1.215 1.147-1.652-.088-.394-.69-.78-1.086-1.744-.347-.84-4.76-7.29 1.224-6.333.22-.045-1.56-5.7-7.16-5.782S7.99 8.196 7.99 8.196" stroke-linejoin="bevel"/>
</g>
<g class="C">
<path d="M11.247 15.768c-.78.872-.55 1.025-2.11 1.346-1.578.325-.65.904-.046 1.056.734.184 2.432.444 3.58-1.163.35-.49-.002-1.27-.482-1.468-.232-.096-.542-.216-.94.23z"/>
<path d="M11.196 15.753c-.08-.513.168-1.122.433-1.836.398-1.07 1.316-2.14.582-5.537-.547-2.53-4.22-.527-4.22-.184s.166 1.74-.06 3.365c-.297 2.122 1.35 3.916 3.246 3.733" class="B"/>
</g>
</g>
<g fill="#fff" class="D">
<path d="M10.322 8.145c-.017.117.215.43.516.472s.558-.202.575-.32-.215-.246-.516-.288-.56.02-.575.136z" stroke-width=".239"/>
<path d="M19.486 7.906c.016.117-.215.43-.516.472s-.56-.202-.575-.32.215-.246.516-.288.56.02.575.136z" stroke-width=".119"/>
</g>
<path d="M20.562 7.095c.05.92-.198 1.545-.23 2.524-.046 1.422.678 3.05-.413 4.68" class="B C E"/>
</g>
</svg>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.99976 2C4.55204 2 4.99976 2.44772 4.99976 3V5.10125C6.26985 3.80489 8.04028 3 9.99976 3C13.0492 3 15.6407 4.94932 16.6012 7.66675C16.7852 8.18747 16.5123 8.75879 15.9916 8.94284C15.4709 9.12689 14.8996 8.85396 14.7155 8.33325C14.0286 6.38991 12.1752 5 9.99976 5C8.36482 5 6.91179 5.78502 5.99911 7H8.99976C9.55204 7 9.99976 7.44771 9.99976 8C9.99976 8.55228 9.55204 9 8.99976 9H3.99976C3.44747 9 2.99976 8.55228 2.99976 8V3C2.99976 2.44772 3.44747 2 3.99976 2ZM4.00792 11.0572C4.52864 10.8731 5.09996 11.146 5.28401 11.6668C5.97088 13.6101 7.82429 15 9.99976 15C11.6347 15 13.0877 14.215 14.0004 13L10.9998 13C10.4475 13 9.99976 12.5523 9.99976 12C9.99976 11.4477 10.4475 11 10.9998 11H15.9998C16.265 11 16.5193 11.1054 16.7069 11.2929C16.8944 11.4804 16.9998 11.7348 16.9998 12V17C16.9998 17.5523 16.552 18 15.9998 18C15.4475 18 14.9998 17.5523 14.9998 17V14.8987C13.7297 16.1951 11.9592 17 9.99976 17C6.95035 17 4.3588 15.0507 3.39833 12.3332C3.21428 11.8125 3.48721 11.2412 4.00792 11.0572Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="3" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 157 B

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.25706 3.09882C9.02167 1.73952 10.9788 1.73952 11.7434 3.09882L17.3237 13.0194C18.0736 14.3526 17.1102 15.9999 15.5805 15.9999H4.4199C2.89025 15.9999 1.92682 14.3526 2.67675 13.0194L8.25706 3.09882ZM11.0001 13C11.0001 13.5523 10.5524 14 10.0001 14C9.44784 14 9.00012 13.5523 9.00012 13C9.00012 12.4477 9.44784 12 10.0001 12C10.5524 12 11.0001 12.4477 11.0001 13ZM10.0001 5C9.44784 5 9.00012 5.44772 9.00012 6V9C9.00012 9.55228 9.44784 10 10.0001 10C10.5524 10 11.0001 9.55228 11.0001 9V6C11.0001 5.44772 10.5524 5 10.0001 5Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 725 B

@@ -0,0 +1,11 @@
(() => {
window.InstallerConstants = Object.freeze({
stepTransitionMs: 260,
errorClearMs: 180,
installPollIntervalMs: 4000,
installFallbackDelayMs: 12000,
redirectDelayMs: 2500,
progressTransitionDelayMs: 320,
progressCompleteDelayMs: 140,
});
})();
+457
View File
@@ -0,0 +1,457 @@
(() => {
const stepContainer = document.querySelector('.installer-step');
const installerCard = document.querySelector('.installer-card');
const backButton = document.querySelector('[data-action="back"]');
const nextButton = document.querySelector('[data-action="next"]');
const installScreen = document.querySelector('.install-screen-content');
const indicatorNodes = Array.from(document.querySelectorAll('.step-indicator'));
const STEP_TRANSITION_TIMEOUT = window.InstallerConstants?.stepTransitionMs ?? 260;
if (!stepContainer || !installerCard) return;
const { validateInstallRequest } = window.InstallerStepsProgress || {};
const isUpgrade = document.body?.dataset.upgrade === 'true';
const stepFlow = isUpgrade ? [1, 6, 4, 5] : [1, 2, 3, 4, 5];
const cardSteps = stepFlow.filter((step) => step !== 5);
const normalizeStep = (step) => {
const numeric = clampStep(step);
if (stepFlow.includes(numeric)) return numeric;
if (numeric <= stepFlow[0]) return stepFlow[0];
for (let i = 0; i < stepFlow.length; i += 1) {
if (numeric < stepFlow[i]) {
return stepFlow[i];
}
}
return stepFlow[stepFlow.length - 1];
};
const buildStepConfig = () => {
const config = {};
stepFlow.forEach((step, index) => {
if (step === 5) {
config[step] = { back: { target: null }, next: { target: null } };
return;
}
const prev = stepFlow[index - 1] ?? null;
const next = stepFlow[index + 1] ?? null;
const label = next === 5 ? (isUpgrade ? 'Update' : 'Install') : 'Next';
config[step] = {
back: { target: prev },
next: { label, target: next }
};
});
return config;
};
const STEP_CONFIG = buildStepConfig();
const stepCache = new Map();
let maxStepHeight = 0;
let isTransitioning = false;
let pendingStep = null;
let pendingPushState = false;
const clampStep = (step) => Math.max(1, Math.min(6, step));
const isInstallLocked = () => Boolean(window.InstallerSteps?.isInstallLocked?.());
const scrollToFirstError = (panel) => {
if (!panel) return;
const getErrorNode = () => panel.querySelector('.field-error.is-visible')
|| panel.querySelector('.field-error')
|| panel.querySelector('.input-field.is-error, .input-action.is-error');
const container = panel.closest('.step-panel') || panel;
const attemptScroll = () => {
const target = getErrorNode();
if (!target || typeof target.getBoundingClientRect !== 'function') return false;
const targetRect = target.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
const targetTop = targetRect.top - containerRect.top + container.scrollTop;
const targetBottom = targetTop + targetRect.height;
const viewTop = container.scrollTop;
const viewBottom = viewTop + containerRect.height;
const padding = 12;
let nextScrollTop = viewTop;
if (targetTop < viewTop + padding) {
nextScrollTop = Math.max(0, targetTop - padding);
} else if (targetBottom > viewBottom - padding) {
nextScrollTop = Math.max(0, targetBottom - containerRect.height + padding);
}
if (Math.abs(nextScrollTop - viewTop) < 1) {
return false;
}
container.scrollTo({ top: nextScrollTop, behavior: 'smooth' });
return true;
};
let remaining = 20;
let lastScrollTop = -1;
const settle = () => {
if (remaining <= 0) return;
const moved = attemptScroll();
remaining -= 1;
const currentTop = container.scrollTop;
const delta = Math.abs(currentTop - lastScrollTop);
lastScrollTop = currentTop;
if (!moved && delta < 0.5) {
return;
}
requestAnimationFrame(settle);
};
requestAnimationFrame(settle);
};
const getStepFromUrl = () => {
const url = new URL(window.location.href);
const step = Number(url.searchParams.get('step') || 1);
return normalizeStep(Number.isNaN(step) ? 1 : step);
};
const buildStepUrl = (step) => {
const url = new URL(window.location.href);
url.searchParams.set('step', step);
return url;
};
const setStepInUrl = (step, pushState) => {
const url = new URL(window.location.href);
url.searchParams.set('step', step);
if (pushState) {
window.history.pushState({ step }, '', url.toString());
}
return url;
};
const updateActionBar = (step) => {
const config = STEP_CONFIG[step] || STEP_CONFIG[1];
if (!backButton || !nextButton) return;
const locked = isInstallLocked();
const setButtonLabel = (button, label) => {
if (!button) return;
let text = button.querySelector('.button-text');
if (!text) {
text = document.createElement('span');
text.className = 'button-text typography-text-m-500';
button.textContent = '';
button.appendChild(text);
}
text.textContent = label;
};
if (!locked && config.back?.target) {
backButton.disabled = false;
backButton.setAttribute('data-step-target', String(config.back.target));
} else {
backButton.disabled = true;
backButton.removeAttribute('data-step-target');
}
setButtonLabel(backButton, 'Back');
if (!locked && config.next?.target) {
setButtonLabel(nextButton, config.next?.label || 'Next');
nextButton.setAttribute('data-step-target', String(config.next?.target || 1));
nextButton.disabled = false;
} else {
setButtonLabel(nextButton, config.next?.label || 'Next');
nextButton.removeAttribute('data-step-target');
nextButton.disabled = true;
}
indicatorNodes.forEach((node, index) => {
const isVisible = index < cardSteps.length;
node.classList.toggle('is-hidden', !isVisible);
if (!isVisible) {
node.classList.remove('is-active');
return;
}
node.classList.toggle('is-active', cardSteps[index] === step);
});
installerCard.setAttribute('data-step', String(step));
document.body.dataset.step = String(step);
if (locked) {
document.body.dataset.installLocked = 'true';
} else {
delete document.body.dataset.installLocked;
}
};
const measureStepHeight = (panel) => {
if (!panel) return;
const height = panel.getBoundingClientRect().height;
if (!height) return;
maxStepHeight = Math.max(maxStepHeight, height);
stepContainer.style.setProperty('--step-min-height', `${maxStepHeight}px`);
};
const runStepInit = (step, rootElement) => {
if (!window.InstallerSteps || typeof window.InstallerSteps.initStep !== 'function') return;
const root = rootElement || stepContainer;
window.InstallerSteps.initStep(step, root);
updateActionBar(step);
};
const fetchStepHtml = (step, url) => {
if (stepCache.has(step)) {
return Promise.resolve(stepCache.get(step));
}
const fetchUrl = new URL(url);
fetchUrl.searchParams.set('partial', '1');
return fetch(fetchUrl.toString(), {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to load step');
}
return response.text();
})
.then((html) => {
stepCache.set(step, html);
return html;
});
};
const preloadSteps = (steps) => {
const current = getStepFromUrl();
const targets = steps.filter((step) => step !== current);
return Promise.all(
targets.map((step) => {
const url = buildStepUrl(step);
return fetchStepHtml(step, url)
.then((html) => {
const panel = document.createElement('div');
panel.className = 'step-panel is-measure';
panel.innerHTML = html;
stepContainer.appendChild(panel);
panel.getBoundingClientRect();
measureStepHeight(panel);
panel.remove();
})
.catch(() => null);
})
);
};
const swapPanels = (step, html, onDone) => {
const activePanel = stepContainer.querySelector('.step-panel');
const measurePanel = document.createElement('div');
measurePanel.className = 'step-panel is-measure';
measurePanel.innerHTML = html;
stepContainer.appendChild(measurePanel);
measurePanel.getBoundingClientRect();
measureStepHeight(measurePanel);
measurePanel.remove();
const newPanel = document.createElement('div');
newPanel.className = 'step-panel is-entering';
newPanel.innerHTML = html;
stepContainer.appendChild(newPanel);
runStepInit(step, newPanel);
newPanel.getBoundingClientRect();
requestAnimationFrame(() => {
newPanel.classList.remove('is-entering');
newPanel.classList.add('is-active');
if (activePanel) {
activePanel.classList.add('is-exiting');
}
});
const finalize = () => {
if (activePanel && activePanel.parentNode) {
activePanel.parentNode.removeChild(activePanel);
}
newPanel.classList.remove('is-entering');
if (typeof onDone === 'function') {
onDone();
}
};
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReduced) {
finalize();
return;
}
let finished = false;
const finishOnce = () => {
if (finished) return;
finished = true;
finalize();
};
newPanel.addEventListener(
'transitionend',
(event) => {
if (event.propertyName === 'opacity') {
finishOnce();
}
},
{ once: true }
);
setTimeout(finishOnce, STEP_TRANSITION_TIMEOUT);
};
const showInstallScreen = (step, html) => {
if (!installScreen) return;
installScreen.innerHTML = html;
runStepInit(step, installScreen);
};
const hideInstallScreen = () => {
if (!installScreen) return;
installScreen.innerHTML = '';
};
const loadStep = (step, pushState) => {
const targetStep = normalizeStep(Number(step));
const currentStep = getStepFromUrl();
if (targetStep === currentStep && pushState) return;
isTransitioning = true;
const url = setStepInUrl(targetStep, pushState);
fetchStepHtml(targetStep, url)
.then((html) => {
if (targetStep === 5) {
showInstallScreen(targetStep, html);
isTransitioning = false;
if (pendingStep !== null && pendingStep !== targetStep) {
const nextStep = pendingStep;
const nextPushState = pendingPushState;
pendingStep = null;
pendingPushState = false;
loadStep(nextStep, nextPushState);
return;
}
pendingStep = null;
pendingPushState = false;
return;
}
hideInstallScreen();
swapPanels(targetStep, html, () => {
isTransitioning = false;
if (pendingStep !== null && pendingStep !== targetStep) {
const nextStep = pendingStep;
const nextPushState = pendingPushState;
pendingStep = null;
pendingPushState = false;
loadStep(nextStep, nextPushState);
return;
}
pendingStep = null;
pendingPushState = false;
});
})
.catch(() => {
isTransitioning = false;
window.location.href = url.toString();
});
};
const requestStep = (step, pushState) => {
const targetStep = normalizeStep(Number(step));
if (isInstallLocked() && targetStep !== 5) {
loadStep(5, true);
return;
}
if (isTransitioning) {
pendingStep = targetStep;
pendingPushState = pendingPushState || pushState;
return;
}
loadStep(targetStep, pushState);
};
document.addEventListener('click', async (event) => {
const button = event.target.closest('[data-step-target]');
if (!button || button.disabled) return;
event.preventDefault();
const target = button.getAttribute('data-step-target');
if (!target) return;
const action = button.getAttribute('data-action');
if (action === 'next') {
const currentStep = getStepFromUrl();
const panel = stepContainer.querySelector('.step-panel') || stepContainer;
const validator = window.InstallerSteps?.validateStep;
if (typeof validator === 'function') {
const valid = validator(currentStep, panel);
if (!valid) {
scrollToFirstError(panel);
return;
}
}
}
if (action === 'next' && String(target) === '5') {
if (typeof validateInstallRequest === 'function') {
const isValid = await validateInstallRequest();
if (!isValid) {
return;
}
}
// Clear stale install data from previous runs so initStep5
// starts a fresh install instead of trying to resume.
const { clearInstallLock, clearInstallId } = window.InstallerStepsState || {};
clearInstallLock?.();
clearInstallId?.();
}
if (isInstallLocked() && Number(target) !== 5) {
requestStep(5, true);
return;
}
requestStep(target, true);
});
window.addEventListener('popstate', (event) => {
const step = event.state?.step || getStepFromUrl();
if (isInstallLocked() && Number(step) !== 5) {
requestStep(5, false);
return;
}
requestStep(step, false);
});
document.addEventListener('DOMContentLoaded', () => {
let step = getStepFromUrl();
if (isInstallLocked() && step !== 5) {
const url = buildStepUrl(5);
window.history.replaceState({ step: 5 }, '', url.toString());
step = 5;
} else {
const url = buildStepUrl(step);
window.history.replaceState({ step }, '', url.toString());
}
const activePanel = stepContainer.querySelector('.step-panel') || stepContainer;
runStepInit(step, activePanel);
measureStepHeight(activePanel);
if (step === 5 && installScreen) {
runStepInit(step, installScreen);
}
const preload = () => {
measureStepHeight(activePanel);
preloadSteps(cardSteps);
};
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(preload).catch(preload);
} else {
preload();
}
});
})();
@@ -0,0 +1,119 @@
(() => {
const getBodyDataset = () => document.body?.dataset ?? {};
const isUpgradeMode = () => getBodyDataset().upgrade === 'true';
const getLockedDatabase = () => getBodyDataset().lockedDatabase || '';
const getEnabledDatabases = () => {
const raw = getBodyDataset().enabledDatabases;
if (!raw) return ['mongodb', 'mariadb', 'postgresql'];
try { return JSON.parse(raw); } catch (e) { return ['mongodb', 'mariadb', 'postgresql']; }
};
const STEP_IDS = Object.freeze({
CONFIG_FILES: 'config-files',
DOCKER_COMPOSE: 'docker-compose',
ENV_VARS: 'env-vars',
DOCKER_CONTAINERS: 'docker-containers',
ACCOUNT_SETUP: 'account-setup',
MIGRATION: 'migration',
SSL_CERTIFICATE: 'ssl-certificate',
REDIRECT: 'redirect'
});
const STATUS = Object.freeze({
IN_PROGRESS: 'in-progress',
COMPLETED: 'completed',
ERROR: 'error'
});
const SSE_EVENTS = Object.freeze({
PING: 'ping',
INSTALL_ID: 'install-id',
PROGRESS: 'progress',
DONE: 'done',
ERROR: 'error'
});
const buildInstallationSteps = (upgrade) => (upgrade ? [
{
id: STEP_IDS.CONFIG_FILES,
inProgress: 'Updating configuration files...',
done: 'Configuration files updated'
},
{
id: STEP_IDS.DOCKER_COMPOSE,
inProgress: 'Updating Docker Compose file...',
done: 'Docker Compose file updated'
},
{
id: STEP_IDS.ENV_VARS,
inProgress: 'Updating environment variables...',
done: 'Environment variables updated'
},
{
id: STEP_IDS.DOCKER_CONTAINERS,
inProgress: 'Restarting Docker containers...',
done: 'Docker containers restarted'
},
{
id: STEP_IDS.MIGRATION,
inProgress: 'Running database migration...',
done: 'Database migration completed'
}
] : [
{
id: STEP_IDS.CONFIG_FILES,
inProgress: 'Creating configuration files...',
done: 'Configuration files created'
},
{
id: STEP_IDS.DOCKER_COMPOSE,
inProgress: 'Generating Docker Compose file...',
done: 'Docker Compose file generated'
},
{
id: STEP_IDS.ENV_VARS,
inProgress: 'Configuring environment variables...',
done: 'Environment variables configured'
},
{
id: STEP_IDS.DOCKER_CONTAINERS,
inProgress: 'Starting Docker containers...',
done: 'Docker containers started'
},
{
id: STEP_IDS.ACCOUNT_SETUP,
inProgress: 'Creating Appwrite account...',
done: 'Appwrite account created'
}
]);
const INSTALLATION_STEPS = buildInstallationSteps(isUpgradeMode());
const CONSTANTS = window.InstallerConstants || {};
const TIMINGS = {
errorClear: CONSTANTS.errorClearMs ?? 180,
installPollInterval: CONSTANTS.installPollIntervalMs ?? 4000,
installFallbackDelay: CONSTANTS.installFallbackDelayMs ?? 12000,
redirectDelay: CONSTANTS.redirectDelayMs ?? 500,
progressTransitionDelay: CONSTANTS.progressTransitionDelayMs ?? 140,
progressCompleteDelay: CONSTANTS.progressCompleteDelayMs ?? 120
};
const clampStep = (step) => {
const numeric = Number(step);
if (Number.isNaN(numeric)) return 1;
return Math.max(1, Math.min(6, numeric));
};
window.InstallerStepsContext = Object.freeze({
getBodyDataset,
isUpgradeMode,
getLockedDatabase,
getEnabledDatabases,
STEP_IDS,
STATUS,
SSE_EVENTS,
INSTALLATION_STEPS,
TIMINGS,
clampStep
});
})();
File diff suppressed because it is too large Load Diff

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