diff --git a/.github/workflows/ai-moderator.yml b/.github/workflows/ai-moderator.yml
index 483f3dbeee..948fa6c0c1 100644
--- a/.github/workflows/ai-moderator.yml
+++ b/.github/workflows/ai-moderator.yml
@@ -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 }}
diff --git a/.github/workflows/auto-label-issue.yml b/.github/workflows/auto-label-issue.yml
index e0eb0de98d..0151c2f9c1 100644
--- a/.github/workflows/auto-label-issue.yml
+++ b/.github/workflows/auto-label-issue.yml
@@ -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
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 260a4656cc..eb58f7f8cd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,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
@@ -43,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
@@ -58,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'
@@ -66,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: '.'
@@ -76,14 +76,14 @@ 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'
@@ -94,10 +94,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup PHP
- uses: shivammathur/setup-php@v2
+ uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0
with:
php-version: '8.3'
tools: composer:v2
@@ -119,7 +119,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 2
@@ -127,7 +127,7 @@ jobs:
if: github.event_name == 'pull_request'
- name: Setup PHP
- uses: shivammathur/setup-php@v2
+ uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0
with:
php-version: '8.3'
tools: composer:v2
@@ -144,10 +144,10 @@ jobs:
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@v2
+ uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0
with:
php-version: '8.3'
tools: composer:v2
@@ -157,7 +157,7 @@ jobs:
run: composer install --prefer-dist --no-progress --ignore-platform-reqs
- name: Cache PHPStan result cache
- uses: actions/cache@v4
+ uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: .phpstan-cache
key: phpstan-${{ github.sha }}
@@ -172,10 +172,10 @@ jobs:
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@v2
+ uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0
with:
php-version: '8.3'
extensions: swoole
@@ -193,10 +193,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out the repo
- uses: actions/checkout@v6
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
- uses: actions/setup-node@v4
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24'
@@ -212,7 +212,7 @@ 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'];
@@ -253,28 +253,28 @@ 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: Login to GHCR
- uses: docker/login-action@v4
+ uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v4
+ uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build and push Appwrite
- uses: docker/build-push-action@v6
+ uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
push: true
@@ -297,16 +297,16 @@ jobs:
packages: read
steps:
- name: checkout
- uses: actions/checkout@v6
+ 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@v4
+ uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -327,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
@@ -350,16 +350,16 @@ jobs:
packages: read
steps:
- name: checkout
- uses: actions/checkout@v6
+ 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@v4
+ uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -385,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
@@ -464,7 +464,7 @@ jobs:
paratest_processes: 1
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set environment
run: |
@@ -488,13 +488,13 @@ 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@v4
+ uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -525,7 +525,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
@@ -573,18 +573,18 @@ jobs:
mode: ${{ fromJSON(needs.matrix.outputs.modes) }}
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
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@v4
+ uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -607,7 +607,7 @@ jobs:
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
@@ -640,16 +640,16 @@ jobs:
mode: ${{ fromJSON(needs.matrix.outputs.modes) }}
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ 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@v4
+ uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -679,7 +679,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
@@ -711,18 +711,18 @@ jobs:
packages: read
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
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@v4
+ uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -735,7 +735,7 @@ jobs:
docker tag ${{ env.REGISTRY_IMAGE }}:${{ github.sha }} ${{ env.IMAGE }}:after
- name: Setup k6
- uses: grafana/setup-k6-action@ffe7d7290dfa715e48c2ccc924d068444c94bde2
+ uses: grafana/setup-k6-action@db07bd9765aac508ef18982e52ab937fe633a065 # v1.2.1
with:
k6-version: ${{ env.K6_VERSION }}
@@ -774,7 +774,7 @@ jobs:
- name: Benchmark before
if: steps.benchmark_before_start.outcome == 'success'
continue-on-error: true
- uses: grafana/run-k6-action@a15e2072ede004e8d46141e33d7f7dad8ad08d9d
+ uses: grafana/run-k6-action@de51a7390bdf0ac85a3bef493691bd71d4c7c158 # v1.4.0
env:
APPWRITE_ENDPOINT: 'http://localhost/v1'
APPWRITE_BENCHMARK_ITERATIONS: '5'
@@ -826,7 +826,7 @@ jobs:
- name: Benchmark after
id: benchmark_after
continue-on-error: true
- uses: grafana/run-k6-action@a15e2072ede004e8d46141e33d7f7dad8ad08d9d
+ uses: grafana/run-k6-action@de51a7390bdf0ac85a3bef493691bd71d4c7c158 # v1.4.0
env:
APPWRITE_ENDPOINT: 'http://localhost/v1'
APPWRITE_BENCHMARK_ITERATIONS: '5'
@@ -846,7 +846,7 @@ jobs:
- name: Comment on PR
if: always()
- uses: actions/github-script@v8
+ 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 }}
@@ -856,7 +856,7 @@ jobs:
await comment({ github, context, core });
- name: Save results
- uses: actions/upload-artifact@v7
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: benchmark-results
diff --git a/.github/workflows/cleanup-cache.yml b/.github/workflows/cleanup-cache.yml
index 4b6b13d35d..e4f28816be 100644
--- a/.github/workflows/cleanup-cache.yml
+++ b/.github/workflows/cleanup-cache.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
- uses: actions/checkout@v6
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Cleanup
run: |
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 7edfde0aae..cb9b09b496 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -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
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index c4289678bb..0a49f658ac 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -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@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
+ uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: 'appwrite_image:latest'
format: 'sarif'
@@ -24,7 +24,7 @@ jobs:
ignore-unfixed: 'false'
severity: 'CRITICAL,HIGH'
- name: Upload Docker 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'
@@ -35,16 +35,16 @@ jobs:
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@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.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@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'
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 692861d44d..68ab657213 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -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
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 84fc4c9fba..ed4e46d811 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -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
diff --git a/.github/workflows/sdk-preview.yml b/.github/workflows/sdk-preview.yml
index f81346a7d1..dacc37a64a 100644
--- a/.github/workflows/sdk-preview.yml
+++ b/.github/workflows/sdk-preview.yml
@@ -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
diff --git a/.github/workflows/specs.yml b/.github/workflows/specs.yml
index 6f377354d5..85c76bacd3 100644
--- a/.github/workflows/specs.yml
+++ b/.github/workflows/specs.yml
@@ -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
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 6e4a8ba73b..73b767aafe 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -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."
diff --git a/app/config/errors.php b/app/config/errors.php
index 0293be3212..e7b6839a20 100644
--- a/app/config/errors.php
+++ b/app/config/errors.php
@@ -1246,6 +1246,26 @@ return [
'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 => [
diff --git a/app/config/sdks.php b/app/config/sdks.php
index e89265b05e..cbfe76dce4 100644
--- a/app/config/sdks.php
+++ b/app/config/sdks.php
@@ -320,6 +320,26 @@ return [
'repoBranch' => 'main',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/claude-plugin/CHANGELOG.md'),
],
+ [
+ 'key' => 'codex-plugin',
+ 'name' => 'CodexPlugin',
+ 'version' => '0.1.0',
+ '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'),
+ ],
],
],
diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php
index 65fd0bfb83..ac07efc72b 100644
--- a/app/controllers/api/account.php
+++ b/app/controllers/api/account.php
@@ -332,15 +332,15 @@ Http::post('/v1/account')
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
- if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
+ if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
- if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
+ if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
}
- if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
+ if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
@@ -1676,15 +1676,15 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
$failureRedirect(Exception::GENERAL_INVALID_EMAIL);
}
- if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
+ if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
$failureRedirect(Exception::USER_EMAIL_DISPOSABLE);
}
- if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
+ if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
$failureRedirect(Exception::USER_EMAIL_NOT_CANONICAL);
}
- if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
+ if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
$failureRedirect(Exception::USER_EMAIL_FREE);
}
@@ -1817,15 +1817,15 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
$failureRedirect(Exception::GENERAL_INVALID_EMAIL);
}
- if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
+ if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
$failureRedirect(Exception::USER_EMAIL_DISPOSABLE);
}
- if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
+ if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
$failureRedirect(Exception::USER_EMAIL_NOT_CANONICAL);
}
- if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
+ if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
$failureRedirect(Exception::USER_EMAIL_FREE);
}
@@ -2175,15 +2175,15 @@ Http::post('/v1/account/tokens/magic-url')
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
- if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
+ if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
- if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
+ if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
}
- if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
+ if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
@@ -2496,15 +2496,15 @@ Http::post('/v1/account/tokens/email')
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
- if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
+ if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
- if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
+ if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
}
- if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
+ if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
@@ -3417,15 +3417,15 @@ Http::patch('/v1/account/email')
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
- if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
+ if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
- if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
+ if ((($project->getId() === 'console') || ($plan['supportsCanonicalEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
}
- if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
+ if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php
index abcecac396..3f52069609 100644
--- a/app/controllers/api/users.php
+++ b/app/controllers/api/users.php
@@ -131,15 +131,15 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
} catch (\Throwable) {
}
- if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
+ if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
- if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
+ 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 (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
+ if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
@@ -1563,15 +1563,15 @@ Http::patch('/v1/users/:userId/email')
} catch (\Throwable) {
}
- if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
+ if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
- if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
+ 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 (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
+ if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php
index c9e4f8b47d..14ffdc059f 100644
--- a/app/controllers/shared/api.php
+++ b/app/controllers/shared/api.php
@@ -3,7 +3,6 @@
use Appwrite\Auth\Key;
use Appwrite\Auth\MFA\Type\TOTP;
use Appwrite\Bus\Events\RequestCompleted;
-use Appwrite\Event\Build;
use Appwrite\Event\Context\Audit as AuditContext;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
@@ -20,6 +19,8 @@ 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;
@@ -489,7 +490,6 @@ Http::init()
->inject('auditContext')
->inject('queueForDeletes')
->inject('queueForDatabase')
- ->inject('queueForBuilds')
->inject('usage')
->inject('queueForFunctions')
->inject('queueForMails')
@@ -503,7 +503,8 @@ Http::init()
->inject('telemetry')
->inject('platform')
->inject('authorization')
- ->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Messaging $queueForMessaging, AuditContext $auditContext, 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 (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Messaging $queueForMessaging, AuditContext $auditContext, Delete $queueForDeletes, EventDatabase $queueForDatabase, 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, callable $cacheControlForStorage) {
$response->setUser($user);
$request->setUser($user);
@@ -618,12 +619,10 @@ Http::init()
$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);
@@ -643,6 +642,7 @@ 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];
@@ -695,6 +695,21 @@ 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', '');
@@ -707,7 +722,7 @@ Http::init()
}
$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']);
@@ -800,7 +815,6 @@ Http::shutdown()
->inject('publisherForUsage')
->inject('queueForDeletes')
->inject('queueForDatabase')
- ->inject('queueForBuilds')
->inject('queueForMessaging')
->inject('queueForFunctions')
->inject('queueForWebhooks')
@@ -812,7 +826,7 @@ Http::shutdown()
->inject('bus')
->inject('apiKey')
->inject('mode')
- ->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, AuditContext $auditContext, Audit $publisherForAudits, 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, string $mode) use ($parseLabel) {
+ ->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, AuditContext $auditContext, Audit $publisherForAudits, Context $usage, UsagePublisher $publisherForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor, Bus $bus, ?Key $apiKey, string $mode) use ($parseLabel) {
$responsePayload = $response->getPayload();
@@ -961,10 +975,6 @@ Http::shutdown()
$queueForDatabase->trigger();
}
- if (! empty($queueForBuilds->getType())) {
- $queueForBuilds->trigger();
- }
-
if (! empty($queueForMessaging->getType())) {
$queueForMessaging->trigger();
}
diff --git a/app/init/resources.php b/app/init/resources.php
index 96457294de..c5d034a125 100644
--- a/app/init/resources.php
+++ b/app/init/resources.php
@@ -2,12 +2,14 @@
use Appwrite\Event\Event;
use Appwrite\Event\Publisher\Audit as AuditPublisher;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Event\Publisher\Certificate as CertificatePublisher;
use Appwrite\Event\Publisher\Execution as ExecutionPublisher;
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
use Appwrite\Event\Publisher\Screenshot as ScreenshotPublisher;
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
use Appwrite\Event\Publisher\Usage as UsagePublisher;
+use Appwrite\Platform\Modules\Storage\Config\StorageCacheControl;
use Appwrite\Utopia\Database\Documents\User;
use Executor\Executor;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
@@ -112,6 +114,10 @@ $container->set('publisherForStatsResources', fn (Publisher $publisher) => new S
$publisher,
new Queue(System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME))
), ['publisher']);
+$container->set('publisherForBuilds', fn (Publisher $publisher) => new BuildPublisher(
+ $publisher,
+ new Queue(System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME))
+), ['publisher']);
/**
* Platform configuration
@@ -198,6 +204,10 @@ $container->set('cache', function (Group $pools, Telemetry $telemetry) {
return $cache;
}, ['pools', 'telemetry']);
+$container->set('cacheControlForStorage', fn () => function (StorageCacheControl $config): string {
+ return \sprintf('private, max-age=%d', $config->maxAge);
+});
+
$container->set('redis', function () {
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
$port = System::getEnv('_APP_REDIS_PORT', 6379);
diff --git a/app/init/resources/request.php b/app/init/resources/request.php
index 70d691370d..9fd282c4ed 100644
--- a/app/init/resources/request.php
+++ b/app/init/resources/request.php
@@ -4,7 +4,6 @@ use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Key;
use Appwrite\Databases\TransactionState;
-use Appwrite\Event\Build;
use Appwrite\Event\Context\Audit as AuditContext;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
@@ -123,9 +122,6 @@ return function (Container $container): void {
$container->set('queueForMails', function (Publisher $publisher) {
return new Mail($publisher);
}, ['publisher']);
- $container->set('queueForBuilds', function (Publisher $publisher) {
- return new Build($publisher);
- }, ['publisher']);
$container->set('queueForDatabase', function (Publisher $publisher) {
return new EventDatabase($publisher);
}, ['publisher']);
diff --git a/app/init/worker/message.php b/app/init/worker/message.php
index 17796fadcd..1469934ad4 100644
--- a/app/init/worker/message.php
+++ b/app/init/worker/message.php
@@ -1,6 +1,5 @@
set('queueForBuilds', function (Publisher $publisher) {
- return new Build($publisher);
- }, ['publisher']);
-
$container->set('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml
index 1bf36b7f6d..6ce1fb5cea 100644
--- a/app/views/install/compose.phtml
+++ b/app/views/install/compose.phtml
@@ -881,6 +881,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _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
@@ -909,6 +916,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _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
diff --git a/composer.json b/composer.json
index 9a84be6111..55106d33d1 100644
--- a/composer.json
+++ b/composer.json
@@ -68,11 +68,11 @@
"utopia-php/dns": "1.6.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/http": "0.34.*",
- "utopia-php/fetch": "0.5.*",
+ "utopia-php/fetch": "^1.1",
"utopia-php/validators": "0.2.*",
"utopia-php/image": "0.8.*",
"utopia-php/locale": "0.8.*",
- "utopia-php/logger": "0.6.*",
+ "utopia-php/logger": "0.8.*",
"utopia-php/messaging": "0.22.*",
"utopia-php/migration": "1.*",
"utopia-php/platform": "0.13.*",
diff --git a/composer.lock b/composer.lock
index bbf0d59a96..41242fa1c6 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "ec2ad489c60f0102f0dfab223b6d1fe4",
+ "content-hash": "4ef65b015dba97e91f6571b061787653",
"packages": [
{
"name": "adhocore/jwt",
@@ -2641,16 +2641,16 @@
},
{
"name": "symfony/deprecation-contracts",
- "version": "v3.6.0",
+ "version": "v3.7.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
+ "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
- "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b",
+ "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b",
"shasum": ""
},
"require": {
@@ -2663,7 +2663,7 @@
"name": "symfony/contracts"
},
"branch-alias": {
- "dev-main": "3.6-dev"
+ "dev-main": "3.7-dev"
}
},
"autoload": {
@@ -2688,7 +2688,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0"
},
"funding": [
{
@@ -2699,12 +2699,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-09-25T14:21:43+00:00"
+ "time": "2026-04-13T15:52:40+00:00"
},
{
"name": "symfony/http-client",
@@ -2809,16 +2813,16 @@
},
{
"name": "symfony/http-client-contracts",
- "version": "v3.6.0",
+ "version": "v3.7.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client-contracts.git",
- "reference": "75d7043853a42837e68111812f4d964b01e5101c"
+ "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c",
- "reference": "75d7043853a42837e68111812f4d964b01e5101c",
+ "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d",
+ "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d",
"shasum": ""
},
"require": {
@@ -2831,7 +2835,7 @@
"name": "symfony/contracts"
},
"branch-alias": {
- "dev-main": "3.6-dev"
+ "dev-main": "3.7-dev"
}
},
"autoload": {
@@ -2867,7 +2871,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0"
+ "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0"
},
"funding": [
{
@@ -2878,12 +2882,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-04-29T11:18:49+00:00"
+ "time": "2026-03-06T13:17:50+00:00"
},
{
"name": "symfony/polyfill-mbstring",
@@ -3212,16 +3220,16 @@
},
{
"name": "symfony/service-contracts",
- "version": "v3.6.1",
+ "version": "v3.7.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43"
+ "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43",
- "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a",
+ "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a",
"shasum": ""
},
"require": {
@@ -3239,7 +3247,7 @@
"name": "symfony/contracts"
},
"branch-alias": {
- "dev-main": "3.6-dev"
+ "dev-main": "3.7-dev"
}
},
"autoload": {
@@ -3275,7 +3283,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.6.1"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.7.0"
},
"funding": [
{
@@ -3295,7 +3303,7 @@
"type": "tidelift"
}
],
- "time": "2025-07-15T11:30:57+00:00"
+ "time": "2026-03-28T09:44:51+00:00"
},
{
"name": "tbachert/spi",
@@ -3403,21 +3411,21 @@
},
{
"name": "utopia-php/agents",
- "version": "1.2.1",
+ "version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/agents.git",
- "reference": "052227953678a30ecc4b5467401fcb0b2386471e"
+ "reference": "0703f4cae02261e09a1bf0d39a4b1ce649cae634"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/agents/zipball/052227953678a30ecc4b5467401fcb0b2386471e",
- "reference": "052227953678a30ecc4b5467401fcb0b2386471e",
+ "url": "https://api.github.com/repos/utopia-php/agents/zipball/0703f4cae02261e09a1bf0d39a4b1ce649cae634",
+ "reference": "0703f4cae02261e09a1bf0d39a4b1ce649cae634",
"shasum": ""
},
"require": {
"php": ">=8.3",
- "utopia-php/fetch": "0.5.*"
+ "utopia-php/fetch": "^1.1.0"
},
"require-dev": {
"laravel/pint": "^1.18",
@@ -3450,9 +3458,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/agents/issues",
- "source": "https://github.com/utopia-php/agents/tree/1.2.1"
+ "source": "https://github.com/utopia-php/agents/tree/1.2.2"
},
- "time": "2026-02-24T06:03:55+00:00"
+ "time": "2026-05-08T10:38:23+00:00"
},
{
"name": "utopia-php/analytics",
@@ -3502,22 +3510,22 @@
},
{
"name": "utopia-php/audit",
- "version": "2.2.2",
+ "version": "2.2.3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
- "reference": "90886c202e7983999e6b6a8201004d5ab61d4b57"
+ "reference": "95e9961fa286d2fdb6bf3eaa198f21d51bf58d9c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/audit/zipball/90886c202e7983999e6b6a8201004d5ab61d4b57",
- "reference": "90886c202e7983999e6b6a8201004d5ab61d4b57",
+ "url": "https://api.github.com/repos/utopia-php/audit/zipball/95e9961fa286d2fdb6bf3eaa198f21d51bf58d9c",
+ "reference": "95e9961fa286d2fdb6bf3eaa198f21d51bf58d9c",
"shasum": ""
},
"require": {
"php": ">=8.0",
"utopia-php/database": "5.*",
- "utopia-php/fetch": "0.5.*",
+ "utopia-php/fetch": "^1.1",
"utopia-php/validators": "0.2.*"
},
"require-dev": {
@@ -3545,9 +3553,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
- "source": "https://github.com/utopia-php/audit/tree/2.2.2"
+ "source": "https://github.com/utopia-php/audit/tree/2.2.3"
},
- "time": "2026-05-04T06:48:58+00:00"
+ "time": "2026-05-08T10:38:23+00:00"
},
{
"name": "utopia-php/auth",
@@ -4172,22 +4180,21 @@
},
{
"name": "utopia-php/emails",
- "version": "0.6.9",
+ "version": "0.6.10",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/emails.git",
- "reference": "3a59fb392a03a88f5497e5fdb0ea84a252a4dfdf"
+ "reference": "2e397754ce68c2ba918564b9f31d9923c0a90429"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/emails/zipball/3a59fb392a03a88f5497e5fdb0ea84a252a4dfdf",
- "reference": "3a59fb392a03a88f5497e5fdb0ea84a252a4dfdf",
+ "url": "https://api.github.com/repos/utopia-php/emails/zipball/2e397754ce68c2ba918564b9f31d9923c0a90429",
+ "reference": "2e397754ce68c2ba918564b9f31d9923c0a90429",
"shasum": ""
},
"require": {
"php": ">=8.0",
"utopia-php/domains": "^1.0",
- "utopia-php/fetch": "^0.5",
"utopia-php/validators": "0.*"
},
"require-dev": {
@@ -4195,7 +4202,8 @@
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.3",
"utopia-php/cli": "^0.22",
- "utopia-php/console": "0.*"
+ "utopia-php/console": "0.*",
+ "utopia-php/fetch": "^1.1"
},
"type": "library",
"autoload": {
@@ -4227,22 +4235,22 @@
],
"support": {
"issues": "https://github.com/utopia-php/emails/issues",
- "source": "https://github.com/utopia-php/emails/tree/0.6.9"
+ "source": "https://github.com/utopia-php/emails/tree/0.6.10"
},
- "time": "2026-03-14T13:52:56+00:00"
+ "time": "2026-05-08T10:16:22+00:00"
},
{
"name": "utopia-php/fetch",
- "version": "0.5.1",
+ "version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/fetch.git",
- "reference": "a96a010e1c273f3888765449687baf58cbc61fcd"
+ "reference": "64f2b3a789480f1deb102ce684dac4217d8e98d5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/fetch/zipball/a96a010e1c273f3888765449687baf58cbc61fcd",
- "reference": "a96a010e1c273f3888765449687baf58cbc61fcd",
+ "url": "https://api.github.com/repos/utopia-php/fetch/zipball/64f2b3a789480f1deb102ce684dac4217d8e98d5",
+ "reference": "64f2b3a789480f1deb102ce684dac4217d8e98d5",
"shasum": ""
},
"require": {
@@ -4251,7 +4259,8 @@
"require-dev": {
"laravel/pint": "^1.5.0",
"phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^9.5"
+ "phpunit/phpunit": "^9.5",
+ "swoole/ide-helper": "^6.0"
},
"type": "library",
"autoload": {
@@ -4266,9 +4275,9 @@
"description": "A simple library that provides an interface for making HTTP Requests.",
"support": {
"issues": "https://github.com/utopia-php/fetch/issues",
- "source": "https://github.com/utopia-php/fetch/tree/0.5.1"
+ "source": "https://github.com/utopia-php/fetch/tree/1.1.2"
},
- "time": "2025-12-18T16:25:10+00:00"
+ "time": "2026-04-29T11:19:19+00:00"
},
{
"name": "utopia-php/http",
@@ -4426,20 +4435,21 @@
},
{
"name": "utopia-php/logger",
- "version": "0.6.2",
+ "version": "0.8.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/logger.git",
- "reference": "25b5bd2ad8bb51292f76332faa7034644fd0941d"
+ "reference": "132236c42222cd614cb882938a48f8729ef3118b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/logger/zipball/25b5bd2ad8bb51292f76332faa7034644fd0941d",
- "reference": "25b5bd2ad8bb51292f76332faa7034644fd0941d",
+ "url": "https://api.github.com/repos/utopia-php/logger/zipball/132236c42222cd614cb882938a48f8729ef3118b",
+ "reference": "132236c42222cd614cb882938a48f8729ef3118b",
"shasum": ""
},
"require": {
- "php": ">=8.0"
+ "php": ">=8.1",
+ "utopia-php/fetch": "^1.1"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -4474,9 +4484,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/logger/issues",
- "source": "https://github.com/utopia-php/logger/tree/0.6.2"
+ "source": "https://github.com/utopia-php/logger/tree/0.8.0"
},
- "time": "2024-10-14T16:02:49+00:00"
+ "time": "2026-05-05T06:04:27+00:00"
},
{
"name": "utopia-php/messaging",
@@ -4531,16 +4541,16 @@
},
{
"name": "utopia-php/migration",
- "version": "1.10.0",
+ "version": "1.10.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
- "reference": "55f4863d690e775f44fec3cae4bd1f4491fed5ea"
+ "reference": "211d01b90ccab9729029151c6c61f543bd755c2e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/migration/zipball/55f4863d690e775f44fec3cae4bd1f4491fed5ea",
- "reference": "55f4863d690e775f44fec3cae4bd1f4491fed5ea",
+ "url": "https://api.github.com/repos/utopia-php/migration/zipball/211d01b90ccab9729029151c6c61f543bd755c2e",
+ "reference": "211d01b90ccab9729029151c6c61f543bd755c2e",
"shasum": ""
},
"require": {
@@ -4580,9 +4590,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
- "source": "https://github.com/utopia-php/migration/tree/1.10.0"
+ "source": "https://github.com/utopia-php/migration/tree/1.10.2"
},
- "time": "2026-05-06T04:35:32+00:00"
+ "time": "2026-05-08T06:25:47+00:00"
},
{
"name": "utopia-php/mongo",
@@ -5228,23 +5238,23 @@
},
{
"name": "utopia-php/vcs",
- "version": "3.2.0",
+ "version": "3.2.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/vcs.git",
- "reference": "44a84ab52b42fc12f812b4d7331286b519d39db3"
+ "reference": "03ccd12b75d67d29094eb760b468fddde4b6b5e5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/vcs/zipball/44a84ab52b42fc12f812b4d7331286b519d39db3",
- "reference": "44a84ab52b42fc12f812b4d7331286b519d39db3",
+ "url": "https://api.github.com/repos/utopia-php/vcs/zipball/03ccd12b75d67d29094eb760b468fddde4b6b5e5",
+ "reference": "03ccd12b75d67d29094eb760b468fddde4b6b5e5",
"shasum": ""
},
"require": {
"adhocore/jwt": "^1.1",
"php": ">=8.0",
"utopia-php/cache": "1.0.*",
- "utopia-php/fetch": "0.5.*"
+ "utopia-php/fetch": "^1.1"
},
"require-dev": {
"laravel/pint": "1.*.*",
@@ -5271,9 +5281,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/vcs/issues",
- "source": "https://github.com/utopia-php/vcs/tree/3.2.0"
+ "source": "https://github.com/utopia-php/vcs/tree/3.2.1"
},
- "time": "2026-04-08T16:00:31+00:00"
+ "time": "2026-05-08T10:13:53+00:00"
},
{
"name": "utopia-php/websocket",
@@ -5466,16 +5476,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
- "version": "1.27.5",
+ "version": "1.28.0",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
- "reference": "9faa38b48d422f3da764a719712905c83b3922cb"
+ "reference": "e363fffd220172c5f1a5032038fa3fdafeeb2dfb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/9faa38b48d422f3da764a719712905c83b3922cb",
- "reference": "9faa38b48d422f3da764a719712905c83b3922cb",
+ "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/e363fffd220172c5f1a5032038fa3fdafeeb2dfb",
+ "reference": "e363fffd220172c5f1a5032038fa3fdafeeb2dfb",
"shasum": ""
},
"require": {
@@ -5511,9 +5521,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
- "source": "https://github.com/appwrite/sdk-generator/tree/1.27.5"
+ "source": "https://github.com/appwrite/sdk-generator/tree/1.28.0"
},
- "time": "2026-05-05T12:09:40+00:00"
+ "time": "2026-05-08T03:37:44+00:00"
},
{
"name": "brianium/paratest",
diff --git a/docker-compose.yml b/docker-compose.yml
index da5efac438..2ae1bf486a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1114,6 +1114,13 @@ services:
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _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
@@ -1145,6 +1152,13 @@ services:
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _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
@@ -1478,4 +1492,4 @@ volumes:
appwrite-sites:
appwrite-builds:
appwrite-config:
- appwrite-models:
\ No newline at end of file
+ appwrite-models:
diff --git a/docs/references/vcs/list-repository-branches.md b/docs/references/vcs/list-repository-branches.md
index eea1795a3e..b614c2ad13 100644
--- a/docs/references/vcs/list-repository-branches.md
+++ b/docs/references/vcs/list-repository-branches.md
@@ -1 +1 @@
-Get a list of all branches from a GitHub repository in your installation. This endpoint returns the names of all branches in the repository and their total count. The GitHub installation must be properly configured and have access to the requested repository for this endpoint to work.
+Get a list of branches from a GitHub repository in your installation. This endpoint supports filtering by a search term and pagination using query strings such as `Query.limit()`, `Query.offset()`, `Query.cursorAfter()`, and `Query.cursorBefore()`. It returns branch names along with the total number of matches. The GitHub installation must be properly configured and have access to the requested repository for this endpoint to work.
diff --git a/src/Appwrite/Auth/OAuth2/Google.php b/src/Appwrite/Auth/OAuth2/Google.php
index 79894c2422..6028bd109b 100644
--- a/src/Appwrite/Auth/OAuth2/Google.php
+++ b/src/Appwrite/Auth/OAuth2/Google.php
@@ -72,7 +72,7 @@ class Google extends OAuth2
'https://oauth2.googleapis.com/token?' . \http_build_query([
'code' => $code,
'client_id' => $this->appID,
- 'client_secret' => $this->appSecret,
+ 'client_secret' => $this->getClientSecret(),
'redirect_uri' => $this->callback,
'scope' => null,
'grant_type' => 'authorization_code'
@@ -95,7 +95,7 @@ class Google extends OAuth2
'https://oauth2.googleapis.com/token?' . \http_build_query([
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
- 'client_secret' => $this->appSecret,
+ 'client_secret' => $this->getClientSecret(),
'grant_type' => 'refresh_token'
])
), true);
@@ -177,4 +177,37 @@ class Google extends OAuth2
return $this->user;
}
+
+ /**
+ * Extracts the Client Secret from the JSON stored in appSecret
+ *
+ * @return string
+ */
+ protected function getClientSecret(): string
+ {
+ $secret = $this->getAppSecret();
+
+ return $secret['clientSecret'] ?? $this->appSecret;
+ }
+
+ /**
+ * Decode the JSON stored in appSecret.
+ * Falls back to treating the raw string as the client secret for backwards compatibility.
+ *
+ * @return array
+ */
+ protected function getAppSecret(): array
+ {
+ try {
+ $secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
+ } catch (\Throwable $th) {
+ return ['clientSecret' => $this->appSecret];
+ }
+
+ if (!\is_array($secret)) {
+ return ['clientSecret' => $this->appSecret];
+ }
+
+ return $secret;
+ }
}
diff --git a/src/Appwrite/Event/Build.php b/src/Appwrite/Event/Build.php
deleted file mode 100644
index 4eaf108f15..0000000000
--- a/src/Appwrite/Event/Build.php
+++ /dev/null
@@ -1,146 +0,0 @@
-setQueue(System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME))
- ->setClass(System::getEnv('_APP_BUILDS_CLASS_NAME', Event::BUILDS_CLASS_NAME));
- }
-
- /**
- * Sets template for the build event.
- *
- * @param Document $template
- * @return self
- */
- public function setTemplate(Document $template): self
- {
- $this->template = $template;
-
- return $this;
- }
-
- /**
- * Sets resource document for the build event.
- *
- * @param Document $resource
- * @return self
- */
- public function setResource(Document $resource): self
- {
- $this->resource = $resource;
-
- return $this;
- }
-
- /**
- * Returns set resource document for the build event.
- *
- * @return null|Document
- */
- public function getResource(): ?Document
- {
- return $this->resource;
- }
-
- /**
- * Sets deployment for the build event.
- *
- * @param Document $deployment
- * @return self
- */
- public function setDeployment(Document $deployment): self
- {
- $this->deployment = $deployment;
-
- return $this;
- }
-
- /**
- * Returns set deployment for the build event.
- *
- * @return null|Document
- */
- public function getDeployment(): ?Document
- {
- return $this->deployment;
- }
-
- /**
- * Sets type for the build event.
- *
- * @param string $type Can be `BUILD_TYPE_DEPLOYMENT` or `BUILD_TYPE_RETRY`.
- * @return self
- */
- public function setType(string $type): self
- {
- $this->type = $type;
-
- return $this;
- }
-
- /**
- * Returns set type for the function event.
- *
- * @return string
- */
- public function getType(): string
- {
- return $this->type;
- }
-
- /**
- * Prepare payload for queue.
- *
- * @return array
- */
- protected function preparePayload(): array
- {
- $platform = $this->platform;
- if (empty($platform)) {
- $platform = Config::getParam('platform', []);
- }
-
- return [
- 'project' => $this->project,
- 'resource' => $this->resource,
- 'deployment' => $this->deployment,
- 'type' => $this->type,
- 'template' => $this->template,
- 'platform' => $platform,
- ];
- }
-
- /**
- * Resets event.
- *
- * @return self
- */
- public function reset(): self
- {
- $this->type = '';
- $this->resource = null;
- $this->deployment = null;
- $this->template = null;
- $this->platform = [];
- parent::reset();
-
- return $this;
- }
-}
diff --git a/src/Appwrite/Event/Message/Build.php b/src/Appwrite/Event/Message/Build.php
new file mode 100644
index 0000000000..0c8967aff6
--- /dev/null
+++ b/src/Appwrite/Event/Message/Build.php
@@ -0,0 +1,45 @@
+platform) ? $this->platform : Config::getParam('platform', []);
+
+ return [
+ 'project' => $this->project->getArrayCopy(),
+ 'resource' => $this->resource->getArrayCopy(),
+ 'deployment' => $this->deployment->getArrayCopy(),
+ 'type' => $this->type,
+ 'template' => $this->template?->getArrayCopy(),
+ 'platform' => $platform,
+ ];
+ }
+
+ public static function fromArray(array $data): static
+ {
+ return new self(
+ project: new Document($data['project'] ?? []),
+ resource: new Document($data['resource'] ?? []),
+ deployment: new Document($data['deployment'] ?? []),
+ type: $data['type'] ?? '',
+ template: !empty($data['template']) ? new Document($data['template']) : null,
+ platform: $data['platform'] ?? [],
+ );
+ }
+}
diff --git a/src/Appwrite/Event/Publisher/Build.php b/src/Appwrite/Event/Publisher/Build.php
new file mode 100644
index 0000000000..9b2a3b68a0
--- /dev/null
+++ b/src/Appwrite/Event/Publisher/Build.php
@@ -0,0 +1,27 @@
+publish($queue ?? $this->queue, $message);
+ }
+
+ public function getSize(bool $failed = false, ?Queue $queue = null): int
+ {
+ return $this->getQueueSize($queue ?? $this->queue, $failed);
+ }
+}
diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php
index 2cf8e79944..42dcacaf34 100644
--- a/src/Appwrite/Extend/Exception.php
+++ b/src/Appwrite/Extend/Exception.php
@@ -348,6 +348,10 @@ class Exception extends \Exception
public const string MIGRATION_IN_PROGRESS = 'migration_in_progress';
public const string MIGRATION_PROVIDER_ERROR = 'migration_provider_error';
public const string MIGRATION_DATABASE_TYPE_UNSUPPORTED = 'migration_database_type_unsupported';
+ public const string MIGRATION_SOURCE_PROJECT_ID_REQUIRED = 'migration_source_project_id_required';
+ public const string MIGRATION_SOURCE_PROJECT_NOT_FOUND = 'migration_source_project_not_found';
+ public const string MIGRATION_SOURCE_TYPE_INVALID = 'migration_source_type_invalid';
+ public const string MIGRATION_DESTINATION_TYPE_INVALID = 'migration_destination_type_invalid';
/** Realtime */
public const string REALTIME_MESSAGE_FORMAT_INVALID = 'realtime_message_format_invalid';
diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php
index 85dfec3cfd..b0efac3829 100644
--- a/src/Appwrite/Platform/Modules/Compute/Base.php
+++ b/src/Appwrite/Platform/Modules/Compute/Base.php
@@ -2,7 +2,8 @@
namespace Appwrite\Platform\Modules\Compute;
-use Appwrite\Event\Build;
+use Appwrite\Event\Message\Build as BuildMessage;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
use Appwrite\Platform\Action;
@@ -57,7 +58,7 @@ class Base extends Action
return $allowedSpecifications[0] ?? APP_COMPUTE_SPECIFICATION_DEFAULT;
}
- public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, string $referenceType = 'branch', string $reference = ''): Document
+ public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, BuildPublisher $publisherForBuilds, Document $template, GitHub $github, bool $activate, array $platform = [], string $referenceType = 'branch', string $reference = ''): Document
{
$deploymentId = ID::unique();
$entrypoint = $function->getAttribute('entrypoint', '');
@@ -150,16 +151,19 @@ class Base extends Action
'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
]));
- $queueForBuilds
- ->setType(BUILD_TYPE_DEPLOYMENT)
- ->setResource($function)
- ->setDeployment($deployment)
- ->setTemplate($template);
+ $publisherForBuilds->enqueue(new BuildMessage(
+ project: $project,
+ resource: $function,
+ deployment: $deployment,
+ type: BUILD_TYPE_DEPLOYMENT,
+ template: $template,
+ platform: $platform,
+ ));
return $deployment;
}
- public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, Authorization $authorization, array $platform, string $referenceType = 'branch', string $reference = ''): Document
+ public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, BuildPublisher $publisherForBuilds, Document $template, GitHub $github, bool $activate, Authorization $authorization, array $platform, string $referenceType = 'branch', string $reference = ''): Document
{
$deploymentId = ID::unique();
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
@@ -358,11 +362,14 @@ class Base extends Action
$this->updateEmptyManualRule($project, $site, $deployment, $dbForPlatform, $authorization);
- $queueForBuilds
- ->setType(BUILD_TYPE_DEPLOYMENT)
- ->setResource($site)
- ->setDeployment($deployment)
- ->setTemplate($template);
+ $publisherForBuilds->enqueue(new BuildMessage(
+ project: $project,
+ resource: $site,
+ deployment: $deployment,
+ type: BUILD_TYPE_DEPLOYMENT,
+ template: $template,
+ platform: $platform,
+ ));
return $deployment;
}
diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php
index 757edc0484..57c465faef 100644
--- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php
+++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php
@@ -2,8 +2,9 @@
namespace Appwrite\Platform\Modules\Functions\Http\Deployments;
-use Appwrite\Event\Build;
use Appwrite\Event\Event;
+use Appwrite\Event\Message\Build as BuildMessage;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -87,9 +88,10 @@ class Create extends Action
->inject('project')
->inject('deviceForFunctions')
->inject('deviceForLocal')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('plan')
->inject('authorization')
+ ->inject('platform')
->callback($this->action(...));
}
@@ -106,9 +108,10 @@ class Create extends Action
Document $project,
Device $deviceForFunctions,
Device $deviceForLocal,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
array $plan,
- Authorization $authorization
+ Authorization $authorization,
+ array $platform
) {
$activate = \strval($activate) === 'true' || \strval($activate) === '1';
@@ -272,10 +275,13 @@ class Create extends Action
}
// Start the build
- $queueForBuilds
- ->setType(BUILD_TYPE_DEPLOYMENT)
- ->setResource($function)
- ->setDeployment($deployment);
+ $publisherForBuilds->enqueue(new BuildMessage(
+ project: $project,
+ resource: $function,
+ deployment: $deployment,
+ type: BUILD_TYPE_DEPLOYMENT,
+ platform: $platform,
+ ));
} else {
if ($deployment->isEmpty()) {
$deployment = $dbForProject->createDocument('deployments', new Document([
diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php
index 9884b12dba..76070c8bf5 100644
--- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php
+++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php
@@ -2,8 +2,9 @@
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Duplicate;
-use Appwrite\Event\Build;
use Appwrite\Event\Event;
+use Appwrite\Event\Message\Build as BuildMessage;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -61,8 +62,10 @@ class Create extends Action
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('deviceForFunctions')
+ ->inject('project')
+ ->inject('platform')
->callback($this->action(...));
}
@@ -73,8 +76,10 @@ class Create extends Action
Response $response,
Database $dbForProject,
Event $queueForEvents,
- Build $queueForBuilds,
- Device $deviceForFunctions
+ BuildPublisher $publisherForBuilds,
+ Device $deviceForFunctions,
+ Document $project,
+ array $platform
) {
$function = $dbForProject->getDocument('functions', $functionId);
@@ -127,10 +132,13 @@ class Create extends Action
'latestDeploymentStatus' => $function->getAttribute('latestDeploymentStatus'),
]));
- $queueForBuilds
- ->setType(BUILD_TYPE_DEPLOYMENT)
- ->setResource($function)
- ->setDeployment($deployment);
+ $publisherForBuilds->enqueue(new BuildMessage(
+ project: $project,
+ resource: $function,
+ deployment: $deployment,
+ type: BUILD_TYPE_DEPLOYMENT,
+ platform: $platform,
+ ));
$queueForEvents
->setParam('functionId', $function->getId())
diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php
index 53af82e701..f18543c60e 100644
--- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php
+++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php
@@ -2,8 +2,9 @@
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Template;
-use Appwrite\Event\Build;
use Appwrite\Event\Event;
+use Appwrite\Event\Message\Build as BuildMessage;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
@@ -76,9 +77,10 @@ class Create extends Base
->inject('dbForPlatform')
->inject('queueForEvents')
->inject('project')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('gitHub')
->inject('authorization')
+ ->inject('platform')
->callback($this->action(...));
}
@@ -96,9 +98,10 @@ class Create extends Base
Database $dbForPlatform,
Event $queueForEvents,
Document $project,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
GitHub $github,
- Authorization $authorization
+ Authorization $authorization,
+ array $platform
) {
$function = $dbForProject->getDocument('functions', $functionId);
@@ -127,10 +130,11 @@ class Create extends Base
project: $project,
installation: $installation,
dbForProject: $dbForProject,
- queueForBuilds: $queueForBuilds,
+ publisherForBuilds: $publisherForBuilds,
template: $template,
github: $github,
activate: $activate,
+ platform: $platform,
referenceType: $type,
reference: $reference
);
@@ -184,11 +188,14 @@ class Create extends Base
$this->updateEmptyManualRule($project, $function, $deployment, $dbForPlatform, $authorization);
- $queueForBuilds
- ->setType(BUILD_TYPE_DEPLOYMENT)
- ->setResource($function)
- ->setDeployment($deployment)
- ->setTemplate($template);
+ $publisherForBuilds->enqueue(new BuildMessage(
+ project: $project,
+ resource: $function,
+ deployment: $deployment,
+ type: BUILD_TYPE_DEPLOYMENT,
+ template: $template,
+ platform: $platform,
+ ));
$queueForEvents
->setParam('functionId', $function->getId())
diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php
index 587c09beba..a74fc12593 100644
--- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php
+++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php
@@ -2,8 +2,8 @@
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Vcs;
-use Appwrite\Event\Build;
use Appwrite\Event\Event;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
@@ -70,8 +70,9 @@ class Create extends Base
->inject('dbForPlatform')
->inject('project')
->inject('queueForEvents')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('gitHub')
+ ->inject('platform')
->callback($this->action(...));
}
@@ -86,8 +87,9 @@ class Create extends Base
Database $dbForPlatform,
Document $project,
Event $queueForEvents,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
GitHub $github,
+ array $platform,
) {
$function = $dbForProject->getDocument('functions', $functionId);
@@ -105,10 +107,11 @@ class Create extends Base
project: $project,
installation: $installation,
dbForProject: $dbForProject,
- queueForBuilds: $queueForBuilds,
+ publisherForBuilds: $publisherForBuilds,
template: $template,
github: $github,
activate: $activate,
+ platform: $platform,
reference: $reference,
referenceType: $type
);
diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php
index 7b294f3f90..00a91141fb 100644
--- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php
+++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php
@@ -2,9 +2,10 @@
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
-use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
+use Appwrite\Event\Message\Build as BuildMessage;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Event\Realtime;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Event\Webhook;
@@ -115,7 +116,7 @@ class Create extends Base
->inject('timelimit')
->inject('project')
->inject('queueForEvents')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('queueForRealtime')
->inject('queueForWebhooks')
->inject('queueForFunctions')
@@ -157,7 +158,7 @@ class Create extends Base
callable $timelimit,
Document $project,
Event $queueForEvents,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
Realtime $queueForRealtime,
Webhook $queueForWebhooks,
Func $queueForFunctions,
@@ -326,10 +327,11 @@ class Create extends Base
project: $project,
installation: $installation,
dbForProject: $dbForProject,
- queueForBuilds: $queueForBuilds,
+ publisherForBuilds: $publisherForBuilds,
template: $template,
github: $github,
activate: true,
+ platform: $platform,
reference: $providerBranch,
referenceType: 'branch'
);
@@ -367,11 +369,14 @@ class Create extends Base
'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
]));
- $queueForBuilds
- ->setType(BUILD_TYPE_DEPLOYMENT)
- ->setResource($function)
- ->setDeployment($deployment)
- ->setTemplate($template);
+ $publisherForBuilds->enqueue(new BuildMessage(
+ project: $project,
+ resource: $function,
+ deployment: $deployment,
+ type: BUILD_TYPE_DEPLOYMENT,
+ template: $template,
+ platform: $platform,
+ ));
}
$functionsDomain = $platform['functionsDomain'];
diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php
index 7d6572d336..b3fcb2c021 100644
--- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php
+++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php
@@ -2,8 +2,8 @@
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
-use Appwrite\Event\Build;
use Appwrite\Event\Event;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
@@ -105,11 +105,12 @@ class Update extends Base
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('dbForPlatform')
->inject('gitHub')
->inject('executor')
->inject('authorization')
+ ->inject('platform')
->callback($this->action(...));
}
@@ -139,11 +140,12 @@ class Update extends Base
Database $dbForProject,
Document $project,
Event $queueForEvents,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
Database $dbForPlatform,
GitHub $github,
Executor $executor,
- Authorization $authorization
+ Authorization $authorization,
+ array $platform
) {
// TODO: If only branch changes, re-deploy
$function = $dbForProject->getDocument('functions', $functionId);
@@ -281,7 +283,7 @@ class Update extends Base
// Redeploy logic
if (!$isConnected && !empty($providerRepositoryId)) {
- $this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github, true);
+ $this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $publisherForBuilds, new Document(), $github, true, $platform);
}
// Inform scheduler if function is still active
diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Screenshots.php b/src/Appwrite/Platform/Modules/Functions/Workers/Screenshots.php
index 7d1cdc4980..c766f73929 100644
--- a/src/Appwrite/Platform/Modules/Functions/Workers/Screenshots.php
+++ b/src/Appwrite/Platform/Modules/Functions/Workers/Screenshots.php
@@ -109,9 +109,7 @@ class Screenshots extends Action
throw new \Exception("Rule for deployment not found");
}
- $client = new FetchClient();
- $client->setTimeout(\intval($site->getAttribute('timeout', '15')) * 1000);
- $client->addHeader('content-type', FetchClient::CONTENT_TYPE_APPLICATION_JSON);
+ $timeout = \intval($site->getAttribute('timeout', '15')) * 1000;
$bucket = $dbForPlatform->getDocument('buckets', 'screenshots');
@@ -162,8 +160,8 @@ class Screenshots extends Action
]);
$screenshotError = null;
- $screenshots = batch(\array_map(function ($key) use ($configs, $apiKey, $site, $client, &$screenshotError) {
- return function () use ($key, $configs, $apiKey, $site, $client, &$screenshotError) {
+ $screenshots = batch(\array_map(function ($key) use ($configs, $apiKey, $site, $timeout, &$screenshotError) {
+ return function () use ($key, $configs, $apiKey, $site, $timeout, &$screenshotError) {
try {
$config = $configs[$key];
@@ -179,6 +177,10 @@ class Screenshots extends Action
}
$browserEndpoint = System::getEnv('_APP_BROWSER_HOST', 'http://appwrite-browser:3000/v1');
+ $client = new FetchClient();
+ $client->setTimeout($timeout);
+ $client->addHeader('content-type', FetchClient::CONTENT_TYPE_APPLICATION_JSON);
+
$fetchResponse = $client->fetch(
url: $browserEndpoint . '/screenshots',
method: 'POST',
diff --git a/src/Appwrite/Platform/Modules/Health/Http/Health/Queue/Builds/Get.php b/src/Appwrite/Platform/Modules/Health/Http/Health/Queue/Builds/Get.php
index 8ae7c8687a..98e65e37e5 100644
--- a/src/Appwrite/Platform/Modules/Health/Http/Health/Queue/Builds/Get.php
+++ b/src/Appwrite/Platform/Modules/Health/Http/Health/Queue/Builds/Get.php
@@ -2,7 +2,7 @@
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\Builds;
-use Appwrite\Event\Build;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -42,16 +42,16 @@ class Get extends Base
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('response')
->callback($this->action(...));
}
- public function action(int|string $threshold, Build $queueForBuilds, Response $response): void
+ public function action(int|string $threshold, BuildPublisher $publisherForBuilds, Response $response): void
{
$threshold = (int) $threshold;
- $size = $queueForBuilds->getSize();
+ $size = $publisherForBuilds->getSize();
$this->assertQueueThreshold($size, $threshold);
diff --git a/src/Appwrite/Platform/Modules/Health/Http/Health/Queue/Failed/Get.php b/src/Appwrite/Platform/Modules/Health/Http/Health/Queue/Failed/Get.php
index 7602de45d3..0d0a787b46 100644
--- a/src/Appwrite/Platform/Modules/Health/Http/Health/Queue/Failed/Get.php
+++ b/src/Appwrite/Platform/Modules/Health/Http/Health/Queue/Failed/Get.php
@@ -2,7 +2,6 @@
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\Failed;
-use Appwrite\Event\Build;
use Appwrite\Event\Database;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
@@ -10,6 +9,7 @@ use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Publisher\Audit;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Event\Publisher\Certificate;
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
use Appwrite\Event\Publisher\Screenshot;
@@ -83,7 +83,7 @@ class Get extends Base
->inject('publisherForUsage')
->inject('queueForWebhooks')
->inject('publisherForCertificates')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('queueForMessaging')
->inject('publisherForMigrations')
->inject('publisherForScreenshots')
@@ -103,7 +103,7 @@ class Get extends Base
UsagePublisher $publisherForUsage,
Webhook $queueForWebhooks,
Certificate $publisherForCertificates,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
Messaging $queueForMessaging,
MigrationPublisher $publisherForMigrations,
Screenshot $publisherForScreenshots,
@@ -120,7 +120,7 @@ class Get extends Base
System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME) => $publisherForUsage,
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME) => $queueForWebhooks,
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME) => $publisherForCertificates,
- System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME) => $queueForBuilds,
+ System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME) => $publisherForBuilds,
System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME) => $publisherForScreenshots,
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME) => $queueForMessaging,
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME) => $publisherForMigrations,
diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php
index 71ea5ceb2f..63ed776709 100644
--- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php
+++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php
@@ -2,8 +2,9 @@
namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
-use Appwrite\Event\Build;
use Appwrite\Event\Event;
+use Appwrite\Event\Message\Build as BuildMessage;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -85,7 +86,7 @@ class Create extends Action
->inject('queueForEvents')
->inject('deviceForSites')
->inject('deviceForLocal')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('plan')
->inject('authorization')
->inject('platform')
@@ -107,7 +108,7 @@ class Create extends Action
Event $queueForEvents,
Device $deviceForSites,
Device $deviceForLocal,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
array $plan,
Authorization $authorization,
array $platform,
@@ -315,10 +316,13 @@ class Create extends Action
}
// Start the build
- $queueForBuilds
- ->setType(BUILD_TYPE_DEPLOYMENT)
- ->setResource($site)
- ->setDeployment($deployment);
+ $publisherForBuilds->enqueue(new BuildMessage(
+ project: $project,
+ resource: $site,
+ deployment: $deployment,
+ type: BUILD_TYPE_DEPLOYMENT,
+ platform: $platform,
+ ));
} else {
if ($deployment->isEmpty()) {
$deployment = $dbForProject->createDocument('deployments', new Document([
diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php
index 546549604b..b3619c6017 100644
--- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php
+++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php
@@ -2,8 +2,9 @@
namespace Appwrite\Platform\Modules\Sites\Http\Deployments\Duplicate;
-use Appwrite\Event\Build;
use Appwrite\Event\Event;
+use Appwrite\Event\Message\Build as BuildMessage;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -63,7 +64,7 @@ class Create extends Action
->inject('dbForProject')
->inject('dbForPlatform')
->inject('queueForEvents')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('deviceForSites')
->inject('authorization')
->inject('platform')
@@ -79,7 +80,7 @@ class Create extends Action
Database $dbForProject,
Database $dbForPlatform,
Event $queueForEvents,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
Device $deviceForSites,
Authorization $authorization,
array $platform
@@ -177,10 +178,13 @@ class Create extends Action
]))
);
- $queueForBuilds
- ->setType(BUILD_TYPE_DEPLOYMENT)
- ->setResource($site)
- ->setDeployment($deployment);
+ $publisherForBuilds->enqueue(new BuildMessage(
+ project: $project,
+ resource: $site,
+ deployment: $deployment,
+ type: BUILD_TYPE_DEPLOYMENT,
+ platform: $platform,
+ ));
$queueForEvents
->setParam('siteId', $site->getId())
diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
index f648c57a83..29854d473b 100644
--- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
+++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
@@ -2,8 +2,9 @@
namespace Appwrite\Platform\Modules\Sites\Http\Deployments\Template;
-use Appwrite\Event\Build;
use Appwrite\Event\Event;
+use Appwrite\Event\Message\Build as BuildMessage;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
@@ -77,7 +78,7 @@ class Create extends Base
->inject('dbForPlatform')
->inject('project')
->inject('queueForEvents')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('gitHub')
->inject('authorization')
->inject('platform')
@@ -98,7 +99,7 @@ class Create extends Base
Database $dbForPlatform,
Document $project,
Event $queueForEvents,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
GitHub $github,
Authorization $authorization,
array $platform
@@ -130,7 +131,7 @@ class Create extends Base
installation: $installation,
dbForProject: $dbForProject,
dbForPlatform: $dbForPlatform,
- queueForBuilds: $queueForBuilds,
+ publisherForBuilds: $publisherForBuilds,
template: $template,
github: $github,
activate: $activate,
@@ -223,11 +224,14 @@ class Create extends Base
$this->updateEmptyManualRule($project, $site, $deployment, $dbForPlatform, $authorization);
- $queueForBuilds
- ->setType(BUILD_TYPE_DEPLOYMENT)
- ->setResource($site)
- ->setDeployment($deployment)
- ->setTemplate($template);
+ $publisherForBuilds->enqueue(new BuildMessage(
+ project: $project,
+ resource: $site,
+ deployment: $deployment,
+ type: BUILD_TYPE_DEPLOYMENT,
+ template: $template,
+ platform: $platform,
+ ));
$queueForEvents
->setParam('siteId', $site->getId())
diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Vcs/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Vcs/Create.php
index 4351dd8dd9..d34b8c4055 100644
--- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Vcs/Create.php
+++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Vcs/Create.php
@@ -2,8 +2,8 @@
namespace Appwrite\Platform\Modules\Sites\Http\Deployments\Vcs;
-use Appwrite\Event\Build;
use Appwrite\Event\Event;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
@@ -71,7 +71,7 @@ class Create extends Base
->inject('dbForPlatform')
->inject('project')
->inject('queueForEvents')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('gitHub')
->inject('authorization')
->inject('platform')
@@ -89,7 +89,7 @@ class Create extends Base
Database $dbForPlatform,
Document $project,
Event $queueForEvents,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
GitHub $github,
Authorization $authorization,
array $platform
@@ -111,7 +111,7 @@ class Create extends Base
installation: $installation,
dbForProject: $dbForProject,
dbForPlatform: $dbForPlatform,
- queueForBuilds: $queueForBuilds,
+ publisherForBuilds: $publisherForBuilds,
template: $template,
github: $github,
activate: $activate,
diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php
index 3c0d090b7b..2aee03265e 100644
--- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php
+++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php
@@ -2,8 +2,8 @@
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
-use Appwrite\Event\Build;
use Appwrite\Event\Event;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Validator\Specification;
@@ -99,10 +99,11 @@ class Update extends Base
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('dbForPlatform')
->inject('gitHub')
->inject('executor')
+ ->inject('platform')
->callback($this->action(...));
}
@@ -133,10 +134,11 @@ class Update extends Base
Database $dbForProject,
Document $project,
Event $queueForEvents,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
Database $dbForPlatform,
GitHub $github,
- Executor $executor
+ Executor $executor,
+ array $platform
) {
if (!empty($adapter)) {
$configFramework = Config::getParam('frameworks')[$framework] ?? [];
@@ -279,7 +281,7 @@ class Update extends Base
// Redeploy logic
if (!$isConnected && !empty($providerRepositoryId)) {
- $this->redeployVcsFunction($request, $site, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github, true);
+ $this->redeployVcsFunction($request, $site, $project, $installation, $dbForProject, $publisherForBuilds, new Document(), $github, true, $platform);
}
$queueForEvents->setParam('siteId', $site->getId());
diff --git a/src/Appwrite/Platform/Modules/Storage/Config/CacheControl.php b/src/Appwrite/Platform/Modules/Storage/Config/CacheControl.php
new file mode 100644
index 0000000000..ef2ace34ff
--- /dev/null
+++ b/src/Appwrite/Platform/Modules/Storage/Config/CacheControl.php
@@ -0,0 +1,20 @@
+inject('project')
->inject('authorization')
->inject('user')
+ ->inject('cacheControlForStorage')
->callback($this->action(...));
}
@@ -120,7 +123,8 @@ class Get extends Action
Device $deviceForLocal,
Document $project,
Authorization $authorization,
- User $user
+ User $user,
+ callable $cacheControlForStorage
) {
if (!\extension_loaded('imagick')) {
@@ -241,28 +245,43 @@ class Get extends Action
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED, $e->getMessage());
}
- $image->crop((int) $width, (int) $height, $gravity);
+ if ($width > 0 || $height > 0 || $gravity !== Image::GRAVITY_CENTER) {
+ Span::add('storage.transform.crop.width', $width);
+ Span::add('storage.transform.crop.height', $height);
+ Span::add('storage.transform.crop.gravity', $gravity);
+ $image->crop($width, $height, $gravity);
+ }
- if (!empty($opacity)) {
+ if ($opacity !== 1.0) {
+ Span::add('storage.transform.opacity', $opacity);
$image->setOpacity($opacity);
}
if (!empty($background)) {
+ Span::add('storage.transform.background', $background);
$image->setBackground('#' . $background);
}
- if (!empty($borderWidth)) {
+ if ($borderWidth > 0) {
+ Span::add('storage.transform.border.width', $borderWidth);
+ Span::add('storage.transform.border.color', $borderColor);
$image->setBorder($borderWidth, '#' . $borderColor);
}
- if (!empty($borderRadius)) {
+ if ($borderRadius > 0) {
+ Span::add('storage.transform.borderRadius', $borderRadius);
$image->setBorderRadius($borderRadius);
}
- if (!empty($rotation)) {
+ if ($rotation !== 0) {
+ Span::add('storage.transform.rotation', $rotation);
$image->setRotation(($rotation + 360) % 360);
}
+ if ($quality !== -1) {
+ Span::add('storage.transform.quality', $quality);
+ }
+
$data = $image->output($output, $quality);
$renderingTime = \microtime(true) - $startTime - $downloadTime - $decryptionTime - $decompressionTime;
@@ -294,8 +313,20 @@ class Get extends Action
}
}
+ $maxAge = 2592000; // 30 days
+ $cacheControl = $cacheControlForStorage(new StorageCacheControl(
+ source: CacheControl::SOURCE_ACTION,
+ user: $user,
+ maxAge: $maxAge,
+ project: $project,
+ bucket: $bucket,
+ file: $file,
+ resourceToken: $resourceToken,
+ fileSecurity: $fileSecurity,
+ ));
+
$response
- ->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
+ ->addHeader('Cache-Control', $cacheControl)
->setContentType($contentType)
->file($data);
diff --git a/src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php b/src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php
index e174029031..51115b7861 100644
--- a/src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php
+++ b/src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php
@@ -189,15 +189,15 @@ class Create extends Action
} catch (\Throwable) {
}
- if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
+ if ((($project->getId() === 'console') || ($plan['supportsDisposableEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
- if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
+ 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 (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
+ if ((($project->getId() === 'console') || ($plan['supportsFreeEmailValidation'] ?? false)) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
diff --git a/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php b/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
index 8b320535e9..a40d7fc6b9 100644
--- a/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
+++ b/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
@@ -2,7 +2,7 @@
namespace Appwrite\Platform\Modules\VCS\Http\GitHub\Authorize\External;
-use Appwrite\Event\Build;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\VCS\Http\GitHub\Deployment;
@@ -60,7 +60,7 @@ class Update extends Action
->inject('dbForPlatform')
->inject('authorization')
->inject('getProjectDB')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('platform')
->callback($this->action(...));
}
@@ -75,7 +75,7 @@ class Update extends Action
Database $dbForPlatform,
Authorization $authorization,
callable $getProjectDB,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
array $platform
) {
$installation = $dbForPlatform->getDocument('installations', $installationId);
@@ -130,7 +130,7 @@ class Update extends Action
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
$providerCommitAuthorUrl = $commitDetails["commitAuthorUrl"] ?? '';
- $this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, true, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $platform);
+ $this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, true, $dbForPlatform, $authorization, $publisherForBuilds, $getProjectDB, $platform);
$response->noContent();
}
diff --git a/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php b/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php
index 33d7e984fb..8bc090bb03 100644
--- a/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php
+++ b/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php
@@ -2,8 +2,9 @@
namespace Appwrite\Platform\Modules\VCS\Http\GitHub;
-use Appwrite\Event\Build;
use Appwrite\Event\Event;
+use Appwrite\Event\Message\Build as BuildMessage;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
use Appwrite\Vcs\Comment;
@@ -43,7 +44,7 @@ trait Deployment
bool $external,
Database $dbForPlatform,
Authorization $authorization,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
callable $getProjectDB,
array $platform,
) {
@@ -528,14 +529,16 @@ trait Deployment
$queueName = $this->getBuildQueueName($project, $dbForPlatform, $authorization);
- $queueForBuilds
- ->setQueue($queueName)
- ->setType(BUILD_TYPE_DEPLOYMENT)
- ->setResource($resource)
- ->setDeployment($deployment)
- ->setProject($project); // set the project because it won't be set for git deployments
-
- $queueForBuilds->trigger(); // must trigger here so that we create a build for each function/site
+ $publisherForBuilds->enqueue(
+ new BuildMessage(
+ project: $project,
+ resource: $resource,
+ deployment: $deployment,
+ type: BUILD_TYPE_DEPLOYMENT,
+ platform: $platform,
+ ),
+ new \Utopia\Queue\Queue($queueName)
+ );
Span::add("{$logBase}.build.triggered", 'true');
//TODO: Add event?
@@ -545,8 +548,6 @@ trait Deployment
}
}
- $queueForBuilds->reset(); // prevent shutdown hook from triggering again
-
if (!empty($errors)) {
throw new Exception(Exception::GENERAL_UNKNOWN, \implode("\n", $errors));
}
diff --git a/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php b/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
index e3dbcfa0e9..0b81504309 100644
--- a/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
+++ b/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
@@ -2,7 +2,7 @@
namespace Appwrite\Platform\Modules\VCS\Http\GitHub\Events;
-use Appwrite\Event\Build;
+use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\VCS\Http\GitHub\Deployment;
@@ -41,7 +41,7 @@ class Create extends Action
->inject('dbForPlatform')
->inject('authorization')
->inject('getProjectDB')
- ->inject('queueForBuilds')
+ ->inject('publisherForBuilds')
->inject('platform')
->callback($this->action(...));
}
@@ -53,7 +53,7 @@ class Create extends Action
Database $dbForPlatform,
Authorization $authorization,
callable $getProjectDB,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
array $platform
) {
$this->preprocessEvent($request);
@@ -78,8 +78,8 @@ class Create extends Action
match ($event) {
$github::EVENT_INSTALLATION => $this->handleInstallationEvent($parsedPayload, $dbForPlatform, $authorization),
- $github::EVENT_PUSH => $this->handlePushEvent($parsedPayload, $githubAppId, $privateKey, $github, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $platform),
- $github::EVENT_PULL_REQUEST => $this->handlePullRequestEvent($parsedPayload, $privateKey, $githubAppId, $github, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $platform),
+ $github::EVENT_PUSH => $this->handlePushEvent($parsedPayload, $githubAppId, $privateKey, $github, $dbForPlatform, $authorization, $publisherForBuilds, $getProjectDB, $platform),
+ $github::EVENT_PULL_REQUEST => $this->handlePullRequestEvent($parsedPayload, $privateKey, $githubAppId, $github, $dbForPlatform, $authorization, $publisherForBuilds, $getProjectDB, $platform),
default => null,
};
@@ -129,7 +129,7 @@ class Create extends Action
GitHub $github,
Database $dbForPlatform,
Authorization $authorization,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
callable $getProjectDB,
array $platform,
) {
@@ -164,7 +164,7 @@ class Create extends Action
// Create new deployment only on push (not committed by us) and not when branch is deleted
if ($providerCommitAuthorEmail !== APP_VCS_GITHUB_EMAIL && !$providerBranchDeleted) {
- $this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $platform);
+ $this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $authorization, $publisherForBuilds, $getProjectDB, $platform);
}
}
@@ -175,7 +175,7 @@ class Create extends Action
GitHub $github,
Database $dbForPlatform,
Authorization $authorization,
- Build $queueForBuilds,
+ BuildPublisher $publisherForBuilds,
callable $getProjectDB,
array $platform,
) {
@@ -216,7 +216,7 @@ class Create extends Action
Query::orderDesc('$createdAt')
]));
- $this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $platform);
+ $this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $authorization, $publisherForBuilds, $getProjectDB, $platform);
} elseif ($action == "closed") {
// Allowed external contributions cleanup
diff --git a/src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Branches/XList.php b/src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Branches/XList.php
index 8ead94b7cb..fda462159f 100644
--- a/src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Branches/XList.php
+++ b/src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Branches/XList.php
@@ -7,9 +7,12 @@ use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
+use Appwrite\Utopia\Database\Validator\Queries\Branches;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
+use Utopia\Database\Exception\Query as QueryException;
+use Utopia\Database\Query;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\Text;
@@ -49,6 +52,8 @@ class XList extends Action
))
->param('installationId', '', new Text(256), 'Installation Id')
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
+ ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
+ ->param('queries', [], new Branches(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit, offset, cursorAfter, and cursorBefore', true)
->inject('gitHub')
->inject('response')
->inject('dbForPlatform')
@@ -58,10 +63,18 @@ class XList extends Action
public function action(
string $installationId,
string $providerRepositoryId,
+ string $search,
+ array $queries,
GitHub $github,
Response $response,
Database $dbForPlatform
) {
+ try {
+ $queries = Query::parseQueries($queries);
+ } catch (QueryException $e) {
+ throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
+ }
+
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
@@ -85,11 +98,48 @@ class XList extends Action
$branches = $github->listBranches($owner, $repositoryName);
+ if (!empty($search)) {
+ $branches = \array_values(\array_filter($branches, fn (string $branch) => \stripos($branch, $search) !== false));
+ }
+
+ $total = \count($branches);
+ [
+ 'limit' => $limit,
+ 'offset' => $offset,
+ ] = Query::groupByType($queries);
+ $cursorQuery = \current(Query::getCursorQueries($queries, false));
+
+ $limit ??= APP_LIMIT_LIST_DEFAULT;
+ $offset ??= 0;
+
+ if ($cursorQuery instanceof Query) {
+ $cursor = $cursorQuery->getValue();
+ $cursorDirection = $cursorQuery->getMethod() === Query::TYPE_CURSOR_AFTER
+ ? Database::CURSOR_AFTER
+ : Database::CURSOR_BEFORE;
+
+ $cursorIndex = \array_search($cursor, $branches, true);
+ if ($cursorIndex === false) {
+ throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Branch '{$cursor}' for the 'cursor' value not found.");
+ }
+
+ $offset += $cursorDirection === Database::CURSOR_AFTER ? $cursorIndex + 1 : 0;
+
+ if ($cursorDirection === Database::CURSOR_BEFORE) {
+ $start = \max(0, $cursorIndex - $limit);
+ $branches = \array_slice($branches, $start, $cursorIndex - $start);
+ } else {
+ $branches = \array_slice($branches, $offset, $limit);
+ }
+ } else {
+ $branches = \array_slice($branches, $offset, $limit);
+ }
+
$response->dynamic(new Document([
'branches' => \array_map(function ($branch) {
return new Document(['name' => $branch]);
}, $branches),
- 'total' => \count($branches),
+ 'total' => $total,
]), Response::MODEL_BRANCH_LIST);
}
}
diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php
index b1580f0e68..fbf965bd00 100644
--- a/src/Appwrite/Platform/Tasks/SDKs.php
+++ b/src/Appwrite/Platform/Tasks/SDKs.php
@@ -7,6 +7,7 @@ use Appwrite\SDK\Language\Android;
use Appwrite\SDK\Language\Apple;
use Appwrite\SDK\Language\ClaudePlugin;
use Appwrite\SDK\Language\CLI;
+use Appwrite\SDK\Language\CodexPlugin;
use Appwrite\SDK\Language\CursorPlugin;
use Appwrite\SDK\Language\Dart;
use Appwrite\SDK\Language\Deno;
@@ -455,6 +456,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
case 'claude-plugin':
$config = new ClaudePlugin();
break;
+ case 'codex-plugin':
+ $config = new CodexPlugin();
+ break;
default:
throw new \Exception('Language "' . $language['key'] . '" not supported');
}
diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php
index 39895d38a3..b6c295b3bb 100644
--- a/src/Appwrite/Platform/Workers/Migrations.php
+++ b/src/Appwrite/Platform/Workers/Migrations.php
@@ -197,13 +197,13 @@ class Migrations extends Action
$projectDB = null;
$useAppwriteApiSource = false;
if ($source === SourceAppwrite::getName() && empty($credentials['projectId'])) {
- throw new \Exception('Source projectId is required for Appwrite migrations');
+ throw new Exception(Exception::MIGRATION_SOURCE_PROJECT_ID_REQUIRED);
}
if (! empty($credentials['projectId'])) {
$this->sourceProject = $this->dbForPlatform->getDocument('projects', $credentials['projectId']);
if ($this->sourceProject->isEmpty()) {
- throw new \Exception('Source project not found for provided projectId');
+ throw new Exception(Exception::MIGRATION_SOURCE_PROJECT_NOT_FOUND);
}
$sourceRegion = $this->sourceProject->getAttribute('region', 'default');
@@ -266,7 +266,7 @@ class Migrations extends Action
$this->deviceForMigrations,
$this->dbForProject,
),
- default => throw new \Exception('Invalid source type'),
+ default => throw new Exception(Exception::MIGRATION_SOURCE_TYPE_INVALID),
};
$resources = $migration->getAttribute('resources', []);
@@ -312,7 +312,7 @@ class Migrations extends Action
$options['filename'],
$options['columns'] ?? [],
),
- default => throw new \Exception('Invalid destination type'),
+ default => throw new Exception(Exception::MIGRATION_DESTINATION_TYPE_INVALID),
};
}
@@ -438,6 +438,7 @@ class Migrations extends Action
$transfer = $source = $destination = null;
$aggregatedResources = [];
+ $caughtError = null;
$host = System::getEnv('_APP_MIGRATION_HOST');
if (empty($host)) {
@@ -531,7 +532,6 @@ class Migrations extends Action
if (!empty($sourceErrors) || ! empty($destinationErrors)) {
$migration->setAttribute('status', 'failed');
$migration->setAttribute('stage', 'finished');
- $migration->setAttribute('errors', $this->sanitizeErrors($sourceErrors, $destinationErrors));
return;
}
@@ -546,35 +546,69 @@ class Migrations extends Action
$migration->setAttribute('status', 'failed');
$migration->setAttribute('stage', 'finished');
- call_user_func($this->logError, $th, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [
- 'migrationId' => $migration->getId(),
- 'source' => $migration->getAttribute('source') ?? '',
- 'destination' => $migration->getAttribute('destination') ?? '',
- ]);
+ $caughtError = $th;
+ // Mirror general.php's HTTP-error pattern: typed AppwriteException uses its
+ // registry-driven isPublishable() flag; library-thrown Migration\Exception is
+ // always user-facing; anything else is unknown and surfaced to Sentry.
+ if ($th instanceof Exception) {
+ $publish = $th->isPublishable();
+ } elseif ($th instanceof MigrationException) {
+ $publish = false;
+ } else {
+ $publish = true;
+ }
+
+ if ($publish) {
+ call_user_func($this->logError, $th, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [
+ 'migrationId' => $migration->getId(),
+ 'source' => $migration->getAttribute('source') ?? '',
+ 'destination' => $migration->getAttribute('destination') ?? '',
+ ]);
+ }
} finally {
try {
+ $sourceErrors = $source?->getErrors() ?? [];
+ $destinationErrors = $destination?->getErrors() ?? [];
+
+ if ($caughtError !== null) {
+ if ($caughtError instanceof MigrationException) {
+ // library-thrown, message constructed by us
+ $bubbled = $caughtError;
+ } elseif ($caughtError instanceof Exception) {
+ // typed AppwriteException â message comes from the curated registry
+ $bubbled = new MigrationException(
+ resourceName: '',
+ resourceGroup: '',
+ message: $caughtError->getMessage(),
+ code: $caughtError->getCode(),
+ previous: $caughtError,
+ );
+ } else {
+ // unknown throwable â raw message may embed internal hostnames,
+ // DSNs, tokens, etc. Replace with a generic user-facing string;
+ // the original is preserved on `previous:` for Sentry.
+ $bubbled = new MigrationException(
+ resourceName: '',
+ resourceGroup: '',
+ message: 'Migration failed due to an unexpected error.',
+ code: $caughtError->getCode() ?: 500,
+ previous: $caughtError,
+ );
+ }
+ $destinationErrors[] = $bubbled;
+ }
+
+ $migration->setAttribute('errors', $this->sanitizeErrors(
+ $sourceErrors,
+ $destinationErrors,
+ ));
+
$this->updateMigrationDocument($migration, $project, $queueForRealtime);
if ($migration->getAttribute('status', '') === 'failed') {
Console::error('Migration(' . $migration->getSequence() . ':' . $migration->getId() . ') failed, Project(' . $this->project->getSequence() . ':' . $this->project->getId() . ')');
- $sourceErrors = $source?->getErrors() ?? [];
- $destinationErrors = $destination?->getErrors() ?? [];
-
- foreach ([...$sourceErrors, ...$destinationErrors] as $error) {
- /** @var MigrationException $error */
- if ($error->getCode() === 0 || $error->getCode() >= 500) {
- ($this->logError)($error, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [
- 'migrationId' => $migration->getId(),
- 'source' => $migration->getAttribute('source') ?? '',
- 'destination' => $migration->getAttribute('destination') ?? '',
- 'resourceName' => $error->getResourceName(),
- 'resourceGroup' => $error->getResourceGroup(),
- ]);
- }
- }
-
$source?->error();
$destination?->error();
}
diff --git a/src/Appwrite/SDK/Specification/Format/OpenAPI3.php b/src/Appwrite/SDK/Specification/Format/OpenAPI3.php
index 962bc8948a..f69ec3972a 100644
--- a/src/Appwrite/SDK/Specification/Format/OpenAPI3.php
+++ b/src/Appwrite/SDK/Specification/Format/OpenAPI3.php
@@ -524,6 +524,7 @@ class OpenAPI3 extends Format
case \Appwrite\Utopia\Database\Validator\Queries\Identities::class:
case \Appwrite\Utopia\Database\Validator\Queries\Indexes::class:
case \Appwrite\Utopia\Database\Validator\Queries\Installations::class:
+ case \Appwrite\Utopia\Database\Validator\Queries\Branches::class:
case \Appwrite\Utopia\Database\Validator\Queries\Memberships::class:
case \Appwrite\Utopia\Database\Validator\Queries\Messages::class:
case \Appwrite\Utopia\Database\Validator\Queries\Migrations::class:
@@ -755,7 +756,18 @@ class OpenAPI3 extends Format
$node['schema']['default'] = $param['default'];
}
- if (false !== \strpos($url, ':' . $name)) { // Param is in URL path
+ $pathAliases = [$name, ...($param['aliases'] ?? [])];
+ $pathAliasMap = \array_flip($pathAliases);
+ $isPathParam = false;
+
+ foreach (\explode('/', $url) as $segment) {
+ if ($segment !== '' && $segment[0] === ':' && isset($pathAliasMap[\substr($segment, 1)])) {
+ $isPathParam = true;
+ break;
+ }
+ }
+
+ if ($isPathParam) { // Param is in URL path (directly or through alias)
$node['in'] = 'path';
$temp['parameters'][] = $node;
} elseif ($route->getMethod() == 'GET') { // Param is in query
@@ -796,7 +808,14 @@ class OpenAPI3 extends Format
}
}
- $url = \str_replace(':' . $name, '{' . $name . '}', $url);
+ $segments = \explode('/', $url);
+ foreach ($segments as &$segment) {
+ if ($segment !== '' && $segment[0] === ':' && isset($pathAliasMap[\substr($segment, 1)])) {
+ $segment = '{' . $name . '}';
+ }
+ }
+ unset($segment);
+ $url = \implode('/', $segments);
}
if (!empty($bodyRequired)) {
diff --git a/src/Appwrite/SDK/Specification/Format/Swagger2.php b/src/Appwrite/SDK/Specification/Format/Swagger2.php
index d07d957577..52e33bcc27 100644
--- a/src/Appwrite/SDK/Specification/Format/Swagger2.php
+++ b/src/Appwrite/SDK/Specification/Format/Swagger2.php
@@ -511,6 +511,7 @@ class Swagger2 extends Format
case \Utopia\Database\Validator\Queries::class:
case \Utopia\Database\Validator\Queries\Document::class:
case \Utopia\Database\Validator\Queries\Documents::class:
+ case \Appwrite\Utopia\Database\Validator\Queries\Branches::class:
case \Appwrite\Utopia\Database\Validator\Queries\Columns::class:
case \Appwrite\Utopia\Database\Validator\Queries\Tables::class:
$node['type'] = 'array';
@@ -722,7 +723,18 @@ class Swagger2 extends Format
$node['default'] = $param['default'];
}
- if (\str_contains($url, ':' . $name)) { // Param is in URL path
+ $pathAliases = [$name, ...($param['aliases'] ?? [])];
+ $pathAliasMap = \array_flip($pathAliases);
+ $isPathParam = false;
+
+ foreach (\explode('/', $url) as $segment) {
+ if ($segment !== '' && $segment[0] === ':' && isset($pathAliasMap[\substr($segment, 1)])) {
+ $isPathParam = true;
+ break;
+ }
+ }
+
+ if ($isPathParam) { // Param is in URL path (directly or through alias)
$node['in'] = 'path';
$temp['parameters'][] = $node;
} elseif ($route->getMethod() == 'GET') { // Param is in query
@@ -767,7 +779,14 @@ class Swagger2 extends Format
}
}
- $url = \str_replace(':' . $name, '{' . $name . '}', $url);
+ $segments = \explode('/', $url);
+ foreach ($segments as &$segment) {
+ if ($segment !== '' && $segment[0] === ':' && isset($pathAliasMap[\substr($segment, 1)])) {
+ $segment = '{' . $name . '}';
+ }
+ }
+ unset($segment);
+ $url = \implode('/', $segments);
}
if (!empty($bodyRequired)) {
diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Branches.php b/src/Appwrite/Utopia/Database/Validator/Queries/Branches.php
new file mode 100644
index 0000000000..82ca911747
--- /dev/null
+++ b/src/Appwrite/Utopia/Database/Validator/Queries/Branches.php
@@ -0,0 +1,20 @@
+getMethod();
+
+ if (!\in_array($method, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE], true)) {
+ $this->message = 'Invalid query method: ' . $method;
+ return false;
+ }
+
+ $cursor = $value->getValue();
+
+ $validator = new Text(256);
+ if (!$validator->isValid($cursor)) {
+ $this->message = 'Invalid cursor: ' . $validator->getDescription();
+ return false;
+ }
+
+ return true;
+ }
+
+ public function getMethodType(): string
+ {
+ return self::METHOD_TYPE_CURSOR;
+ }
+}
diff --git a/src/Appwrite/Vcs/Comment.php b/src/Appwrite/Vcs/Comment.php
index 4dc0174e50..8741ecff6c 100644
--- a/src/Appwrite/Vcs/Comment.php
+++ b/src/Appwrite/Vcs/Comment.php
@@ -50,6 +50,8 @@ class Comment
protected string $statePrefix = '[appwrite]: #';
+ protected ?string $tip = null;
+
/**
* @var mixed[] $builds
*/
@@ -81,7 +83,14 @@ class Comment
public function generateComment(): string
{
- $json = \json_encode($this->builds);
+ if ($this->tip === null) {
+ $this->tip = $this->tips[\array_rand($this->tips)];
+ }
+
+ $json = \json_encode([
+ 'builds' => $this->builds,
+ 'tip' => $this->tip,
+ ]);
$text = $this->statePrefix . \base64_encode($json) . "\n\n";
@@ -226,8 +235,7 @@ class Comment
$i++;
}
- $tip = $this->tips[array_rand($this->tips)];
- $text .= "\n
\n\n> [!TIP]\n> $tip\n\n";
+ $text .= "\n
\n\n> [!TIP]\n> {$this->tip}\n\n";
return $text;
}
@@ -252,8 +260,15 @@ class Comment
$json = \base64_decode($state);
- $builds = \json_decode($json, true);
- $this->builds = \is_array($builds) ? $builds : [];
+ $data = \json_decode($json, true);
+
+ if (\is_array($data) && \array_key_exists('builds', $data)) {
+ $this->builds = \is_array($data['builds']) ? $data['builds'] : [];
+ $this->tip = $data['tip'] ?? null;
+ } else {
+ // Backward compatibility with old state format (builds array only)
+ $this->builds = \is_array($data) ? $data : [];
+ }
return $this;
}
diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php
index c42679018e..9010490cc9 100644
--- a/tests/e2e/Services/GraphQL/Base.php
+++ b/tests/e2e/Services/GraphQL/Base.php
@@ -4,6 +4,7 @@ namespace Tests\E2E\Services\GraphQL;
use CURLFile;
use Utopia\Console;
+use Utopia\Image\Image;
trait Base
{
@@ -516,6 +517,21 @@ trait Base
}
';
+ protected function assertFilePreviewResponse(array $file): void
+ {
+ $this->assertEquals(200, $file['headers']['status-code']);
+ $this->assertEquals('image/png', $file['headers']['content-type']);
+ $this->assertNotEmpty($file['body']);
+
+ $image = new Image($file['body']);
+ $dimensions = \getimagesizefromstring($file['body']);
+
+ $this->assertNotEmpty($image->output('png'));
+ $this->assertIsArray($dimensions);
+ $this->assertEquals(100, $dimensions[0]);
+ $this->assertEquals(100, $dimensions[1]);
+ }
+
public function getQuery(string $name): string
{
switch ($name) {
@@ -2388,8 +2404,8 @@ trait Base
}
}';
case self::GET_FILE_PREVIEW:
- return 'query getFilePreview($bucketId: String!, $fileId: String!) {
- storageGetFilePreview(bucketId: $bucketId, fileId: $fileId) {
+ return 'query getFilePreview($bucketId: String!, $fileId: String!, $width: Int, $height: Int) {
+ storageGetFilePreview(bucketId: $bucketId, fileId: $fileId, width: $width, height: $height) {
status
}
}';
diff --git a/tests/e2e/Services/GraphQL/StorageClientTest.php b/tests/e2e/Services/GraphQL/StorageClientTest.php
index dd89819c34..9cdf523a0a 100644
--- a/tests/e2e/Services/GraphQL/StorageClientTest.php
+++ b/tests/e2e/Services/GraphQL/StorageClientTest.php
@@ -200,7 +200,7 @@ class StorageClientTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $gqlPayload);
- $this->assertEquals(46719, \strlen($file['body']));
+ $this->assertFilePreviewResponse($file);
return $file;
}
diff --git a/tests/e2e/Services/GraphQL/StorageServerTest.php b/tests/e2e/Services/GraphQL/StorageServerTest.php
index 1377ef9207..7808c50be6 100644
--- a/tests/e2e/Services/GraphQL/StorageServerTest.php
+++ b/tests/e2e/Services/GraphQL/StorageServerTest.php
@@ -262,7 +262,7 @@ class StorageServerTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $gqlPayload);
- $this->assertEquals(46719, \strlen($file['body']));
+ $this->assertFilePreviewResponse($file);
return $file;
}
diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php
index 29f7d70435..5e09031a9c 100644
--- a/tests/e2e/Services/Storage/StorageBase.php
+++ b/tests/e2e/Services/Storage/StorageBase.php
@@ -957,6 +957,68 @@ trait StorageBase
$this->assertNotEquals($imageBefore->getImageBlob(), $imageAfter->getImageBlob());
}
+ public function testFilePreviewCacheControlOnCacheHit(): void
+ {
+ $data = $this->setupBucketFile();
+ $bucketId = $data['bucketId'];
+ $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([
+ 'content-type' => 'multipart/form-data',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'fileId' => ID::unique(),
+ 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'),
+ 'permissions' => [
+ Permission::read(Role::any()),
+ Permission::update(Role::any()),
+ Permission::delete(Role::any()),
+ ],
+ ]);
+ $this->assertEquals(201, $file['headers']['status-code']);
+ $this->assertNotEmpty($file['body']['$id']);
+
+ $fileId = $file['body']['$id'];
+ $params = [
+ 'width' => 123,
+ 'height' => 45,
+ 'output' => 'png',
+ 'quality' => 80,
+ ];
+ $headers = array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders());
+
+ $preview = $this->client->call(
+ Client::METHOD_GET,
+ '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/preview',
+ $headers,
+ $params
+ );
+
+ $this->assertEquals(200, $preview['headers']['status-code']);
+ $this->assertEquals('image/png', $preview['headers']['content-type']);
+ $this->assertEquals('private, max-age=2592000', $preview['headers']['cache-control']);
+ $this->assertEquals('miss', $preview['headers']['x-appwrite-cache']);
+ $this->assertNotEmpty($preview['body']);
+
+ $cachedPreview = [];
+ $this->assertEventually(function () use (&$cachedPreview, $bucketId, $fileId, $headers, $params) {
+ $cachedPreview = $this->client->call(
+ Client::METHOD_GET,
+ '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/preview',
+ $headers,
+ $params
+ );
+
+ $this->assertEquals('hit', $cachedPreview['headers']['x-appwrite-cache']);
+ });
+
+ $this->assertEquals(200, $cachedPreview['headers']['status-code']);
+ $this->assertEquals('image/png', $cachedPreview['headers']['content-type']);
+ $this->assertStringStartsWith('private, max-age=', $cachedPreview['headers']['cache-control']);
+ $this->assertEquals($preview['body'], $cachedPreview['body']);
+ }
+
public function testFilePreviewZstdCompression(): void
{
$data = $this->setupZstdCompressionBucket();
diff --git a/tests/e2e/Services/VCS/VCSConsoleClientTest.php b/tests/e2e/Services/VCS/VCSConsoleClientTest.php
index 854e7110f1..23007339de 100644
--- a/tests/e2e/Services/VCS/VCSConsoleClientTest.php
+++ b/tests/e2e/Services/VCS/VCSConsoleClientTest.php
@@ -513,6 +513,59 @@ class VCSConsoleClientTest extends Scope
$this->assertEquals($repositoryBranches['body']['branches'][0]['name'], 'main');
$this->assertEquals($repositoryBranches['body']['branches'][1]['name'], 'test');
+ $repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'search' => 'tes',
+ ]);
+
+ $this->assertEquals(200, $repositoryBranches['headers']['status-code']);
+ $this->assertEquals($repositoryBranches['body']['total'], 1);
+ $this->assertCount(1, $repositoryBranches['body']['branches']);
+ $this->assertEquals($repositoryBranches['body']['branches'][0]['name'], 'test');
+
+ $repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'queries' => [
+ Query::limit(1)->toString(),
+ Query::offset(1)->toString(),
+ ],
+ ]);
+
+ $this->assertEquals(200, $repositoryBranches['headers']['status-code']);
+ $this->assertEquals($repositoryBranches['body']['total'], 2);
+ $this->assertCount(1, $repositoryBranches['body']['branches']);
+ $this->assertEquals($repositoryBranches['body']['branches'][0]['name'], 'test');
+
+ $repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'queries' => [
+ Query::limit(1)->toString(),
+ Query::cursorAfter(new \Utopia\Database\Document(['$id' => 'main']))->toString(),
+ ],
+ ]);
+
+ $this->assertEquals(200, $repositoryBranches['headers']['status-code']);
+ $this->assertEquals($repositoryBranches['body']['total'], 2);
+ $this->assertCount(1, $repositoryBranches['body']['branches']);
+ $this->assertEquals($repositoryBranches['body']['branches'][0]['name'], 'test');
+
+ $repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'queries' => [
+ Query::limit(1)->toString(),
+ Query::cursorBefore(new \Utopia\Database\Document(['$id' => 'test']))->toString(),
+ ],
+ ]);
+
+ $this->assertEquals(200, $repositoryBranches['headers']['status-code']);
+ $this->assertEquals($repositoryBranches['body']['total'], 2);
+ $this->assertCount(1, $repositoryBranches['body']['branches']);
+ $this->assertEquals($repositoryBranches['body']['branches'][0]['name'], 'main');
+
/**
* Test for FAILURE
*/
@@ -522,6 +575,16 @@ class VCSConsoleClientTest extends Scope
], $this->getHeaders()));
$this->assertEquals(404, $repositoryBranches['headers']['status-code']);
+
+ $repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'queries' => [
+ Query::cursorAfter(new \Utopia\Database\Document(['$id' => 'missing-branch']))->toString(),
+ ],
+ ]);
+
+ $this->assertEquals(400, $repositoryBranches['headers']['status-code']);
}
public function testCreateFunctionUsingVCS(): void
diff --git a/tests/unit/Vcs/CommentTest.php b/tests/unit/Vcs/CommentTest.php
new file mode 100644
index 0000000000..29973089c6
--- /dev/null
+++ b/tests/unit/Vcs/CommentTest.php
@@ -0,0 +1,154 @@
+ 'localhost']);
+ $comment->addBuild(
+ new Document(['$id' => 'project1', 'name' => 'Test Project', 'region' => 'default']),
+ new Document(['$id' => 'func1', 'name' => 'Test Function']),
+ 'function',
+ 'ready',
+ 'dep1',
+ ['type' => 'logs'],
+ ''
+ );
+
+ $first = $comment->generateComment();
+ $firstTip = $this->extractTip($first);
+
+ $this->assertNotNull($firstTip);
+ $this->assertNotEmpty($firstTip);
+
+ $second = $comment->generateComment();
+ $secondTip = $this->extractTip($second);
+
+ $this->assertEquals($firstTip, $secondTip);
+ }
+
+ public function testTipIsRestoredFromParsedComment(): void
+ {
+ $comment = new Comment(['consoleHostname' => 'localhost']);
+ $comment->addBuild(
+ new Document(['$id' => 'project1', 'name' => 'Test Project', 'region' => 'default']),
+ new Document(['$id' => 'func1', 'name' => 'Test Function']),
+ 'function',
+ 'ready',
+ 'dep1',
+ ['type' => 'logs'],
+ ''
+ );
+
+ $original = $comment->generateComment();
+ $originalTip = $this->extractTip($original);
+
+ $parsed = new Comment(['consoleHostname' => 'localhost']);
+ $parsed->parseComment($original);
+ $parsed->addBuild(
+ new Document(['$id' => 'project1', 'name' => 'Test Project', 'region' => 'default']),
+ new Document(['$id' => 'func2', 'name' => 'Another Function']),
+ 'function',
+ 'building',
+ 'dep2',
+ ['type' => 'logs'],
+ ''
+ );
+
+ $regenerated = $parsed->generateComment();
+ $regeneratedTip = $this->extractTip($regenerated);
+
+ $this->assertEquals($originalTip, $regeneratedTip);
+ }
+
+ public function testBackwardCompatibilityWithOldStateFormat(): void
+ {
+ $oldBuilds = [
+ 'project1_func1' => [
+ 'projectName' => 'Test Project',
+ 'projectId' => 'project1',
+ 'region' => 'default',
+ 'resourceName' => 'Test Function',
+ 'resourceId' => 'func1',
+ 'resourceType' => 'function',
+ 'buildStatus' => 'ready',
+ 'deploymentId' => 'dep1',
+ 'action' => ['type' => 'logs'],
+ 'previewUrl' => '',
+ ],
+ ];
+
+ $oldState = '[appwrite]: #' . \base64_encode(\json_encode($oldBuilds)) . "\n\n";
+ $oldState .= "> [!TIP]\n> Old tip that should be ignored\n\n";
+
+ $comment = new Comment(['consoleHostname' => 'localhost']);
+ $comment->parseComment($oldState);
+
+ $new = $comment->generateComment();
+ $newTip = $this->extractTip($new);
+
+ $this->assertNotNull($newTip);
+ $this->assertNotEquals('Old tip that should be ignored', $newTip);
+ $this->assertContains($newTip, $this->getTips());
+ }
+
+ public function testParseOldStateFormatWithOnlyBuilds(): void
+ {
+ $oldBuilds = [
+ 'project1_func1' => [
+ 'projectName' => 'Test Project',
+ 'projectId' => 'project1',
+ 'region' => 'default',
+ 'resourceName' => 'Test Function',
+ 'resourceId' => 'func1',
+ 'resourceType' => 'function',
+ 'buildStatus' => 'ready',
+ 'deploymentId' => 'dep1',
+ 'action' => ['type' => 'logs'],
+ 'previewUrl' => '',
+ ],
+ ];
+
+ $state = '[appwrite]: #' . \base64_encode(\json_encode($oldBuilds)) . "\n\n";
+
+ $comment = new Comment(['consoleHostname' => 'localhost']);
+ $comment->parseComment($state);
+
+ $this->assertEquals(false, $comment->isEmpty());
+
+ $first = $comment->generateComment();
+ $firstTip = $this->extractTip($first);
+
+ $this->assertNotNull($firstTip);
+ $this->assertNotEmpty($firstTip);
+ $this->assertContains($firstTip, $this->getTips());
+
+ $second = $comment->generateComment();
+ $secondTip = $this->extractTip($second);
+
+ $this->assertEquals($firstTip, $secondTip);
+ }
+
+ private function extractTip(string $comment): ?string
+ {
+ if (\preg_match('/> \[!TIP\]\n> (.+)/', $comment, $matches)) {
+ return $matches[1];
+ }
+
+ return null;
+ }
+
+ private function getTips(): array
+ {
+ $reflection = new \ReflectionClass(Comment::class);
+ $property = $reflection->getProperty('tips');
+
+ return $property->getValue(new Comment(['consoleHostname' => 'localhost']));
+ }
+}