Merge remote-tracking branch 'origin/feat-mongodb' into feat-installer

# Conflicts:
#	.github/workflows/tests.yml
#	Dockerfile
#	app/views/install/compose.phtml
#	composer.lock
#	mongo-entrypoint.sh
#	src/Appwrite/Platform/Tasks/Install.php
#	src/Appwrite/Platform/Tasks/Upgrade.php
#	tests/e2e/Client.php
#	tests/e2e/Services/Databases/DatabasesBase.php
#	tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php
#	tests/e2e/Services/Databases/Legacy/DatabasesCustomServerTest.php
#	tests/e2e/Services/Databases/TablesDB/DatabasesBase.php
#	tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php
#	tests/e2e/Services/Databases/TablesDB/DatabasesCustomServerTest.php
#	tests/e2e/Services/Databases/Transactions/TransactionsBase.php
#	tests/e2e/Services/GraphQL/Legacy/DatabaseServerTest.php
#	tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
#	tests/e2e/Services/Projects/ProjectsConsoleClientTest.php
#	tests/e2e/Services/Teams/TeamsCustomClientTest.php
This commit is contained in:
Jake Barnby 2026-02-13 17:09:42 +13:00
commit b41678d57a
462 changed files with 12441 additions and 8860 deletions

8
.env
View file

@ -10,6 +10,7 @@ _APP_CONSOLE_SESSION_ALERTS=enabled
_APP_CONSOLE_WHITELIST_IPS=
_APP_CONSOLE_COUNTRIES_DENYLIST=AQ
_APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io
_APP_MIGRATION_HOST=appwrite
_APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=noreply@appwrite.io
_APP_SYSTEM_TEAM_EMAIL=team@appwrite.io
@ -25,6 +26,7 @@ _APP_OPENSSL_KEY_V1=your-secret-key
_APP_DNS=172.16.238.100 # CoreDNS
_APP_DOMAIN=appwrite.test
_APP_CONSOLE_DOMAIN=localhost
_APP_CONSOLE_TRUSTED_PROJECTS=trusted-project,another-trusted-project
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_SITES=sites.localhost,rebranded.localhost
_APP_DOMAIN_TARGET_CNAME=cname.localhost
@ -36,8 +38,10 @@ _APP_REDIS_HOST=redis
_APP_REDIS_PORT=6379
_APP_REDIS_PASS=
_APP_REDIS_USER=
_APP_DB_HOST=mariadb
_APP_DB_PORT=3306
COMPOSE_PROFILES=mongodb
_APP_DB_ADAPTER=mongodb
_APP_DB_HOST=mongodb
_APP_DB_PORT=27017
_APP_DB_SCHEMA=appwrite
_APP_DB_USER=user
_APP_DB_PASS=password

View file

@ -3,6 +3,7 @@ concurrency:
group: '${{ github.workflow }}-${{ github.ref }}'
cancel-in-progress: true
env:
COMPOSE_FILE: docker-compose.yml
IMAGE: appwrite-dev
CACHE_KEY: 'appwrite-dev-${{ github.event.pull_request.head.sha }}'
'on':
@ -13,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up Docker Buildx
@ -28,6 +29,7 @@ jobs:
cache-from: type=gha
cache-to: 'type=gha,mode=max'
outputs: 'type=docker,dest=/tmp/${{ env.IMAGE }}.tar'
target: development
build-args: |
DEBUG=false
TESTING=true
@ -45,7 +47,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v4
with:
@ -97,7 +99,7 @@ jobs:
echo "| 200 | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
echo "| P99 | $(jq -r '.latencyPercentiles.p99' benchmark.json ) | $(jq -r '.latencyPercentiles.p99' benchmark-latest.json ) | " >> benchmark.txt
- name: Save results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
if: '${{ !cancelled() }}'
with:
name: benchmark.json

View file

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Cleanup
run: |

View file

@ -34,7 +34,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.

View file

@ -12,7 +12,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 2
@ -20,9 +20,9 @@ jobs:
- name: Validate composer.json and composer.lock
run: |
docker run --rm -v $PWD:/app composer sh -c \
docker run --rm -v $PWD:/app composer:2.8 sh -c \
"composer validate"
- name: Run Linter
run: |
docker run --rm -v $PWD:/app composer sh -c \
docker run --rm -v $PWD:/app composer:2.8 sh -c \
"composer install --profile --ignore-platform-reqs && composer lint"

View file

@ -10,11 +10,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build the Docker image
run: docker build . -t appwrite_image:latest
run: DOCKER_BUILDKIT=1 docker build . --target production -t appwrite_image:latest
- name: Run Trivy vulnerability scanner on image
uses: aquasecurity/trivy-action@0.20.0
with:
@ -33,7 +33,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Run Trivy vulnerability scanner on filesystem
uses: aquasecurity/trivy-action@0.20.0
with:

View file

@ -11,19 +11,20 @@ jobs:
pull-requests: write
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
submodules: 'recursive'
- name: Build the Docker image
uses: docker/build-push-action@v5
with:
uses: docker/build-push-action@v6
with:
context: .
push: false
load: true
tags: pr_image:${{ github.sha }}
target: production
- name: Run Trivy vulnerability scanner on image
uses: aquasecurity/trivy-action@0.20.0
@ -44,7 +45,7 @@ jobs:
- name: Process Trivy scan results
id: process-results
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');

View file

@ -12,7 +12,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 2
submodules: recursive
@ -38,7 +38,7 @@ jobs:
type=ref,event=tag
- name: Build & Publish to DockerHub
uses: docker/build-push-action@v4
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64

View file

@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@ -42,7 +42,7 @@ jobs:
type=semver,pattern={{major}}
- name: Build and push
uses: docker/build-push-action@v4
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64

View file

@ -1,5 +1,8 @@
name: "SDK Preview"
env:
COMPOSE_FILE: docker-compose.yml
on:
pull_request:
paths:
@ -19,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set SDK type
id: set-sdk

View file

@ -8,11 +8,11 @@ jobs:
steps:
- name: Check out the repo
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Run CodeQL
run: |
docker run --rm -v $PWD:/app composer:2.6 sh -c \
docker run --rm -v $PWD:/app composer:2.8 sh -c \
"composer install --profile --ignore-platform-reqs && composer check"
- name: Run Locale check

View file

@ -5,6 +5,7 @@ concurrency:
cancel-in-progress: true
env:
COMPOSE_FILE: docker-compose.yml
IMAGE: appwrite-dev
CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }}
@ -26,7 +27,7 @@ jobs:
database_changed: ${{ steps.check.outputs.database_changed }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Fetch base branch
run: git fetch origin ${{ github.event.pull_request.base.ref }}
@ -48,7 +49,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
@ -65,6 +66,7 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar
target: development
build-args: |
DEBUG=false
TESTING=true
@ -80,10 +82,13 @@ jobs:
name: Unit Test
runs-on: ubuntu-latest
needs: setup
permissions:
contents: read
pull-requests: write
steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v4
@ -108,18 +113,28 @@ jobs:
run: docker compose exec -T appwrite vars
- name: Run Unit Tests
run: |
docker compose exec \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
uses: itznotabug/php-retry@v3
with:
max_attempts: 3
timeout_minutes: 30
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/unit
command: >-
docker compose exec
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
appwrite test /usr/src/code/tests/unit
e2e_general_test:
name: E2E General Test
runs-on: ubuntu-latest
needs: setup
permissions:
contents: read
pull-requests: write
steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v4
@ -143,26 +158,39 @@ jobs:
done
- name: Run General Tests
run: |
docker compose exec -T \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
uses: itznotabug/php-retry@v3
with:
max_attempts: 3
timeout_minutes: 30
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/e2e/General
command: >-
docker compose exec -T
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
appwrite test /usr/src/code/tests/e2e/General --debug
- name: Failure Logs
if: failure()
run: |
echo "=== Appwrite Worker Builds Logs ==="
docker compose logs appwrite-worker-builds
echo "=== OpenRuntimes Executor Logs ==="
docker compose logs openruntimes-executor
echo "=== Appwrite Logs ==="
docker compose logs
e2e_service_test:
name: E2E Service Test
runs-on: ubuntu-latest
needs: setup
permissions:
contents: read
pull-requests: write
strategy:
fail-fast: false
matrix:
db_adapter: [
MARIADB,
POSTGRESQL,
MONGODB
]
service: [
Account,
Avatars,
@ -188,7 +216,7 @@ jobs:
]
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v4
@ -197,10 +225,31 @@ jobs:
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
- name: Set DB Adapter environment
id: set-db-env
run: |
DB_ADAPTER_LOWER=$(echo "${{ matrix.db_adapter }}" | tr 'A-Z' 'a-z')
echo "COMPOSE_PROFILES=${DB_ADAPTER_LOWER}" >> $GITHUB_ENV
if [ "${{ matrix.db_adapter }}" = "MARIADB" ]; then
echo "_APP_DB_ADAPTER=mariadb" >> $GITHUB_ENV
echo "_APP_DB_HOST=mariadb" >> $GITHUB_ENV
echo "_APP_DB_PORT=3306" >> $GITHUB_ENV
elif [ "${{ matrix.db_adapter }}" = "MONGODB" ]; then
echo "_APP_DB_ADAPTER=mongodb" >> $GITHUB_ENV
echo "_APP_DB_HOST=mongodb" >> $GITHUB_ENV
echo "_APP_DB_PORT=27017" >> $GITHUB_ENV
elif [ "${{ matrix.db_adapter }}" = "POSTGRESQL" ]; then
echo "_APP_DB_ADAPTER=postgresql" >> $GITHUB_ENV
echo "_APP_DB_HOST=postgresql" >> $GITHUB_ENV
echo "_APP_DB_PORT=5432" >> $GITHUB_ENV
fi
- name: Load and Start Appwrite
env:
_APP_BROWSER_HOST: http://invalid-browser/v1
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
sed -i 's|^_APP_BROWSER_HOST=.*|_APP_BROWSER_HOST=http://invalid-browser/v1|' .env
docker compose up -d
sleep 30
@ -213,19 +262,29 @@ jobs:
done
- name: Run ${{ matrix.service }} tests with Project table mode
run: |
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
export _APP_DATABASE_SHARED_TABLES_V1=
uses: itznotabug/php-retry@v3
env:
_APP_DB_SCHEMA: appwrite
with:
max_attempts: 3
timeout_minutes: 30
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/e2e/Services/${{ matrix.service }}
command: |
echo "Using project tables"
SERVICE_PATH="/usr/src/code/tests/e2e/Services/${{ matrix.service }}"
SERVICE_PATH="/usr/src/code/tests/e2e/Services/${{ matrix.service }}"
echo "Running with paratest (parallel) for: ${{ matrix.service }}"
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite vendor/bin/paratest --processes $(nproc) "$SERVICE_PATH" --exclude-group abuseEnabled --exclude-group screenshots
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES="" \
-e _APP_DATABASE_SHARED_TABLES_V1="" \
-e _APP_DB_ADAPTER="${{ env._APP_DB_ADAPTER }}" \
-e _APP_DB_HOST="${{ env._APP_DB_HOST }}" \
-e _APP_DB_PORT="${{ env._APP_DB_PORT }}" \
-e _APP_DB_SCHEMA="${{ env._APP_DB_SCHEMA }}" \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite vendor/bin/paratest --processes $(nproc) "$SERVICE_PATH" --exclude-group abuseEnabled --exclude-group screenshots
- name: Failure Logs
if: failure()
@ -238,6 +297,9 @@ jobs:
runs-on: ubuntu-latest
needs: [ setup, check_database_changes ]
if: needs.check_database_changes.outputs.database_changed == 'true'
permissions:
contents: read
pull-requests: write
strategy:
fail-fast: false
matrix:
@ -272,7 +334,7 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v4
@ -328,9 +390,12 @@ jobs:
name: E2E Service Test (Abuse enabled)
runs-on: ubuntu-latest
needs: setup
permissions:
contents: read
pull-requests: write
steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v4
@ -347,16 +412,23 @@ jobs:
sleep 30
- name: Run Projects tests in dedicated table mode
run: |
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
export _APP_DATABASE_SHARED_TABLES_V1=
uses: itznotabug/php-retry@v3
with:
max_attempts: 3
timeout_minutes: 30
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/e2e/Services/Projects
command: |
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
export _APP_DATABASE_SHARED_TABLES_V1=
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=abuseEnabled
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite vendor/bin/paratest --processes $(nproc) /usr/src/code/tests/e2e/Services/Projects --group abuseEnabled
- name: Failure Logs
if: failure()
@ -371,6 +443,9 @@ jobs:
runs-on: ubuntu-latest
needs: [ setup, check_database_changes ]
if: needs.check_database_changes.outputs.database_changed == 'true'
permissions:
contents: read
pull-requests: write
strategy:
fail-fast: false
matrix:
@ -380,7 +455,7 @@ jobs:
]
steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v4
@ -397,22 +472,29 @@ jobs:
sleep 30
- name: Run Projects tests in ${{ matrix.tables-mode }} table mode
run: |
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
echo "Using shared tables V1"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=database_db_main
elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then
echo "Using shared tables V2"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=
fi
uses: itznotabug/php-retry@v3
with:
max_attempts: 3
timeout_minutes: 30
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/e2e/Services/Projects
command: |
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
echo "Using shared tables V1"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=database_db_main
elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then
echo "Using shared tables V2"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=
fi
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=abuseEnabled
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite vendor/bin/paratest --processes $(nproc) /usr/src/code/tests/e2e/Services/Projects --group abuseEnabled
- name: Failure Logs
if: failure()
@ -426,9 +508,12 @@ jobs:
name: E2E Service Test (Site Screenshots)
runs-on: ubuntu-latest
needs: setup
permissions:
contents: read
pull-requests: write
steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v4
@ -445,17 +530,24 @@ jobs:
sleep 30
- name: Run Site tests with browser connected in dedicated table mode
run: |
echo "Keeping original value of _APP_BROWSER_HOST"
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
export _APP_DATABASE_SHARED_TABLES_V1=
uses: itznotabug/php-retry@v3
with:
max_attempts: 3
timeout_minutes: 30
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/e2e/Services/Sites
command: |
echo "Keeping original value of _APP_BROWSER_HOST"
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
export _APP_DATABASE_SHARED_TABLES_V1=
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite vendor/bin/paratest --processes $(nproc) /usr/src/code/tests/e2e/Services/Sites --group screenshots
- name: Failure Logs
if: failure()
@ -470,6 +562,9 @@ jobs:
runs-on: ubuntu-latest
needs: [ setup, check_database_changes ]
if: needs.check_database_changes.outputs.database_changed == 'true'
permissions:
contents: read
pull-requests: write
strategy:
fail-fast: false
matrix:
@ -479,7 +574,7 @@ jobs:
]
steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Load Cache
uses: actions/cache@v4
@ -496,23 +591,30 @@ jobs:
sleep 30
- name: Run Site tests with browser connected in ${{ matrix.tables-mode }} table mode
run: |
echo "Keeping original value of _APP_BROWSER_HOST"
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
echo "Using shared tables V1"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=database_db_main
elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then
echo "Using shared tables V2"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=
fi
uses: itznotabug/php-retry@v3
with:
max_attempts: 3
timeout_minutes: 30
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/e2e/Services/Sites
command: |
echo "Keeping original value of _APP_BROWSER_HOST"
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
echo "Using shared tables V1"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=database_db_main
elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then
echo "Using shared tables V2"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=
fi
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite vendor/bin/paratest --processes $(nproc) /usr/src/code/tests/e2e/Services/Sites --group screenshots
- name: Failure Logs
if: failure()
@ -520,4 +622,4 @@ jobs:
echo "=== Appwrite Worker Builds Logs ==="
docker compose logs appwrite-worker-builds
echo "=== OpenRuntimes Executor Logs ==="
docker compose logs openruntimes-executor
docker compose logs openruntimes-executor

View file

@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
FROM appwrite/base:0.11.5 AS base
FROM appwrite/base:1.0.0 AS base
LABEL maintainer="team@appwrite.io"
@ -38,6 +38,7 @@ COPY ./public /usr/src/code/public
COPY ./bin /usr/local/bin
COPY ./src /usr/src/code/src
COPY ./dev /usr/src/code/dev
COPY ./mongo-init.js /usr/src/code/mongo-init.js
COPY ./mongo-entrypoint.sh /usr/src/code/mongo-entrypoint.sh
# Set Volumes
@ -83,6 +84,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-certificates && \
chmod +x /usr/local/bin/worker-databases && \
chmod +x /usr/local/bin/worker-deletes && \
chmod +x /usr/local/bin/worker-executions && \
chmod +x /usr/local/bin/worker-functions && \
chmod +x /usr/local/bin/worker-mails && \
chmod +x /usr/local/bin/worker-messaging && \

View file

