mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch '1.8.x' into ser-1127
This commit is contained in:
+139
-152
@@ -53,8 +53,14 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build Appwrite
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -73,7 +79,7 @@ jobs:
|
||||
VERSION=dev
|
||||
|
||||
- name: Cache Docker Image
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
@@ -91,21 +97,24 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
timeout-minutes: 3
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
until docker compose exec -T appwrite doctor > /dev/null 2>&1; do
|
||||
echo "Waiting for Appwrite to be ready..."
|
||||
sleep 2
|
||||
done
|
||||
docker compose pull --quiet --ignore-buildable
|
||||
docker compose up -d --quiet-pull --wait
|
||||
|
||||
- name: Environment Variables
|
||||
run: docker compose exec -T appwrite vars
|
||||
@@ -114,7 +123,7 @@ jobs:
|
||||
uses: itznotabug/php-retry@v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 300
|
||||
retry_wait_seconds: 60
|
||||
timeout_minutes: 15
|
||||
job_id: ${{ job.check_run_id }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -136,21 +145,24 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
timeout-minutes: 3
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
until docker compose exec -T appwrite doctor > /dev/null 2>&1; do
|
||||
echo "Waiting for Appwrite to be ready..."
|
||||
sleep 2
|
||||
done
|
||||
docker compose pull --quiet --ignore-buildable
|
||||
docker compose up -d --quiet-pull --wait
|
||||
|
||||
- name: Wait for Open Runtimes
|
||||
timeout-minutes: 3
|
||||
@@ -164,7 +176,7 @@ jobs:
|
||||
uses: itznotabug/php-retry@v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 300
|
||||
retry_wait_seconds: 60
|
||||
timeout_minutes: 15
|
||||
job_id: ${{ job.check_run_id }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -172,7 +184,7 @@ jobs:
|
||||
command: >-
|
||||
docker compose exec -T
|
||||
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
|
||||
appwrite test /usr/src/code/tests/e2e/General --debug
|
||||
appwrite test /usr/src/code/tests/e2e/General
|
||||
|
||||
- name: Failure Logs
|
||||
if: failure()
|
||||
@@ -223,7 +235,7 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
@@ -234,7 +246,7 @@ jobs:
|
||||
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
|
||||
@@ -249,17 +261,22 @@ jobs:
|
||||
echo "_APP_DB_PORT=5432" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
timeout-minutes: 3
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
_APP_BROWSER_HOST: http://invalid-browser/v1
|
||||
_APP_DATABASE_SHARED_TABLES: ""
|
||||
_APP_DATABASE_SHARED_TABLES_V1: ""
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
until docker compose exec -T appwrite doctor > /dev/null 2>&1; do
|
||||
echo "Waiting for Appwrite to be ready..."
|
||||
sleep 2
|
||||
done
|
||||
docker compose pull --quiet --ignore-buildable
|
||||
docker compose up -d --quiet-pull --wait
|
||||
|
||||
- name: Wait for Open Runtimes
|
||||
timeout-minutes: 3
|
||||
@@ -273,13 +290,12 @@ jobs:
|
||||
uses: itznotabug/php-retry@v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 300
|
||||
retry_wait_seconds: 60
|
||||
timeout_minutes: 20
|
||||
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 }}"
|
||||
|
||||
# Services that rely on sequential test method execution (shared static state)
|
||||
@@ -288,14 +304,7 @@ jobs:
|
||||
Databases|Functions|Realtime) FUNCTIONAL_FLAG="" ;;
|
||||
esac
|
||||
|
||||
echo "Running with paratest (parallel) for: ${{ matrix.service }} ${FUNCTIONAL_FLAG:+(functional)}"
|
||||
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=appwrite \
|
||||
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
|
||||
appwrite vendor/bin/paratest --processes $(nproc) $FUNCTIONAL_FLAG "$SERVICE_PATH" --exclude-group abuseEnabled --exclude-group screenshots --exclude-group ciIgnore --log-junit tests/e2e/Services/${{ matrix.service }}/junit.xml
|
||||
|
||||
@@ -350,21 +359,27 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
timeout-minutes: 3
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
_APP_DATABASE_SHARED_TABLES: database_db_main
|
||||
_APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.tables-mode == 'Shared V1' && 'database_db_main' || '' }}
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
until docker compose exec -T appwrite doctor > /dev/null 2>&1; do
|
||||
echo "Waiting for Appwrite to be ready..."
|
||||
sleep 2
|
||||
done
|
||||
docker compose pull --quiet --ignore-buildable
|
||||
docker compose up -d --quiet-pull --wait
|
||||
|
||||
- name: Wait for Open Runtimes
|
||||
timeout-minutes: 3
|
||||
@@ -378,22 +393,12 @@ jobs:
|
||||
uses: itznotabug/php-retry@v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 300
|
||||
retry_wait_seconds: 60
|
||||
timeout_minutes: 20
|
||||
job_id: ${{ job.check_run_id }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
test_dir: tests/e2e/Services/${{ matrix.service }}
|
||||
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
|
||||
|
||||
SERVICE_PATH="/usr/src/code/tests/e2e/Services/${{ matrix.service }}"
|
||||
|
||||
# Services that rely on sequential test method execution (shared static state)
|
||||
@@ -403,8 +408,6 @@ jobs:
|
||||
esac
|
||||
|
||||
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) $FUNCTIONAL_FLAG "$SERVICE_PATH" --exclude-group abuseEnabled --exclude-group screenshots --exclude-group ciIgnore --log-junit tests/e2e/Services/${{ matrix.service }}/junit.xml
|
||||
|
||||
@@ -417,7 +420,7 @@ jobs:
|
||||
docker compose logs openruntimes-executor
|
||||
|
||||
e2e_abuse_enabled:
|
||||
name: E2E Service Test (Abuse enabled)
|
||||
name: E2E Service Test (Abuse)
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
permissions:
|
||||
@@ -428,42 +431,42 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
timeout-minutes: 3
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
_APP_OPTIONS_ABUSE: enabled
|
||||
_APP_DATABASE_SHARED_TABLES: ""
|
||||
_APP_DATABASE_SHARED_TABLES_V1: ""
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env
|
||||
docker compose up -d
|
||||
until docker compose exec -T appwrite doctor > /dev/null 2>&1; do
|
||||
echo "Waiting for Appwrite to be ready..."
|
||||
sleep 2
|
||||
done
|
||||
docker compose pull --quiet --ignore-buildable
|
||||
docker compose up -d --quiet-pull --wait
|
||||
|
||||
- name: Run Projects tests in dedicated table mode
|
||||
- name: Run abuse-enabled tests in dedicated table mode
|
||||
uses: itznotabug/php-retry@v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 300
|
||||
retry_wait_seconds: 60
|
||||
timeout_minutes: 15
|
||||
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
|
||||
test_dir: tests/e2e
|
||||
command: >-
|
||||
docker compose exec -T
|
||||
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
|
||||
appwrite test /usr/src/code/tests/e2e --group=abuseEnabled
|
||||
|
||||
- name: Failure Logs
|
||||
if: failure()
|
||||
@@ -474,7 +477,7 @@ jobs:
|
||||
docker compose logs openruntimes-executor
|
||||
|
||||
e2e_abuse_enabled_shared_mode:
|
||||
name: E2E Shared Mode Service Test (Abuse enabled)
|
||||
name: E2E Shared Mode Service Test (Abuse)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ setup, check_database_changes ]
|
||||
if: needs.check_database_changes.outputs.database_changed == 'true'
|
||||
@@ -493,48 +496,42 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
timeout-minutes: 3
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
_APP_OPTIONS_ABUSE: enabled
|
||||
_APP_DATABASE_SHARED_TABLES: database_db_main
|
||||
_APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.tables-mode == 'Shared V1' && 'database_db_main' || '' }}
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env
|
||||
docker compose up -d
|
||||
until docker compose exec -T appwrite doctor > /dev/null 2>&1; do
|
||||
echo "Waiting for Appwrite to be ready..."
|
||||
sleep 2
|
||||
done
|
||||
docker compose pull --quiet --ignore-buildable
|
||||
docker compose up -d --quiet-pull --wait
|
||||
|
||||
- name: Run Projects tests in ${{ matrix.tables-mode }} table mode
|
||||
- name: Run abuse-enabled tests in ${{ matrix.tables-mode }} table mode
|
||||
uses: itznotabug/php-retry@v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 300
|
||||
retry_wait_seconds: 60
|
||||
timeout_minutes: 15
|
||||
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
|
||||
test_dir: tests/e2e
|
||||
command: >-
|
||||
docker compose exec -T
|
||||
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
|
||||
appwrite test /usr/src/code/tests/e2e --group=abuseEnabled
|
||||
|
||||
- name: Failure Logs
|
||||
if: failure()
|
||||
@@ -556,22 +553,27 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
timeout-minutes: 3
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
_APP_DATABASE_SHARED_TABLES: ""
|
||||
_APP_DATABASE_SHARED_TABLES_V1: ""
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env
|
||||
docker compose up -d
|
||||
until docker compose exec -T appwrite doctor > /dev/null 2>&1; do
|
||||
echo "Waiting for Appwrite to be ready..."
|
||||
sleep 2
|
||||
done
|
||||
docker compose pull --quiet --ignore-buildable
|
||||
docker compose up -d --quiet-pull --wait
|
||||
|
||||
- name: Wait for Open Runtimes
|
||||
timeout-minutes: 3
|
||||
@@ -585,22 +587,15 @@ jobs:
|
||||
uses: itznotabug/php-retry@v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 300
|
||||
retry_wait_seconds: 60
|
||||
timeout_minutes: 15
|
||||
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
|
||||
command: >-
|
||||
docker compose exec -T
|
||||
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
|
||||
appwrite test /usr/src/code/tests/e2e/Services/Sites --group=screenshots
|
||||
|
||||
- name: Failure Logs
|
||||
if: failure()
|
||||
@@ -630,22 +625,27 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
timeout-minutes: 3
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
_APP_DATABASE_SHARED_TABLES: database_db_main
|
||||
_APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.tables-mode == 'Shared V1' && 'database_db_main' || '' }}
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env
|
||||
docker compose up -d
|
||||
until docker compose exec -T appwrite doctor > /dev/null 2>&1; do
|
||||
echo "Waiting for Appwrite to be ready..."
|
||||
sleep 2
|
||||
done
|
||||
docker compose pull --quiet --ignore-buildable
|
||||
docker compose up -d --quiet-pull --wait
|
||||
|
||||
- name: Wait for Open Runtimes
|
||||
timeout-minutes: 3
|
||||
@@ -659,28 +659,15 @@ jobs:
|
||||
uses: itznotabug/php-retry@v3
|
||||
with:
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 300
|
||||
retry_wait_seconds: 60
|
||||
timeout_minutes: 15
|
||||
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
|
||||
command: >-
|
||||
docker compose exec -T
|
||||
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
|
||||
appwrite test /usr/src/code/tests/e2e/Services/Sites --group=screenshots
|
||||
|
||||
- name: Failure Logs
|
||||
if: failure()
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/sdks && \
|
||||
chmod +x /usr/local/bin/specs && \
|
||||
chmod +x /usr/local/bin/ssl && \
|
||||
chmod +x /usr/local/bin/time-travel && \
|
||||
chmod +x /usr/local/bin/task-time-travel && \
|
||||
chmod +x /usr/local/bin/screenshot && \
|
||||
chmod +x /usr/local/bin/test && \
|
||||
chmod +x /usr/local/bin/upgrade && \
|
||||
|
||||
@@ -1560,12 +1560,15 @@ Http::patch('/v1/users/:userId/phone')
|
||||
|
||||
$oldPhone = $user->getAttribute('phone');
|
||||
|
||||
// Store null instead of empty string so unique constraint allows multiple users without phone
|
||||
$phoneValue = $number !== '' ? $number : null;
|
||||
|
||||
$user
|
||||
->setAttribute('phone', $number)
|
||||
->setAttribute('phone', $phoneValue)
|
||||
->setAttribute('phoneVerification', false)
|
||||
;
|
||||
|
||||
if (\strlen($number) !== 0) {
|
||||
if ($number !== '') {
|
||||
$target = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$number]),
|
||||
]);
|
||||
@@ -1577,7 +1580,7 @@ Http::patch('/v1/users/:userId/phone')
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document([
|
||||
'phone' => $user->getAttribute('phone'),
|
||||
'phone' => $phoneValue,
|
||||
'phoneVerification' => $user->getAttribute('phoneVerification'),
|
||||
]));
|
||||
/**
|
||||
@@ -1586,14 +1589,14 @@ Http::patch('/v1/users/:userId/phone')
|
||||
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
|
||||
|
||||
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
|
||||
if (\strlen($number) !== 0) {
|
||||
if ($number !== '') {
|
||||
$dbForProject->updateDocument('targets', $oldTarget->getId(), new Document(['identifier' => $number]));
|
||||
$oldTarget->setAttribute('identifier', $number);
|
||||
} else {
|
||||
$dbForProject->deleteDocument('targets', $oldTarget->getId());
|
||||
}
|
||||
} else {
|
||||
if (\strlen($number) !== 0) {
|
||||
if ($number !== '') {
|
||||
$target = $dbForProject->createDocument('targets', new Document([
|
||||
'$permissions' => [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
|
||||
@@ -1414,6 +1414,9 @@ Http::error()
|
||||
$sdk = $route?->getLabel("sdk", false);
|
||||
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD';
|
||||
if (!empty($sdk)) {
|
||||
if (\is_array($sdk)) {
|
||||
$sdk = $sdk[0];
|
||||
}
|
||||
/** @var \Appwrite\SDK\Method $sdk */
|
||||
$action = $sdk->getNamespace() . '.' . $sdk->getMethodName();
|
||||
} elseif ($route === null) {
|
||||
|
||||
@@ -581,6 +581,9 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
|
||||
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD';
|
||||
if (!empty($sdk)) {
|
||||
if (\is_array($sdk)) {
|
||||
$sdk = $sdk[0];
|
||||
}
|
||||
/** @var Appwrite\SDK\Method $sdk */
|
||||
$action = $sdk->getNamespace() . '.' . $sdk->getMethodName();
|
||||
} elseif ($route === null) {
|
||||
|
||||
@@ -362,6 +362,12 @@ const METRIC_AVATARS_SCREENSHOTS_GENERATED = 'avatars.screenshotsGenerated';
|
||||
const METRIC_FUNCTIONS_RUNTIME = 'functions.runtimes.{runtime}';
|
||||
const METRIC_SITES_FRAMEWORK = 'sites.frameworks.{framework}';
|
||||
|
||||
// Realtime metrics
|
||||
const METRIC_REALTIME_CONNECTIONS = 'realtime.connections';
|
||||
const METRIC_REALTIME_CONNECTIONS_MESSAGES_SENT = 'realtime.messages.sent';
|
||||
const METRIC_REALTIME_INBOUND = 'realtime.inbound';
|
||||
const METRIC_REALTIME_OUTBOUND = 'realtime.outbound';
|
||||
|
||||
// Resource types
|
||||
const RESOURCE_TYPE_PROJECTS = 'projects';
|
||||
const RESOURCE_TYPE_FUNCTIONS = 'functions';
|
||||
|
||||
@@ -420,6 +420,7 @@ $register->set('smtp', function () {
|
||||
$mail->Password = $password;
|
||||
$mail->SMTPSecure = System::getEnv('_APP_SMTP_SECURE', '');
|
||||
$mail->SMTPAutoTLS = false;
|
||||
$mail->SMTPKeepAlive = true;
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->Timeout = 10; /* Connection timeout */
|
||||
$mail->getSMTPInstance()->Timelimit = 30; /* Timeout for each individual SMTP command (e.g. HELO, EHLO, etc.) */
|
||||
|
||||
+6
-1
@@ -5,4 +5,9 @@ use Utopia\Span\Span;
|
||||
use Utopia\Span\Storage;
|
||||
|
||||
Span::setStorage(new Storage\Coroutine());
|
||||
Span::addExporter(new Exporter\Pretty());
|
||||
Span::addExporter(new Exporter\Pretty(), function (Span $span): bool {
|
||||
if (\str_starts_with($span->getAction(), 'listener.')) {
|
||||
return $span->getError() !== null;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
+90
-9
@@ -224,6 +224,13 @@ if (!function_exists('getTelemetry')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('triggerStats')) {
|
||||
function triggerStats(array $event, string $projectId): void
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$realtime = getRealtime();
|
||||
|
||||
/**
|
||||
@@ -548,20 +555,41 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
}
|
||||
|
||||
$total = 0;
|
||||
$outboundBytes = 0;
|
||||
|
||||
foreach ($groups as $group) {
|
||||
$data = $event['data'];
|
||||
$data['subscriptions'] = $group['subscriptions'];
|
||||
|
||||
$server->send($group['ids'], json_encode([
|
||||
$payloadJson = json_encode([
|
||||
'type' => 'event',
|
||||
'data' => $data
|
||||
]));
|
||||
$total += count($group['ids']);
|
||||
]);
|
||||
|
||||
$server->send($group['ids'], $payloadJson);
|
||||
|
||||
$count = count($group['ids']);
|
||||
$total += $count;
|
||||
$outboundBytes += strlen($payloadJson) * $count;
|
||||
}
|
||||
|
||||
if ($total > 0) {
|
||||
$register->get('telemetry.messageSentCounter')->add($total);
|
||||
$stats->incr($event['project'], 'messages', $total);
|
||||
|
||||
$projectId = $event['project'] ?? null;
|
||||
|
||||
if (!empty($projectId)) {
|
||||
$metrics = [
|
||||
METRIC_REALTIME_CONNECTIONS_MESSAGES_SENT => $total,
|
||||
];
|
||||
|
||||
if ($outboundBytes > 0) {
|
||||
$metrics[METRIC_REALTIME_OUTBOUND] = $outboundBytes;
|
||||
}
|
||||
|
||||
triggerStats($metrics, $projectId);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable $th) {
|
||||
@@ -638,6 +666,12 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many requests');
|
||||
}
|
||||
|
||||
$rawSize = $request->getSize();
|
||||
|
||||
triggerStats([
|
||||
METRIC_REALTIME_INBOUND => $rawSize,
|
||||
], $project->getId());
|
||||
|
||||
/*
|
||||
* Validate Client Domain - Check to avoid CSRF attack.
|
||||
* Adding Appwrite API domains to allow XDOMAIN communication.
|
||||
@@ -692,14 +726,16 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
|
||||
$user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_ACCOUNT);
|
||||
|
||||
$server->send([$connection], json_encode([
|
||||
$connectedPayloadJson = json_encode([
|
||||
'type' => 'connected',
|
||||
'data' => [
|
||||
'channels' => $names,
|
||||
'subscriptions' => $mapping,
|
||||
'user' => $user
|
||||
]
|
||||
]));
|
||||
]);
|
||||
|
||||
$server->send([$connection], $connectedPayloadJson);
|
||||
|
||||
$register->get('telemetry.connectionCounter')->add(1);
|
||||
$register->get('telemetry.connectionCreatedCounter')->add(1);
|
||||
@@ -710,6 +746,12 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
]);
|
||||
$stats->incr($project->getId(), 'connections');
|
||||
$stats->incr($project->getId(), 'connectionsTotal');
|
||||
|
||||
$connectedOutboundBytes = \strlen($connectedPayloadJson);
|
||||
|
||||
triggerStats([METRIC_REALTIME_CONNECTIONS => 1, METRIC_REALTIME_OUTBOUND => $connectedOutboundBytes], $project->getId());
|
||||
|
||||
|
||||
} catch (Throwable $th) {
|
||||
logError($th, 'realtime', project: $project, user: $logUser, authorization: $authorization);
|
||||
|
||||
@@ -751,6 +793,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
$authorization = null;
|
||||
|
||||
try {
|
||||
$rawSize = \strlen($message);
|
||||
$response = new Response(new SwooleResponse());
|
||||
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
|
||||
|
||||
@@ -789,6 +832,13 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many messages.');
|
||||
}
|
||||
|
||||
// Record realtime inbound bytes for this project
|
||||
if ($project !== null && !$project->isEmpty()) {
|
||||
triggerStats([
|
||||
METRIC_REALTIME_INBOUND => $rawSize,
|
||||
], $project->getId());
|
||||
}
|
||||
|
||||
$message = json_decode($message, true);
|
||||
|
||||
if (is_null($message) || (!array_key_exists('type', $message) && !array_key_exists('data', $message))) {
|
||||
@@ -802,9 +852,21 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
|
||||
switch ($message['type']) {
|
||||
case 'ping':
|
||||
$server->send([$connection], json_encode([
|
||||
$pongPayloadJson = json_encode([
|
||||
'type' => 'pong'
|
||||
]));
|
||||
]);
|
||||
|
||||
$server->send([$connection], $pongPayloadJson);
|
||||
|
||||
if ($project !== null && !$project->isEmpty()) {
|
||||
$pongOutboundBytes = \strlen($pongPayloadJson);
|
||||
|
||||
if ($pongOutboundBytes > 0) {
|
||||
triggerStats([
|
||||
METRIC_REALTIME_OUTBOUND => $pongOutboundBytes,
|
||||
], $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'authentication':
|
||||
@@ -865,14 +927,27 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
}
|
||||
|
||||
$user = $response->output($user, Response::MODEL_ACCOUNT);
|
||||
$server->send([$connection], json_encode([
|
||||
|
||||
$authResponsePayloadJson = json_encode([
|
||||
'type' => 'response',
|
||||
'data' => [
|
||||
'to' => 'authentication',
|
||||
'success' => true,
|
||||
'user' => $user
|
||||
]
|
||||
]));
|
||||
]);
|
||||
|
||||
$server->send([$connection], $authResponsePayloadJson);
|
||||
|
||||
if ($project !== null && !$project->isEmpty()) {
|
||||
$authOutboundBytes = \strlen($authResponsePayloadJson);
|
||||
|
||||
if ($authOutboundBytes > 0) {
|
||||
triggerStats([
|
||||
METRIC_REALTIME_OUTBOUND => $authOutboundBytes,
|
||||
], $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -913,6 +988,12 @@ $server->onClose(function (int $connection) use ($realtime, $stats, $register) {
|
||||
if (array_key_exists($connection, $realtime->connections)) {
|
||||
$stats->decr($realtime->connections[$connection]['projectId'], 'connectionsTotal');
|
||||
$register->get('telemetry.connectionCounter')->add(-1);
|
||||
|
||||
$projectId = $realtime->connections[$connection]['projectId'];
|
||||
|
||||
triggerStats([
|
||||
METRIC_REALTIME_CONNECTIONS => -1,
|
||||
], $projectId);
|
||||
}
|
||||
$realtime->unsubscribe($connection);
|
||||
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@
|
||||
"utopia-php/locale": "0.8.*",
|
||||
"utopia-php/logger": "0.6.*",
|
||||
"utopia-php/messaging": "0.20.*",
|
||||
"utopia-php/migration": "1.6.*",
|
||||
"utopia-php/migration": "1.7.*",
|
||||
"utopia-php/platform": "0.7.*",
|
||||
"utopia-php/pools": "1.*",
|
||||
"utopia-php/span": "1.1.*",
|
||||
|
||||
Generated
+78
-79
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "1cc64e07484256225f56bd525674c3b8",
|
||||
"content-hash": "b99693284208ff3d006260a089a4f7b9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -3850,16 +3850,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "5.3.7",
|
||||
"version": "5.3.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "438cc82af2981cd41ad200dd9b0df5bf00f3046a"
|
||||
"reference": "4920bb60afb98d4bd81f4d331765716ae1d40255"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/438cc82af2981cd41ad200dd9b0df5bf00f3046a",
|
||||
"reference": "438cc82af2981cd41ad200dd9b0df5bf00f3046a",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/4920bb60afb98d4bd81f4d331765716ae1d40255",
|
||||
"reference": "4920bb60afb98d4bd81f4d331765716ae1d40255",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3902,9 +3902,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/5.3.7"
|
||||
"source": "https://github.com/utopia-php/database/tree/5.3.8"
|
||||
},
|
||||
"time": "2026-03-09T04:28:56+00:00"
|
||||
"time": "2026-03-11T01:03:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/detector",
|
||||
@@ -4058,16 +4058,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/domains.git",
|
||||
"reference": "0edf6bb2b07f30db849a267027077bf5abb994c6"
|
||||
"reference": "b4896a6746f0fbe29dfd5e32f7790bd94c1af1e6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/domains/zipball/0edf6bb2b07f30db849a267027077bf5abb994c6",
|
||||
"reference": "0edf6bb2b07f30db849a267027077bf5abb994c6",
|
||||
"url": "https://api.github.com/repos/utopia-php/domains/zipball/b4896a6746f0fbe29dfd5e32f7790bd94c1af1e6",
|
||||
"reference": "b4896a6746f0fbe29dfd5e32f7790bd94c1af1e6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4114,9 +4114,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/domains/issues",
|
||||
"source": "https://github.com/utopia-php/domains/tree/1.0.5"
|
||||
"source": "https://github.com/utopia-php/domains/tree/1.0.2"
|
||||
},
|
||||
"time": "2026-03-03T09:20:50+00:00"
|
||||
"time": "2026-02-25T08:18:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/dsn",
|
||||
@@ -4517,16 +4517,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
"version": "1.6.3",
|
||||
"version": "1.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "c2d016944cb029fa5ff822ceee704785a06ef289"
|
||||
"reference": "97583ae502e40621ea91a71de19d053c5ae2e558"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/c2d016944cb029fa5ff822ceee704785a06ef289",
|
||||
"reference": "c2d016944cb029fa5ff822ceee704785a06ef289",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/97583ae502e40621ea91a71de19d053c5ae2e558",
|
||||
"reference": "97583ae502e40621ea91a71de19d053c5ae2e558",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4566,9 +4566,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.6.3"
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.7.0"
|
||||
},
|
||||
"time": "2026-03-04T07:08:22+00:00"
|
||||
"time": "2026-03-10T06:36:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
@@ -5215,16 +5215,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/vcs",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/vcs.git",
|
||||
"reference": "92a1650824ba0c5e6a1bc46e622ac87c50a08920"
|
||||
"reference": "058049326e04a2a0c2f0ce8ad00c7e84825aba14"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/92a1650824ba0c5e6a1bc46e622ac87c50a08920",
|
||||
"reference": "92a1650824ba0c5e6a1bc46e622ac87c50a08920",
|
||||
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/058049326e04a2a0c2f0ce8ad00c7e84825aba14",
|
||||
"reference": "058049326e04a2a0c2f0ce8ad00c7e84825aba14",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5258,9 +5258,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/vcs/issues",
|
||||
"source": "https://github.com/utopia-php/vcs/tree/2.0.1"
|
||||
"source": "https://github.com/utopia-php/vcs/tree/2.0.0"
|
||||
},
|
||||
"time": "2026-02-27T12:18:49+00:00"
|
||||
"time": "2026-02-25T11:36:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/websocket",
|
||||
@@ -5438,16 +5438,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "1.11.6",
|
||||
"version": "1.11.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "f80e302d000cdc2f98b4bb5ff2fc3bd0bdff7b38"
|
||||
"reference": "6ff411f26f2750eea05c7598c14bb3a2ada898cb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/f80e302d000cdc2f98b4bb5ff2fc3bd0bdff7b38",
|
||||
"reference": "f80e302d000cdc2f98b4bb5ff2fc3bd0bdff7b38",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6ff411f26f2750eea05c7598c14bb3a2ada898cb",
|
||||
"reference": "6ff411f26f2750eea05c7598c14bb3a2ada898cb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5483,22 +5483,22 @@
|
||||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.11.6"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.11.1"
|
||||
},
|
||||
"time": "2026-03-09T07:12:51+00:00"
|
||||
"time": "2026-02-25T07:15:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brianium/paratest",
|
||||
"version": "v7.19.1",
|
||||
"version": "v7.19.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paratestphp/paratest.git",
|
||||
"reference": "95b03194f4cdf5c83175ceead673e21cb66465e7"
|
||||
"reference": "7c6c29af7c4b406b49ce0c6b0a3a81d3684474e6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/95b03194f4cdf5c83175ceead673e21cb66465e7",
|
||||
"reference": "95b03194f4cdf5c83175ceead673e21cb66465e7",
|
||||
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/7c6c29af7c4b406b49ce0c6b0a3a81d3684474e6",
|
||||
"reference": "7c6c29af7c4b406b49ce0c6b0a3a81d3684474e6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5512,7 +5512,7 @@
|
||||
"phpunit/php-code-coverage": "^12.5.3 || ^13.0.1",
|
||||
"phpunit/php-file-iterator": "^6.0.1 || ^7",
|
||||
"phpunit/php-timer": "^8 || ^9",
|
||||
"phpunit/phpunit": "^12.5.14 || ^13.0.5",
|
||||
"phpunit/phpunit": "^12.5.9 || ^13",
|
||||
"sebastian/environment": "^8.0.3 || ^9",
|
||||
"symfony/console": "^7.4.4 || ^8.0.4",
|
||||
"symfony/process": "^7.4.5 || ^8.0.5"
|
||||
@@ -5522,10 +5522,10 @@
|
||||
"ext-pcntl": "*",
|
||||
"ext-pcov": "*",
|
||||
"ext-posix": "*",
|
||||
"phpstan/phpstan": "^2.1.40",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.4",
|
||||
"phpstan/phpstan-phpunit": "^2.0.16",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.10",
|
||||
"phpstan/phpstan": "^2.1.38",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.3",
|
||||
"phpstan/phpstan-phpunit": "^2.0.12",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.8",
|
||||
"symfony/filesystem": "^7.4.0 || ^8.0.1"
|
||||
},
|
||||
"bin": [
|
||||
@@ -5566,7 +5566,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/paratestphp/paratest/issues",
|
||||
"source": "https://github.com/paratestphp/paratest/tree/v7.19.1"
|
||||
"source": "https://github.com/paratestphp/paratest/tree/v7.19.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5578,7 +5578,7 @@
|
||||
"type": "paypal"
|
||||
}
|
||||
],
|
||||
"time": "2026-02-25T14:53:45+00:00"
|
||||
"time": "2026-02-06T10:53:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "czproject/git-php",
|
||||
@@ -6398,16 +6398,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpbench/phpbench",
|
||||
"version": "1.5.1",
|
||||
"version": "1.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpbench/phpbench.git",
|
||||
"reference": "9a28fd0833f11171b949843c6fd663eb69b6d14c"
|
||||
"reference": "b641dde59d969ea42eed70a39f9b51950bc96878"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpbench/phpbench/zipball/9a28fd0833f11171b949843c6fd663eb69b6d14c",
|
||||
"reference": "9a28fd0833f11171b949843c6fd663eb69b6d14c",
|
||||
"url": "https://api.github.com/repos/phpbench/phpbench/zipball/b641dde59d969ea42eed70a39f9b51950bc96878",
|
||||
"reference": "b641dde59d969ea42eed70a39f9b51950bc96878",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6418,7 +6418,7 @@
|
||||
"ext-reflection": "*",
|
||||
"ext-spl": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"php": "^8.2",
|
||||
"php": "^8.1",
|
||||
"phpbench/container": "^2.2",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0",
|
||||
"seld/jsonlint": "^1.1",
|
||||
@@ -6438,9 +6438,8 @@
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^11.5",
|
||||
"phpunit/phpunit": "^10.4 || ^11.0",
|
||||
"rector/rector": "^1.2",
|
||||
"sebastian/exporter": "^6.3.2",
|
||||
"symfony/error-handler": "^6.1 || ^7.0 || ^8.0",
|
||||
"symfony/var-dumper": "^6.1 || ^7.0 || ^8.0"
|
||||
},
|
||||
@@ -6485,7 +6484,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpbench/phpbench/issues",
|
||||
"source": "https://github.com/phpbench/phpbench/tree/1.5.1"
|
||||
"source": "https://github.com/phpbench/phpbench/tree/1.4.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6493,15 +6492,15 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-05T08:18:58+00:00"
|
||||
"time": "2025-11-06T19:07:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.12.33",
|
||||
"version": "1.12.32",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/37982d6fc7cbb746dda7773530cda557cdf119e1",
|
||||
"reference": "37982d6fc7cbb746dda7773530cda557cdf119e1",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8",
|
||||
"reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6546,7 +6545,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-02-28T20:30:03+00:00"
|
||||
"time": "2025-09-30T10:16:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@@ -8096,16 +8095,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v8.0.7",
|
||||
"version": "v8.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a"
|
||||
"reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
|
||||
"reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/ace03c4cf9805080ff40cbeec69fca180c339a3b",
|
||||
"reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8162,7 +8161,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v8.0.7"
|
||||
"source": "https://github.com/symfony/console/tree/v8.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8182,20 +8181,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-06T14:06:22+00:00"
|
||||
"time": "2026-01-13T13:06:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v8.0.6",
|
||||
"version": "v8.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "7bf9162d7a0dff98d079b72948508fa48018a770"
|
||||
"reference": "d937d400b980523dc9ee946bb69972b5e619058d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/7bf9162d7a0dff98d079b72948508fa48018a770",
|
||||
"reference": "7bf9162d7a0dff98d079b72948508fa48018a770",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d",
|
||||
"reference": "d937d400b980523dc9ee946bb69972b5e619058d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8232,7 +8231,7 @@
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v8.0.6"
|
||||
"source": "https://github.com/symfony/filesystem/tree/v8.0.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8252,20 +8251,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-02-25T16:59:43+00:00"
|
||||
"time": "2025-12-01T09:13:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v8.0.6",
|
||||
"version": "v8.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c"
|
||||
"reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/441404f09a54de6d1bd6ad219e088cdf4c91f97c",
|
||||
"reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/8bd576e97c67d45941365bf824e18dc8538e6eb0",
|
||||
"reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8300,7 +8299,7 @@
|
||||
"description": "Finds files and directories via an intuitive fluent interface",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/finder/tree/v8.0.6"
|
||||
"source": "https://github.com/symfony/finder/tree/v8.0.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8320,7 +8319,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-29T09:41:02+00:00"
|
||||
"time": "2026-01-26T15:08:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
@@ -8790,16 +8789,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v8.0.6",
|
||||
"version": "v8.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4"
|
||||
"reference": "758b372d6882506821ed666032e43020c4f57194"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
|
||||
"reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194",
|
||||
"reference": "758b372d6882506821ed666032e43020c4f57194",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8856,7 +8855,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v8.0.6"
|
||||
"source": "https://github.com/symfony/string/tree/v8.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8876,7 +8875,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-02-09T10:14:57+00:00"
|
||||
"time": "2026-01-12T12:37:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "textalk/websocket",
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
# Dev tools for local development only.
|
||||
# This file is automatically loaded by `docker compose` alongside docker-compose.yml.
|
||||
# CI sets COMPOSE_FILE=docker-compose.yml explicitly, so these services are excluded from test runs.
|
||||
|
||||
services:
|
||||
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
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
container_name: appwrite-adminer
|
||||
restart: always
|
||||
ports:
|
||||
- 9506:8080
|
||||
networks:
|
||||
- appwrite
|
||||
- gateway
|
||||
environment:
|
||||
- ADMINER_DESIGN=pepa-linha
|
||||
- ADMINER_DEFAULT_SERVER=mariadb
|
||||
- ADMINER_DEFAULT_USERNAME=root
|
||||
- ADMINER_DEFAULT_PASSWORD=rootsecretpassword
|
||||
- ADMINER_DEFAULT_DB=appwrite
|
||||
configs:
|
||||
- source: adminer-index.php
|
||||
target: /var/www/html/index.php
|
||||
mode: 0755
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.constraint-label-stack=appwrite"
|
||||
- "traefik.docker.network=gateway"
|
||||
- "traefik.http.services.appwrite_adminer.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.appwrite_adminer_http.entrypoints=appwrite_web"
|
||||
- "traefik.http.routers.appwrite_adminer_http.rule=Host(`mysql.localhost`)"
|
||||
- "traefik.http.routers.appwrite_adminer_http.service=appwrite_adminer"
|
||||
- "traefik.http.routers.appwrite_adminer_https.entrypoints=appwrite_websecure"
|
||||
- "traefik.http.routers.appwrite_adminer_https.rule=Host(`mysql.localhost`)"
|
||||
- "traefik.http.routers.appwrite_adminer_https.service=appwrite_adminer"
|
||||
- "traefik.http.routers.appwrite_adminer_https.tls=true"
|
||||
|
||||
redis-insight:
|
||||
image: redis/redisinsight:latest
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
- gateway
|
||||
environment:
|
||||
- RI_PRE_SETUP_DATABASES_PATH=/mnt/connections.json
|
||||
configs:
|
||||
- source: redisinsight-connections.json
|
||||
target: /mnt/connections.json
|
||||
mode: 0755
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.constraint-label-stack=appwrite"
|
||||
- "traefik.docker.network=gateway"
|
||||
- "traefik.http.services.appwrite_redisinsight.loadbalancer.server.port=5540"
|
||||
- "traefik.http.routers.appwrite_redisinsight_http.entrypoints=appwrite_web"
|
||||
- "traefik.http.routers.appwrite_redisinsight_http.rule=Host(`redis.localhost`)"
|
||||
- "traefik.http.routers.appwrite_redisinsight_http.service=appwrite_redisinsight"
|
||||
- "traefik.http.routers.appwrite_redisinsight_https.entrypoints=appwrite_websecure"
|
||||
- "traefik.http.routers.appwrite_redisinsight_https.rule=Host(`redis.localhost`)"
|
||||
- "traefik.http.routers.appwrite_redisinsight_https.service=appwrite_redisinsight"
|
||||
- "traefik.http.routers.appwrite_redisinsight_https.tls=true"
|
||||
ports:
|
||||
- "8081:5540"
|
||||
|
||||
graphql-explorer:
|
||||
container_name: appwrite-graphql-explorer
|
||||
image: appwrite/altair:0.3.0
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
ports:
|
||||
- "9509:3000"
|
||||
environment:
|
||||
- SERVER_URL=http://localhost/v1/graphql
|
||||
|
||||
configs:
|
||||
redisinsight-connections.json:
|
||||
content: |
|
||||
[
|
||||
{
|
||||
"compressor": "NONE",
|
||||
"id": "104dc90a-21ef-4d5e-8912-b30baabb152f",
|
||||
"host": "redis",
|
||||
"port": 6379,
|
||||
"name": "redis:6379",
|
||||
"db": 0,
|
||||
"username": "default",
|
||||
"password": null,
|
||||
"connectionType": "STANDALONE",
|
||||
"nameFromProvider": null,
|
||||
"provider": "REDIS",
|
||||
"lastConnection": "2025-10-16T09:22:02.591Z",
|
||||
"modules": [
|
||||
{
|
||||
"name": "ReJSON",
|
||||
"version": 20808,
|
||||
"semanticVersion": "2.8.8"
|
||||
},
|
||||
{
|
||||
"name": "search",
|
||||
"version": 21015,
|
||||
"semanticVersion": "2.10.15"
|
||||
}
|
||||
],
|
||||
"tls": false,
|
||||
"tlsServername": null,
|
||||
"verifyServerCert": null,
|
||||
"caCert": null,
|
||||
"clientCert": null,
|
||||
"ssh": false,
|
||||
"sshOptions": null,
|
||||
"forceStandalone": false,
|
||||
"tags": []
|
||||
}
|
||||
]
|
||||
|
||||
adminer-index.php:
|
||||
content: |
|
||||
<?php
|
||||
if(!count($$_GET)) {
|
||||
$$_POST['auth'] = [
|
||||
'server' => $$_ENV['ADMINER_DEFAULT_SERVER'],
|
||||
'driver' => 'server', /* seems to autodetect the driver from server settings */
|
||||
'username' => $$_ENV['ADMINER_DEFAULT_USERNAME'],
|
||||
'password' => $$_ENV['ADMINER_DEFAULT_PASSWORD'],
|
||||
'db' => $$_ENV['ADMINER_DEFAULT_DB'],
|
||||
];
|
||||
}
|
||||
include './adminer.php';
|
||||
+58
-172
@@ -10,6 +10,15 @@ x-logging: &x-logging
|
||||
max-file: "5"
|
||||
max-size: "10m"
|
||||
|
||||
x-build: &x-build
|
||||
build:
|
||||
context: .
|
||||
target: development
|
||||
args:
|
||||
DEBUG: false
|
||||
TESTING: true
|
||||
VERSION: dev
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:3.6
|
||||
@@ -50,15 +59,13 @@ services:
|
||||
|
||||
appwrite:
|
||||
container_name: appwrite
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
image: appwrite-dev
|
||||
build:
|
||||
context: .
|
||||
target: development
|
||||
args:
|
||||
DEBUG: false
|
||||
TESTING: true
|
||||
VERSION: dev
|
||||
healthcheck:
|
||||
test: ["CMD", "doctor"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
ports:
|
||||
- 9501:80
|
||||
networks:
|
||||
@@ -101,10 +108,10 @@ services:
|
||||
- ./dev:/usr/src/code/dev
|
||||
|
||||
depends_on:
|
||||
- ${_APP_DB_HOST:-mongodb}
|
||||
- redis
|
||||
- coredns
|
||||
# - clamav
|
||||
redis:
|
||||
condition: service_healthy
|
||||
coredns:
|
||||
condition: service_started
|
||||
entrypoint:
|
||||
- php
|
||||
- -e
|
||||
@@ -260,7 +267,7 @@ services:
|
||||
|
||||
appwrite-realtime:
|
||||
entrypoint: realtime
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-realtime
|
||||
image: appwrite-dev
|
||||
restart: unless-stopped
|
||||
@@ -312,7 +319,7 @@ services:
|
||||
|
||||
appwrite-worker-audits:
|
||||
entrypoint: worker-audits
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-audits
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -343,7 +350,7 @@ services:
|
||||
|
||||
appwrite-worker-webhooks:
|
||||
entrypoint: worker-webhooks
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-webhooks
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -378,7 +385,7 @@ services:
|
||||
|
||||
appwrite-worker-deletes:
|
||||
entrypoint: worker-deletes
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-deletes
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -443,7 +450,7 @@ services:
|
||||
|
||||
appwrite-worker-databases:
|
||||
entrypoint: worker-databases
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-databases
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -476,7 +483,7 @@ services:
|
||||
|
||||
appwrite-worker-builds:
|
||||
entrypoint: worker-builds
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-builds
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -551,7 +558,7 @@ services:
|
||||
|
||||
appwrite-worker-screenshots:
|
||||
entrypoint: worker-screenshots
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-screenshots
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -614,7 +621,7 @@ services:
|
||||
|
||||
appwrite-worker-certificates:
|
||||
entrypoint: worker-certificates
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-certificates
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -656,7 +663,7 @@ services:
|
||||
|
||||
appwrite-worker-executions:
|
||||
entrypoint: worker-executions
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-executions
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -686,7 +693,7 @@ services:
|
||||
|
||||
appwrite-worker-functions:
|
||||
entrypoint: worker-functions
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-functions
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -730,7 +737,7 @@ services:
|
||||
|
||||
appwrite-worker-mails:
|
||||
entrypoint: worker-mails
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-mails
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -772,7 +779,7 @@ services:
|
||||
|
||||
appwrite-worker-messaging:
|
||||
entrypoint: worker-messaging
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-messaging
|
||||
restart: unless-stopped
|
||||
image: appwrite-dev
|
||||
@@ -829,7 +836,7 @@ services:
|
||||
|
||||
appwrite-worker-migrations:
|
||||
entrypoint: worker-migrations
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-migrations
|
||||
restart: unless-stopped
|
||||
image: appwrite-dev
|
||||
@@ -874,7 +881,7 @@ services:
|
||||
|
||||
appwrite-task-maintenance:
|
||||
entrypoint: maintenance
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-task-maintenance
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -920,7 +927,7 @@ services:
|
||||
|
||||
appwrite-task-interval:
|
||||
entrypoint: interval
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-task-interval
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -961,7 +968,7 @@ services:
|
||||
appwrite-task-stats-resources:
|
||||
container_name: appwrite-task-stats-resources
|
||||
entrypoint: stats-resources
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -993,7 +1000,7 @@ services:
|
||||
|
||||
appwrite-worker-stats-resources:
|
||||
entrypoint: worker-stats-resources
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-stats-resources
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -1026,7 +1033,7 @@ services:
|
||||
|
||||
appwrite-worker-stats-usage:
|
||||
entrypoint: worker-stats-usage
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-worker-stats-usage
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -1059,7 +1066,7 @@ services:
|
||||
|
||||
appwrite-task-scheduler-functions:
|
||||
entrypoint: schedule-functions
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-task-scheduler-functions
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -1089,7 +1096,7 @@ services:
|
||||
|
||||
appwrite-task-scheduler-executions:
|
||||
entrypoint: schedule-executions
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-task-scheduler-executions
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -1118,7 +1125,7 @@ services:
|
||||
|
||||
appwrite-task-scheduler-messages:
|
||||
entrypoint: schedule-messages
|
||||
<<: *x-logging
|
||||
<<: [*x-logging, *x-build]
|
||||
container_name: appwrite-task-scheduler-messages
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
@@ -1237,6 +1244,11 @@ services:
|
||||
- MYSQL_PASSWORD=${_APP_DB_PASS}
|
||||
- MARIADB_AUTO_UPGRADE=1
|
||||
command: "mysqld --innodb-flush-method=fsync"
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
mongodb:
|
||||
profiles: ["mongodb"]
|
||||
@@ -1275,20 +1287,7 @@ services:
|
||||
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"]
|
||||
@@ -1309,6 +1308,11 @@ services:
|
||||
- POSTGRES_USER=${_APP_DB_USER}
|
||||
- POSTGRES_PASSWORD=${_APP_DB_PASS}
|
||||
command: "postgres"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${_APP_DB_USER}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
redis:
|
||||
image: redis:7.4.7-alpine
|
||||
@@ -1325,6 +1329,11 @@ services:
|
||||
- appwrite
|
||||
volumes:
|
||||
- appwrite-redis:/data:rw
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
coredns: # DNS server for testing purposes (Proxy APIs)
|
||||
image: coredns/coredns:1.12.4
|
||||
@@ -1396,78 +1405,8 @@ services:
|
||||
networks:
|
||||
- appwrite
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
container_name: appwrite-adminer
|
||||
<<: *x-logging
|
||||
restart: always
|
||||
ports:
|
||||
- 9506:8080
|
||||
networks:
|
||||
- appwrite
|
||||
- gateway
|
||||
environment:
|
||||
- ADMINER_DESIGN=pepa-linha
|
||||
- ADMINER_DEFAULT_SERVER=mariadb
|
||||
- ADMINER_DEFAULT_USERNAME=root
|
||||
- ADMINER_DEFAULT_PASSWORD=rootsecretpassword
|
||||
- ADMINER_DEFAULT_DB=appwrite
|
||||
configs:
|
||||
- source: adminer-index.php
|
||||
target: /var/www/html/index.php
|
||||
mode: 0755
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.constraint-label-stack=appwrite"
|
||||
- "traefik.docker.network=gateway"
|
||||
- "traefik.http.services.appwrite_adminer.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.appwrite_adminer_http.entrypoints=appwrite_web"
|
||||
- "traefik.http.routers.appwrite_adminer_http.rule=Host(`mysql.localhost`)"
|
||||
- "traefik.http.routers.appwrite_adminer_http.service=appwrite_adminer"
|
||||
- "traefik.http.routers.appwrite_adminer_https.entrypoints=appwrite_websecure"
|
||||
- "traefik.http.routers.appwrite_adminer_https.rule=Host(`mysql.localhost`)"
|
||||
- "traefik.http.routers.appwrite_adminer_https.service=appwrite_adminer"
|
||||
- "traefik.http.routers.appwrite_adminer_https.tls=true"
|
||||
|
||||
redis-insight:
|
||||
image: redis/redisinsight:latest
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
- gateway
|
||||
environment:
|
||||
- RI_PRE_SETUP_DATABASES_PATH=/mnt/connections.json
|
||||
configs:
|
||||
- source: redisinsight-connections.json
|
||||
target: /mnt/connections.json
|
||||
mode: 0755
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.constraint-label-stack=appwrite"
|
||||
- "traefik.docker.network=gateway"
|
||||
- "traefik.http.services.appwrite_redisinsight.loadbalancer.server.port=5540"
|
||||
- "traefik.http.routers.appwrite_redisinsight_http.entrypoints=appwrite_web"
|
||||
- "traefik.http.routers.appwrite_redisinsight_http.rule=Host(`redis.localhost`)"
|
||||
- "traefik.http.routers.appwrite_redisinsight_http.service=appwrite_redisinsight"
|
||||
- "traefik.http.routers.appwrite_redisinsight_https.entrypoints=appwrite_websecure"
|
||||
- "traefik.http.routers.appwrite_redisinsight_https.rule=Host(`redis.localhost`)"
|
||||
- "traefik.http.routers.appwrite_redisinsight_https.service=appwrite_redisinsight"
|
||||
- "traefik.http.routers.appwrite_redisinsight_https.tls=true"
|
||||
ports:
|
||||
- "8081:5540"
|
||||
|
||||
graphql-explorer:
|
||||
container_name: appwrite-graphql-explorer
|
||||
image: appwrite/altair:0.3.0
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
ports:
|
||||
- "9509:3000"
|
||||
environment:
|
||||
- SERVER_URL=http://localhost/v1/graphql
|
||||
|
||||
# Dev Tools End ------------------------------------------------------------------------------------------
|
||||
# Dev tools (adminer, redis-insight, mongo-express, graphql-explorer)
|
||||
# are defined in docker-compose.override.yml
|
||||
|
||||
networks:
|
||||
gateway:
|
||||
@@ -1480,60 +1419,7 @@ networks:
|
||||
runtimes:
|
||||
name: runtimes
|
||||
|
||||
configs:
|
||||
redisinsight-connections.json:
|
||||
content: |
|
||||
[
|
||||
{
|
||||
"compressor": "NONE",
|
||||
"id": "104dc90a-21ef-4d5e-8912-b30baabb152f",
|
||||
"host": "redis",
|
||||
"port": 6379,
|
||||
"name": "redis:6379",
|
||||
"db": 0,
|
||||
"username": "default",
|
||||
"password": null,
|
||||
"connectionType": "STANDALONE",
|
||||
"nameFromProvider": null,
|
||||
"provider": "REDIS",
|
||||
"lastConnection": "2025-10-16T09:22:02.591Z",
|
||||
"modules": [
|
||||
{
|
||||
"name": "ReJSON",
|
||||
"version": 20808,
|
||||
"semanticVersion": "2.8.8"
|
||||
},
|
||||
{
|
||||
"name": "search",
|
||||
"version": 21015,
|
||||
"semanticVersion": "2.10.15"
|
||||
}
|
||||
],
|
||||
"tls": false,
|
||||
"tlsServername": null,
|
||||
"verifyServerCert": null,
|
||||
"caCert": null,
|
||||
"clientCert": null,
|
||||
"ssh": false,
|
||||
"sshOptions": null,
|
||||
"forceStandalone": false,
|
||||
"tags": []
|
||||
}
|
||||
]
|
||||
|
||||
adminer-index.php:
|
||||
content: |
|
||||
<?php
|
||||
if(!count($$_GET)) {
|
||||
$$_POST['auth'] = [
|
||||
'server' => $$_ENV['ADMINER_DEFAULT_SERVER'],
|
||||
'driver' => 'server', /* seems to autodetect the driver from server settings */
|
||||
'username' => $$_ENV['ADMINER_DEFAULT_USERNAME'],
|
||||
'password' => $$_ENV['ADMINER_DEFAULT_PASSWORD'],
|
||||
'db' => $$_ENV['ADMINER_DEFAULT_DB'],
|
||||
];
|
||||
}
|
||||
include './adminer.php';
|
||||
|
||||
volumes:
|
||||
appwrite-mariadb:
|
||||
|
||||
@@ -214,6 +214,7 @@ class Mails extends Action
|
||||
$mail->Password = $password;
|
||||
$mail->SMTPSecure = $smtp['secure'];
|
||||
$mail->SMTPAutoTLS = false;
|
||||
$mail->SMTPKeepAlive = true;
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->Timeout = 10; /* Connection timeout */
|
||||
$mail->getSMTPInstance()->Timelimit = 30; /* Timeout for each individual SMTP command (e.g. HELO, EHLO, etc.) */
|
||||
|
||||
@@ -317,6 +317,16 @@ class Migrations extends Action
|
||||
'sites.write',
|
||||
'tokens.read',
|
||||
'tokens.write',
|
||||
'providers.read',
|
||||
'providers.write',
|
||||
'topics.read',
|
||||
'topics.write',
|
||||
'subscribers.read',
|
||||
'subscribers.write',
|
||||
'messages.read',
|
||||
'messages.write',
|
||||
'targets.read',
|
||||
'targets.write',
|
||||
]
|
||||
]);
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ class StatsUsage extends Action
|
||||
}
|
||||
|
||||
$this->stats[$projectId]['project'] = $project;
|
||||
$this->stats[$projectId]['receivedAt'] = DateTime::now();
|
||||
$this->stats[$projectId]['receivedAt'] = DateTime::format(new \DateTime('@' . $message->getTimestamp()));
|
||||
foreach ($payload['metrics'] ?? [] as $metric) {
|
||||
$this->keys++;
|
||||
if (!isset($this->stats[$projectId]['keys'][$metric['key']])) {
|
||||
|
||||
@@ -59,6 +59,30 @@ class MigrationReport extends Model
|
||||
'default' => 0,
|
||||
'example' => 5,
|
||||
])
|
||||
->addRule(Resource::TYPE_PROVIDER, [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Number of providers to be migrated.',
|
||||
'default' => 0,
|
||||
'example' => 5,
|
||||
])
|
||||
->addRule(Resource::TYPE_TOPIC, [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Number of topics to be migrated.',
|
||||
'default' => 0,
|
||||
'example' => 10,
|
||||
])
|
||||
->addRule(Resource::TYPE_SUBSCRIBER, [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Number of subscribers to be migrated.',
|
||||
'default' => 0,
|
||||
'example' => 100,
|
||||
])
|
||||
->addRule(Resource::TYPE_MESSAGE, [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Number of messages to be migrated.',
|
||||
'default' => 0,
|
||||
'example' => 50,
|
||||
])
|
||||
->addRule('size', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Size of files to be migrated in mb.',
|
||||
|
||||
@@ -251,7 +251,7 @@ class Comment
|
||||
$json = \base64_decode($state);
|
||||
|
||||
$builds = \json_decode($json, true);
|
||||
$this->builds = $builds;
|
||||
$this->builds = \is_array($builds) ? $builds : [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -686,7 +686,7 @@ class FunctionsConsoleClientTest extends Scope
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$code = Console::execute("docker exec appwrite time-travel --projectId={$this->getProject()['$id']} --resourceType=deployment --resourceId={$deploymentIdInactiveOld} --createdAt=2020-01-01T00:00:00Z", '', $stdout, $stderr);
|
||||
$code = Console::execute("docker exec appwrite task-time-travel --projectId={$this->getProject()['$id']} --resourceType=deployment --resourceId={$deploymentIdInactiveOld} --createdAt=2020-01-01T00:00:00Z", '', $stdout, $stderr);
|
||||
$this->assertSame(0, $code, "Time-travel command failed with code $code: $stderr ($stdout)");
|
||||
|
||||
$stdout = '';
|
||||
|
||||
@@ -1694,4 +1694,842 @@ trait MigrationsBase
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Messaging
|
||||
*/
|
||||
public function testAppwriteMigrationMessagingProvider(): void
|
||||
{
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/sendgrid', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Migration Sendgrid',
|
||||
'apiKey' => 'my-apikey',
|
||||
'from' => 'migration@test.com',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $provider['headers']['status-code']);
|
||||
$this->assertNotEmpty($provider['body']['$id']);
|
||||
|
||||
$providerId = $provider['body']['$id'];
|
||||
|
||||
$result = $this->performMigrationSync([
|
||||
'resources' => [
|
||||
Resource::TYPE_PROVIDER,
|
||||
],
|
||||
'endpoint' => $this->webEndpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('completed', $result['status']);
|
||||
$this->assertEquals([Resource::TYPE_PROVIDER], $result['resources']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_PROVIDER, $result['statusCounters']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_PROVIDER]['error']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_PROVIDER]['pending']);
|
||||
$this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_PROVIDER]['success']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_PROVIDER]['processing']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_PROVIDER]['warning']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($providerId, $response['body']['$id']);
|
||||
$this->assertEquals('Migration Sendgrid', $response['body']['name']);
|
||||
$this->assertEquals('email', $response['body']['type']);
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testAppwriteMigrationMessagingProviderSMTP(): void
|
||||
{
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/smtp', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Migration SMTP',
|
||||
'host' => 'smtp.test.com',
|
||||
'port' => 587,
|
||||
'from' => 'migration-smtp@test.com',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $provider['headers']['status-code']);
|
||||
$providerId = $provider['body']['$id'];
|
||||
|
||||
$result = $this->performMigrationSync([
|
||||
'resources' => [
|
||||
Resource::TYPE_PROVIDER,
|
||||
],
|
||||
'endpoint' => $this->webEndpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('completed', $result['status']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_PROVIDER, $result['statusCounters']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_PROVIDER]['error']);
|
||||
$this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_PROVIDER]['success']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($providerId, $response['body']['$id']);
|
||||
$this->assertEquals('Migration SMTP', $response['body']['name']);
|
||||
$this->assertEquals('email', $response['body']['type']);
|
||||
$this->assertEquals('smtp', $response['body']['provider']);
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testAppwriteMigrationMessagingProviderTwilio(): void
|
||||
{
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/twilio', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Migration Twilio',
|
||||
'from' => '+15551234567',
|
||||
'accountSid' => 'test-account-sid',
|
||||
'authToken' => 'test-auth-token',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $provider['headers']['status-code']);
|
||||
$providerId = $provider['body']['$id'];
|
||||
|
||||
$result = $this->performMigrationSync([
|
||||
'resources' => [
|
||||
Resource::TYPE_PROVIDER,
|
||||
],
|
||||
'endpoint' => $this->webEndpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('completed', $result['status']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_PROVIDER, $result['statusCounters']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_PROVIDER]['error']);
|
||||
$this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_PROVIDER]['success']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($providerId, $response['body']['$id']);
|
||||
$this->assertEquals('Migration Twilio', $response['body']['name']);
|
||||
$this->assertEquals('sms', $response['body']['type']);
|
||||
$this->assertEquals('twilio', $response['body']['provider']);
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testAppwriteMigrationMessagingTopic(): void
|
||||
{
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/sendgrid', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Migration Sendgrid Topic',
|
||||
'apiKey' => 'my-apikey',
|
||||
'from' => 'migration-topic@test.com',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $provider['headers']['status-code']);
|
||||
$providerId = $provider['body']['$id'];
|
||||
|
||||
$topic = $this->client->call(Client::METHOD_POST, '/messaging/topics', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'topicId' => ID::unique(),
|
||||
'name' => 'Migration Topic',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $topic['headers']['status-code']);
|
||||
$this->assertNotEmpty($topic['body']['$id']);
|
||||
|
||||
$topicId = $topic['body']['$id'];
|
||||
|
||||
$result = $this->performMigrationSync([
|
||||
'resources' => [
|
||||
Resource::TYPE_PROVIDER,
|
||||
Resource::TYPE_TOPIC,
|
||||
],
|
||||
'endpoint' => $this->webEndpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('completed', $result['status']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_TOPIC, $result['statusCounters']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TOPIC]['error']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TOPIC]['pending']);
|
||||
$this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_TOPIC]['success']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TOPIC]['processing']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TOPIC]['warning']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topicId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($topicId, $response['body']['$id']);
|
||||
$this->assertEquals('Migration Topic', $response['body']['name']);
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testAppwriteMigrationMessagingSubscriber(): void
|
||||
{
|
||||
$user = $this->client->call(Client::METHOD_POST, '/users', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => uniqid() . '-migration-sub@test.com',
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $user['headers']['status-code']);
|
||||
$userId = $user['body']['$id'];
|
||||
$this->assertEquals(1, \count($user['body']['targets']));
|
||||
$targetId = $user['body']['targets'][0]['$id'];
|
||||
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/sendgrid', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Migration Sendgrid Subscriber',
|
||||
'apiKey' => 'my-apikey',
|
||||
'from' => uniqid() . '-migration-sub@test.com',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $provider['headers']['status-code']);
|
||||
$providerId = $provider['body']['$id'];
|
||||
|
||||
$topic = $this->client->call(Client::METHOD_POST, '/messaging/topics', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'topicId' => ID::unique(),
|
||||
'name' => 'Migration Subscriber Topic',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $topic['headers']['status-code']);
|
||||
$topicId = $topic['body']['$id'];
|
||||
|
||||
$subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topicId . '/subscribers', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'subscriberId' => ID::unique(),
|
||||
'targetId' => $targetId,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $subscriber['headers']['status-code']);
|
||||
|
||||
$result = $this->performMigrationSync([
|
||||
'resources' => [
|
||||
Resource::TYPE_USER,
|
||||
Resource::TYPE_PROVIDER,
|
||||
Resource::TYPE_TOPIC,
|
||||
Resource::TYPE_SUBSCRIBER,
|
||||
],
|
||||
'endpoint' => $this->webEndpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('completed', $result['status']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_SUBSCRIBER, $result['statusCounters']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_SUBSCRIBER]['error']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_SUBSCRIBER]['pending']);
|
||||
$this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_SUBSCRIBER]['success']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_SUBSCRIBER]['processing']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_SUBSCRIBER]['warning']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topicId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($topicId, $response['body']['$id']);
|
||||
$this->assertGreaterThanOrEqual(1, $response['body']['emailTotal']);
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testAppwriteMigrationMessagingMessage(): void
|
||||
{
|
||||
$this->getDestinationProject(true);
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/users', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => uniqid() . '-migration-msg@test.com',
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $user['headers']['status-code']);
|
||||
$userId = $user['body']['$id'];
|
||||
$this->assertEquals(1, \count($user['body']['targets']));
|
||||
$targetId = $user['body']['targets'][0]['$id'];
|
||||
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/sendgrid', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Migration Sendgrid Message',
|
||||
'apiKey' => 'my-apikey',
|
||||
'from' => 'migration-msg@test.com',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $provider['headers']['status-code']);
|
||||
$providerId = $provider['body']['$id'];
|
||||
|
||||
$topic = $this->client->call(Client::METHOD_POST, '/messaging/topics', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'topicId' => ID::unique(),
|
||||
'name' => 'Migration Message Topic',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $topic['headers']['status-code']);
|
||||
$topicId = $topic['body']['$id'];
|
||||
|
||||
$message = $this->client->call(Client::METHOD_POST, '/messaging/messages/email', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'messageId' => ID::unique(),
|
||||
'targets' => [$targetId],
|
||||
'topics' => [$topicId],
|
||||
'subject' => 'Migration Test Email',
|
||||
'content' => 'This is a migration test email',
|
||||
'draft' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $message['headers']['status-code']);
|
||||
$this->assertNotEmpty($message['body']['$id']);
|
||||
|
||||
$messageId = $message['body']['$id'];
|
||||
|
||||
$result = $this->performMigrationSync([
|
||||
'resources' => [
|
||||
Resource::TYPE_USER,
|
||||
Resource::TYPE_PROVIDER,
|
||||
Resource::TYPE_TOPIC,
|
||||
Resource::TYPE_SUBSCRIBER,
|
||||
Resource::TYPE_MESSAGE,
|
||||
],
|
||||
'endpoint' => $this->webEndpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('completed', $result['status']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_MESSAGE, $result['statusCounters']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MESSAGE]['error']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MESSAGE]['pending']);
|
||||
$this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_MESSAGE]['success']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MESSAGE]['processing']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MESSAGE]['warning']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $messageId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($messageId, $response['body']['$id']);
|
||||
$this->assertEquals('draft', $response['body']['status']);
|
||||
$this->assertEquals('Migration Test Email', $response['body']['data']['subject']);
|
||||
$this->assertEquals('This is a migration test email', $response['body']['data']['content']);
|
||||
$this->assertContains($topicId, $response['body']['topics']);
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/messages/' . $messageId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/messages/' . $messageId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testAppwriteMigrationMessagingSmsMessage(): void
|
||||
{
|
||||
$this->getDestinationProject(true);
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/users', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => uniqid() . '-migration-sms@test.com',
|
||||
'phone' => '+1' . str_pad((string) rand(200000000, 999999999), 10, '0', STR_PAD_LEFT),
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $user['headers']['status-code']);
|
||||
$userId = $user['body']['$id'];
|
||||
$this->assertGreaterThanOrEqual(1, \count($user['body']['targets']));
|
||||
|
||||
$smsTarget = null;
|
||||
foreach ($user['body']['targets'] as $target) {
|
||||
if ($target['providerType'] === 'sms') {
|
||||
$smsTarget = $target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertNotNull($smsTarget);
|
||||
$targetId = $smsTarget['$id'];
|
||||
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/twilio', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Migration Twilio SMS Msg',
|
||||
'from' => '+15559876543',
|
||||
'accountSid' => 'test-account-sid',
|
||||
'authToken' => 'test-auth-token',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $provider['headers']['status-code']);
|
||||
$providerId = $provider['body']['$id'];
|
||||
|
||||
$topic = $this->client->call(Client::METHOD_POST, '/messaging/topics', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'topicId' => ID::unique(),
|
||||
'name' => 'Migration SMS Topic',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $topic['headers']['status-code']);
|
||||
$topicId = $topic['body']['$id'];
|
||||
|
||||
$message = $this->client->call(Client::METHOD_POST, '/messaging/messages/sms', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'messageId' => ID::unique(),
|
||||
'targets' => [$targetId],
|
||||
'topics' => [$topicId],
|
||||
'content' => 'Migration SMS test content',
|
||||
'draft' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $message['headers']['status-code']);
|
||||
$messageId = $message['body']['$id'];
|
||||
|
||||
$result = $this->performMigrationSync([
|
||||
'resources' => [
|
||||
Resource::TYPE_USER,
|
||||
Resource::TYPE_PROVIDER,
|
||||
Resource::TYPE_TOPIC,
|
||||
Resource::TYPE_SUBSCRIBER,
|
||||
Resource::TYPE_MESSAGE,
|
||||
],
|
||||
'endpoint' => $this->webEndpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('completed', $result['status']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_MESSAGE, $result['statusCounters']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MESSAGE]['error']);
|
||||
$this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_MESSAGE]['success']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $messageId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($messageId, $response['body']['$id']);
|
||||
$this->assertEquals('draft', $response['body']['status']);
|
||||
$this->assertEquals('Migration SMS test content', $response['body']['data']['content']);
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/messages/' . $messageId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/messages/' . $messageId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testAppwriteMigrationMessagingScheduledMessage(): void
|
||||
{
|
||||
$this->getDestinationProject(true);
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/users', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => uniqid() . '-migration-sched@test.com',
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $user['headers']['status-code']);
|
||||
$userId = $user['body']['$id'];
|
||||
$targetId = $user['body']['targets'][0]['$id'];
|
||||
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/sendgrid', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Migration Sendgrid Scheduled',
|
||||
'apiKey' => 'my-apikey',
|
||||
'from' => 'migration-sched@test.com',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $provider['headers']['status-code']);
|
||||
$providerId = $provider['body']['$id'];
|
||||
|
||||
$topic = $this->client->call(Client::METHOD_POST, '/messaging/topics', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'topicId' => ID::unique(),
|
||||
'name' => 'Migration Scheduled Topic',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $topic['headers']['status-code']);
|
||||
$topicId = $topic['body']['$id'];
|
||||
|
||||
$subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topicId . '/subscribers', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'subscriberId' => ID::unique(),
|
||||
'targetId' => $targetId,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $subscriber['headers']['status-code']);
|
||||
|
||||
// Create a scheduled message with a future date using topics only
|
||||
// Direct targets use source IDs which won't resolve in the destination via API
|
||||
$futureDate = (new \DateTime('+1 year'))->format(\DateTime::ATOM);
|
||||
$message = $this->client->call(Client::METHOD_POST, '/messaging/messages/email', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'messageId' => ID::unique(),
|
||||
'topics' => [$topicId],
|
||||
'subject' => 'Migration Scheduled Email',
|
||||
'content' => 'This is a scheduled migration test email',
|
||||
'scheduledAt' => $futureDate,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $message['headers']['status-code']);
|
||||
$messageId = $message['body']['$id'];
|
||||
$this->assertEquals('scheduled', $message['body']['status']);
|
||||
|
||||
$result = $this->performMigrationSync([
|
||||
'resources' => [
|
||||
Resource::TYPE_USER,
|
||||
Resource::TYPE_PROVIDER,
|
||||
Resource::TYPE_TOPIC,
|
||||
Resource::TYPE_SUBSCRIBER,
|
||||
Resource::TYPE_MESSAGE,
|
||||
],
|
||||
'endpoint' => $this->webEndpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('completed', $result['status']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_MESSAGE, $result['statusCounters']);
|
||||
$this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MESSAGE]['error']);
|
||||
$this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_MESSAGE]['success']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $messageId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($messageId, $response['body']['$id']);
|
||||
$this->assertEquals('scheduled', $response['body']['status']);
|
||||
$this->assertEquals('Migration Scheduled Email', $response['body']['data']['subject']);
|
||||
$this->assertEquals(
|
||||
(new \DateTime($futureDate))->getTimestamp(),
|
||||
(new \DateTime($response['body']['scheduledAt']))->getTimestamp(),
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/messages/' . $messageId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/messages/' . $messageId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,12 +180,12 @@ class SitesConsoleClientTest extends Scope
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$code = Console::execute("docker exec appwrite-task-maintenance time-travel --projectId={$this->getProject()['$id']} --resourceType=deployment --resourceId={$deploymentIdInactiveOld} --createdAt=2020-01-01T00:00:00Z", '', $stdout, $stderr);
|
||||
$code = Console::execute("docker exec appwrite task-time-travel --projectId={$this->getProject()['$id']} --resourceType=deployment --resourceId={$deploymentIdInactiveOld} --createdAt=2020-01-01T00:00:00Z", '', $stdout, $stderr);
|
||||
$this->assertSame(0, $code, "Time-travel command failed with code $code: $stderr ($stdout)");
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$code = Console::execute("docker exec appwrite-task-maintenance maintenance --type=trigger", '', $stdout, $stderr);
|
||||
$code = Console::execute("docker exec appwrite maintenance --type=trigger", '', $stdout, $stderr);
|
||||
$this->assertSame(0, $code, "Maintenance command failed with code $code: $stderr ($stdout)");
|
||||
|
||||
$this->assertEventually(function () use ($siteId) {
|
||||
|
||||
@@ -1596,7 +1596,7 @@ trait UsersBase
|
||||
]);
|
||||
|
||||
$this->assertEquals($user['headers']['status-code'], 200);
|
||||
$this->assertEquals($user['body']['phone'], $updatedNumber);
|
||||
$this->assertEmpty($user['body']['phone'] ?? '');
|
||||
|
||||
$user = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -1604,7 +1604,7 @@ trait UsersBase
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($user['headers']['status-code'], 200);
|
||||
$this->assertEquals($user['body']['phone'], $updatedNumber);
|
||||
$this->assertEmpty($user['body']['phone'] ?? '');
|
||||
|
||||
$updatedNumber = "+910000000000"; //dummy number
|
||||
$user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/phone', array_merge([
|
||||
@@ -1648,6 +1648,58 @@ trait UsersBase
|
||||
static::$userNumberUpdated = true;
|
||||
}
|
||||
|
||||
public function testUpdateTwoUsersPhoneToEmpty(): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$headers = array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders());
|
||||
|
||||
// Create two users with distinct valid phone numbers
|
||||
$user1 = $this->client->call(Client::METHOD_POST, '/users', $headers, [
|
||||
'userId' => ID::unique(),
|
||||
'email' => 'user1-phone-empty-test@appwrite.io',
|
||||
'password' => 'password',
|
||||
'name' => 'User One',
|
||||
'phone' => '+16175551201',
|
||||
]);
|
||||
$this->assertEquals(201, $user1['headers']['status-code']);
|
||||
$this->assertEquals('+16175551201', $user1['body']['phone']);
|
||||
|
||||
$user2 = $this->client->call(Client::METHOD_POST, '/users', $headers, [
|
||||
'userId' => ID::unique(),
|
||||
'email' => 'user2-phone-empty-test@appwrite.io',
|
||||
'password' => 'password',
|
||||
'name' => 'User Two',
|
||||
'phone' => '+16175551202',
|
||||
]);
|
||||
$this->assertEquals(201, $user2['headers']['status-code']);
|
||||
$this->assertEquals('+16175551202', $user2['body']['phone']);
|
||||
|
||||
// Update first user's phone to empty - must succeed
|
||||
$response1 = $this->client->call(Client::METHOD_PATCH, '/users/' . $user1['body']['$id'] . '/phone', $headers, [
|
||||
'number' => '',
|
||||
]);
|
||||
$this->assertEquals(200, $response1['headers']['status-code'], 'First user phone should update to empty');
|
||||
$this->assertEmpty($response1['body']['phone'] ?? '');
|
||||
|
||||
// Update second user's phone to empty - must succeed (would fail with duplicate if empty was stored as '')
|
||||
$response2 = $this->client->call(Client::METHOD_PATCH, '/users/' . $user2['body']['$id'] . '/phone', $headers, [
|
||||
'number' => '',
|
||||
]);
|
||||
$this->assertEquals(200, $response2['headers']['status-code'], 'Second user phone should update to empty without duplicate error');
|
||||
$this->assertEmpty($response2['body']['phone'] ?? '');
|
||||
|
||||
// Verify both users have empty phone via GET
|
||||
$get1 = $this->client->call(Client::METHOD_GET, '/users/' . $user1['body']['$id'], $headers);
|
||||
$get2 = $this->client->call(Client::METHOD_GET, '/users/' . $user2['body']['$id'], $headers);
|
||||
$this->assertEquals(200, $get1['headers']['status-code']);
|
||||
$this->assertEquals(200, $get2['headers']['status-code']);
|
||||
$this->assertEmpty($get1['body']['phone'] ?? '');
|
||||
$this->assertEmpty($get2['body']['phone'] ?? '');
|
||||
}
|
||||
|
||||
public function testUpdateUserNumberSearch(): void
|
||||
{
|
||||
$data = $this->ensureUserNumberUpdated();
|
||||
|
||||
Reference in New Issue
Block a user