@ -16,13 +16,13 @@ use Swoole\Timer;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\CLI;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\DI\Dependency;
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Platform\Service;
@ -45,9 +45,32 @@ Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
require_once __DIR__ . '/controllers/general.php';
global $register;
CLI::setResource('register', fn () => $register);
CLI::setResource('cache', function ($pools) {
$platform = new Appwrite();
$args = $platform->getEnv('argv');
\array_shift($args);
if (!isset($args[0])) {
Console::error('Missing task name');
Console::exit(1);
}
$taskName = $args[0];
$platform->init(Service::TYPE_TASK);
$cli = $platform->getCli();
$setResource = function (string $name, callable $callback, array $injections = []) use ($cli) {
$dependency = new Dependency();
$dependency->setName($name)->setCallback($callback);
foreach ($injections as $injection) {
$dependency->inject($injection);
}
$cli->setResource($dependency);
};
$setResource('register', fn () => $register, []);
$setResource('cache', function ($pools) {
$list = Config::getParam('pools-cache', []);
$adapters = [];
@ -58,17 +81,17 @@ CLI::setResource('cache', function ($pools) {
return new Cache(new Sharding($adapters));
}, ['pools']);
CLI::setResource('pools', function (Registry $register) {
$setResource('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
CLI::setResource('authorization', function () {
$setResource('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
$setResource('dbForPlatform', function ($pools, $cache, $authorization) {
$sleep = 3;
$maxAttempts = 5;
$attempts = 0;
@ -111,16 +134,17 @@ CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
return $dbForPlatform;
}, ['pools', 'cache', 'authorization']);
CLI::setResource('console', function () {
$setResource('console', function () {
return new Document(Config::getParam('console'));
}, []);
CLI::setResource(
$setResource(
'isResourceBlocked',
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false,
[]
);
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
$setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
@ -182,7 +206,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
@ -210,40 +234,40 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizatio
return $database;
};
}, ['pools', 'cache', 'authorization']);
CLI::setResource('publisher', function (Group $pools) {
$setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
CLI::setResource('publisherDatabases', function (BrokerPool $publisher) {
$setResource('publisherDatabases', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
CLI::setResource('publisherFunctions', function (BrokerPool $publisher) {
$setResource('publisherFunctions', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
CLI::setResource('publisherMigrations', function (BrokerPool $publisher) {
$setResource('publisherMigrations', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
CLI::setResource('publisherStatsUsage', function (BrokerPool $publisher) {
$setResource('publisherStatsUsage', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
CLI::setResource('publisherMessaging', function (BrokerPool $publisher) {
$setResource('publisherMessaging', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
CLI::setResource('queueForStatsUsage', function (Publisher $publisher) {
$setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
CLI::setResource('queueForStatsResources', function (Publisher $publisher) {
$setResource('queueForStatsResources', function (Publisher $publisher) {
return new StatsResources($publisher);
}, ['publisher']);
CLI::setResource('queueForFunctions', function (Publisher $publisher) {
$setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
CLI::setResource('queueForDeletes', function (Publisher $publisher) {
$setResource('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
CLI::setResource('queueForCertificates', function (Publisher $publisher) {
$setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
CLI::setResource('logError', function (Registry $register) {
$setResource('logError', function (Registry $register) {
return function (Throwable $error, string $namespace, string $action) use ($register) {
Console::error('[Error] Timestamp: ' . date('c', time()));
Console::error('[Error] Type: ' . get_class($error));
@ -295,22 +319,9 @@ CLI::setResource('logError', function (Registry $register) {
};
}, ['register']);
CLI::setResource('executor', fn () => new Executor());
$setResource('executor', fn () => new Executor(), []);
CLI::setResource('telemetry', fn () => new NoTelemetry());
$platform = new Appwrite();
$args = $platform->getEnv('argv');
if (!isset($args[0])) {
Console::error('Missing task name');
Console::exit(1);
}
\array_shift($args);
$taskName = $args[0];
$platform->init(Service::TYPE_TASK);
$cli = $platform->getCli();
$setResource('telemetry', fn () => new NoTelemetry(), []);
$cli
->error()

View file

@ -1617,13 +1617,6 @@ return [
],
],
'indexes' => [
[
'$id' => ID::custom('_fulltext_name'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['name'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_search'),
'type' => Database::INDEX_FULLTEXT,
@ -1853,13 +1846,6 @@ return [
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['name'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_type'),
'type' => Database::INDEX_KEY,
@ -2127,14 +2113,8 @@ return [
'filters' => ['topicSearch'],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['name'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_search'),
'type' => Database::INDEX_FULLTEXT,

View file

@ -342,6 +342,17 @@ $platformCollections = [
'array' => true,
'filters' => [],
],
[
'$id' => 'status',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 100,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[

View file

@ -139,6 +139,11 @@ return [
'description' => 'There was an error processing your request. Please check the inputs and try again.',
'code' => 400,
],
Exception::GENERAL_FEATURE_UNSUPPORTED => [
'name' => Exception::GENERAL_FEATURE_UNSUPPORTED,
'description' => 'This feature is not supported with your current configuration.',
'code' => 400,
],
/** User Errors */
Exception::USER_COUNT_EXCEEDED => [
@ -1139,6 +1144,11 @@ return [
'description' => 'Key with the requested ID could not be found.',
'code' => 404,
],
Exception::KEY_ALREADY_EXISTS => [
'name' => Exception::KEY_ALREADY_EXISTS,
'description' => 'Key with the same ID already exists. Try again with a different ID.',
'code' => 409,
],
Exception::PLATFORM_NOT_FOUND => [
'name' => Exception::PLATFORM_NOT_FOUND,
'description' => 'Platform with the requested ID could not be found.',

View file

@ -22,6 +22,7 @@ return [
'hostnames' => array_filter(array_unique([
System::getEnv('_APP_DOMAIN', 'localhost'),
System::getEnv('_APP_CONSOLE_DOMAIN', 'localhost'),
System::getEnv('_APP_MIGRATION_HOST'),
])),
'platformName' => APP_EMAIL_PLATFORM_NAME,
'logoUrl' => APP_EMAIL_LOGO_URL,

View file

@ -11,7 +11,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '22.0.0',
'version' => '22.1.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@ -60,7 +60,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '21.0.0',
'version' => '21.1.0',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@ -79,7 +79,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '14.0.0',
'version' => '14.1.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@ -117,7 +117,7 @@ return [
'key' => 'android',
'name' => 'Android',
'namespace' => 'io.appwrite',
'version' => '12.0.0',
'version' => '12.1.0',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@ -140,7 +140,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.20.0',
'version' => '0.21.0',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@ -227,7 +227,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '13.2.1',
'version' => '13.4.0',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@ -253,7 +253,7 @@ return [
[
'key' => 'markdown',
'name' => 'Markdown',
'version' => '0.2.0',
'version' => '0.3.0',
'url' => 'https://github.com/appwrite/sdk-for-md.git',
'package' => 'https://www.npmjs.com/package/@appwrite.io/docs',
'enabled' => true,
@ -270,6 +270,25 @@ return [
'repoBranch' => 'main',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/md/CHANGELOG.md'),
],
[
'key' => 'agent-skills',
'name' => 'AgentSkills',
'version' => '0.1.0',
'url' => 'https://github.com/appwrite/agent-skills.git',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_SDK_PLATFORM_CONSOLE,
'prism' => 'agent-skills',
'source' => \realpath(__DIR__ . '/../sdks/console-agent-skills'),
'gitUrl' => 'git@github.com:appwrite/agent-skills.git',
'gitRepoName' => 'agent-skills',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'repoBranch' => 'main',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/agent-skills/CHANGELOG.md'),
],
],
],
@ -283,7 +302,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '22.0.0',
'version' => '22.0.1',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@ -302,7 +321,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '20.0.0',
'version' => '20.0.1',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@ -321,7 +340,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '15.0.0',
'version' => '15.1.0',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@ -340,7 +359,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '21.0.0',
'version' => '21.0.1',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@ -359,7 +378,7 @@ return [
[
'key' => 'go',
'name' => 'Go',
'version' => 'v0.16.0',
'version' => 'v0.16.1',
'url' => 'https://github.com/appwrite/sdk-for-go',
'package' => 'https://github.com/appwrite/sdk-for-go',
'enabled' => true,
@ -378,7 +397,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.25.0',
'version' => '0.26.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@ -397,7 +416,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '21.0.0',
'version' => '21.0.1',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@ -417,7 +436,7 @@ return [
'key' => 'kotlin',
'name' => 'Kotlin',
'namespace' => 'io.appwrite',
'version' => '14.0.0',
'version' => '14.0.1',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@ -440,7 +459,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '15.0.0',
'version' => '15.1.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,

View file

@ -555,7 +555,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 253,
"weight": 239,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@ -627,7 +627,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 255,
"weight": 241,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@ -751,7 +751,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 256,
"weight": 242,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@ -891,7 +891,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 257,
"weight": 243,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@ -1015,7 +1015,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 261,
"weight": 247,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@ -1149,7 +1149,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 262,
"weight": 248,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@ -1287,7 +1287,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 254,
"weight": 240,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@ -1388,7 +1388,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 260,
"weight": 246,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@ -1487,7 +1487,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 258,
"weight": 244,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@ -1586,7 +1586,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 259,
"weight": 245,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@ -4051,7 +4051,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 264,
"weight": 250,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@ -4179,7 +4179,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 263,
"weight": 249,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@ -4313,7 +4313,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 267,
"weight": 253,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@ -4373,7 +4373,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 265,
"weight": 251,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@ -4863,7 +4863,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 266,
"weight": 252,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@ -4947,7 +4947,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 269,
"weight": 255,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@ -5041,7 +5041,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 268,
"weight": 254,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@ -5135,7 +5135,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 270,
"weight": 256,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@ -5888,7 +5888,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 346,
"weight": 332,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@ -5955,7 +5955,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 342,
"weight": 328,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@ -6026,7 +6026,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 343,
"weight": 329,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@ -6090,7 +6090,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 344,
"weight": 330,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@ -6168,7 +6168,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 345,
"weight": 331,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@ -6234,7 +6234,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 347,
"weight": 333,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@ -6319,7 +6319,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 297,
"weight": 283,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@ -6431,7 +6431,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 289,
"weight": 275,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@ -6592,7 +6592,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 290,
"weight": 276,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@ -6703,7 +6703,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 293,
"weight": 279,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@ -6858,7 +6858,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 291,
"weight": 277,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@ -6970,7 +6970,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 295,
"weight": 281,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@ -7077,7 +7077,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 300,
"weight": 286,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@ -7206,7 +7206,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 299,
"weight": 285,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@ -7335,7 +7335,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 447,
"weight": 436,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@ -7422,7 +7422,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 445,
"weight": 434,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@ -7540,7 +7540,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 446,
"weight": 435,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@ -7615,7 +7615,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 190,
"weight": 176,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@ -7669,7 +7669,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 189,
"weight": 175,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@ -8155,7 +8155,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 237,
"weight": 223,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@ -8239,7 +8239,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 241,
"weight": 227,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@ -8315,7 +8315,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 543,
"weight": 532,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@ -8414,7 +8414,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 541,
"weight": 530,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@ -8516,7 +8516,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 542,
"weight": 531,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@ -8590,7 +8590,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 544,
"weight": 533,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@ -8682,7 +8682,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 545,
"weight": 534,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@ -8751,7 +8751,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 547,
"weight": 536,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@ -8831,7 +8831,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 546,
"weight": 535,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@ -9061,7 +9061,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 548,
"weight": 537,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@ -9148,7 +9148,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 419,
"weight": 405,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@ -9218,7 +9218,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 415,
"weight": 401,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@ -9292,7 +9292,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 416,
"weight": 402,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@ -9359,7 +9359,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 417,
"weight": 403,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@ -9440,7 +9440,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 418,
"weight": 404,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@ -9509,7 +9509,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 420,
"weight": 406,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@ -9597,7 +9597,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 411,
"weight": 397,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@ -9708,7 +9708,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 403,
"weight": 389,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@ -9864,7 +9864,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 404,
"weight": 390,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@ -9974,7 +9974,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 407,
"weight": 393,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@ -10124,7 +10124,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 405,
"weight": 391,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@ -10235,7 +10235,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 409,
"weight": 395,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@ -10341,7 +10341,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 414,
"weight": 400,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@ -10469,7 +10469,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 413,
"weight": 399,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
@ -10597,7 +10597,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 110,
"weight": 107,
"cookies": false,
"type": "",
"demo": "teams\/list.md",
@ -10686,7 +10686,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 109,
"weight": 106,
"cookies": false,
"type": "",
"demo": "teams\/create.md",
@ -10773,7 +10773,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 111,
"weight": 108,
"cookies": false,
"type": "",
"demo": "teams\/get.md",
@ -10837,7 +10837,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 113,
"weight": 110,
"cookies": false,
"type": "",
"demo": "teams\/update-name.md",
@ -10913,7 +10913,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 115,
"weight": 112,
"cookies": false,
"type": "",
"demo": "teams\/delete.md",
@ -10979,7 +10979,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 117,
"weight": 114,
"cookies": false,
"type": "",
"demo": "teams\/list-memberships.md",
@ -11078,7 +11078,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 116,
"weight": 113,
"cookies": false,
"type": "",
"demo": "teams\/create-membership.md",
@ -11146,14 +11146,7 @@
"description": "Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions). Maximum of 100 roles are allowed, each 32 characters long.",
"x-example": null,
"items": {
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
"type": "string"
}
},
"url": {
@ -11201,7 +11194,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 118,
"weight": 115,
"cookies": false,
"type": "",
"demo": "teams\/get-membership.md",
@ -11275,7 +11268,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 119,
"weight": 116,
"cookies": false,
"type": "",
"demo": "teams\/update-membership.md",
@ -11336,14 +11329,7 @@
"description": "An array of strings. Use this param to set the user's roles in the team. A role can be any string. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions). Maximum of 100 roles are allowed, each 32 characters long.",
"x-example": null,
"items": {
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
"type": "string"
}
}
},
@ -11371,7 +11357,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 121,
"weight": 118,
"cookies": false,
"type": "",
"demo": "teams\/delete-membership.md",
@ -11447,7 +11433,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 120,
"weight": 117,
"cookies": false,
"type": "",
"demo": "teams\/update-membership-status.md",
@ -11547,7 +11533,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 112,
"weight": 109,
"cookies": false,
"type": "",
"demo": "teams\/get-prefs.md",
@ -11610,7 +11596,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 114,
"weight": 111,
"cookies": false,
"type": "",
"demo": "teams\/update-prefs.md",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -555,7 +555,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 253,
"weight": 239,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@ -627,7 +627,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 255,
"weight": 241,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@ -751,7 +751,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 256,
"weight": 242,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@ -891,7 +891,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 257,
"weight": 243,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@ -1015,7 +1015,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 261,
"weight": 247,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@ -1149,7 +1149,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 262,
"weight": 248,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@ -1287,7 +1287,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 254,
"weight": 240,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@ -1388,7 +1388,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 260,
"weight": 246,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@ -1487,7 +1487,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 258,
"weight": 244,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@ -1586,7 +1586,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 259,
"weight": 245,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@ -4051,7 +4051,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 264,
"weight": 250,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@ -4179,7 +4179,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 263,
"weight": 249,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@ -4313,7 +4313,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 267,
"weight": 253,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@ -4373,7 +4373,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 265,
"weight": 251,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@ -4863,7 +4863,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 266,
"weight": 252,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@ -4947,7 +4947,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 269,
"weight": 255,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@ -5041,7 +5041,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 268,
"weight": 254,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@ -5135,7 +5135,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 270,
"weight": 256,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@ -5888,7 +5888,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 346,
"weight": 332,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@ -5955,7 +5955,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 342,
"weight": 328,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@ -6026,7 +6026,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 343,
"weight": 329,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@ -6090,7 +6090,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 344,
"weight": 330,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@ -6168,7 +6168,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 345,
"weight": 331,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@ -6234,7 +6234,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 347,
"weight": 333,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@ -6319,7 +6319,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 297,
"weight": 283,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@ -6431,7 +6431,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 289,
"weight": 275,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@ -6592,7 +6592,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 290,
"weight": 276,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@ -6703,7 +6703,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 293,
"weight": 279,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@ -6858,7 +6858,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 291,
"weight": 277,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@ -6970,7 +6970,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 295,
"weight": 281,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@ -7077,7 +7077,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 300,
"weight": 286,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@ -7206,7 +7206,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 299,
"weight": 285,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@ -7335,7 +7335,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 447,
"weight": 436,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@ -7422,7 +7422,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 445,
"weight": 434,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@ -7540,7 +7540,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 446,
"weight": 435,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@ -7615,7 +7615,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 190,
"weight": 176,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@ -7669,7 +7669,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 189,
"weight": 175,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@ -8155,7 +8155,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 237,
"weight": 223,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@ -8239,7 +8239,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 241,
"weight": 227,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@ -8315,7 +8315,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 543,
"weight": 532,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@ -8414,7 +8414,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 541,
"weight": 530,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@ -8516,7 +8516,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 542,
"weight": 531,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@ -8590,7 +8590,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 544,
"weight": 533,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@ -8682,7 +8682,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 545,
"weight": 534,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@ -8751,7 +8751,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 547,
"weight": 536,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@ -8831,7 +8831,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 546,
"weight": 535,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@ -9061,7 +9061,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 548,
"weight": 537,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@ -9148,7 +9148,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 419,
"weight": 405,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@ -9218,7 +9218,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 415,
"weight": 401,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@ -9292,7 +9292,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 416,
"weight": 402,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@ -9359,7 +9359,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 417,
"weight": 403,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@ -9440,7 +9440,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 418,
"weight": 404,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@ -9509,7 +9509,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 420,
"weight": 406,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@ -9597,7 +9597,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 411,
"weight": 397,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@ -9708,7 +9708,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 403,
"weight": 389,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@ -9864,7 +9864,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 404,
"weight": 390,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@ -9974,7 +9974,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 407,
"weight": 393,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@ -10124,7 +10124,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 405,
"weight": 391,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@ -10235,7 +10235,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 409,
"weight": 395,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@ -10341,7 +10341,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 414,
"weight": 400,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@ -10469,7 +10469,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 413,
"weight": 399,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
@ -10597,7 +10597,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 110,
"weight": 107,
"cookies": false,
"type": "",
"demo": "teams\/list.md",
@ -10686,7 +10686,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 109,
"weight": 106,
"cookies": false,
"type": "",
"demo": "teams\/create.md",
@ -10773,7 +10773,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 111,
"weight": 108,
"cookies": false,
"type": "",
"demo": "teams\/get.md",
@ -10837,7 +10837,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 113,
"weight": 110,
"cookies": false,
"type": "",
"demo": "teams\/update-name.md",
@ -10913,7 +10913,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 115,
"weight": 112,
"cookies": false,
"type": "",
"demo": "teams\/delete.md",
@ -10979,7 +10979,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 117,
"weight": 114,
"cookies": false,
"type": "",
"demo": "teams\/list-memberships.md",
@ -11078,7 +11078,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 116,
"weight": 113,
"cookies": false,
"type": "",
"demo": "teams\/create-membership.md",
@ -11146,14 +11146,7 @@
"description": "Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions). Maximum of 100 roles are allowed, each 32 characters long.",
"x-example": null,
"items": {
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
"type": "string"
}
},
"url": {
@ -11201,7 +11194,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 118,
"weight": 115,
"cookies": false,
"type": "",
"demo": "teams\/get-membership.md",
@ -11275,7 +11268,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 119,
"weight": 116,
"cookies": false,
"type": "",
"demo": "teams\/update-membership.md",
@ -11336,14 +11329,7 @@
"description": "An array of strings. Use this param to set the user's roles in the team. A role can be any string. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions). Maximum of 100 roles are allowed, each 32 characters long.",
"x-example": null,
"items": {
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
"type": "string"
}
}
},
@ -11371,7 +11357,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 121,
"weight": 118,
"cookies": false,
"type": "",
"demo": "teams\/delete-membership.md",
@ -11447,7 +11433,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 120,
"weight": 117,
"cookies": false,
"type": "",
"demo": "teams\/update-membership-status.md",
@ -11547,7 +11533,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 112,
"weight": 109,
"cookies": false,
"type": "",
"demo": "teams\/get-prefs.md",
@ -11610,7 +11596,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 114,
"weight": 111,
"cookies": false,
"type": "",
"demo": "teams\/update-prefs.md",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -612,7 +612,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 253,
"weight": 239,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@ -687,7 +687,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 255,
"weight": 241,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@ -811,7 +811,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 256,
"weight": 242,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@ -952,7 +952,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 257,
"weight": 243,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@ -1076,7 +1076,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 261,
"weight": 247,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@ -1213,7 +1213,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 262,
"weight": 248,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@ -1353,7 +1353,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 254,
"weight": 240,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@ -1454,7 +1454,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 260,
"weight": 246,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@ -1555,7 +1555,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 258,
"weight": 244,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@ -1656,7 +1656,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 259,
"weight": 245,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@ -4203,7 +4203,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 264,
"weight": 250,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@ -4329,7 +4329,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 263,
"weight": 249,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@ -4461,7 +4461,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 267,
"weight": 253,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@ -4525,7 +4525,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 265,
"weight": 251,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@ -5013,7 +5013,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 266,
"weight": 252,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@ -5097,7 +5097,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 269,
"weight": 255,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@ -5189,7 +5189,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 268,
"weight": 254,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@ -5281,7 +5281,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 270,
"weight": 256,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@ -5994,7 +5994,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 346,
"weight": 332,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@ -6061,7 +6061,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 342,
"weight": 328,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@ -6132,7 +6132,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 343,
"weight": 329,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@ -6195,7 +6195,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 344,
"weight": 330,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@ -6274,7 +6274,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 345,
"weight": 331,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@ -6339,7 +6339,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 347,
"weight": 333,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@ -6420,7 +6420,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 297,
"weight": 283,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@ -6524,7 +6524,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 289,
"weight": 275,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@ -6683,7 +6683,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 290,
"weight": 276,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@ -6786,7 +6786,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 293,
"weight": 279,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@ -6937,7 +6937,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 291,
"weight": 277,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@ -7047,7 +7047,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 295,
"weight": 281,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@ -7148,7 +7148,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 300,
"weight": 286,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@ -7271,7 +7271,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 299,
"weight": 285,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@ -7392,7 +7392,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 447,
"weight": 436,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@ -7475,7 +7475,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 445,
"weight": 434,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@ -7594,7 +7594,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 446,
"weight": 435,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@ -7666,7 +7666,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 190,
"weight": 176,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@ -7741,7 +7741,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 189,
"weight": 175,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@ -8240,7 +8240,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 237,
"weight": 223,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@ -8325,7 +8325,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 241,
"weight": 227,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@ -8396,7 +8396,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 543,
"weight": 532,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@ -8489,7 +8489,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 541,
"weight": 530,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@ -8580,7 +8580,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 542,
"weight": 531,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@ -8651,7 +8651,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 544,
"weight": 533,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@ -8742,7 +8742,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 545,
"weight": 534,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@ -8813,7 +8813,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 547,
"weight": 536,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@ -8893,7 +8893,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 546,
"weight": 535,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@ -9101,7 +9101,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 548,
"weight": 537,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@ -9181,7 +9181,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 419,
"weight": 405,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@ -9251,7 +9251,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 415,
"weight": 401,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@ -9325,7 +9325,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 416,
"weight": 402,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@ -9391,7 +9391,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 417,
"weight": 403,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@ -9473,7 +9473,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 418,
"weight": 404,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@ -9541,7 +9541,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 420,
"weight": 406,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@ -9625,7 +9625,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 411,
"weight": 397,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@ -9728,7 +9728,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 403,
"weight": 389,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@ -9882,7 +9882,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 404,
"weight": 390,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@ -9984,7 +9984,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 407,
"weight": 393,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@ -10130,7 +10130,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 405,
"weight": 391,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@ -10239,7 +10239,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 409,
"weight": 395,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@ -10339,7 +10339,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 414,
"weight": 400,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@ -10461,7 +10461,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 413,
"weight": 399,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
@ -10581,7 +10581,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 110,
"weight": 107,
"cookies": false,
"type": "",
"demo": "teams\/list.md",
@ -10666,7 +10666,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 109,
"weight": 106,
"cookies": false,
"type": "",
"demo": "teams\/create.md",
@ -10757,7 +10757,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 111,
"weight": 108,
"cookies": false,
"type": "",
"demo": "teams\/get.md",
@ -10820,7 +10820,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 113,
"weight": 110,
"cookies": false,
"type": "",
"demo": "teams\/update-name.md",
@ -10896,7 +10896,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 115,
"weight": 112,
"cookies": false,
"type": "",
"demo": "teams\/delete.md",
@ -10959,7 +10959,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 117,
"weight": 114,
"cookies": false,
"type": "",
"demo": "teams\/list-memberships.md",
@ -11052,7 +11052,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 116,
"weight": 113,
"cookies": false,
"type": "",
"demo": "teams\/create-membership.md",
@ -11120,14 +11120,7 @@
"default": null,
"x-example": null,
"items": {
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
"type": "string"
}
},
"url": {
@ -11176,7 +11169,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 118,
"weight": 115,
"cookies": false,
"type": "",
"demo": "teams\/get-membership.md",
@ -11247,7 +11240,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 119,
"weight": 116,
"cookies": false,
"type": "",
"demo": "teams\/update-membership.md",
@ -11303,14 +11296,7 @@
"default": null,
"x-example": null,
"items": {
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
"type": "string"
}
}
},
@ -11341,7 +11327,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 121,
"weight": 118,
"cookies": false,
"type": "",
"demo": "teams\/delete-membership.md",
@ -11414,7 +11400,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 120,
"weight": 117,
"cookies": false,
"type": "",
"demo": "teams\/update-membership-status.md",
@ -11510,7 +11496,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 112,
"weight": 109,
"cookies": false,
"type": "",
"demo": "teams\/get-prefs.md",
@ -11573,7 +11559,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 114,
"weight": 111,
"cookies": false,
"type": "",
"demo": "teams\/update-prefs.md",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -612,7 +612,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 253,
"weight": 239,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@ -687,7 +687,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 255,
"weight": 241,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@ -811,7 +811,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 256,
"weight": 242,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@ -952,7 +952,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 257,
"weight": 243,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@ -1076,7 +1076,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 261,
"weight": 247,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@ -1213,7 +1213,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 262,
"weight": 248,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@ -1353,7 +1353,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 254,
"weight": 240,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@ -1454,7 +1454,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 260,
"weight": 246,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@ -1555,7 +1555,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 258,
"weight": 244,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@ -1656,7 +1656,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 259,
"weight": 245,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@ -4203,7 +4203,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 264,
"weight": 250,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@ -4329,7 +4329,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 263,
"weight": 249,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@ -4461,7 +4461,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 267,
"weight": 253,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@ -4525,7 +4525,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 265,
"weight": 251,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@ -5013,7 +5013,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 266,
"weight": 252,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@ -5097,7 +5097,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 269,
"weight": 255,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@ -5189,7 +5189,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 268,
"weight": 254,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@ -5281,7 +5281,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 270,
"weight": 256,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@ -5994,7 +5994,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 346,
"weight": 332,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@ -6061,7 +6061,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 342,
"weight": 328,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@ -6132,7 +6132,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 343,
"weight": 329,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@ -6195,7 +6195,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 344,
"weight": 330,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@ -6274,7 +6274,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 345,
"weight": 331,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@ -6339,7 +6339,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 347,
"weight": 333,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@ -6420,7 +6420,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 297,
"weight": 283,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@ -6524,7 +6524,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 289,
"weight": 275,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@ -6683,7 +6683,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 290,
"weight": 276,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@ -6786,7 +6786,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 293,
"weight": 279,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@ -6937,7 +6937,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 291,
"weight": 277,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@ -7047,7 +7047,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 295,
"weight": 281,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@ -7148,7 +7148,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 300,
"weight": 286,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@ -7271,7 +7271,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 299,
"weight": 285,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@ -7392,7 +7392,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 447,
"weight": 436,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@ -7475,7 +7475,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 445,
"weight": 434,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@ -7594,7 +7594,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 446,
"weight": 435,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@ -7666,7 +7666,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 190,
"weight": 176,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@ -7741,7 +7741,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 189,
"weight": 175,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@ -8240,7 +8240,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 237,
"weight": 223,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@ -8325,7 +8325,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 241,
"weight": 227,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@ -8396,7 +8396,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 543,
"weight": 532,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@ -8489,7 +8489,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 541,
"weight": 530,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@ -8580,7 +8580,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 542,
"weight": 531,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@ -8651,7 +8651,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 544,
"weight": 533,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@ -8742,7 +8742,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 545,
"weight": 534,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@ -8813,7 +8813,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 547,
"weight": 536,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@ -8893,7 +8893,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 546,
"weight": 535,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@ -9101,7 +9101,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 548,
"weight": 537,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@ -9181,7 +9181,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 419,
"weight": 405,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@ -9251,7 +9251,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 415,
"weight": 401,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@ -9325,7 +9325,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 416,
"weight": 402,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@ -9391,7 +9391,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 417,
"weight": 403,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@ -9473,7 +9473,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 418,
"weight": 404,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@ -9541,7 +9541,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 420,
"weight": 406,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@ -9625,7 +9625,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 411,
"weight": 397,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@ -9728,7 +9728,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 403,
"weight": 389,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@ -9882,7 +9882,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 404,
"weight": 390,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@ -9984,7 +9984,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 407,
"weight": 393,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@ -10130,7 +10130,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 405,
"weight": 391,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@ -10239,7 +10239,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 409,
"weight": 395,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@ -10339,7 +10339,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 414,
"weight": 400,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@ -10461,7 +10461,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 413,
"weight": 399,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
@ -10581,7 +10581,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 110,
"weight": 107,
"cookies": false,
"type": "",
"demo": "teams\/list.md",
@ -10666,7 +10666,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 109,
"weight": 106,
"cookies": false,
"type": "",
"demo": "teams\/create.md",
@ -10757,7 +10757,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 111,
"weight": 108,
"cookies": false,
"type": "",
"demo": "teams\/get.md",
@ -10820,7 +10820,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 113,
"weight": 110,
"cookies": false,
"type": "",
"demo": "teams\/update-name.md",
@ -10896,7 +10896,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 115,
"weight": 112,
"cookies": false,
"type": "",
"demo": "teams\/delete.md",
@ -10959,7 +10959,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 117,
"weight": 114,
"cookies": false,
"type": "",
"demo": "teams\/list-memberships.md",
@ -11052,7 +11052,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 116,
"weight": 113,
"cookies": false,
"type": "",
"demo": "teams\/create-membership.md",
@ -11120,14 +11120,7 @@
"default": null,
"x-example": null,
"items": {
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
"type": "string"
}
},
"url": {
@ -11176,7 +11169,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 118,
"weight": 115,
"cookies": false,
"type": "",
"demo": "teams\/get-membership.md",
@ -11247,7 +11240,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 119,
"weight": 116,
"cookies": false,
"type": "",
"demo": "teams\/update-membership.md",
@ -11303,14 +11296,7 @@
"default": null,
"x-example": null,
"items": {
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
"type": "string"
}
}
},
@ -11341,7 +11327,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 121,
"weight": 118,
"cookies": false,
"type": "",
"demo": "teams\/delete-membership.md",
@ -11414,7 +11400,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 120,
"weight": 117,
"cookies": false,
"type": "",
"demo": "teams\/update-membership-status.md",
@ -11510,7 +11496,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 112,
"weight": 109,
"cookies": false,
"type": "",
"demo": "teams\/get-prefs.md",
@ -11573,7 +11559,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 114,
"weight": 111,
"cookies": false,
"type": "",
"demo": "teams\/update-prefs.md",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -369,6 +369,75 @@ return [
]
],
],
[
'category' => 'Database',
'description' => 'Appwrite uses a database for storing user and meta data. You can choose between MariaDB, MongoDB or PostgreSQL.',
'variables' => [
[
'name' => '_APP_DB_ADAPTER',
'description' => 'Which database to use. Must be one of: MariaDB, MongoDB, or PostgreSQL',
'introduction' => '1.9.0',
'default' => 'mongodb',
'required' => true,
'question' => 'Choose your database (mariadb|mongodb|postgresql)',
'filter' => ''
],
[
'name' => '_APP_DB_HOST',
'description' => 'Database server host name address. Default value is: \'mongodb\'.',
'introduction' => '',
'default' => 'mongodb',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DB_PORT',
'description' => 'Database server TCP port. Default value is: \'27017\'.',
'introduction' => '',
'default' => '27017',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DB_SCHEMA',
'description' => 'Database server database schema. Default value is: \'appwrite\'.',
'introduction' => '',
'default' => 'appwrite',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DB_USER',
'description' => 'Database server user name. Default value is: \'user\'.',
'introduction' => '',
'default' => 'user',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DB_PASS',
'description' => 'Database server user password. Default value is: \'password\'.',
'introduction' => '',
'default' => 'password',
'required' => false,
'question' => '',
'filter' => 'password'
],
[
'name' => '_APP_DB_ROOT_PASS',
'description' => 'Database server root password. Default value is: \'rootsecretpassword\'.',
'introduction' => '',
'default' => 'rootsecretpassword',
'required' => false,
'question' => '',
'filter' => 'password'
],
],
],
[
'category' => 'Redis',
'description' => 'Appwrite uses a Redis server for managing cache, queues and scheduled tasks. The Redis env vars are used to allow Appwrite server to connect to the Redis container.',
@ -411,66 +480,6 @@ return [
],
],
],
[
'category' => 'MariaDB',
'description' => 'Appwrite is using a MariaDB server for managing persistent database data. The MariaDB env vars are used to allow Appwrite server to connect to the MariaDB container.',
'variables' => [
[
'name' => '_APP_DB_HOST',
'description' => 'MariaDB server host name address. Default value is: \'mariadb\'.',
'introduction' => '',
'default' => 'mariadb',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DB_PORT',
'description' => 'MariaDB server TCP port. Default value is: \'3306\'.',
'introduction' => '',
'default' => '3306',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DB_SCHEMA',
'description' => 'MariaDB server database schema. Default value is: \'appwrite\'.',
'introduction' => '',
'default' => 'appwrite',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DB_USER',
'description' => 'MariaDB server user name. Default value is: \'user\'.',
'introduction' => '',
'default' => 'user',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DB_PASS',
'description' => 'MariaDB server user password. Default value is: \'password\'.',
'introduction' => '',
'default' => 'password',
'required' => false,
'question' => '',
'filter' => 'password'
],
[
'name' => '_APP_DB_ROOT_PASS',
'description' => 'MariaDB server root password. Default value is: \'rootsecretpassword\'.',
'introduction' => '',
'default' => 'rootsecretpassword',
'required' => false,
'question' => '',
'filter' => 'password'
],
],
],
[
'category' => 'InfluxDB',
'description' => 'Deprecated since 1.4.8.',

View file

@ -60,7 +60,7 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\Storage\Validator\FileName;
use Utopia\System\System;
@ -367,7 +367,7 @@ Http::post('/v1/account')
contentType: ContentType::JSON
))
->label('abuse-limit', 10)
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be between 8 and 256 chars.', false, ['project', 'passwordsDictionary'])
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@ -728,7 +728,7 @@ Http::get('/v1/account/sessions/:sessionId')
],
contentType: ContentType::JSON
))
->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to get the current device session.')
->param('sessionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Session ID. Use the string \'current\' to get the current device session.', false, ['dbForProject'])
->inject('response')
->inject('user')
->inject('locale')
@ -780,7 +780,7 @@ Http::delete('/v1/account/sessions/:sessionId')
contentType: ContentType::NONE
))
->label('abuse-limit', 100)
->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to delete the current device session.')
->param('sessionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Session ID. Use the string \'current\' to delete the current device session.', false, ['dbForProject'])
->inject('requestTimestamp')
->inject('request')
->inject('response')
@ -868,7 +868,7 @@ Http::patch('/v1/account/sessions/:sessionId')
contentType: ContentType::JSON
))
->label('abuse-limit', 10)
->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to update the current device session.')
->param('sessionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Session ID. Use the string \'current\' to update the current device session.', false, ['dbForProject'])
->inject('response')
->inject('user')
->inject('dbForProject')
@ -1262,7 +1262,7 @@ Http::post('/v1/account/sessions/token')
->label('abuse-limit', 10)
->label('abuse-key', 'ip:{ip},userId:{param-userId}')
->label('abuse-reset', [201])
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('secret', '', new Text(256), 'Secret of a token generated by login methods. For example, the `createMagicURLToken` or `createPhoneToken` methods.')
->inject('request')
->inject('response')
@ -1469,13 +1469,14 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
->inject('devKey')
->inject('user')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('geodb')
->inject('queueForEvents')
->inject('store')
->inject('proofForPassword')
->inject('proofForToken')
->inject('authorization')
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Validator $redirectValidator, Document $devKey, User $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) use ($oauthDefaultSuccess) {
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Validator $redirectValidator, Document $devKey, User $user, Database $dbForProject, Database $dbForPlatform, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) use ($oauthDefaultSuccess) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
@ -1484,6 +1485,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
@ -1512,6 +1514,29 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
$state = $defaultState;
}
// Allow redirect to rule URL if related to project
// Check if $redirectValidator is instance of Redirect class
if ($redirectValidator instanceof Redirect) {
$domains = \array_filter([
parse_url($state['success'], PHP_URL_HOST) ?? '',
parse_url($state['failure'], PHP_URL_HOST) ?? ''
], fn ($domain) => \is_string($domain) && $domain !== '');
if (!empty($domains)) {
$rules = $authorization->skip(fn () => $dbForPlatform->find('rules', [
Query::equal('domain', \array_values(\array_unique($domains))),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::limit(2)
]));
foreach ($rules as $rule) {
$allowedHostnames = $redirectValidator->getAllowedHostnames();
$allowedHostnames[] = $rule->getAttribute('domain', '');
$redirectValidator->setAllowedHostnames($allowedHostnames);
}
}
}
if ($devKey->isEmpty() && !$redirectValidator->isValid($state['success'])) {
throw new Exception(Exception::PROJECT_INVALID_SUCCESS_URL);
}
@ -1523,6 +1548,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
if (!empty($state['failure'])) {
$failure = URLParser::parse($state['failure']);
}
$failureRedirect = (function (string $type, ?string $message = null, ?int $code = null) use ($failure, $response) {
$exception = new Exception($type, $message, $code);
if (!empty($failure)) {
@ -1568,7 +1594,9 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
$accessToken = $oauth2->getAccessToken($code);
$refreshToken = $oauth2->getRefreshToken($code);
$accessTokenExpiry = $oauth2->getAccessTokenExpiry($code);
} catch (OAuth2Exception $ex) {
$failureRedirect(
$ex->getType(),
'Failed to obtain access token. The ' . $providerName . ' OAuth2 provider returned an error: ' . $ex->getMessage(),
@ -2068,7 +2096,7 @@ Http::post('/v1/account/tokens/magic-url')
))
->label('abuse-limit', 60)
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('url', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
@ -2348,7 +2376,7 @@ Http::post('/v1/account/tokens/email')
))
->label('abuse-limit', 10)
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
->inject('request')
@ -2655,7 +2683,7 @@ Http::put('/v1/account/sessions/magic-url')
->label('abuse-limit', 10)
->label('abuse-key', 'ip:{ip},userId:{param-userId}')
->label('abuse-reset', [201])
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('secret', '', new Text(256), 'Valid verification token.')
->inject('request')
->inject('response')
@ -2704,7 +2732,7 @@ Http::put('/v1/account/sessions/phone')
))
->label('abuse-limit', 10)
->label('abuse-key', 'ip:{ip},userId:{param-userId}')
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('secret', '', new Text(256), 'Valid verification token.')
->inject('request')
->inject('response')
@ -2747,7 +2775,7 @@ Http::post('/v1/account/tokens/phone')
))
->label('abuse-limit', 10)
->label('abuse-key', ['url:{url},phone:{param-phone}', 'url:{url},ip:{ip}'])
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the phone number has never been used, a new account is created using the provided userId. Otherwise, if the phone number is already attached to an account, the user ID is ignored.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the phone number has never been used, a new account is created using the provided userId. Otherwise, if the phone number is already attached to an account, the user ID is ignored.', false, ['dbForProject'])
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
->inject('request')
->inject('response')
@ -3725,7 +3753,7 @@ Http::put('/v1/account/recovery')
))
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},userId:{param-userId}')
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('secret', '', new Text(256), 'Valid reset token.')
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be between 8 and 256 chars.', false, ['project', 'passwordsDictionary'])
->inject('response')
@ -4076,7 +4104,7 @@ Http::put('/v1/account/verifications/email')
])
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},userId:{param-userId}')
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('secret', '', new Text(256), 'Valid verification token.')
->inject('response')
->inject('user')
@ -4292,7 +4320,7 @@ Http::put('/v1/account/verifications/phone')
))
->label('abuse-limit', 10)
->label('abuse-key', 'userId:{param-userId}')
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('secret', '', new Text(256), 'Valid verification token.')
->inject('response')
->inject('user')
@ -4357,9 +4385,9 @@ Http::post('/v1/account/targets/push')
],
contentType: ContentType::JSON
))
->param('targetId', '', new CustomId(), 'Target ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('targetId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true, ['dbForProject'])
->inject('queueForEvents')
->inject('user')
->inject('request')
@ -4441,7 +4469,7 @@ Http::put('/v1/account/targets/:targetId/push')
],
contentType: ContentType::JSON
))
->param('targetId', '', new UID(), 'Target ID.')
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID.', false, ['dbForProject'])
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
->inject('queueForEvents')
->inject('user')
@ -4507,7 +4535,7 @@ Http::delete('/v1/account/targets/:targetId/push')
],
contentType: ContentType::NONE
))
->param('targetId', '', new UID(), 'Target ID.')
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID.', false, ['dbForProject'])
->inject('queueForEvents')
->inject('queueForDeletes')
->inject('user')
@ -4574,16 +4602,10 @@ Http::get('/v1/account/identities')
$queries[] = Query::equal('userInternalId', [$user->getSequence()]);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@ -4635,7 +4657,7 @@ Http::delete('/v1/account/identities/:identityId')
],
contentType: ContentType::NONE
))
->param('identityId', '', new UID(), 'Identity ID.')
->param('identityId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Identity ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')

View file

@ -20,7 +20,7 @@ use GraphQL\Validator\Rules\QueryDepth;
use Swoole\Coroutine\WaitGroup;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\System\System;
use Utopia\Validator\JSON;
use Utopia\Validator\Text;

View file

@ -8,7 +8,7 @@ use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
Http::get('/v1/locale')

View file

@ -43,7 +43,7 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\Roles;
use Utopia\Database\Validator\UID;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@ -78,7 +78,7 @@ Http::post('/v1/messaging/providers/mailgun')
)
]
))
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('providerId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.')
->param('apiKey', '', new Text(0), 'Mailgun API Key.', true)
->param('domain', '', new Text(0), 'Mailgun Domain.', true)
@ -172,7 +172,7 @@ Http::post('/v1/messaging/providers/sendgrid')
)
]
))
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('providerId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.')
->param('apiKey', '', new Text(0), 'Sendgrid API key.', true)
->param('fromName', '', new Text(128, 0), 'Sender Name.', true)
@ -254,7 +254,7 @@ Http::post('/v1/messaging/providers/resend')
)
]
))
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('providerId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.')
->param('apiKey', '', new Text(0), 'Resend API key.', true)
->param('fromName', '', new Text(128, 0), 'Sender Name.', true)
@ -356,7 +356,7 @@ Http::post('/v1/messaging/providers/smtp')
]
)
])
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('providerId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.')
->param('host', '', new Text(0), 'SMTP hosts. Either a single hostname or multiple semicolon-delimited hostnames. You can also specify a different port for each host such as `smtp1.example.com:25;smtp2.example.com`. You can also specify encryption type, for example: `tls://smtp1.example.com:587;ssl://smtp2.example.com:465"`. Hosts will be tried in order.')
->param('port', 587, new Range(1, 65535), 'The default SMTP server port.', true)
@ -451,7 +451,7 @@ Http::post('/v1/messaging/providers/msg91')
)
]
))
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('providerId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.')
->param('templateId', '', new Text(0), 'Msg91 template ID', true)
->param('senderId', '', new Text(0), 'Msg91 sender ID.', true)
@ -534,7 +534,7 @@ Http::post('/v1/messaging/providers/telesign')
)
]
))
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('providerId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.')
->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('customerId', '', new Text(0), 'Telesign customer ID.', true)
@ -618,7 +618,7 @@ Http::post('/v1/messaging/providers/textmagic')
)
]
))
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('providerId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.')
->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('username', '', new Text(0), 'Textmagic username.', true)
@ -702,7 +702,7 @@ Http::post('/v1/messaging/providers/twilio')
)
]
))
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('providerId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.')
->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('accountSid', '', new Text(0), 'Twilio account secret ID.', true)
@ -786,7 +786,7 @@ Http::post('/v1/messaging/providers/vonage')
)
]
))
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('providerId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.')
->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('apiKey', '', new Text(0), 'Vonage API key.', true)
@ -890,7 +890,7 @@ Http::post('/v1/messaging/providers/fcm')
]
)
])
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('providerId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.')
->param('serviceAccountJSON', null, new Nullable(new JSON()), 'FCM service account JSON.', true)
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
@ -980,7 +980,7 @@ Http::post('/v1/messaging/providers/apns')
]
)
])
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('providerId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.')
->param('authKey', '', new Text(0), 'APNS authentication key.', true)
->param('authKeyId', '', new Text(0), 'APNS authentication key ID.', true)
@ -1087,15 +1087,10 @@ Http::get('/v1/messaging/providers')
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor) {
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@ -1140,7 +1135,7 @@ Http::get('/v1/messaging/providers/:providerId/logs')
)
]
))
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->param('queries', [], new Queries([new Limit(), new Offset()]), '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 and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
@ -1236,7 +1231,7 @@ Http::get('/v1/messaging/providers/:providerId')
)
]
))
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->inject('dbForProject')
->inject('response')
->action(function (string $providerId, Database $dbForProject, Response $response) {
@ -1270,7 +1265,7 @@ Http::patch('/v1/messaging/providers/mailgun/:providerId')
)
]
))
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.', true)
->param('apiKey', '', new Text(0), 'Mailgun API Key.', true)
->param('domain', '', new Text(0), 'Mailgun Domain.', true)
@ -1383,7 +1378,7 @@ Http::patch('/v1/messaging/providers/sendgrid/:providerId')
)
]
))
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
->param('apiKey', '', new Text(0), 'Sendgrid API key.', true)
@ -1481,7 +1476,7 @@ Http::patch('/v1/messaging/providers/resend/:providerId')
)
]
))
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
->param('apiKey', '', new Text(0), 'Resend API key.', true)
@ -1599,7 +1594,7 @@ Http::patch('/v1/messaging/providers/smtp/:providerId')
]
)
])
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.', true)
->param('host', '', new Text(0), 'SMTP hosts. Either a single hostname or multiple semicolon-delimited hostnames. You can also specify a different port for each host such as `smtp1.example.com:25;smtp2.example.com`. You can also specify encryption type, for example: `tls://smtp1.example.com:587;ssl://smtp2.example.com:465"`. Hosts will be tried in order.', true)
->param('port', null, new Nullable(new Range(1, 65535)), 'SMTP port.', true)
@ -1728,7 +1723,7 @@ Http::patch('/v1/messaging/providers/msg91/:providerId')
)
]
))
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
->param('templateId', '', new Text(0), 'Msg91 template ID.', true)
@ -1815,7 +1810,7 @@ Http::patch('/v1/messaging/providers/telesign/:providerId')
)
]
))
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
->param('customerId', '', new Text(0), 'Telesign customer ID.', true)
@ -1904,7 +1899,7 @@ Http::patch('/v1/messaging/providers/textmagic/:providerId')
)
]
))
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
->param('username', '', new Text(0), 'Textmagic username.', true)
@ -1993,7 +1988,7 @@ Http::patch('/v1/messaging/providers/twilio/:providerId')
)
]
))
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
->param('accountSid', '', new Text(0), 'Twilio account secret ID.', true)
@ -2082,7 +2077,7 @@ Http::patch('/v1/messaging/providers/vonage/:providerId')
)
]
))
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
->param('apiKey', '', new Text(0), 'Vonage API key.', true)
@ -2191,7 +2186,7 @@ Http::patch('/v1/messaging/providers/fcm/:providerId')
]
)
])
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
->param('serviceAccountJSON', null, new Nullable(new JSON()), 'FCM service account JSON.', true)
@ -2287,7 +2282,7 @@ Http::patch('/v1/messaging/providers/apns/:providerId')
]
)
])
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
->param('authKey', '', new Text(0), 'APNS authentication key.', true)
@ -2390,7 +2385,7 @@ Http::delete('/v1/messaging/providers/:providerId')
],
contentType: ContentType::NONE
))
->param('providerId', '', new UID(), 'Provider ID.')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID.', false, ['dbForProject'])
->inject('queueForEvents')
->inject('dbForProject')
->inject('response')
@ -2432,7 +2427,7 @@ Http::post('/v1/messaging/topics')
)
]
))
->param('topicId', '', new CustomId(), 'Topic ID. Choose a custom Topic ID or a new Topic ID.')
->param('topicId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Topic ID. Choose a custom Topic ID or a new Topic ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Topic Name.')
->param('subscribe', [Role::users()], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with subscribe permission. By default all users are granted with any subscribe permission. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
->inject('queueForEvents')
@ -2496,15 +2491,10 @@ Http::get('/v1/messaging/topics')
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor) {
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@ -2549,7 +2539,7 @@ Http::get('/v1/messaging/topics/:topicId/logs')
)
]
))
->param('topicId', '', new UID(), 'Topic ID.')
->param('topicId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Topic ID.', false, ['dbForProject'])
->param('queries', [], new Queries([new Limit(), new Offset()]), '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 and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
@ -2646,7 +2636,7 @@ Http::get('/v1/messaging/topics/:topicId')
)
]
))
->param('topicId', '', new UID(), 'Topic ID.')
->param('topicId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Topic ID.', false, ['dbForProject'])
->inject('dbForProject')
->inject('response')
->action(function (string $topicId, Database $dbForProject, Response $response) {
@ -2681,7 +2671,7 @@ Http::patch('/v1/messaging/topics/:topicId')
)
]
))
->param('topicId', '', new UID(), 'Topic ID.')
->param('topicId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Topic ID.', false, ['dbForProject'])
->param('name', null, new Nullable(new Text(128)), 'Topic Name.', true)
->param('subscribe', null, new Nullable(new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE)), 'An array of role strings with subscribe permission. By default all users are granted with any subscribe permission. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
->inject('queueForEvents')
@ -2733,7 +2723,7 @@ Http::delete('/v1/messaging/topics/:topicId')
],
contentType: ContentType::NONE
))
->param('topicId', '', new UID(), 'Topic ID.')
->param('topicId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Topic ID.', false, ['dbForProject'])
->inject('queueForEvents')
->inject('dbForProject')
->inject('queueForDeletes')
@ -2780,9 +2770,9 @@ Http::post('/v1/messaging/topics/:topicId/subscribers')
)
]
))
->param('subscriberId', '', new CustomId(), 'Subscriber ID. Choose a custom Subscriber ID or a new Subscriber ID.')
->param('topicId', '', new UID(), 'Topic ID. The topic ID to subscribe to.')
->param('targetId', '', new UID(), 'Target ID. The target ID to link to the specified Topic ID.')
->param('subscriberId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Subscriber ID. Choose a custom Subscriber ID or a new Subscriber ID.', false, ['dbForProject'])
->param('topicId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Topic ID. The topic ID to subscribe to.', false, ['dbForProject'])
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID. The target ID to link to the specified Topic ID.', false, ['dbForProject'])
->inject('queueForEvents')
->inject('dbForProject')
->inject('authorization')
@ -2878,8 +2868,8 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
)
]
))
->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.')
->param('queries', [], new Subscribers(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Providers::ALLOWED_ATTRIBUTES), true)
->param('topicId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Topic ID. The topic ID subscribed to.', false, ['dbForProject'])
->param('queries', [], new Subscribers(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Subscribers::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('dbForProject')
@ -2904,15 +2894,10 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
$queries[] = Query::equal('topicInternalId', [$topic->getSequence()]);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor) {
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@ -2969,7 +2954,7 @@ Http::get('/v1/messaging/subscribers/:subscriberId/logs')
)
]
))
->param('subscriberId', '', new UID(), 'Subscriber ID.')
->param('subscriberId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Subscriber ID.', false, ['dbForProject'])
->param('queries', [], new Queries([new Limit(), new Offset()]), '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 and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
@ -3066,8 +3051,8 @@ Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
)
]
))
->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.')
->param('subscriberId', '', new UID(), 'Subscriber ID.')
->param('topicId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Topic ID. The topic ID subscribed to.', false, ['dbForProject'])
->param('subscriberId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Subscriber ID.', false, ['dbForProject'])
->inject('dbForProject')
->inject('authorization')
->inject('response')
@ -3117,8 +3102,8 @@ Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
],
contentType: ContentType::NONE
))
->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.')
->param('subscriberId', '', new UID(), 'Subscriber ID.')
->param('topicId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Topic ID. The topic ID subscribed to.', false, ['dbForProject'])
->param('subscriberId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Subscriber ID.', false, ['dbForProject'])
->inject('queueForEvents')
->inject('dbForProject')
->inject('authorization')
@ -3184,14 +3169,14 @@ Http::post('/v1/messaging/messages/email')
)
]
))
->param('messageId', '', new CustomId(), 'Message ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('messageId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Message ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('subject', '', new Text(998), 'Email Subject.')
->param('content', '', new Text(64230), 'Email Content.')
->param('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('cc', [], new ArrayList(new UID()), 'Array of target IDs to be added as CC.', true)
->param('bcc', [], new ArrayList(new UID()), 'Array of target IDs to be added as BCC.', true)
->param('topics', [], fn (Database $dbForProject) => new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'List of Topic IDs.', true, ['dbForProject'])
->param('users', [], fn (Database $dbForProject) => new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'List of User IDs.', true, ['dbForProject'])
->param('targets', [], fn (Database $dbForProject) => new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'List of Targets IDs.', true, ['dbForProject'])
->param('cc', [], fn (Database $dbForProject) => new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Array of target IDs to be added as CC.', true, ['dbForProject'])
->param('bcc', [], fn (Database $dbForProject) => new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Array of target IDs to be added as BCC.', true, ['dbForProject'])
->param('attachments', [], new ArrayList(new CompoundUID()), 'Array of compound ID strings of bucket IDs and file IDs to be attached to the email. They should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
->param('draft', false, new Boolean(), 'Is message a draft', true)
->param('html', false, new Boolean(), 'Is content of type HTML', true)
@ -3363,11 +3348,11 @@ Http::post('/v1/messaging/messages/sms')
]
)
])
->param('messageId', '', new CustomId(), 'Message ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('messageId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Message ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('content', '', new Text(64230), 'SMS Content.')
->param('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('topics', [], fn (Database $dbForProject) => new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'List of Topic IDs.', true, ['dbForProject'])
->param('users', [], fn (Database $dbForProject) => new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'List of User IDs.', true, ['dbForProject'])
->param('targets', [], fn (Database $dbForProject) => new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'List of Targets IDs.', true, ['dbForProject'])
->param('draft', false, new Boolean(), 'Is message a draft', true)
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->inject('queueForEvents')
@ -3486,12 +3471,12 @@ Http::post('/v1/messaging/messages/push')
)
]
))
->param('messageId', '', new CustomId(), 'Message ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('messageId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Message ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('title', '', new Text(256), 'Title for push notification.', true)
->param('body', '', new Text(64230), 'Body for push notification.', true)
->param('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('topics', [], fn (Database $dbForProject) => new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'List of Topic IDs.', true, ['dbForProject'])
->param('users', [], fn (Database $dbForProject) => new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'List of User IDs.', true, ['dbForProject'])
->param('targets', [], fn (Database $dbForProject) => new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'List of Targets IDs.', true, ['dbForProject'])
->param('data', null, new Nullable(new JSON()), 'Additional key-value pair data for push notification.', true)
->param('action', '', new Text(256), 'Action for push notification.', true)
->param('image', '', new CompoundUID(), 'Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
@ -3719,15 +3704,10 @@ Http::get('/v1/messaging/messages')
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor) {
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@ -3772,7 +3752,7 @@ Http::get('/v1/messaging/messages/:messageId/logs')
)
],
))
->param('messageId', '', new UID(), 'Message ID.')
->param('messageId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Message ID.', false, ['dbForProject'])
->param('queries', [], new Queries([new Limit(), new Offset()]), '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 and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
@ -3869,7 +3849,7 @@ Http::get('/v1/messaging/messages/:messageId/targets')
)
],
))
->param('messageId', '', new UID(), 'Message ID.')
->param('messageId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Message ID.', false, ['dbForProject'])
->param('queries', [], new Targets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Targets::ALLOWED_ATTRIBUTES), true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
@ -3899,15 +3879,10 @@ Http::get('/v1/messaging/messages/:messageId/targets')
$queries[] = Query::equal('$id', $targetIDs);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor) {
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@ -3952,7 +3927,7 @@ Http::get('/v1/messaging/messages/:messageId')
)
]
))
->param('messageId', '', new UID(), 'Message ID.')
->param('messageId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Message ID.', false, ['dbForProject'])
->inject('dbForProject')
->inject('response')
->action(function (string $messageId, Database $dbForProject, Response $response) {
@ -3986,16 +3961,16 @@ Http::patch('/v1/messaging/messages/email/:messageId')
)
]
))
->param('messageId', '', new UID(), 'Message ID.')
->param('topics', null, new Nullable(new ArrayList(new UID())), 'List of Topic IDs.', true)
->param('users', null, new Nullable(new ArrayList(new UID())), 'List of User IDs.', true)
->param('targets', null, new Nullable(new ArrayList(new UID())), 'List of Targets IDs.', true)
->param('messageId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Message ID.', false, ['dbForProject'])
->param('topics', null, fn (Database $dbForProject) => new Nullable(new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength()))), 'List of Topic IDs.', true, ['dbForProject'])
->param('users', null, fn (Database $dbForProject) => new Nullable(new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength()))), 'List of User IDs.', true, ['dbForProject'])
->param('targets', null, fn (Database $dbForProject) => new Nullable(new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength()))), 'List of Targets IDs.', true, ['dbForProject'])
->param('subject', null, new Nullable(new Text(998)), 'Email Subject.', true)
->param('content', null, new Nullable(new Text(64230)), 'Email Content.', true)
->param('draft', null, new Nullable(new Boolean()), 'Is message a draft', true)
->param('html', null, new Nullable(new Boolean()), 'Is content of type HTML', true)
->param('cc', null, new Nullable(new ArrayList(new UID())), 'Array of target IDs to be added as CC.', true)
->param('bcc', null, new Nullable(new ArrayList(new UID())), 'Array of target IDs to be added as BCC.', true)
->param('cc', null, fn (Database $dbForProject) => new Nullable(new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength()))), 'Array of target IDs to be added as CC.', true, ['dbForProject'])
->param('bcc', null, fn (Database $dbForProject) => new Nullable(new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength()))), 'Array of target IDs to be added as BCC.', true, ['dbForProject'])
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->param('attachments', null, new Nullable(new ArrayList(new CompoundUID())), 'Array of compound ID strings of bucket IDs and file IDs to be attached to the email. They should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
->inject('queueForEvents')
@ -4213,10 +4188,10 @@ Http::patch('/v1/messaging/messages/sms/:messageId')
]
)
])
->param('messageId', '', new UID(), 'Message ID.')
->param('topics', null, new Nullable(new ArrayList(new UID())), 'List of Topic IDs.', true)
->param('users', null, new Nullable(new ArrayList(new UID())), 'List of User IDs.', true)
->param('targets', null, new Nullable(new ArrayList(new UID())), 'List of Targets IDs.', true)
->param('messageId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Message ID.', false, ['dbForProject'])
->param('topics', null, fn (Database $dbForProject) => new Nullable(new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength()))), 'List of Topic IDs.', true, ['dbForProject'])
->param('users', null, fn (Database $dbForProject) => new Nullable(new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength()))), 'List of User IDs.', true, ['dbForProject'])
->param('targets', null, fn (Database $dbForProject) => new Nullable(new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength()))), 'List of Targets IDs.', true, ['dbForProject'])
->param('content', null, new Nullable(new Text(64230)), 'Email Content.', true)
->param('draft', null, new Nullable(new Boolean()), 'Is message a draft', true)
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
@ -4375,10 +4350,10 @@ Http::patch('/v1/messaging/messages/push/:messageId')
)
]
))
->param('messageId', '', new UID(), 'Message ID.')
->param('topics', null, new Nullable(new ArrayList(new UID())), 'List of Topic IDs.', true)
->param('users', null, new Nullable(new ArrayList(new UID())), 'List of User IDs.', true)
->param('targets', null, new Nullable(new ArrayList(new UID())), 'List of Targets IDs.', true)
->param('messageId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Message ID.', false, ['dbForProject'])
->param('topics', null, fn (Database $dbForProject) => new Nullable(new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength()))), 'List of Topic IDs.', true, ['dbForProject'])
->param('users', null, fn (Database $dbForProject) => new Nullable(new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength()))), 'List of User IDs.', true, ['dbForProject'])
->param('targets', null, fn (Database $dbForProject) => new Nullable(new ArrayList(new UID($dbForProject->getAdapter()->getMaxUIDLength()))), 'List of Targets IDs.', true, ['dbForProject'])
->param('title', null, new Nullable(new Text(256)), 'Title for push notification.', true)
->param('body', null, new Nullable(new Text(64230)), 'Body for push notification.', true)
->param('data', null, new Nullable(new JSON()), 'Additional Data for push notification.', true)
@ -4637,7 +4612,7 @@ Http::delete('/v1/messaging/messages/:messageId')
],
contentType: ContentType::NONE
))
->param('messageId', '', new UID(), 'Message ID.')
->param('messageId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Message ID.', false, ['dbForProject'])
->inject('dbForProject')
->inject('dbForPlatform')
->inject('queueForEvents')

View file

@ -21,7 +21,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Queries\Documents;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Migration\Resource;
use Utopia\Migration\Sources\Appwrite;
use Utopia\Migration\Sources\CSV;
@ -64,7 +64,7 @@ Http::post('/v1/migrations/appwrite')
))
->param('resources', [], new ArrayList(new WhiteList(Appwrite::getSupportedResources())), 'List of resources to migrate')
->param('endpoint', '', new URL(), 'Source Appwrite endpoint')
->param('projectId', '', new UID(), 'Source Project ID')
->param('projectId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Source Project ID', false, ['dbForProject'])
->param('apiKey', '', new Text(512), 'Source API Key')
->inject('response')
->inject('dbForProject')
@ -335,8 +335,8 @@ Http::post('/v1/migrations/csv/imports')
)
]
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->param('bucketId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).', false, ['dbForProject'])
->param('fileId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'File ID.', false, ['dbForProject'])
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.')
->param('internalFile', false, new Boolean(), 'Is the file stored in an internal bucket?', true)
->inject('response')
@ -629,16 +629,10 @@ Http::get('/v1/migrations')
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@ -684,7 +678,7 @@ Http::get('/v1/migrations/:migrationId')
)
]
))
->param('migrationId', '', new UID(), 'Migration unique ID.')
->param('migrationId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Migration unique ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $migrationId, Response $response, Database $dbForProject) {
@ -917,7 +911,7 @@ Http::patch('/v1/migrations/:migrationId')
)
]
))
->param('migrationId', '', new UID(), 'Migration unique ID.')
->param('migrationId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Migration unique ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('project')
@ -971,7 +965,7 @@ Http::delete('/v1/migrations/:migrationId')
],
contentType: ContentType::NONE
))
->param('migrationId', '', new UID(), 'Migration ID.')
->param('migrationId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Migration ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')

View file

@ -16,7 +16,7 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DateTimeValidator;
use Utopia\Database\Validator\UID;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
@ -496,7 +496,7 @@ Http::get('/v1/project/variables/:variableId')
)
]
))
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('variableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Variable unique ID.', false, ['dbForProject'])
->inject('response')
->inject('project')
->inject('dbForProject')
@ -526,7 +526,7 @@ Http::put('/v1/project/variables/:variableId')
)
]
))
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('variableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Variable unique ID.', false, ['dbForProject'])
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
->param('value', null, new Nullable(new Text(8192, 0)), 'Variable value. Max length: 8192 chars.', true)
->param('secret', null, new Nullable(new Boolean()), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
@ -585,7 +585,7 @@ Http::delete('/v1/project/variables/:variableId')
],
contentType: ContentType::NONE
))
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('variableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Variable unique ID.', false, ['dbForProject'])
->inject('project')
->inject('response')
->inject('dbForProject')

View file

@ -6,7 +6,6 @@ use Appwrite\Event\Delete;
use Appwrite\Event\Mail;
use Appwrite\Event\Validator\Event;
use Appwrite\Extend\Exception;
use Appwrite\Hooks\Hooks;
use Appwrite\Network\Platform;
use Appwrite\Network\Validator\Email;
use Appwrite\SDK\AuthType;
@ -15,31 +14,25 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Template\Template;
use Appwrite\Utopia\Database\Validator\ProjectId;
use Appwrite\Utopia\Database\Validator\Queries\Projects;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Keys;
use Appwrite\Utopia\Response;
use PHPMailer\PHPMailer\PHPMailer;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Validator\PublicDomain;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\Pools\Group;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
@ -61,251 +54,6 @@ Http::init()
}
});
Http::post('/v1/projects')
->desc('Create project')
->groups(['api', 'projects'])
->label('audits.event', 'projects.create')
->label('audits.resource', 'project/{response.$id}')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'create',
description: '/docs/references/projects/create.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_PROJECT,
)
]
))
->param('projectId', '', new ProjectId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, and hyphen. Can\'t start with a special char. Max length is 36 chars.')
->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
->param('teamId', '', new UID(), 'Team unique ID.')
->param('region', System::getEnv('_APP_REGION', 'default'), new Whitelist(array_keys(array_filter(Config::getParam('regions'), fn ($config) => !$config['disabled']))), 'Project Region.', true)
->param('description', '', new Text(256), 'Project description. Max length: 256 chars.', true)
->param('logo', '', new Text(1024), 'Project logo.', true)
->param('url', '', new URL(), 'Project URL.', true)
->param('legalName', '', new Text(256), 'Project legal Name. Max length: 256 chars.', true)
->param('legalCountry', '', new Text(256), 'Project legal Country. Max length: 256 chars.', true)
->param('legalState', '', new Text(256), 'Project legal State. Max length: 256 chars.', true)
->param('legalCity', '', new Text(256), 'Project legal City. Max length: 256 chars.', true)
->param('legalAddress', '', new Text(256), 'Project legal Address. Max length: 256 chars.', true)
->param('legalTaxId', '', new Text(256), 'Project legal Tax ID. Max length: 256 chars.', true)
->inject('request')
->inject('response')
->inject('dbForPlatform')
->inject('cache')
->inject('pools')
->inject('hooks')
->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForPlatform, Cache $cache, Group $pools, Hooks $hooks) {
$team = $dbForPlatform->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$allowList = \array_filter(\explode(',', System::getEnv('_APP_PROJECT_REGIONS', '')));
if (!empty($allowList) && !\in_array($region, $allowList)) {
throw new Exception(Exception::PROJECT_REGION_UNSUPPORTED, 'Region "' . $region . '" is not supported');
}
$auth = Config::getParam('auth', []);
$auths = [
'limit' => 0,
'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT,
'passwordHistory' => 0,
'passwordDictionary' => false,
'duration' => TOKEN_EXPIRATION_LOGIN_LONG,
'personalDataCheck' => false,
'mockNumbers' => [],
'sessionAlerts' => false,
'membershipsUserName' => false,
'membershipsUserEmail' => false,
'membershipsMfa' => false,
'invalidateSessions' => true
];
foreach ($auth as $method) {
$auths[$method['key'] ?? ''] = true;
}
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
if ($projectId === 'console') {
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
}
$databases = Config::getParam('pools-database', []);
if ($region !== 'default') {
$databaseKeys = System::getEnv('_APP_DATABASE_KEYS', '');
$keys = explode(',', $databaseKeys);
$databases = array_filter($keys, function ($value) use ($region) {
return str_contains($value, $region);
});
}
$databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE');
$index = \array_search($databaseOverride, $databases);
if ($index !== false) {
$dsn = $databases[$index];
} else {
$dsn = $databases[array_rand($databases)];
}
// TODO: Temporary until all projects are using shared tables.
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn, $sharedTables)) {
$schema = 'appwrite';
$database = 'appwrite';
$namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', '');
$dsn = $schema . '://' . $dsn . '?database=' . $database;
if (!empty($namespace)) {
$dsn .= '&namespace=' . $namespace;
}
}
try {
$project = $dbForPlatform->createDocument('projects', new Document([
'$id' => $projectId,
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'name' => $name,
'teamInternalId' => $team->getSequence(),
'teamId' => $team->getId(),
'region' => $region,
'description' => $description,
'logo' => $logo,
'url' => $url,
'version' => APP_VERSION_STABLE,
'legalName' => $legalName,
'legalCountry' => $legalCountry,
'legalState' => $legalState,
'legalCity' => $legalCity,
'legalAddress' => $legalAddress,
'legalTaxId' => ID::custom($legalTaxId),
'services' => new stdClass(),
'platforms' => null,
'oAuthProviders' => [],
'webhooks' => null,
'keys' => null,
'auths' => $auths,
'accessedAt' => DateTime::now(),
'search' => implode(' ', [$projectId, $name]),
'database' => $dsn,
'labels' => [],
]));
} catch (Duplicate) {
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
}
try {
$dsn = new DSN($dsn);
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $dsn);
}
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
$sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', ''));
$projectTables = !\in_array($dsn->getHost(), $sharedTables);
$sharedTablesV1 = \in_array($dsn->getHost(), $sharedTablesV1);
$sharedTablesV2 = !$projectTables && !$sharedTablesV1;
$sharedTables = $sharedTablesV1 || $sharedTablesV2;
if (!$sharedTablesV2) {
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$dbForProject = new Database($adapter, $cache);
$dbForProject->setDatabase(APP_DATABASE);
if ($sharedTables) {
$dbForProject
->setSharedTables(true)
->setTenant($sharedTablesV1 ? (int)$project->getSequence() : null)
->setNamespace($dsn->getParam('namespace'));
} else {
$dbForProject
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
}
$create = true;
try {
$dbForProject->create();
} catch (Duplicate) {
$create = false;
}
if ($create || $projectTables) {
$adapter = new AdapterDatabase($dbForProject);
$audit = new Audit($adapter);
$audit->setup();
}
if (!$create && $sharedTablesV1) {
$adapter = new AdapterDatabase($dbForProject);
$attributes = $adapter->getAttributeDocuments();
$indexes = $adapter->getIndexDocuments();
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom('audit'),
'$permissions' => [Permission::create(Role::any())],
'name' => 'audit',
'attributes' => $attributes,
'indexes' => $indexes,
'documentSecurity' => true
]));
}
if ($create || $sharedTablesV1) {
/** @var array $collections */
$collections = Config::getParam('collections', [])['projects'] ?? [];
foreach ($collections as $key => $collection) {
if (($collection['$collection'] ?? '') !== Database::METADATA) {
continue;
}
$attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
$indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
try {
$dbForProject->createCollection($key, $attributes, $indexes);
} catch (Duplicate) {
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom($key),
'$permissions' => [Permission::create(Role::any())],
'name' => $key,
'attributes' => $attributes,
'indexes' => $indexes,
'documentSecurity' => true
]));
}
}
}
}
// Hook allowing instant project mirroring during migration
// Outside of migration, hook is not registered and has no effect
$hooks->trigger('afterProjectCreation', [$project, $pools, $cache]);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($project, Response::MODEL_PROJECT);
});
Http::get('/v1/projects/:projectId')
->desc('Get project')
->groups(['api', 'projects'])
@ -323,7 +71,7 @@ Http::get('/v1/projects/:projectId')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, Response $response, Database $dbForPlatform) {
@ -337,137 +85,6 @@ Http::get('/v1/projects/:projectId')
$response->dynamic($project, Response::MODEL_PROJECT);
});
Http::patch('/v1/projects/:projectId')
->desc('Update project')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('audits.event', 'projects.update')
->label('audits.resource', 'project/{request.projectId}')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'update',
description: '/docs/references/projects/update.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PROJECT,
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
->param('description', '', new Text(256), 'Project description. Max length: 256 chars.', true)
->param('logo', '', new Text(1024), 'Project logo.', true)
->param('url', '', new URL(), 'Project URL.', true)
->param('legalName', '', new Text(256), 'Project legal name. Max length: 256 chars.', true)
->param('legalCountry', '', new Text(256), 'Project legal country. Max length: 256 chars.', true)
->param('legalState', '', new Text(256), 'Project legal state. Max length: 256 chars.', true)
->param('legalCity', '', new Text(256), 'Project legal city. Max length: 256 chars.', true)
->param('legalAddress', '', new Text(256), 'Project legal address. Max length: 256 chars.', true)
->param('legalTaxId', '', new Text(256), 'Project legal tax ID. Max length: 256 chars.', true)
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForPlatform) {
$project = $dbForPlatform->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project
->setAttribute('name', $name)
->setAttribute('description', $description)
->setAttribute('logo', $logo)
->setAttribute('url', $url)
->setAttribute('legalName', $legalName)
->setAttribute('legalCountry', $legalCountry)
->setAttribute('legalState', $legalState)
->setAttribute('legalCity', $legalCity)
->setAttribute('legalAddress', $legalAddress)
->setAttribute('legalTaxId', $legalTaxId)
->setAttribute('search', implode(' ', [$projectId, $name])));
$response->dynamic($project, Response::MODEL_PROJECT);
});
Http::patch('/v1/projects/:projectId/team')
->desc('Update project team')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'updateTeam',
description: '/docs/references/projects/update-team.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PROJECT,
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('teamId', '', new UID(), 'Team ID of the team to transfer project to.')
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $teamId, Response $response, Database $dbForPlatform) {
$project = $dbForPlatform->getDocument('projects', $projectId);
$team = $dbForPlatform->getDocument('teams', $teamId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
if ($team->isEmpty()) {
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$permissions = [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
];
$project
->setAttribute('teamId', $teamId)
->setAttribute('teamInternalId', $team->getSequence())
->setAttribute('$permissions', $permissions);
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project);
$installations = $dbForPlatform->find('installations', [
Query::equal('projectInternalId', [$project->getSequence()]),
]);
foreach ($installations as $installation) {
$installation->getAttribute('$permissions', $permissions);
$dbForPlatform->updateDocument('installations', $installation->getId(), $installation);
}
$repositories = $dbForPlatform->find('repositories', [
Query::equal('projectInternalId', [$project->getSequence()]),
]);
foreach ($repositories as $repository) {
$repository->getAttribute('$permissions', $permissions);
$dbForPlatform->updateDocument('repositories', $repository->getId(), $repository);
}
$vcsComments = $dbForPlatform->find('vcsComments', [
Query::equal('projectInternalId', [$project->getSequence()]),
]);
foreach ($vcsComments as $vcsComment) {
$vcsComment->getAttribute('$permissions', $permissions);
$dbForPlatform->updateDocument('vcsComments', $vcsComment->getId(), $vcsComment);
}
$response->dynamic($project, Response::MODEL_PROJECT);
});
Http::patch('/v1/projects/:projectId/service')
->desc('Update service status')
->groups(['api', 'projects'])
@ -485,7 +102,7 @@ Http::patch('/v1/projects/:projectId/service')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('service', '', new WhiteList(array_keys(array_filter(Config::getParam('services'), fn ($element) => $element['optional'])), true), 'Service name.')
->param('status', null, new Boolean(), 'Service status.')
->inject('response')
@ -523,7 +140,7 @@ Http::patch('/v1/projects/:projectId/service/all')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('status', null, new Boolean(), 'Service status.')
->inject('response')
->inject('dbForPlatform')
@ -584,7 +201,7 @@ Http::patch('/v1/projects/:projectId/api')
]
)
])
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('api', '', new WhiteList(array_keys(Config::getParam('apis')), true), 'API name.')
->param('status', null, new Boolean(), 'API status.')
->inject('response')
@ -642,7 +259,7 @@ Http::patch('/v1/projects/:projectId/api/all')
]
)
])
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('status', null, new Boolean(), 'API status.')
->inject('response')
->inject('dbForPlatform')
@ -683,7 +300,7 @@ Http::patch('/v1/projects/:projectId/oauth2')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'Provider Name')
->param('appId', null, new Nullable(new Text(256)), 'Provider app ID. Max length: 256 chars.', true)
->param('secret', null, new Nullable(new text(512)), 'Provider secret key. Max length: 512 chars.', true)
@ -734,7 +351,7 @@ Http::patch('/v1/projects/:projectId/auth/session-alerts')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('alerts', false, new Boolean(true), 'Set to true to enable session emails.')
->inject('response')
->inject('dbForPlatform')
@ -772,7 +389,7 @@ Http::patch('/v1/projects/:projectId/auth/memberships-privacy')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('userName', true, new Boolean(true), 'Set to true to show userName to members of a team.')
->param('userEmail', true, new Boolean(true), 'Set to true to show email to members of a team.')
->param('mfa', true, new Boolean(true), 'Set to true to show mfa to members of a team.')
@ -814,7 +431,7 @@ Http::patch('/v1/projects/:projectId/auth/limit')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('limit', false, new Range(0, APP_LIMIT_USERS), 'Set the max number of users allowed in this project. Use 0 for unlimited.')
->inject('response')
->inject('dbForPlatform')
@ -852,7 +469,7 @@ Http::patch('/v1/projects/:projectId/auth/duration')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('duration', 31536000, new Range(0, 31536000), 'Project session length in seconds. Max length: 31536000 seconds.')
->inject('response')
->inject('dbForPlatform')
@ -890,7 +507,7 @@ Http::patch('/v1/projects/:projectId/auth/:method')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('method', '', new WhiteList(\array_keys(Config::getParam('auth')), true), 'Auth Method. Possible values: ' . implode(',', \array_keys(Config::getParam('auth'))), false)
->param('status', false, new Boolean(true), 'Set the status of this auth method.')
->inject('response')
@ -931,7 +548,7 @@ Http::patch('/v1/projects/:projectId/auth/password-history')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('limit', 0, new Range(0, APP_LIMIT_USER_PASSWORD_HISTORY), 'Set the max number of passwords to store in user history. User can\'t choose a new password that is already stored in the password history list. Max number of passwords allowed in history is' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default value is 0')
->inject('response')
->inject('dbForPlatform')
@ -969,7 +586,7 @@ Http::patch('/v1/projects/:projectId/auth/password-dictionary')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('enabled', false, new Boolean(false), 'Set whether or not to enable checking user\'s password against most commonly used passwords. Default is false.')
->inject('response')
->inject('dbForPlatform')
@ -1007,7 +624,7 @@ Http::patch('/v1/projects/:projectId/auth/personal-data')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('enabled', false, new Boolean(false), 'Set whether or not to check a password for similarity with personal data. Default is false.')
->inject('response')
->inject('dbForPlatform')
@ -1045,7 +662,7 @@ Http::patch('/v1/projects/:projectId/auth/max-sessions')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('limit', false, new Range(1, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of users allowed in this project. Value allowed is between 1-' . APP_LIMIT_USER_SESSIONS_MAX . '. Default is ' . APP_LIMIT_USER_SESSIONS_DEFAULT)
->inject('response')
->inject('dbForPlatform')
@ -1083,7 +700,7 @@ Http::patch('/v1/projects/:projectId/auth/mock-numbers')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('numbers', '', new ArrayList(new MockNumber(), 10), 'An array of mock numbers and their corresponding verification codes (OTPs). Each number should be a valid E.164 formatted phone number. Maximum of 10 numbers are allowed.')
->inject('response')
->inject('dbForPlatform')
@ -1132,7 +749,7 @@ Http::delete('/v1/projects/:projectId')
],
contentType: ContentType::NONE
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->inject('response')
->inject('user')
->inject('dbForPlatform')
@ -1175,7 +792,7 @@ Http::post('/v1/projects/:projectId/webhooks')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
->param('enabled', true, new Boolean(true), 'Enable or disable a webhook.', true)
->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
@ -1240,7 +857,7 @@ Http::get('/v1/projects/:projectId/webhooks')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForPlatform')
@ -1280,8 +897,8 @@ Http::get('/v1/projects/:projectId/webhooks/:webhookId')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('webhookId', '', new UID(), 'Webhook unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook unique ID.', false, ['dbForPlatform'])
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForPlatform) {
@ -1321,8 +938,8 @@ Http::put('/v1/projects/:projectId/webhooks/:webhookId')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('webhookId', '', new UID(), 'Webhook unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook unique ID.', false, ['dbForPlatform'])
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
->param('enabled', true, new Boolean(true), 'Enable or disable a webhook.', true)
->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
@ -1387,8 +1004,8 @@ Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('webhookId', '', new UID(), 'Webhook unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook unique ID.', false, ['dbForPlatform'])
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForPlatform) {
@ -1434,8 +1051,8 @@ Http::delete('/v1/projects/:projectId/webhooks/:webhookId')
],
contentType: ContentType::NONE
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('webhookId', '', new UID(), 'Webhook unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook unique ID.', false, ['dbForPlatform'])
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForPlatform) {
@ -1481,13 +1098,15 @@ Http::post('/v1/projects/:projectId/keys')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
// TODO: When migrating to Platform API, mark keyId required for consistency
->param('keyId', 'unique()', fn (Database $dbForPlatform) => new CustomId($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Key ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true, ['dbForPlatform'])->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) {
->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) {
$keyId = $keyId == 'unique()' ? ID::unique() : $keyId;
$project = $dbForPlatform->getDocument('projects', $projectId);
@ -1496,7 +1115,7 @@ Http::post('/v1/projects/:projectId/keys')
}
$key = new Document([
'$id' => ID::unique(),
'$id' => $keyId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
@ -1513,7 +1132,11 @@ Http::post('/v1/projects/:projectId/keys')
'secret' => API_KEY_STANDARD . '_' . \bin2hex(\random_bytes(128)),
]);
$key = $dbForPlatform->createDocument('keys', $key);
try {
$key = $dbForPlatform->createDocument('keys', $key);
} catch (Duplicate) {
throw new Exception(Exception::KEY_ALREADY_EXISTS);
}
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
@ -1539,11 +1162,12 @@ Http::get('/v1/projects/:projectId/keys')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('queries', [], new Keys(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Keys::ALLOWED_ATTRIBUTES), true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) {
->action(function (string $projectId, array $queries, bool $includeTotal, Response $response, Database $dbForPlatform) {
$project = $dbForPlatform->getDocument('projects', $projectId);
@ -1551,15 +1175,46 @@ Http::get('/v1/projects/:projectId/keys')
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$keys = $dbForPlatform->find('keys', [
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
Query::limit(5000),
]);
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
// Backwards compatibility
if (\count(Query::getByType($queries, [Query::TYPE_LIMIT])) === 0) {
$queries[] = Query::limit(5000);
}
$queries[] = Query::equal('resourceType', ['projects']);
$queries[] = Query::equal('resourceInternalId', [$project->getSequence()]);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$keyId = $cursor->getValue();
$cursorDocument = $dbForPlatform->getDocument('keys', $keyId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Key '{$keyId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$keys = $dbForPlatform->find('keys', $queries);
$response->dynamic(new Document([
'keys' => $keys,
'total' => $includeTotal ? count($keys) : 0,
'total' => $includeTotal ? $dbForPlatform->count('keys', $filterQueries, APP_LIMIT_COUNT) : 0,
]), Response::MODEL_KEY_LIST);
});
@ -1580,8 +1235,8 @@ Http::get('/v1/projects/:projectId/keys/:keyId')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('keyId', '', new UID(), 'Key unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('keyId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Key unique ID.', false, ['dbForPlatform'])
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $keyId, Response $response, Database $dbForPlatform) {
@ -1622,8 +1277,8 @@ Http::put('/v1/projects/:projectId/keys/:keyId')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('keyId', '', new UID(), 'Key unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('keyId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Key unique ID.', false, ['dbForPlatform'])
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
@ -1677,8 +1332,8 @@ Http::delete('/v1/projects/:projectId/keys/:keyId')
],
contentType: ContentType::NONE
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('keyId', '', new UID(), 'Key unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('keyId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Key unique ID.', false, ['dbForPlatform'])
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $keyId, Response $response, Database $dbForPlatform) {
@ -1725,7 +1380,7 @@ Http::post('/v1/projects/:projectId/jwts')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->inject('response')
@ -1769,7 +1424,7 @@ Http::post('/v1/projects/:projectId/platforms')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param(
'type',
null,
@ -1847,7 +1502,7 @@ Http::get('/v1/projects/:projectId/platforms')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForPlatform')
@ -1887,8 +1542,8 @@ Http::get('/v1/projects/:projectId/platforms/:platformId')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('platformId', '', new UID(), 'Platform unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform unique ID.', false, ['dbForPlatform'])
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $platformId, Response $response, Database $dbForPlatform) {
@ -1928,8 +1583,8 @@ Http::put('/v1/projects/:projectId/platforms/:platformId')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('platformId', '', new UID(), 'Platform unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform unique ID.', false, ['dbForPlatform'])
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
->param('key', '', new Text(256), 'Package name for android or bundle ID for iOS. Max length: 256 chars.', true)
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
@ -1985,8 +1640,8 @@ Http::delete('/v1/projects/:projectId/platforms/:platformId')
],
contentType: ContentType::NONE
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('platformId', '', new UID(), 'Platform unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform unique ID.', false, ['dbForPlatform'])
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $platformId, Response $response, Database $dbForPlatform) {
@ -2052,7 +1707,7 @@ Http::patch('/v1/projects/:projectId/smtp')
]
)
])
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('enabled', false, new Boolean(), 'Enable custom SMTP service')
->param('senderName', '', new Text(255, 0), 'Name of the email sender', true)
->param('senderEmail', '', new Email(), 'Email of the sender', true)
@ -2170,7 +1825,7 @@ Http::post('/v1/projects/:projectId/smtp/tests')
]
)
])
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('emails', [], new ArrayList(new Email(), 10), 'Array of emails to send test email to. Maximum of 10 emails are allowed.')
->param('senderName', System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'), new Text(255, 0), 'Name of the email sender')
->param('senderEmail', System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), new Email(), 'Email of the sender')
@ -2265,7 +1920,7 @@ Http::get('/v1/projects/:projectId/templates/sms/:type/:locale')
]
)
])
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('type', '', new WhiteList(Config::getParam('locale-templates')['sms'] ?? []), 'Template type')
->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes'])
->inject('response')
@ -2313,7 +1968,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('type', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? []), 'Template type')
->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes'])
->inject('response')
@ -2432,7 +2087,7 @@ Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
]
)
])
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('type', '', new WhiteList(Config::getParam('locale-templates')['sms'] ?? []), 'Template type')
->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes'])
->param('message', '', new Text(0), 'Template message')
@ -2479,7 +2134,7 @@ Http::patch('/v1/projects/:projectId/templates/email/:type/:locale')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('type', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? []), 'Template type')
->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes'])
->param('subject', '', new Text(255), 'Email Subject')
@ -2558,7 +2213,7 @@ Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
contentType: ContentType::JSON
)
])
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('type', '', new WhiteList(Config::getParam('locale-templates')['sms'] ?? []), 'Template type')
->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes'])
->inject('response')
@ -2609,7 +2264,7 @@ Http::delete('/v1/projects/:projectId/templates/email/:type/:locale')
],
contentType: ContentType::JSON
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('type', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? []), 'Template type')
->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes'])
->inject('response')
@ -2661,7 +2316,7 @@ Http::patch('/v1/projects/:projectId/auth/session-invalidation')
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
->param('enabled', false, new Boolean(), 'Update authentication session invalidation status. Use this endpoint to enable or disable session invalidation on password change')
->inject('response')
->inject('dbForPlatform')

View file

@ -51,7 +51,7 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@ -80,9 +80,9 @@ Http::post('/v1/teams')
)
]
))
->param('teamId', '', new CustomId(), 'Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('teamId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', null, new Text(128), 'Team name. Max length: 128 chars.')
->param('roles', ['owner'], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', true)
->param('roles', ['owner'], fn (Database $dbForProject) => new ArrayList(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', true, ['dbForProject'])
->inject('response')
->inject('user')
->inject('dbForProject')
@ -191,16 +191,10 @@ Http::get('/v1/teams')
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@ -247,7 +241,7 @@ Http::get('/v1/teams/:teamId')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $teamId, Response $response, Database $dbForProject) {
@ -278,7 +272,7 @@ Http::get('/v1/teams/:teamId/prefs')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $teamId, Response $response, Database $dbForProject) {
@ -320,7 +314,7 @@ Http::put('/v1/teams/:teamId')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
->inject('requestTimestamp')
->inject('response')
@ -366,7 +360,7 @@ Http::put('/v1/teams/:teamId/prefs')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
->inject('response')
->inject('dbForProject')
@ -414,7 +408,7 @@ Http::delete('/v1/teams/:teamId')
],
contentType: ContentType::NONE
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->inject('response')
->inject('getProjectDB')
->inject('dbForProject')
@ -480,20 +474,20 @@ Http::post('/v1/teams/:teamId/memberships')
]
))
->label('abuse-limit', 10)
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'Email of the new team member.', true)
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'ID of the user to be added to a team.', true, ['dbForProject'])
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('roles', [], function (Document $project) {
->param('roles', [], function (Document $project, Database $dbForProject) {
if ($project->getId() === 'console') {
$roles = array_keys(Config::getParam('roles', []));
$roles = array_filter($roles, function ($role) {
$roles = array_values(array_filter($roles, function ($role) {
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
});
}));
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
}, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
return new ArrayList(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE);
}, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project', 'dbForProject'])
->param('url', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator']) // TODO add our own built-in confirm page
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
->inject('response')
@ -861,7 +855,7 @@ Http::get('/v1/teams/:teamId/memberships')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
@ -889,22 +883,15 @@ Http::get('/v1/teams/:teamId/memberships')
// Set internal queries
$queries[] = Query::equal('teamInternalId', [$team->getSequence()]);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$membershipId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('memberships', $membershipId);
@ -1005,8 +992,8 @@ Http::get('/v1/teams/:teamId/memberships/:membershipId')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('membershipId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Membership ID.', false, ['dbForProject'])
->inject('response')
->inject('project')
->inject('dbForProject')
@ -1093,18 +1080,18 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('roles', [], function (Document $project) {
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('membershipId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Membership ID.', false, ['dbForProject'])
->param('roles', [], function (Document $project, Database $dbForProject) {
if ($project->getId() === 'console') {
$roles = array_keys(Config::getParam('roles', []));
$roles = array_filter($roles, function ($role) {
$roles = array_values(array_filter($roles, function ($role) {
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
});
}));
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
}, 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
return new ArrayList(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE);
}, 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project', 'dbForProject'])
->inject('request')
->inject('response')
->inject('user')
@ -1204,9 +1191,9 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('userId', '', new UID(), 'User ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('membershipId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Membership ID.', false, ['dbForProject'])
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('secret', '', new Text(256), 'Secret key.')
->inject('request')
->inject('response')
@ -1371,8 +1358,8 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId')
],
contentType: ContentType::NONE
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('membershipId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Membership ID.', false, ['dbForProject'])
->inject('user')
->inject('project')
->inject('response')
@ -1467,7 +1454,7 @@ Http::get('/v1/teams/:teamId/logs')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('queries', [], new Queries([new Limit(), new Offset()]), '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 and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')

View file

@ -60,7 +60,7 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@ -246,7 +246,7 @@ Http::post('/v1/users')
)
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('email', null, new Nullable(new EmailValidator()), 'User email.', true)
->param('phone', null, new Nullable(new Phone()), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary'])
@ -283,7 +283,7 @@ Http::post('/v1/users/bcrypt')
)
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Bcrypt.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@ -321,7 +321,7 @@ Http::post('/v1/users/md5')
)
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using MD5.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@ -358,7 +358,7 @@ Http::post('/v1/users/argon2')
)
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Argon2.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@ -395,7 +395,7 @@ Http::post('/v1/users/sha')
)
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using SHA.')
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true)
@ -436,7 +436,7 @@ Http::post('/v1/users/phpass')
)
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or pass the string `ID.unique()`to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or pass the string `ID.unique()`to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using PHPass.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@ -473,7 +473,7 @@ Http::post('/v1/users/scrypt')
)
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Scrypt.')
->param('passwordSalt', '', new Text(128), 'Optional salt used to hash password.')
@ -521,7 +521,7 @@ Http::post('/v1/users/scrypt-modified')
)
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Scrypt Modified.')
->param('passwordSalt', '', new Text(128), 'Salt used to hash password.')
@ -566,11 +566,11 @@ Http::post('/v1/users/:userId/targets')
)
]
))
->param('targetId', '', new CustomId(), 'Target ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', new UID(), 'User ID.')
->param('targetId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('providerType', '', new WhiteList([MESSAGE_TYPE_EMAIL, MESSAGE_TYPE_SMS, MESSAGE_TYPE_PUSH]), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.')
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true, ['dbForProject'])
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
->inject('queueForEvents')
->inject('response')
@ -675,16 +675,10 @@ Http::get('/v1/users')
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@ -737,7 +731,7 @@ Http::get('/v1/users/:userId')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $userId, Response $response, Database $dbForProject) {
@ -768,7 +762,7 @@ Http::get('/v1/users/:userId/prefs')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $userId, Response $response, Database $dbForProject) {
@ -801,8 +795,8 @@ Http::get('/v1/users/:userId/targets/:targetId')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('targetId', '', new UID(), 'Target ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $userId, string $targetId, Response $response, Database $dbForProject) {
@ -839,7 +833,7 @@ Http::get('/v1/users/:userId/sessions')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
@ -883,7 +877,7 @@ Http::get('/v1/users/:userId/memberships')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
@ -938,7 +932,7 @@ Http::get('/v1/users/:userId/logs')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('queries', [], new Queries([new Limit(), new Offset()]), '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 and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
@ -1025,7 +1019,7 @@ Http::get('/v1/users/:userId/targets')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('queries', [], new Targets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Targets::ALLOWED_ATTRIBUTES), true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
@ -1042,14 +1036,11 @@ Http::get('/v1/users/:userId/targets')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries[] = Query::equal('userId', [$userId]);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@ -1106,15 +1097,11 @@ Http::get('/v1/users/identities')
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@ -1160,7 +1147,7 @@ Http::patch('/v1/users/:userId/status')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('status', null, new Boolean(true), 'User Status. To activate the user pass `true` and to block the user pass `false`.')
->inject('response')
->inject('dbForProject')
@ -1201,7 +1188,7 @@ Http::put('/v1/users/:userId/labels')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('labels', [], new ArrayList(new Text(36, allowList: [...Text::NUMBERS, ...Text::ALPHABET_UPPER, ...Text::ALPHABET_LOWER]), APP_LIMIT_ARRAY_LABELS_SIZE), 'Array of user labels. Replaces the previous labels. Maximum of ' . APP_LIMIT_ARRAY_LABELS_SIZE . ' labels are allowed, each up to 36 alphanumeric characters long.')
->inject('response')
->inject('dbForProject')
@ -1244,7 +1231,7 @@ Http::patch('/v1/users/:userId/verification/phone')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('phoneVerification', false, new Boolean(), 'User phone verification status.')
->inject('response')
->inject('dbForProject')
@ -1286,7 +1273,7 @@ Http::patch('/v1/users/:userId/name')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('name', '', new Text(128, 0), 'User name. Max length: 128 chars.')
->inject('response')
->inject('dbForProject')
@ -1329,7 +1316,7 @@ Http::patch('/v1/users/:userId/password')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, enabled: $project->getAttribute('auths', [])['passwordDictionary'] ?? false, allowEmpty: true), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
->inject('response')
->inject('project')
@ -1428,7 +1415,7 @@ Http::patch('/v1/users/:userId/email')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('email', '', new EmailValidator(allowEmpty: true), 'User email.')
->inject('response')
->inject('dbForProject')
@ -1539,7 +1526,7 @@ Http::patch('/v1/users/:userId/phone')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('number', '', new Phone(allowEmpty: true), 'User phone number.')
->inject('response')
->inject('dbForProject')
@ -1629,7 +1616,7 @@ Http::patch('/v1/users/:userId/verification')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('emailVerification', false, new Boolean(), 'User email verification status.')
->inject('response')
->inject('dbForProject')
@ -1667,7 +1654,7 @@ Http::patch('/v1/users/:userId/prefs')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
->inject('response')
->inject('dbForProject')
@ -1708,10 +1695,10 @@ Http::patch('/v1/users/:userId/targets/:targetId')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('targetId', '', new UID(), 'Target ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID.', false, ['dbForProject'])
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)', true)
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true, ['dbForProject'])
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
->inject('queueForEvents')
->inject('response')
@ -1833,7 +1820,7 @@ Http::patch('/v1/users/:userId/mfa')
]
)
])
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('mfa', null, new Boolean(), 'Enable or disable MFA.')
->inject('response')
->inject('dbForProject')
@ -1893,7 +1880,7 @@ Http::get('/v1/users/:userId/mfa/factors')
]
)
])
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $userId, Response $response, Database $dbForProject) {
@ -1952,7 +1939,7 @@ Http::get('/v1/users/:userId/mfa/recovery-codes')
]
)
])
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $userId, Response $response, Database $dbForProject) {
@ -2017,7 +2004,7 @@ Http::patch('/v1/users/:userId/mfa/recovery-codes')
]
)
])
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@ -2090,7 +2077,7 @@ Http::put('/v1/users/:userId/mfa/recovery-codes')
public: false,
)
])
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@ -2163,7 +2150,7 @@ Http::delete('/v1/users/:userId/mfa/authenticators/:type')
contentType: ContentType::NONE
)
])
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.')
->inject('response')
->inject('dbForProject')
@ -2210,7 +2197,7 @@ Http::post('/v1/users/:userId/sessions')
)
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->inject('request')
->inject('response')
->inject('dbForProject')
@ -2302,7 +2289,7 @@ Http::post('/v1/users/:userId/tokens')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('length', 6, new Range(4, 128), 'Token length in characters. The default length is 6 characters', true)
->param('expire', TOKEN_EXPIRATION_GENERIC, new Range(60, TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true)
->inject('request')
@ -2368,8 +2355,8 @@ Http::delete('/v1/users/:userId/sessions/:sessionId')
],
contentType: ContentType::NONE
))
->param('userId', '', new UID(), 'User ID.')
->param('sessionId', '', new UID(), 'Session ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('sessionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Session ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@ -2419,7 +2406,7 @@ Http::delete('/v1/users/:userId/sessions')
],
contentType: ContentType::NONE
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@ -2469,7 +2456,7 @@ Http::delete('/v1/users/:userId')
],
contentType: ContentType::NONE
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@ -2521,8 +2508,8 @@ Http::delete('/v1/users/:userId/targets/:targetId')
],
contentType: ContentType::NONE
))
->param('userId', '', new UID(), 'User ID.')
->param('targetId', '', new UID(), 'Target ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID.', false, ['dbForProject'])
->inject('queueForEvents')
->inject('queueForDeletes')
->inject('response')
@ -2579,7 +2566,7 @@ Http::delete('/v1/users/identities/:identityId')
],
contentType: ContentType::NONE,
))
->param('identityId', '', new UID(), 'Identity ID.')
->param('identityId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Identity ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@ -2618,8 +2605,8 @@ Http::post('/v1/users/:userId/jwts')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('sessionId', '', new UID(), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.', true)
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('sessionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.', true, ['dbForProject'])
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->inject('response')
->inject('dbForProject')

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ use Appwrite\Auth\Key;
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete as DeleteEvent;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Execution;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Network\Cors;
@ -34,8 +34,8 @@ use Appwrite\Utopia\View;
use Executor\Executor;
use MaxMind\Db\Reader;
use Swoole\Http\Request as SwooleRequest;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
@ -45,7 +45,7 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Domains\Domain;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Log;
@ -60,7 +60,7 @@ Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
{
$host = $request->getHostname() ?? '';
if (!empty($previewHostname)) {
@ -630,8 +630,15 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
$headerOverrides['x-appwrite-log-id'] = $execution->getId();
}
// Headers that must have single values (RFC 7230)
$singleValueHeaders = ['content-length', 'content-type'];
foreach ($headerOverrides as $key => $value) {
if (\array_key_exists($key, $executionResponse['headers'])) {
$keyLower = \strtolower($key);
if (\in_array($keyLower, $singleValueHeaders)) {
// Single-value headers must replace, not append
$executionResponse['headers'][$key] = $value;
} elseif (\array_key_exists($key, $executionResponse['headers'])) {
if (\is_array($executionResponse['headers'][$key])) {
$executionResponse['headers'][$key][] = $value;
} else {
@ -696,14 +703,11 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
throw $th;
}
} finally {
if ($type === 'function') {
$queueForFunctions
->setType(Func::TYPE_ASYNC_WRITE)
if ($type === 'function' || $type === 'site') {
$queueForExecutions
->setExecution($execution)
->setProject($project)
->trigger();
} elseif ($type === 'site') { // TODO: Move it to logs worker later
$dbForProject->createDocument('executions', $execution);
}
}
@ -877,7 +881,7 @@ Http::init()
->inject('geodb')
->inject('queueForStatsUsage')
->inject('queueForEvents')
->inject('queueForFunctions')
->inject('queueForExecutions')
->inject('executor')
->inject('platform')
->inject('isResourceBlocked')
@ -888,7 +892,7 @@ Http::init()
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Func $queueForFunctions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Execution $queueForExecutions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
/*
* Appwrite Router
*/
@ -896,7 +900,7 @@ Http::init()
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!\in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
}
}
@ -950,7 +954,15 @@ Http::init()
$endDomain->getRegisterable() !== ''
);
$isLocalHost = $request->getHostname() === 'localhost' || $request->getHostname() === 'localhost:' . $request->getPort();
$localHosts = ['localhost','localhost:'.$request->getPort()];
$migrationHost = System::getEnv('_APP_MIGRATION_HOST');
if (!empty($migrationHost)) {
$localHosts[] = $migrationHost;
$localHosts[] = $migrationHost.':'.$request->getPort();
}
$isLocalHost = in_array($request->getHostname(), $localHosts);
$isIpAddress = filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false;
$isConsoleProject = $project->getAttribute('$id', '') === 'console';
@ -997,7 +1009,7 @@ Http::init()
}
if (System::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost') { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
if ($request->getProtocol() !== 'https' && !in_array(($swooleRequest->header['host'] ?? ''), $localHosts)) { // localhost allowed for proxy
if ($request->getMethod() !== Request::METHOD_GET) {
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.');
}
@ -1167,7 +1179,7 @@ Http::options()
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
->inject('queueForExecutions')
->inject('executor')
->inject('geodb')
->inject('isResourceBlocked')
@ -1180,14 +1192,14 @@ Http::options()
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
/*
* Appwrite Router
*/
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
}
}
@ -1247,7 +1259,7 @@ Http::error()
}
switch ($class) {
case Utopia\Exception::class:
case Utopia\Http\Exception::class:
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
switch ($code) {
case 400:
@ -1563,7 +1575,7 @@ Http::get('/robots.txt')
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
->inject('queueForExecutions')
->inject('executor')
->inject('geodb')
->inject('isResourceBlocked')
@ -1573,13 +1585,13 @@ Http::get('/robots.txt')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
$platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
}
}
@ -1598,7 +1610,7 @@ Http::get('/humans.txt')
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
->inject('queueForExecutions')
->inject('executor')
->inject('geodb')
->inject('isResourceBlocked')
@ -1608,13 +1620,13 @@ Http::get('/humans.txt')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
$platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
}
}

View file

@ -12,7 +12,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\UID;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\Text;
@ -31,7 +31,6 @@ Http::get('/v1/mock/tests/general/oauth2')
->param('state', '', new Text(1024), 'OAuth2 state.')
->inject('response')
->action(function (string $client_id, string $redirectURI, string $scope, string $state, Response $response) {
$response->redirect($redirectURI . '?' . \http_build_query(['code' => 'abcdef', 'state' => $state]));
});

View file

@ -30,7 +30,7 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Validator\WhiteList;
@ -397,6 +397,7 @@ Http::init()
/*
* Abuse Check
*/
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$timeLimitArray = [];
@ -432,6 +433,7 @@ Http::init()
$abuse = new Abuse($timeLimit);
$remaining = $timeLimit->remaining();
$limit = $timeLimit->limit();
$time = $timeLimit->time() + $route->getLabel('abuse-time', 3600);

View file

@ -8,7 +8,7 @@ use Utopia\Config\Config;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\System\System;
Http::init()

View file

@ -2,7 +2,7 @@
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Http;
use Utopia\Http\Http;
Http::get('/versions')
->desc('Get Version')

View file

@ -14,9 +14,9 @@ use Swoole\Timer;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Adapter\SQL as AuditAdapterSQL;
use Utopia\Audit\Audit;
use Utopia\CLI\Console;
use Utopia\Compression\Compression;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@ -26,17 +26,17 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Http;
use Utopia\Http\Files;
use Utopia\Http\Http;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Utopia\Pools\Group;
use Utopia\Swoole\Files;
use Utopia\System\System;
Files::load(__DIR__.'/../public');
const DOMAIN_SYNC_TIMER = 30; // 30 seconds
$files = null;
$domains = new Table(1_000_000); // 1 million rows
$domains->column('value', Table::TYPE_INT, 1);
$domains->create();
@ -162,7 +162,11 @@ $http
Constant::OPTION_TASK_WORKER_NUM => 1, // required for the task to fetch domains background
]);
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) use (&$files) {
if (!$server->taskworker) {
$files = new Files();
$files->load(__DIR__ . '/../public');
}
Console::success('Worker ' . ++$workerId . ' started successfully');
});
@ -181,10 +185,10 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server) {
include __DIR__ . '/controllers/general.php';
function createDatabase(Http $app, string $resourceKey, string $dbName, array $collections, mixed $pools, callable $extraSetup = null): void
function createDatabase(Http $app, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void
{
$max = 10;
$sleep = 1;
$max = 15;
$sleep = 2;
$attempts = 0;
while (true) {
@ -194,8 +198,8 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
/* @var $database Database */
$database = is_callable($resource) ? $resource() : $resource;
break; // exit loop on success
} catch (\Exception $e) {
Console::warning(" └── Database not ready. Retrying connection ({$attempts})...");
} catch (\Throwable $e) {
Console::warning(" └── Database not ready ({$dbName}). Retrying connection ({$attempts}): " . $e->getMessage());
if ($attempts >= $max) {
throw new \Exception(' └── Failed to connect to database: ' . $e->getMessage());
}
@ -205,12 +209,26 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
Console::success("[Setup] - $dbName database init started...");
// Attempt to create the database
try {
Console::info(" └── Creating database: $dbName...");
$database->create();
} catch (\Exception $e) {
Console::info(" └── Skip: metadata table already exists");
$attempts = 0;
while (true) {
try {
$attempts++;
Console::info(" └── Creating database: $dbName...");
$database->create();
break; // exit loop on success
} catch (\Exception $e) {
if ($e instanceof DuplicateException) {
Console::info(" └── Skip: metadata table already exists");
break;
}
Console::warning(" └── Database create failed. Retrying ({$attempts})...");
if ($attempts >= $max) {
throw new \Exception(' └── Failed to create database: ' . $e->getMessage());
}
\sleep($sleep);
}
}
// Process collections
@ -394,11 +412,25 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
->setTenant(null)
->setNamespace(System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''));
try {
Console::success('[Setup] - Creating project database: ' . $hostname . '...');
$dbForProject->create();
} catch (DuplicateException) {
Console::success('[Setup] - Skip: metadata table already exists');
$max = 15;
$sleep = 2;
$attempts = 0;
while (true) {
try {
$attempts++;
Console::success('[Setup] - Creating project database: ' . $hostname . '...');
$dbForProject->create();
break; // exit loop on success
} catch (DuplicateException) {
Console::success('[Setup] - Skip: metadata table already exists');
break;
} catch (\Throwable $e) {
Console::warning(" └── Project database create failed. Retrying ({$attempts})...");
if ($attempts >= $max) {
throw new \Exception(' └── Failed to create project database: ' . $e->getMessage());
}
sleep($sleep);
}
}
if ($dbForProject->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
@ -440,21 +472,21 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
});
});
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register, &$files) {
Http::setResource('swooleRequest', fn () => $swooleRequest);
Http::setResource('swooleResponse', fn () => $swooleResponse);
$request = new Request($swooleRequest);
$response = new Response($swooleResponse);
if (Files::isFileLoaded($request->getURI())) {
$time = (60 * 60 * 24 * 365 * 2); // 45 days cache
if ($files instanceof Files && $files->isFileLoaded($request->getURI())) {
$time = (60 * 60 * 24 * 45); // 45 days cache
$response
->setContentType(Files::getFileMimeType($request->getURI()))
->setContentType($files->getFileMimeType($request->getURI()))
->addHeader('Cache-Control', 'public, max-age=' . $time)
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
->send(Files::getFileContents($request->getURI()));
->send($files->getFileContents($request->getURI()));
return;
}
@ -585,7 +617,7 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
if ($latestDocument !== null) {
$queries[] = Query::cursorAfter($latestDocument);
}
if ($lastSyncUpdate != null) {
if ($lastSyncUpdate !== null) {
$queries[] = Query::greaterThanEqual('$updatedAt', $lastSyncUpdate);
}
$results = [];
@ -593,7 +625,7 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
$authorization = $app->getResource('authorization');
$results = $authorization->skip(fn () => $dbForPlatform->find('rules', $queries));
} catch (Throwable $th) {
Console::error($th->getMessage());
Console::error('rules ' . $th->getMessage());
}
$sum = count($results);

View file

@ -5,6 +5,8 @@ use Appwrite\Platform\Modules\Compute\Specification;
const APP_NAME = 'Appwrite';
const APP_DOMAIN = 'appwrite.io';
const APP_VIEWS_DIR = __DIR__ . '/../views';
// Email
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
const APP_EMAIL_SECURITY = ''; // Default security email address
@ -369,6 +371,7 @@ const RESOURCE_TYPE_TOPICS = 'topics';
const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers';
const RESOURCE_TYPE_MESSAGES = 'messages';
const RESOURCE_TYPE_EXECUTIONS = 'executions';
const RESOURCE_TYPE_VCS = 'vcs';
// Resource types for Tokens
const TOKENS_RESOURCE_TYPE_FILES = 'files';
@ -387,3 +390,6 @@ const COOKIE_NAME_PREVIEW = 'a_jwt_console';
// Cache Reconnect
const CACHE_RECONNECT_MAX_RETRIES = 2;
const CACHE_RECONNECT_RETRY_DELAY = 1000;
// Project status
const PROJECT_STATUS_ACTIVE = 'active';

View file

@ -72,7 +72,7 @@ Database::addFilter(
$attributes = $database->getAuthorization()->skip(fn () => $database->find('attributes', [
Query::equal('collectionInternalId', [$document->getSequence()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForAttributes()),
Query::limit($database->getLimitForAttributes() ?: APP_LIMIT_SUBQUERY),
]));
foreach ($attributes as $attribute) {

View file

@ -9,20 +9,23 @@ use MaxMind\Db\Reader;
use PHPMailer\PHPMailer\PHPMailer;
use Swoole\Database\PDOProxy;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Adapter\Mongo;
use Utopia\Database\Adapter\MySQL;
use Utopia\Database\Adapter\Postgres;
use Utopia\Database\Adapter\SQL;
use Utopia\Database\PDO;
use Utopia\Domains\Validator\PublicDomain;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Logger\Adapter\AppSignal;
use Utopia\Logger\Adapter\LogOwl;
use Utopia\Logger\Adapter\Raygun;
use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Logger;
use Utopia\Mongo\Client as MongoClient;
use Utopia\Pools\Adapter\Stack as StackPool;
use Utopia\Pools\Adapter\Swoole as SwoolePool;
use Utopia\Pools\Group;
@ -150,13 +153,14 @@ $register->set('pools', function () {
$group = new Group();
$fallbackForDB = 'db_main=' . AppwriteURL::unparse([
'scheme' => 'mariadb',
'host' => System::getEnv('_APP_DB_HOST', 'mariadb'),
'port' => System::getEnv('_APP_DB_PORT', '3306'),
'scheme' => System::getEnv('_APP_DB_ADAPTER', 'mongodb'),
'host' => System::getEnv('_APP_DB_HOST', 'mongodb'),
'port' => System::getEnv('_APP_DB_PORT', '27017'),
'user' => System::getEnv('_APP_DB_USER', ''),
'pass' => System::getEnv('_APP_DB_PASS', ''),
'path' => System::getEnv('_APP_DB_SCHEMA', ''),
]);
$fallbackForRedis = 'redis_main=' . AppwriteURL::unparse([
'scheme' => 'redis',
'host' => System::getEnv('_APP_REDIS_HOST', 'redis'),
@ -170,19 +174,19 @@ $register->set('pools', function () {
'type' => 'database',
'dsns' => $fallbackForDB,
'multiple' => false,
'schemes' => ['mariadb', 'mysql'],
'schemes' => ['mariadb', 'mongodb', 'mysql', 'postgresql'],
],
'database' => [
'type' => 'database',
'dsns' => $fallbackForDB,
'multiple' => true,
'schemes' => ['mariadb', 'mysql'],
'schemes' => ['mariadb', 'mongodb', 'mysql', 'postgresql'],
],
'logs' => [
'type' => 'database',
'dsns' => System::getEnv('_APP_CONNECTIONS_DB_LOGS', $fallbackForDB),
'multiple' => false,
'schemes' => ['mariadb', 'mysql'],
'schemes' => ['mariadb', 'mongodb', 'mysql', 'postgresql'],
],
'publisher' => [
'type' => 'publisher',
@ -262,6 +266,7 @@ $register->set('pools', function () {
*
* Resource assignment to an adapter will happen below.
*/
$resource = match ($dsnScheme) {
'mysql',
'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
@ -275,6 +280,27 @@ $register->set('pools', function () {
]);
});
},
'mongodb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase, $dsn) {
try {
$mongo = new MongoClient($dsnDatabase, $dsnHost, (int)$dsnPort, $dsnUser, $dsnPass, false);
@$mongo->connect();
return $mongo;
} catch (\Throwable $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "MongoDB connection failed: " . $e->getMessage());
}
},
'postgresql' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDO("pgsql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};connect_timeout=3", $dsnUser, $dsnPass, array(
\PDO::ATTR_TIMEOUT => 3, // Seconds
\PDO::ATTR_PERSISTENT => false,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
\PDO::ATTR_EMULATE_PREPARES => true,
\PDO::ATTR_STRINGIFY_FETCHES => true
));
});
},
'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
$redis = new \Redis();
@$redis->pconnect($dsnHost, (int)$dsnPort);
@ -297,6 +323,8 @@ $register->set('pools', function () {
$adapter = match ($dsn->getScheme()) {
'mariadb' => new MariaDB($resource()),
'mysql' => new MySQL($resource()),
'mongodb' => new Mongo($resource()),
'postgresql' => new Postgres($resource()),
default => null
};
@ -351,14 +379,29 @@ $register->set('db', function () {
$dbPort = System::getEnv('_APP_DB_PORT', '');
$dbUser = System::getEnv('_APP_DB_USER', '');
$dbPass = System::getEnv('_APP_DB_PASS', '');
$dbScheme = System::getEnv('_APP_DB_SCHEMA', '');
$dbSchema = System::getEnv('_APP_DB_SCHEMA', '');
$dbAdapter = System::getEnv('_APP_DB_ADAPTER', 'mongodb');
$dsn = '';
return new PDO(
"mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4",
$dbUser,
$dbPass,
SQL::getPDOAttributes()
);
switch ($dbAdapter) {
case 'mongodb':
try {
$mongo = new MongoClient($dbSchema, $dbHost, (int)$dbPort, $dbUser, $dbPass, false);
@$mongo->connect();
return $mongo;
} catch (\Throwable $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'MongoDB connection failed: ' . $e->getMessage());
}
case 'mysql':
case 'mariadb':
$dsn = "mysql:host={$dbHost};port={$dbPort};dbname={$dbSchema};charset=utf8mb4";
return new PDO($dsn, $dbUser, $dbPass, SQL::getPDOAttributes());
case 'postgresql':
$dsn = "pgsql:host={$dbHost};port={$dbPort};dbname={$dbSchema};connect_timeout=3";
return new PDO($dsn, $dbUser, $dbPass, SQL::getPDOAttributes());
default:
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid database adapter');
}
});
$register->set('smtp', function () {

View file

@ -10,6 +10,7 @@ use Appwrite\Event\Certificate;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Execution;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
@ -42,8 +43,8 @@ use Utopia\Auth\Store;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime as DatabaseDateTime;
@ -51,7 +52,7 @@ use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
@ -159,6 +160,9 @@ Http::setResource('queueForAudits', function (Publisher $publisher) {
Http::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
Http::setResource('queueForExecutions', function (Publisher $publisher) {
return new Execution($publisher);
}, ['publisher']);
Http::setResource('eventProcessor', function () {
return new EventProcessor();
}, []);
@ -198,15 +202,26 @@ Http::setResource('allowedHostnames', function (array $platform, Document $proje
}
$originHostname = parse_url($request->getOrigin(), PHP_URL_HOST);
$refererHostname = parse_url($request->getReferer(), PHP_URL_HOST);
$hostname = $originHostname;
if (empty($hostname)) {
$hostname = $refererHostname;
}
/* Add request hostname for preflight requests */
if ($request->getMethod() === 'OPTIONS') {
$allowed[] = $originHostname;
$allowed[] = $hostname;
}
/* Allow the request origin if a dev key or rule is found */
if ((!$rule->isEmpty() || !$devKey->isEmpty()) && !empty($originHostname)) {
$allowed[] = $originHostname;
/* Allow the request origin of rule */
if (!$rule->isEmpty() && !empty($rule->getAttribute('domain', ''))) {
$allowed[] = $rule->getAttribute('domain', '');
}
/* Allow the request origin if a dev key is found */
if (!$devKey->isEmpty() && !empty($hostname)) {
$allowed[] = $hostname;
}
return array_unique($allowed);
@ -237,6 +252,11 @@ Http::setResource('allowedSchemes', function (Document $project) {
*/
Http::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) {
$domain = \parse_url($request->getOrigin(), PHP_URL_HOST);
if (empty($domain)) {
$domain = \parse_url($request->getReferer(), PHP_URL_HOST);
}
if (empty($domain)) {
return new Document();
}
@ -253,7 +273,24 @@ Http::setResource('rule', function (Request $request, Database $dbForPlatform, D
]) ?? new Document();
});
if ($rule->getAttribute('projectInternalId') !== $project->getSequence()) {
$permitsCurrentProject = $rule->getAttribute('projectInternalId', '') === $project->getSequence();
// Temporary implementation until custom wildcard domains are an official feature
// Allow trusted projects; Used for Console (website) previews
if (!$permitsCurrentProject && !$rule->isEmpty() && !empty($rule->getAttribute('projectId', ''))) {
$trustedProjects = [];
foreach (\explode(',', System::getEnv('_APP_CONSOLE_TRUSTED_PROJECTS', '')) as $trustedProject) {
if (empty($trustedProject)) {
continue;
}
$trustedProjects[] = $trustedProject;
}
if (\in_array($rule->getAttribute('projectId', ''), $trustedProjects)) {
$permitsCurrentProject = true;
}
}
if (!$permitsCurrentProject) {
return new Document();
}
@ -412,13 +449,7 @@ Http::setResource('user', function (string $mode, Document $project, Document $c
) { // Validate user has valid login token
$user = new User([]);
}
// if (APP_MODE_ADMIN === $mode) {
// if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
// $authorization->setDefaultStatus(false); // Cancel security segmentation for admin users.
// } else {
// $user = new Document([]);
// }
// }
$authJWT = $request->getHeader('x-appwrite-jwt', '');
if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
if (!$user->isEmpty()) {
@ -487,6 +518,10 @@ Http::setResource('project', function ($dbForPlatform, $request, $console, $auth
/** @var Utopia\Database\Document $console */
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', ''));
// Realtime channel "project" can send project=Query array
if (!\is_string($projectId)) {
$projectId = $request->getHeader('x-appwrite-project', '');
}
if (empty($projectId) || $projectId === 'console') {
return $console;
@ -1280,6 +1315,8 @@ Http::setResource('team', function (Document $project, Database $dbForPlatform,
}
}
// if teamInternalId is empty, return an empty document
if (empty($teamInternalId)) {
return new Document([]);
}

View file

@ -22,8 +22,8 @@ use Utopia\Auth\Store;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@ -33,7 +33,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Http\Http;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
use Utopia\Registry\Registry;
@ -431,15 +431,20 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
]
];
$subscribers = $realtime->getSubscribers($event); // [connectionId => [subId => queries]]
$subscribers = $realtime->getSubscribers($event);
// For test events, send to all connections with their matched subscription queries
foreach ($subscribers as $connectionId => $matchedSubscriptions) {
$groups = [];
foreach ($subscribers as $id => $matched) {
$key = implode(',', array_keys($matched));
$groups[$key]['ids'][] = $id;
$groups[$key]['subscriptions'] = array_keys($matched);
}
foreach ($groups as $group) {
$data = $event['data'];
// Send matched subscription IDs
$data['subscriptions'] = array_keys($matchedSubscriptions);
$data['subscriptions'] = $group['subscriptions'];
$server->send([$connectionId], json_encode([
$server->send($group['ids'], json_encode([
'type' => 'event',
'data' => $data
]));
@ -484,18 +489,18 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$roles = $user->getRoles($database->getAuthorization());
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$subscriptionMetadata = $realtime->getSubscriptionMetadata($connection);
$meta = $realtime->getSubscriptionMetadata($connection);
$realtime->unsubscribe($connection);
foreach ($subscriptionMetadata as $subscriptionId => $metadata) {
$queries = Query::parseQueries($metadata['queries'] ?? []);
foreach ($meta as $subscriptionId => $subscription) {
$queries = Query::parseQueries($subscription['queries'] ?? []);
$realtime->subscribe(
$projectId,
$connection,
$subscriptionId,
$roles,
$metadata['channels'] ?? [],
$subscription['channels'] ?? [],
$queries
);
}
@ -507,35 +512,38 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
}
}
$receivers = $realtime->getSubscribers($event); // [connectionId => [subId => queries]]
$receivers = $realtime->getSubscribers($event);
if (Http::isDevelopment() && !empty($receivers)) {
Console::log("[Debug][Worker {$workerId}] Receivers: " . count($receivers));
Console::log("[Debug][Worker {$workerId}] Receivers Connection IDs: " . json_encode(array_keys($receivers)));
Console::log("[Debug][Worker {$workerId}] Event Query: " . json_encode(array_values($receivers)));
Console::log("[Debug][Worker {$workerId}] Connection IDs: " . json_encode(array_keys($receivers)));
Console::log("[Debug][Worker {$workerId}] Matched: " . json_encode(array_values($receivers)));
Console::log("[Debug][Worker {$workerId}] Event: " . $payload);
}
$totalMessages = 0;
foreach ($receivers as $connectionId => $matchedSubscriptions) {
$data = $event['data'];
// Send matched subscription IDs
$data['subscriptions'] = array_keys($matchedSubscriptions);
$server->send(
[$connectionId],
json_encode([
'type' => 'event',
'data' => $data
])
);
$totalMessages++;
// Group connections by matched subscription IDs for batch sending
$groups = [];
foreach ($receivers as $id => $matched) {
$key = implode(',', array_keys($matched));
$groups[$key]['ids'][] = $id;
$groups[$key]['subscriptions'] = array_keys($matched);
}
if ($totalMessages > 0) {
$register->get('telemetry.messageSentCounter')->add($totalMessages);
$stats->incr($event['project'], 'messages', $totalMessages);
$total = 0;
foreach ($groups as $group) {
$data = $event['data'];
$data['subscriptions'] = $group['subscriptions'];
$server->send($group['ids'], json_encode([
'type' => 'event',
'data' => $data
]));
$total += count($group['ids']);
}
if ($total > 0) {
$register->get('telemetry.messageSentCounter')->add($total);
$stats->incr($event['project'], 'messages', $total);
}
});
} catch (Throwable $th) {
@ -624,21 +632,19 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing channels');
}
// Reconstruct subscriptions from query params using helper method
$channelNames = array_keys($channels);
$names = array_keys($channels);
try {
$subscriptionsByIndex = Realtime::constructSubscriptions(
$channelNames,
$subscriptions = Realtime::constructSubscriptions(
$names,
fn ($channel) => $request->getQuery($channel, null)
);
} catch (QueryException $e) {
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $e->getMessage());
}
// Generate subscription IDs and subscribe
$subscriptionMapping = [];
foreach ($subscriptionsByIndex as $index => $subscription) {
$mapping = [];
foreach ($subscriptions as $index => $subscription) {
$subscriptionId = ID::unique();
$realtime->subscribe(
@ -647,10 +653,10 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$subscriptionId,
$roles,
$subscription['channels'],
$subscription['queries'] // Query objects
$subscription['queries']
);
$subscriptionMapping[$index] = $subscriptionId;
$mapping[$index] = $subscriptionId;
}
$realtime->connections[$connection]['authorization'] = $authorization;
@ -660,8 +666,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$server->send([$connection], json_encode([
'type' => 'connected',
'data' => [
'channels' => $channelNames,
'subscriptions' => $subscriptionMapping,
'channels' => $names,
'subscriptions' => $mapping,
'user' => $user
]
]));
@ -684,11 +690,11 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$code = 500;
}
$message = $th->getMessage();
// sanitize 0 && 5xx errors
if (($code === 0 || $code >= 500) && !Http::isDevelopment()) {
$realtimeViolation = $th instanceof AppwriteException && $th->getType() === AppwriteException::REALTIME_POLICY_VIOLATION;
if (($code === 0 || $code >= 500) && !$realtimeViolation && !Http::isDevelopment()) {
$message = 'Error: Server Error';
}
@ -711,7 +717,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
}
});
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId, $logError) {
try {
$response = new Response(new SwooleResponse());
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
@ -791,32 +797,29 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
}
$roles = $user->getRoles($database->getAuthorization());
$channelNames = $realtime->connections[$connection]['channels'] ?? [];
$channels = Realtime::convertChannels(array_flip($channelNames), $user->getId());
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
$subscriptionMetadata = $realtime->getSubscriptionMetadata($connection);
$meta = $realtime->getSubscriptionMetadata($connection);
$realtime->unsubscribe($connection);
if (!empty($projectId)) {
foreach ($subscriptionMetadata as $subscriptionId => $metadata) {
$queries = Query::parseQueries($metadata['queries'] ?? []);
foreach ($meta as $subscriptionId => $subscription) {
$queries = Query::parseQueries($subscription['queries'] ?? []);
$realtime->subscribe(
$projectId,
$connection,
$subscriptionId,
$roles,
$metadata['channels'] ?? [],
$subscription['channels'] ?? [],
$queries
);
}
}
// Restore authorization after subscribe
if ($authorization !== null) {
$realtime->connections[$connection]['authorization'] = $authorization;
}
@ -837,6 +840,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message type is not valid.');
}
} catch (Throwable $th) {
$logError($th, "realtimeMessage");
$code = $th->getCode();
if (!is_int($code)) {
$code = 500;

View file

@ -16,7 +16,7 @@ $dbService = $this->getParam('database');
$hostPath = rtrim($this->getParam('hostPath', ''), '/');
?>services:
traefik:
image: traefik:2.11
image: traefik:3.6
container_name: appwrite-traefik
<<: *x-logging
command:
@ -75,8 +75,8 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- appwrite-sites:/storage/sites:rw
- appwrite-builds:/storage/builds:rw
depends_on:
- <?= $dbService . "\n" ?>
- redis
- <?= $dbService . "\n" ?>
# - clamav
environment:
- _APP_ENV
@ -108,11 +108,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
@ -186,7 +188,7 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: <?php echo $organization; ?>/console:7.5.7
image: <?php echo $organization; ?>/console:7.6.4
restart: unless-stopped
networks:
- appwrite
@ -228,8 +230,8 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
networks:
- appwrite
depends_on:
- <?= $dbService . "\n" ?>
- redis
- <?= $dbService . "\n" ?>
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@ -240,11 +242,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_USAGE_STATS
- _APP_LOGGING_CONFIG
@ -267,11 +271,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
appwrite-worker-webhooks:
@ -291,11 +297,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_OPENSSL_KEY_V1
- _APP_EMAIL_SECURITY
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -328,11 +336,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
@ -384,11 +394,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
appwrite-worker-builds:
@ -417,11 +429,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
- _APP_VCS_GITHUB_APP_NAME
- _APP_VCS_GITHUB_PRIVATE_KEY
@ -489,11 +503,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
appwrite-worker-functions:
@ -518,11 +534,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_FUNCTIONS_TIMEOUT
- _APP_SITES_TIMEOUT
- _APP_COMPUTE_BUILD_TIMEOUT
@ -545,17 +563,20 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -581,6 +602,7 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- appwrite-uploads:/storage/uploads:rw
depends_on:
- redis
- <?= $dbService . "\n" ?>
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@ -589,11 +611,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
- _APP_SMS_FROM
- _APP_SMS_PROVIDER
@ -647,11 +671,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
@ -666,6 +692,7 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- appwrite
depends_on:
- redis
- <?= $dbService . "\n" ?>
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@ -681,11 +708,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
@ -710,11 +739,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -739,11 +770,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -767,11 +800,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -799,11 +834,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
appwrite-task-scheduler-executions:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
@ -824,11 +861,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
appwrite-task-scheduler-messages:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
@ -849,11 +888,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
<?php if ($enableAssistant): ?>
appwrite-assistant:
@ -929,16 +970,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
<?php if ($dbService === 'mariadb'): ?>
mariadb:
image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p
image: mariadb:10.11
container_name: appwrite-mariadb
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-mariadb:/var/lib/mysql:rw
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
@ -946,31 +984,100 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- MYSQL_PASSWORD=${_APP_DB_PASS}
- MARIADB_AUTO_UPGRADE=1
command: 'mysqld --innodb-flush-method=fsync'
<?php else: ?>
<?php elseif ($dbService === 'mongodb'): ?>
mongodb:
image: mongo:8.0.8
image: mongo:8.2.5
container_name: appwrite-mongodb
<<: *x-logging
networks:
- appwrite
volumes:
- appwrite-mongodb:/data/db
- appwrite-mongodb-keyfile:/data/keyfile
ports:
- "27017:27017"
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MONGO_INITDB_DATABASE=${_APP_DB_SCHEMA}
- MONGO_INITDB_USERNAME=${_APP_DB_USER}
- MONGO_INITDB_PASSWORD=${_APP_DB_PASS}
entrypoint:
- /bin/bash
- -c
- |
set -e
KEYFILE_PATH="/data/keyfile/mongo-keyfile"
INIT_FLAG="/data/db/.mongodb_initialized"
# Generate keyfile if it doesn't exist
if [ ! -f "$$KEYFILE_PATH" ]; then
echo "Generating random MongoDB keyfile..."
mkdir -p /data/keyfile
openssl rand -base64 756 > "$$KEYFILE_PATH"
fi
chmod 400 "$$KEYFILE_PATH"
chown mongodb:mongodb "$$KEYFILE_PATH" 2>/dev/null || chown 999:999 "$$KEYFILE_PATH"
# If not initialized, start without auth first to set up replica set and users
if [ ! -f "$$INIT_FLAG" ]; then
echo "First-time initialization: starting MongoDB without auth..."
mongod --replSet rs0 --bind_ip_all --fork --logpath /var/log/mongodb/mongod.log --dbpath /data/db
echo "Waiting for MongoDB to start..."
sleep 5
echo "Initializing replica set..."
mongosh --eval "rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'mongodb:27017'}]})"
echo "Waiting for replica set to initialize..."
sleep 5
echo "Creating root user..."
mongosh admin --eval "db.createUser({user: '$$MONGO_INITDB_ROOT_USERNAME', pwd: '$$MONGO_INITDB_ROOT_PASSWORD', roles: ['root']})"
echo "Creating application user..."
mongosh admin --eval "db.createUser({user: '$$MONGO_INITDB_USERNAME', pwd: '$$MONGO_INITDB_PASSWORD', roles: [{role: 'readWrite', db: '$$MONGO_INITDB_DATABASE'}]})"
echo "Shutting down MongoDB..."
mongod --dbpath /data/db --shutdown
touch "$$INIT_FLAG"
echo "Initialization complete."
fi
echo "Starting MongoDB with authentication..."
exec mongod --replSet rs0 --bind_ip_all --auth --keyFile "$$KEYFILE_PATH"
healthcheck:
test: |
mongosh -u root -p "$$MONGO_INITDB_ROOT_PASSWORD" --authenticationDatabase admin --quiet --eval "rs.status().ok" | grep -q 1
interval: 10s
timeout: 10s
retries: 10
start_period: 60s
<?php elseif ($dbService === 'postgresql'): ?>
postgresql:
image: postgres:18
container_name: appwrite-postgresql
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-mongodb:/data/db:rw
- appwrite-mongodb-config:/data/configdb:rw
<?php if ($version === 'local' && !empty($hostPath)): ?>
- <?php echo $hostPath; ?>/mongo-entrypoint.sh:/mongo-entrypoint.sh:ro
<?php else: ?>
- ./mongo-entrypoint.sh:/mongo-entrypoint.sh:ro
<?php endif; ?>
ports:
- "27017:27017"
- appwrite-postgresql:/var/lib/postgresql/data:rw
environment:
- MONGO_INITDB_DATABASE=${_APP_DB_SCHEMA}
entrypoint: ["/bin/bash", "/mongo-entrypoint.sh"]
- POSTGRES_DB=${_APP_DB_SCHEMA}
- POSTGRES_USER=${_APP_DB_USER}
- POSTGRES_PASSWORD=${_APP_DB_PASS}
command: "postgres"
<?php endif; ?>
redis:
image: redis:7.2.4-alpine
image: redis:7.4.7-alpine
container_name: appwrite-redis
<<: *x-logging
restart: unless-stopped
@ -1004,8 +1111,11 @@ networks:
volumes:
<?php if ($dbService === 'mariadb'): ?>
appwrite-mariadb:
<?php else: ?>
<?php elseif ($dbService === 'postgresql'): ?>
appwrite-postgresql:
<?php elseif ($dbService === 'mongodb'): ?>
appwrite-mongodb:
appwrite-mongodb-keyfile:
appwrite-mongodb-config:
<?php endif; ?>
appwrite-redis:

View file

@ -9,6 +9,7 @@ use Appwrite\Event\Certificate;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Execution;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
@ -27,8 +28,8 @@ use Utopia\Audit\Audit as UtopiaAudit;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@ -358,6 +359,10 @@ Server::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
Server::setResource('queueForExecutions', function (Publisher $publisher) {
return new Execution($publisher);
}, ['publisher']);
Server::setResource('queueForRealtime', function () {
return new Realtime();
}, []);

3
bin/worker-executions Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php executions "$@"

View file

@ -32,6 +32,8 @@
"Appwrite\\Tests\\": "tests/extensions"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": ">=8.3.0",
"ext-curl": "*",
@ -49,12 +51,13 @@
"appwrite/php-runtimes": "0.19.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "1.2.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/analytics": "0.15.*",
"utopia-php/audit": "2.2.*",
"utopia-php/auth": "0.5.*",
"utopia-php/cache": "1.0.*",
"utopia-php/cli": "0.15.*",
"utopia-php/cli": "0.22.*",
"utopia-php/config": "1.*",
"utopia-php/console": "0.1.*",
"utopia-php/database": "5.*",
"utopia-php/detector": "0.2.*",
"utopia-php/domains": "1.*",
@ -68,26 +71,24 @@
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.20.*",
"utopia-php/migration": "1.5.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "1.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.15.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "1.*",
"utopia-php/system": "0.9.*",
"utopia-php/system": "0.10.*",
"utopia-php/telemetry": "0.2.*",
"utopia-php/vcs": "1.*",
"utopia-php/websocket": "0.3.*",
"utopia-php/websocket": "1.0.*",
"matomo/device-detector": "6.4.*",
"dragonmantank/cron-expression": "3.4.*",
"phpmailer/phpmailer": "6.9.*",
"chillerlan/php-qrcode": "4.4.*",
"chillerlan/php-qrcode": "4.3.*",
"adhocore/jwt": "1.1.*",
"spomky-labs/otphp": "10.0.*",
"spomky-labs/otphp": "11.*",
"webonyx/graphql-php": "14.11.*",
"league/csv": "9.24.*",
"league/csv": "9.14.*",
"enshrined/svg-sanitize": "0.22.*"
},
"require-dev": {
@ -96,7 +97,7 @@
"brianium/paratest": "7.*",
"phpunit/phpunit": "12.*",
"swoole/ide-helper": "6.*",
"phpstan/phpstan": "1.8.*",
"phpstan/phpstan": "1.12.*",
"textalk/websocket": "1.5.*",
"laravel/pint": "1.*",
"phpbench/phpbench": "1.*"

883
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,7 @@ x-logging: &x-logging
services:
traefik:
image: traefik:2.11
image: traefik:3.6
<<: *x-logging
container_name: appwrite-traefik
command:
@ -64,6 +64,7 @@ services:
networks:
- appwrite
dns:
- 127.0.0.11
- 172.16.238.100
labels:
- "traefik.enable=true"
@ -98,9 +99,9 @@ services:
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
- ./dev:/usr/src/code/dev
# - ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
- redis
- coredns
# - clamav
@ -133,6 +134,7 @@ services:
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_CONSOLE_TRUSTED_PROJECTS
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
@ -143,11 +145,13 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
@ -228,6 +232,7 @@ services:
- _APP_FUNCTIONS_CREATION_ABUSE_LIMIT
- _APP_CUSTOM_DOMAIN_DENY_LIST
- _APP_TRUSTED_HEADERS
- _APP_MIGRATION_HOST
extra_hosts:
- "host.docker.internal:host-gateway"
@ -281,7 +286,7 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
- redis
environment:
- _APP_ENV
@ -293,11 +298,13 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_USAGE_STATS
- _APP_LOGGING_CONFIG
- _APP_LOGGING_CONFIG_REALTIME
@ -316,7 +323,7 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@ -326,11 +333,13 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
@ -346,7 +355,7 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
- request-catcher-sms
- request-catcher-webhook
environment:
@ -355,11 +364,13 @@ services:
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_EMAIL_SECURITY
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -377,7 +388,7 @@ services:
- appwrite
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
@ -396,11 +407,13 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
@ -444,7 +457,7 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@ -454,11 +467,13 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
- _APP_WORKERS_NUM
- _APP_QUEUE_NAME
@ -479,7 +494,7 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@ -491,11 +506,13 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
- _APP_VCS_GITHUB_APP_NAME
- _APP_VCS_GITHUB_PRIVATE_KEY
@ -510,6 +527,7 @@ services:
- _APP_OPTIONS_ROUTER_FORCE_HTTPS
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_CONSOLE_TRUSTED_PROJECTS
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
@ -550,7 +568,7 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
# Specific
- _APP_BROWSER_HOST
@ -565,6 +583,7 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -606,7 +625,7 @@ services:
- appwrite
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
volumes:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
@ -630,6 +649,38 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
appwrite-worker-executions:
entrypoint: worker-executions
<<: *x-logging
container_name: appwrite-worker-executions
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -649,12 +700,8 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
redis:
condition: service_started
mariadb:
condition: service_started
openruntimes-executor:
condition: service_healthy
- ${_APP_DB_HOST:-mongodb}
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@ -666,11 +713,13 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_FUNCTIONS_TIMEOUT
- _APP_SITES_TIMEOUT
- _APP_COMPUTE_BUILD_TIMEOUT
@ -743,11 +792,13 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
- _APP_SMS_FROM
- _APP_SMS_PROVIDER
@ -791,7 +842,7 @@ services:
- ./src:/usr/src/code/src
- ./tests:/usr/src/code/tests
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@ -808,16 +859,19 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_LOGGING_CONFIG
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
- _APP_DATABASE_SHARED_TABLES
- _APP_OPTIONS_FORCE_HTTPS
- _APP_MIGRATION_HOST
appwrite-task-maintenance:
entrypoint: maintenance
@ -830,7 +884,7 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
- redis
environment:
- _APP_ENV
@ -848,11 +902,13 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
@ -875,7 +931,7 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
- redis
environment:
- _APP_ENV
@ -894,6 +950,7 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -915,17 +972,19 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -947,17 +1006,19 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -979,17 +1040,19 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -1010,7 +1073,7 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
- redis
environment:
- _APP_ENV
@ -1021,11 +1084,13 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_DATABASE_SHARED_TABLES
appwrite-task-scheduler-executions:
@ -1039,7 +1104,7 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
- redis
environment:
- _APP_ENV
@ -1050,11 +1115,13 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
appwrite-task-scheduler-messages:
entrypoint: schedule-messages
@ -1067,7 +1134,7 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
- redis
environment:
- _APP_ENV
@ -1078,11 +1145,13 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_DATABASE_SHARED_TABLES
appwrite-assistant:
@ -1159,6 +1228,7 @@ services:
start_period: 5s
mariadb:
profiles: ["mariadb"]
image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
@ -1176,8 +1246,80 @@ services:
- MARIADB_AUTO_UPGRADE=1
command: "mysqld --innodb-flush-method=fsync"
mongodb:
profiles: ["mongodb"]
image: mongo:8.2.5
container_name: appwrite-mongodb
<<: *x-logging
networks:
- appwrite
volumes:
- appwrite-mongodb:/data/db
- appwrite-mongodb-keyfile:/data/keyfile
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
- ./mongo-entrypoint.sh:/mongo-entrypoint.sh:ro
ports:
- "27017:27017"
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MONGO_INITDB_DATABASE=${_APP_DB_SCHEMA}
- MONGO_INITDB_USERNAME=${_APP_DB_USER}
- MONGO_INITDB_PASSWORD=${_APP_DB_PASS}
entrypoint: ["/bin/bash", "/mongo-entrypoint.sh"]
healthcheck:
test: |
bash -c "
if mongosh -u root -p ${_APP_DB_ROOT_PASS} --authenticationDatabase admin --quiet --eval 'rs.status().ok' 2>/dev/null | grep -q 1; then
exit 0
else
mongosh -u root -p ${_APP_DB_ROOT_PASS} --authenticationDatabase admin --quiet --eval \"
rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'appwrite-mongodb:27017'}]})
\" 2>/dev/null || exit 1
fi
"
interval: 10s
timeout: 10s
retries: 10
start_period: 30s
appwrite-mongo-express:
profiles: ["mongodb"]
image: mongo-express
container_name: appwrite-mongo-express
networks:
- appwrite
ports:
- "8082:8081"
environment:
ME_CONFIG_MONGODB_URL: "mongodb://root:${_APP_DB_ROOT_PASS}@appwrite-mongodb:27017/?replicaSet=rs0&directConnection=true"
ME_CONFIG_BASICAUTH_USERNAME: ${_APP_DB_USER}
ME_CONFIG_BASICAUTH_PASSWORD: ${_APP_DB_PASS}
depends_on:
- mongodb
postgresql:
profiles: ["postgresql"]
build:
context: ./tests/resources/postgresql
args:
POSTGRES_VERSION: 17
container_name: appwrite-postgresql
<<: *x-logging
networks:
- appwrite
volumes:
- appwrite-postgresql:/var/lib/postgresql/data:rw
ports:
- "5432:5432"
environment:
- POSTGRES_DB=${_APP_DB_SCHEMA}
- POSTGRES_USER=${_APP_DB_USER}
- POSTGRES_PASSWORD=${_APP_DB_PASS}
command: "postgres"
redis:
image: redis:7.2.4-alpine
image: redis:7.4.7-alpine
<<: *x-logging
container_name: appwrite-redis
command: >
@ -1403,6 +1545,9 @@ configs:
volumes:
appwrite-mariadb:
appwrite-mongodb:
appwrite-mongodb-keyfile:
appwrite-postgresql:
appwrite-redis:
appwrite-cache:
appwrite-uploads:

View file

@ -2,7 +2,6 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Teams;
import io.appwrite.enums.Roles;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@ -12,7 +11,7 @@ Teams teams = new Teams(client);
teams.createMembership(
"<TEAM_ID>", // teamId
Roles.ADMIN, // roles
List.of(), // roles
"email@example.com", // email (optional)
"<USER_ID>", // userId (optional)
"+12065550100", // phone (optional)

View file

@ -2,7 +2,6 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Teams;
import io.appwrite.enums.Roles;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@ -13,7 +12,7 @@ Teams teams = new Teams(client);
teams.updateMembership(
"<TEAM_ID>", // teamId
"<MEMBERSHIP_ID>", // membershipId
Roles.ADMIN, // roles
List.of(), // roles
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();

View file

@ -2,7 +2,6 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Teams
import io.appwrite.enums.Roles
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@ -12,7 +11,7 @@ val teams = Teams(client)
val result = teams.createMembership(
teamId = "<TEAM_ID>",
roles = roles.ADMIN,
roles = listOf(),
email = "email@example.com", // (optional)
userId = "<USER_ID>", // (optional)
phone = "+12065550100", // (optional)

View file

@ -2,7 +2,6 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Teams
import io.appwrite.enums.Roles
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@ -13,5 +12,5 @@ val teams = Teams(client)
val result = teams.updateMembership(
teamId = "<TEAM_ID>",
membershipId = "<MEMBERSHIP_ID>",
roles = roles.ADMIN,
roles = listOf(),
)```

View file

@ -1,6 +1,5 @@
```swift
import Appwrite
import AppwriteEnums
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@ -10,7 +9,7 @@ let teams = Teams(client)
let membership = try await teams.createMembership(
teamId: "<TEAM_ID>",
roles: [.admin],
roles: [],
email: "email@example.com", // optional
userId: "<USER_ID>", // optional
phone: "+12065550100", // optional

View file

@ -1,6 +1,5 @@
```swift
import Appwrite
import AppwriteEnums
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@ -11,7 +10,7 @@ let teams = Teams(client)
let membership = try await teams.updateMembership(
teamId: "<TEAM_ID>",
membershipId: "<MEMBERSHIP_ID>",
roles: [.admin]
roles: []
)
```

View file

@ -1,6 +1,5 @@
```dart
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/enums.dart' as enums;
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@ -10,7 +9,7 @@ Teams teams = Teams(client);
Membership result = await teams.createMembership(
teamId: '<TEAM_ID>',
roles: [enums.Roles.admin],
roles: [],
email: 'email@example.com', // optional
userId: '<USER_ID>', // optional
phone: '+12065550100', // optional

View file

@ -1,6 +1,5 @@
```dart
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/enums.dart' as enums;
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@ -11,6 +10,6 @@ Teams teams = Teams(client);
Membership result = await teams.updateMembership(
teamId: '<TEAM_ID>',
membershipId: '<MEMBERSHIP_ID>',
roles: [enums.Roles.admin],
roles: [],
);
```

View file

@ -1,5 +1,5 @@
```javascript
import { Client, Teams, Roles } from "react-native-appwrite";
import { Client, Teams } from "react-native-appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@ -9,7 +9,7 @@ const teams = new Teams(client);
const result = await teams.createMembership({
teamId: '<TEAM_ID>',
roles: [Roles.Admin],
roles: [],
email: 'email@example.com', // optional
userId: '<USER_ID>', // optional
phone: '+12065550100', // optional

View file

@ -1,5 +1,5 @@
```javascript
import { Client, Teams, Roles } from "react-native-appwrite";
import { Client, Teams } from "react-native-appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@ -10,7 +10,7 @@ const teams = new Teams(client);
const result = await teams.updateMembership({
teamId: '<TEAM_ID>',
membershipId: '<MEMBERSHIP_ID>',
roles: [Roles.Admin]
roles: []
});
console.log(result);

View file

@ -1,5 +1,5 @@
```javascript
import { Client, Teams, Roles } from "appwrite";
import { Client, Teams } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@ -9,7 +9,7 @@ const teams = new Teams(client);
const result = await teams.createMembership({
teamId: '<TEAM_ID>',
roles: [Roles.Admin],
roles: [],
email: 'email@example.com', // optional
userId: '<USER_ID>', // optional
phone: '+12065550100', // optional

View file

@ -1,5 +1,5 @@
```javascript
import { Client, Teams, Roles } from "appwrite";
import { Client, Teams } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@ -10,7 +10,7 @@ const teams = new Teams(client);
const result = await teams.updateMembership({
teamId: '<TEAM_ID>',
membershipId: '<MEMBERSHIP_ID>',
roles: [Roles.Admin]
roles: []
});
console.log(result);

View file

@ -11,6 +11,7 @@ const result = await projects.createKey({
projectId: '<PROJECT_ID>',
name: '<NAME>',
scopes: [Scopes.SessionsWrite],
keyId: '<KEY_ID>', // optional
expire: '' // optional
});

View file

@ -9,6 +9,7 @@ const projects = new Projects(client);
const result = await projects.listKeys({
projectId: '<PROJECT_ID>',
queries: [], // optional
total: false // optional
});

View file

@ -1,5 +1,5 @@
```javascript
import { Client, Teams, Roles } from "@appwrite.io/console";
import { Client, Teams } from "@appwrite.io/console";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@ -9,7 +9,7 @@ const teams = new Teams(client);
const result = await teams.createMembership({
teamId: '<TEAM_ID>',
roles: [Roles.Admin],
roles: [],
email: 'email@example.com', // optional
userId: '<USER_ID>', // optional
phone: '+12065550100', // optional

View file

@ -1,5 +1,5 @@
```javascript
import { Client, Teams, Roles } from "@appwrite.io/console";
import { Client, Teams } from "@appwrite.io/console";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@ -10,7 +10,7 @@ const teams = new Teams(client);
const result = await teams.updateMembership({
teamId: '<TEAM_ID>',
membershipId: '<MEMBERSHIP_ID>',
roles: [Roles.Admin]
roles: []
});
console.log(result);

View file

@ -1,6 +1,5 @@
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@ -11,7 +10,7 @@ Teams teams = Teams(client);
Membership result = await teams.createMembership(
teamId: '<TEAM_ID>',
roles: [enums.Roles.admin],
roles: [],
email: 'email@example.com', // (optional)
userId: '<USER_ID>', // (optional)
phone: '+12065550100', // (optional)

View file

@ -1,6 +1,5 @@
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@ -12,6 +11,6 @@ Teams teams = Teams(client);
Membership result = await teams.updateMembership(
teamId: '<TEAM_ID>',
membershipId: '<MEMBERSHIP_ID>',
roles: [enums.Roles.admin],
roles: [],
);
```

View file

@ -1,6 +1,5 @@
```csharp
using Appwrite;
using Appwrite.Enums;
using Appwrite.Models;
using Appwrite.Services;
@ -13,7 +12,7 @@ Teams teams = new Teams(client);
Membership result = await teams.CreateMembership(
teamId: "<TEAM_ID>",
roles: new List&lt;Roles&gt; { Roles.Admin },
roles: new List<string>(),
email: "email@example.com", // optional
userId: "<USER_ID>", // optional
phone: "+12065550100", // optional

View file

@ -1,6 +1,5 @@
```csharp
using Appwrite;
using Appwrite.Enums;
using Appwrite.Models;
using Appwrite.Services;
@ -14,5 +13,5 @@ Teams teams = new Teams(client);
Membership result = await teams.UpdateMembership(
teamId: "<TEAM_ID>",
membershipId: "<MEMBERSHIP_ID>",
roles: new List&lt;Roles&gt; { Roles.Admin }
roles: new List<string>()
);```

View file

@ -31,6 +31,8 @@ mutation {
lengths
orders
}
bytesMax
bytesUsed
}
}
```

View file

@ -29,6 +29,8 @@ mutation {
lengths
orders
}
bytesMax
bytesUsed
}
}
```

View file

@ -31,6 +31,8 @@ mutation {
lengths
orders
}
bytesMax
bytesUsed
}
}
```

View file

@ -29,6 +29,8 @@ mutation {
lengths
orders
}
bytesMax
bytesUsed
}
}
```

View file

@ -2,7 +2,6 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Teams;
import io.appwrite.enums.Roles;
Client client = new Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@ -13,7 +12,7 @@ Teams teams = new Teams(client);
teams.createMembership(
"<TEAM_ID>", // teamId
List.of(Roles.ADMIN), // roles
List.of(), // roles
"email@example.com", // email (optional)
"<USER_ID>", // userId (optional)
"+12065550100", // phone (optional)

View file

@ -2,7 +2,6 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Teams;
import io.appwrite.enums.Roles;
Client client = new Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@ -14,7 +13,7 @@ Teams teams = new Teams(client);
teams.updateMembership(
"<TEAM_ID>", // teamId
"<MEMBERSHIP_ID>", // membershipId
List.of(Roles.ADMIN), // roles
List.of(), // roles
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();

View file

@ -2,7 +2,6 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Teams
import io.appwrite.enums.Roles
val client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@ -13,7 +12,7 @@ val teams = Teams(client)
val response = teams.createMembership(
teamId = "<TEAM_ID>",
roles = listOf(Roles.ADMIN),
roles = listOf(),
email = "email@example.com", // optional
userId = "<USER_ID>", // optional
phone = "+12065550100", // optional

View file

@ -2,7 +2,6 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Teams
import io.appwrite.enums.Roles
val client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@ -14,6 +13,6 @@ val teams = Teams(client)
val response = teams.updateMembership(
teamId = "<TEAM_ID>",
membershipId = "<MEMBERSHIP_ID>",
roles = listOf(Roles.ADMIN)
roles = listOf()
)
```

View file

@ -10,7 +10,7 @@ const teams = new sdk.Teams(client);
const result = await teams.createMembership({
teamId: '<TEAM_ID>',
roles: [sdk.Roles.Admin],
roles: [],
email: 'email@example.com', // optional
userId: '<USER_ID>', // optional
phone: '+12065550100', // optional

View file

@ -11,6 +11,6 @@ const teams = new sdk.Teams(client);
const result = await teams.updateMembership({
teamId: '<TEAM_ID>',
membershipId: '<MEMBERSHIP_ID>',
roles: [sdk.Roles.Admin]
roles: []
});
```

View file

@ -3,7 +3,6 @@
use Appwrite\Client;
use Appwrite\Services\Teams;
use Appwrite\Enums\Roles;
$client = (new Client())
->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@ -14,7 +13,7 @@ $teams = new Teams($client);
$result = $teams->createMembership(
teamId: '<TEAM_ID>',
roles: [Roles::ADMIN()],
roles: [],
email: 'email@example.com', // optional
userId: '<USER_ID>', // optional
phone: '+12065550100', // optional

View file

@ -3,7 +3,6 @@
use Appwrite\Client;
use Appwrite\Services\Teams;
use Appwrite\Enums\Roles;
$client = (new Client())
->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@ -15,5 +14,5 @@ $teams = new Teams($client);
$result = $teams->updateMembership(
teamId: '<TEAM_ID>',
membershipId: '<MEMBERSHIP_ID>',
roles: [Roles::ADMIN()]
roles: []
);```

View file

@ -1,7 +1,6 @@
```python
from appwrite.client import Client
from appwrite.services.teams import Teams
from appwrite.enums import Roles
client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
@ -12,7 +11,7 @@ teams = Teams(client)
result = teams.create_membership(
team_id = '<TEAM_ID>',
roles = [Roles.ADMIN],
roles = [],
email = 'email@example.com', # optional
user_id = '<USER_ID>', # optional
phone = '+12065550100', # optional

View file

@ -1,7 +1,6 @@
```python
from appwrite.client import Client
from appwrite.services.teams import Teams
from appwrite.enums import Roles
client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
@ -13,6 +12,6 @@ teams = Teams(client)
result = teams.update_membership(
team_id = '<TEAM_ID>',
membership_id = '<MEMBERSHIP_ID>',
roles = [Roles.ADMIN]
roles = []
)
```

View file

@ -2,7 +2,6 @@
require 'appwrite'
include Appwrite
include Appwrite::Enums
client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
@ -13,7 +12,7 @@ teams = Teams.new(client)
result = teams.create_membership(
team_id: '<TEAM_ID>',
roles: [Roles::ADMIN],
roles: [],
email: 'email@example.com', # optional
user_id: '<USER_ID>', # optional
phone: '+12065550100', # optional

View file

@ -2,7 +2,6 @@
require 'appwrite'
include Appwrite
include Appwrite::Enums
client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
@ -14,6 +13,6 @@ teams = Teams.new(client)
result = teams.update_membership(
team_id: '<TEAM_ID>',
membership_id: '<MEMBERSHIP_ID>',
roles: [Roles::ADMIN]
roles: []
)
```

View file

@ -1,6 +1,5 @@
```swift
import Appwrite
import AppwriteEnums
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@ -11,7 +10,7 @@ let teams = Teams(client)
let membership = try await teams.createMembership(
teamId: "<TEAM_ID>",
roles: [.admin],
roles: [],
email: "email@example.com", // optional
userId: "<USER_ID>", // optional
phone: "+12065550100", // optional

View file

@ -1,6 +1,5 @@
```swift
import Appwrite
import AppwriteEnums
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@ -12,7 +11,7 @@ let teams = Teams(client)
let membership = try await teams.updateMembership(
teamId: "<TEAM_ID>",
membershipId: "<MEMBERSHIP_ID>",
roles: [.admin]
roles: []
)
```

View file

@ -0,0 +1,12 @@
# Changelog
## 0.1.0
Initial release compatible with Appwrite server 1.8.x. Follows the [Agent Skills Open Standard](https://agentskills.io/home).
- Added agent skills for 9 languages: TypeScript, Python, PHP, Go, Kotlin, Swift, Ruby, .NET, and Dart
- Coverage for Account, Users, TablesDB, Storage, Teams, Functions, and Realtime services
- Query builder with filtering, sorting, pagination, and field selection
- Permission and Role helpers for access control
- SSR authentication patterns for major frameworks
- Consistent error handling via `AppwriteException` across all languages

View file

@ -0,0 +1,96 @@
## Getting Started
Agent Skills to help developers using AI coding agents with Appwrite. Agent Skills are folders of instructions, scripts, and resources that agents like Claude Code, Cursor, GitHub Copilot, and others can discover and use to work more accurately and efficiently.
These skills follow the Agent Skills Open Standard: https://agentskills.io/home
### Install the skills
Install directly with the Skills CLI:
```bash
npx skills add chiragagg5k/appwrite-agent-skills
```
This installs the packaged `appwrite-*` skills into your local skills directory.
### Available language skills
- `appwrite-typescript`
- `appwrite-dart`
- `appwrite-kotlin`
- `appwrite-swift`
- `appwrite-php`
- `appwrite-python`
- `appwrite-ruby`
- `appwrite-go`
- `appwrite-dotnet`
### Usage
Skills are automatically available once installed. The agent will use them when relevant tasks are detected.
### Prompt examples
Use these as copy-paste prompt starters.
#### TypeScript (server-side)
```text
Use the appwrite-typescript skill.
Create a Node.js script that uses Users service to create a user, then adds an initial profile row in TablesDB.
Use env vars for endpoint, project ID, and API key.
Include error handling and a small retry for transient failures.
```
#### TypeScript (web client)
```text
Use the appwrite-typescript skill.
Build a browser login flow with Account service:
- email/password signup
- email/password session login
- fetch current user
- logout current session
Return production-ready TypeScript code.
```
#### Python
```text
Use the appwrite-python skill.
Write a script that uploads a local file to Storage, then prints the file ID and a preview URL.
Use InputFile.from_path and catch Appwrite exceptions.
```
#### PHP
```text
Use the appwrite-php skill.
Create a service class that lists rows from TablesDB with Query.equal and Query.limit, and maps them to DTOs.
Prefer dependency injection for the Appwrite client.
```
#### Go
```text
Use the appwrite-go skill.
Implement a CLI command that creates a document-style row, then reads it back and prints JSON output.
Use context timeouts and structured error messages.
```
#### Kotlin
```text
Use the appwrite-kotlin skill.
Generate Android repository code to paginate rows using cursor queries and expose a suspend function API.
Keep UI concerns out of the data layer.
```
#### Migration prompt (Databases -> TablesDB)
```text
Use the appwrite-typescript skill.
Migrate this existing Databases-based code to TablesDB APIs.
Keep behavior identical and list each API mapping you changed.
```

View file

@ -1,5 +1,11 @@
# Change Log
## 12.1.0
* Add `queries` parameter to Realtime subscriptions for filtering events
* Add `subscriptions` field to `RealtimeCallback` and `RealtimeResponseEvent` types
* Fix `Roles` enum removed from Teams service; `roles` parameter now accepts `List<String>`
## 12.0.0
* Add array-based enum parameters (e.g., `permissions: List<BrowserPermission>`).

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