mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 17:08:45 +00:00
Merge branch '1.8.x' into feat-update-preview-url-in-vcs-controller
This commit is contained in:
commit
44950eac0b
38 changed files with 289 additions and 562 deletions
28
.github/workflows/tests.yml
vendored
28
.github/workflows/tests.yml
vendored
|
|
@ -8,7 +8,15 @@ env:
|
||||||
IMAGE: appwrite-dev
|
IMAGE: appwrite-dev
|
||||||
CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }}
|
CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
on: [ pull_request ]
|
on:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
response_format:
|
||||||
|
description: 'Response format version to test (e.g., 1.5.0, 1.4.0)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_database_changes:
|
check_database_changes:
|
||||||
|
|
@ -100,7 +108,10 @@ jobs:
|
||||||
run: docker compose exec -T appwrite vars
|
run: docker compose exec -T appwrite vars
|
||||||
|
|
||||||
- name: Run Unit Tests
|
- name: Run Unit Tests
|
||||||
run: docker compose exec appwrite test /usr/src/code/tests/unit
|
run: |
|
||||||
|
docker compose exec \
|
||||||
|
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
|
||||||
|
appwrite test /usr/src/code/tests/unit
|
||||||
|
|
||||||
e2e_general_test:
|
e2e_general_test:
|
||||||
name: E2E General Test
|
name: E2E General Test
|
||||||
|
|
@ -132,7 +143,10 @@ jobs:
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Run General Tests
|
- name: Run General Tests
|
||||||
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/General --debug
|
run: |
|
||||||
|
docker compose exec -T \
|
||||||
|
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
|
||||||
|
appwrite test /usr/src/code/tests/e2e/General --debug
|
||||||
|
|
||||||
- name: Failure Logs
|
- name: Failure Logs
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|
@ -208,6 +222,7 @@ jobs:
|
||||||
docker compose exec -T \
|
docker compose exec -T \
|
||||||
-e _APP_DATABASE_SHARED_TABLES \
|
-e _APP_DATABASE_SHARED_TABLES \
|
||||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
||||||
|
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
|
||||||
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
|
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
|
||||||
|
|
||||||
- name: Failure Logs
|
- name: Failure Logs
|
||||||
|
|
@ -296,6 +311,7 @@ jobs:
|
||||||
docker compose exec -T \
|
docker compose exec -T \
|
||||||
-e _APP_DATABASE_SHARED_TABLES \
|
-e _APP_DATABASE_SHARED_TABLES \
|
||||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
||||||
|
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
|
||||||
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
|
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
|
||||||
|
|
||||||
- name: Failure Logs
|
- name: Failure Logs
|
||||||
|
|
@ -337,6 +353,7 @@ jobs:
|
||||||
docker compose exec -T \
|
docker compose exec -T \
|
||||||
-e _APP_DATABASE_SHARED_TABLES \
|
-e _APP_DATABASE_SHARED_TABLES \
|
||||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
-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=devKeys
|
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
|
||||||
|
|
||||||
- name: Failure Logs
|
- name: Failure Logs
|
||||||
|
|
@ -392,6 +409,7 @@ jobs:
|
||||||
docker compose exec -T \
|
docker compose exec -T \
|
||||||
-e _APP_DATABASE_SHARED_TABLES \
|
-e _APP_DATABASE_SHARED_TABLES \
|
||||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
-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=devKeys
|
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
|
||||||
|
|
||||||
- name: Failure Logs
|
- name: Failure Logs
|
||||||
|
|
@ -434,6 +452,7 @@ jobs:
|
||||||
docker compose exec -T \
|
docker compose exec -T \
|
||||||
-e _APP_DATABASE_SHARED_TABLES \
|
-e _APP_DATABASE_SHARED_TABLES \
|
||||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
-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
|
appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots
|
||||||
|
|
||||||
- name: Failure Logs
|
- name: Failure Logs
|
||||||
|
|
@ -490,6 +509,7 @@ jobs:
|
||||||
docker compose exec -T \
|
docker compose exec -T \
|
||||||
-e _APP_DATABASE_SHARED_TABLES \
|
-e _APP_DATABASE_SHARED_TABLES \
|
||||||
-e _APP_DATABASE_SHARED_TABLES_V1 \
|
-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
|
appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots
|
||||||
|
|
||||||
- name: Failure Logs
|
- name: Failure Logs
|
||||||
|
|
@ -498,4 +518,4 @@ jobs:
|
||||||
echo "=== Appwrite Worker Builds Logs ==="
|
echo "=== Appwrite Worker Builds Logs ==="
|
||||||
docker compose logs appwrite-worker-builds
|
docker compose logs appwrite-worker-builds
|
||||||
echo "=== OpenRuntimes Executor Logs ==="
|
echo "=== OpenRuntimes Executor Logs ==="
|
||||||
docker compose logs openruntimes-executor
|
docker compose logs openruntimes-executor
|
||||||
|
|
@ -435,6 +435,11 @@ return [
|
||||||
'description' => 'The requested favicon could not be found.',
|
'description' => 'The requested favicon could not be found.',
|
||||||
'code' => 404,
|
'code' => 404,
|
||||||
],
|
],
|
||||||
|
Exception::AVATAR_SVG_SANITIZATION_FAILED => [
|
||||||
|
'name' => Exception::AVATAR_SVG_SANITIZATION_FAILED,
|
||||||
|
'description' => 'SVG sanitization failed.',
|
||||||
|
'code' => 400,
|
||||||
|
],
|
||||||
|
|
||||||
/** Storage */
|
/** Storage */
|
||||||
Exception::STORAGE_FILE_ALREADY_EXISTS => [
|
Exception::STORAGE_FILE_ALREADY_EXISTS => [
|
||||||
|
|
|
||||||
|
|
@ -9948,17 +9948,20 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Row automatically incrementing ID.",
|
"description": "Row automatically incrementing ID.",
|
||||||
"x-example": 1,
|
"x-example": 1,
|
||||||
"format": "int32"
|
"format": "int32",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$tableId": {
|
"$tableId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Table ID.",
|
"description": "Table ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$databaseId": {
|
"$databaseId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Database ID.",
|
"description": "Database ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$createdAt": {
|
"$createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
||||||
|
|
@ -47120,17 +47120,20 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Row automatically incrementing ID.",
|
"description": "Row automatically incrementing ID.",
|
||||||
"x-example": 1,
|
"x-example": 1,
|
||||||
"format": "int32"
|
"format": "int32",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$tableId": {
|
"$tableId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Table ID.",
|
"description": "Table ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$databaseId": {
|
"$databaseId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Database ID.",
|
"description": "Database ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$createdAt": {
|
"$createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
||||||
|
|
@ -35978,17 +35978,20 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Row automatically incrementing ID.",
|
"description": "Row automatically incrementing ID.",
|
||||||
"x-example": 1,
|
"x-example": 1,
|
||||||
"format": "int32"
|
"format": "int32",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$tableId": {
|
"$tableId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Table ID.",
|
"description": "Table ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$databaseId": {
|
"$databaseId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Database ID.",
|
"description": "Database ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$createdAt": {
|
"$createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
||||||
|
|
@ -9948,17 +9948,20 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Row automatically incrementing ID.",
|
"description": "Row automatically incrementing ID.",
|
||||||
"x-example": 1,
|
"x-example": 1,
|
||||||
"format": "int32"
|
"format": "int32",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$tableId": {
|
"$tableId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Table ID.",
|
"description": "Table ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$databaseId": {
|
"$databaseId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Database ID.",
|
"description": "Database ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$createdAt": {
|
"$createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
||||||
|
|
@ -47120,17 +47120,20 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Row automatically incrementing ID.",
|
"description": "Row automatically incrementing ID.",
|
||||||
"x-example": 1,
|
"x-example": 1,
|
||||||
"format": "int32"
|
"format": "int32",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$tableId": {
|
"$tableId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Table ID.",
|
"description": "Table ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$databaseId": {
|
"$databaseId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Database ID.",
|
"description": "Database ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$createdAt": {
|
"$createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
||||||
|
|
@ -35978,17 +35978,20 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Row automatically incrementing ID.",
|
"description": "Row automatically incrementing ID.",
|
||||||
"x-example": 1,
|
"x-example": 1,
|
||||||
"format": "int32"
|
"format": "int32",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$tableId": {
|
"$tableId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Table ID.",
|
"description": "Table ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$databaseId": {
|
"$databaseId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Database ID.",
|
"description": "Database ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$createdAt": {
|
"$createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
||||||
|
|
@ -9944,17 +9944,20 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Row automatically incrementing ID.",
|
"description": "Row automatically incrementing ID.",
|
||||||
"x-example": 1,
|
"x-example": 1,
|
||||||
"format": "int32"
|
"format": "int32",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$tableId": {
|
"$tableId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Table ID.",
|
"description": "Table ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$databaseId": {
|
"$databaseId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Database ID.",
|
"description": "Database ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$createdAt": {
|
"$createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
||||||
|
|
@ -47166,17 +47166,20 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Row automatically incrementing ID.",
|
"description": "Row automatically incrementing ID.",
|
||||||
"x-example": 1,
|
"x-example": 1,
|
||||||
"format": "int32"
|
"format": "int32",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$tableId": {
|
"$tableId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Table ID.",
|
"description": "Table ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$databaseId": {
|
"$databaseId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Database ID.",
|
"description": "Database ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$createdAt": {
|
"$createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
||||||
|
|
@ -36115,17 +36115,20 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Row automatically incrementing ID.",
|
"description": "Row automatically incrementing ID.",
|
||||||
"x-example": 1,
|
"x-example": 1,
|
||||||
"format": "int32"
|
"format": "int32",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$tableId": {
|
"$tableId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Table ID.",
|
"description": "Table ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$databaseId": {
|
"$databaseId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Database ID.",
|
"description": "Database ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$createdAt": {
|
"$createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
||||||
|
|
@ -9944,17 +9944,20 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Row automatically incrementing ID.",
|
"description": "Row automatically incrementing ID.",
|
||||||
"x-example": 1,
|
"x-example": 1,
|
||||||
"format": "int32"
|
"format": "int32",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$tableId": {
|
"$tableId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Table ID.",
|
"description": "Table ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$databaseId": {
|
"$databaseId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Database ID.",
|
"description": "Database ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$createdAt": {
|
"$createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
||||||
|
|
@ -47166,17 +47166,20 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Row automatically incrementing ID.",
|
"description": "Row automatically incrementing ID.",
|
||||||
"x-example": 1,
|
"x-example": 1,
|
||||||
"format": "int32"
|
"format": "int32",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$tableId": {
|
"$tableId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Table ID.",
|
"description": "Table ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$databaseId": {
|
"$databaseId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Database ID.",
|
"description": "Database ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$createdAt": {
|
"$createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
||||||
|
|
@ -36115,17 +36115,20 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Row automatically incrementing ID.",
|
"description": "Row automatically incrementing ID.",
|
||||||
"x-example": 1,
|
"x-example": 1,
|
||||||
"format": "int32"
|
"format": "int32",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$tableId": {
|
"$tableId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Table ID.",
|
"description": "Table ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$databaseId": {
|
"$databaseId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Database ID.",
|
"description": "Database ID.",
|
||||||
"x-example": "5e5ea5c15117e"
|
"x-example": "5e5ea5c15117e",
|
||||||
|
"readOnly": true
|
||||||
},
|
},
|
||||||
"$createdAt": {
|
"$createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
||||||
|
|
@ -474,7 +474,7 @@ App::get('/v1/avatars/favicon')
|
||||||
$sanitizer->minify(true);
|
$sanitizer->minify(true);
|
||||||
$cleanSvg = $sanitizer->sanitize($data);
|
$cleanSvg = $sanitizer->sanitize($data);
|
||||||
if ($cleanSvg === false) {
|
if ($cleanSvg === false) {
|
||||||
throw new \Exception('SVG sanitization failed');
|
throw new Exception(Exception::AVATAR_SVG_SANITIZATION_FAILED);
|
||||||
}
|
}
|
||||||
$response
|
$response
|
||||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||||
|
|
|
||||||
|
|
@ -3011,7 +3011,7 @@ App::post('/v1/messaging/messages/email')
|
||||||
->inject('project')
|
->inject('project')
|
||||||
->inject('queueForMessaging')
|
->inject('queueForMessaging')
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, array $cc, array $bcc, array $attachments, bool $draft, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
->action(function (string $messageId, string $subject, string $content, ?array $topics, ?array $users, ?array $targets, ?array $cc, ?array $bcc, ?array $attachments, bool $draft, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||||
$messageId = $messageId == 'unique()'
|
$messageId = $messageId == 'unique()'
|
||||||
? ID::unique()
|
? ID::unique()
|
||||||
: $messageId;
|
: $messageId;
|
||||||
|
|
@ -3184,7 +3184,7 @@ App::post('/v1/messaging/messages/sms')
|
||||||
->inject('project')
|
->inject('project')
|
||||||
->inject('queueForMessaging')
|
->inject('queueForMessaging')
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->action(function (string $messageId, string $content, array $topics, array $users, array $targets, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
->action(function (string $messageId, string $content, ?array $topics, ?array $users, ?array $targets, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||||
$messageId = $messageId == 'unique()'
|
$messageId = $messageId == 'unique()'
|
||||||
? ID::unique()
|
? ID::unique()
|
||||||
: $messageId;
|
: $messageId;
|
||||||
|
|
@ -3319,7 +3319,7 @@ App::post('/v1/messaging/messages/push')
|
||||||
->inject('project')
|
->inject('project')
|
||||||
->inject('queueForMessaging')
|
->inject('queueForMessaging')
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, int $badge, bool $draft, ?string $scheduledAt, bool $contentAvailable, bool $critical, string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
->action(function (string $messageId, string $title, string $body, ?array $topics, ?array $users, ?array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, int $badge, bool $draft, ?string $scheduledAt, bool $contentAvailable, bool $critical, string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||||
$messageId = $messageId == 'unique()'
|
$messageId = $messageId == 'unique()'
|
||||||
? ID::unique()
|
? ID::unique()
|
||||||
: $messageId;
|
: $messageId;
|
||||||
|
|
|
||||||
|
|
@ -1060,26 +1060,6 @@ App::init()
|
||||||
$response->addHeader('Access-Control-Allow-Origin', '*');
|
$response->addHeader('Access-Control-Allow-Origin', '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deprecation Warning
|
|
||||||
*/
|
|
||||||
/** @var \Appwrite\SDK\Method $sdk */
|
|
||||||
$sdk = $route->getLabel('sdk', false);
|
|
||||||
$deprecationWarning = 'This route is deprecated. See the updated documentation for improved compatibility and migration details.';
|
|
||||||
$sdkItems = is_array($sdk) ? $sdk : (!empty($sdk) ? [$sdk] : []);
|
|
||||||
if (!empty($sdkItems) && count($sdkItems) > 0) {
|
|
||||||
$allDeprecated = true;
|
|
||||||
foreach ($sdkItems as $sdkItem) {
|
|
||||||
if (!$sdkItem->isDeprecated()) {
|
|
||||||
$allDeprecated = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($allDeprecated) {
|
|
||||||
$warnings[] = $deprecationWarning;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($warnings)) {
|
if (!empty($warnings)) {
|
||||||
$response->addHeader('X-Appwrite-Warning', implode(';', $warnings));
|
$response->addHeader('X-Appwrite-Warning', implode(';', $warnings));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ use Utopia\Database\DateTime;
|
||||||
use Utopia\Database\Document;
|
use Utopia\Database\Document;
|
||||||
use Utopia\Database\Helpers\Role;
|
use Utopia\Database\Helpers\Role;
|
||||||
use Utopia\Database\Validator\Authorization;
|
use Utopia\Database\Validator\Authorization;
|
||||||
use Utopia\Queue\Broker\Pool as BrokerPool;
|
|
||||||
use Utopia\Queue\Publisher;
|
use Utopia\Queue\Publisher;
|
||||||
use Utopia\System\System;
|
use Utopia\System\System;
|
||||||
use Utopia\Telemetry\Adapter as Telemetry;
|
use Utopia\Telemetry\Adapter as Telemetry;
|
||||||
|
|
@ -416,6 +415,7 @@ App::init()
|
||||||
->inject('user')
|
->inject('user')
|
||||||
->inject('publisher')
|
->inject('publisher')
|
||||||
->inject('publisherFunctions')
|
->inject('publisherFunctions')
|
||||||
|
->inject('publisherWebhooks')
|
||||||
->inject('queueForEvents')
|
->inject('queueForEvents')
|
||||||
->inject('queueForMessaging')
|
->inject('queueForMessaging')
|
||||||
->inject('queueForAudits')
|
->inject('queueForAudits')
|
||||||
|
|
@ -431,7 +431,7 @@ App::init()
|
||||||
->inject('plan')
|
->inject('plan')
|
||||||
->inject('devKey')
|
->inject('devKey')
|
||||||
->inject('telemetry')
|
->inject('telemetry')
|
||||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, BrokerPool $publisherFunctions, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry) use ($usageDatabaseListener, $eventDatabaseListener) {
|
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||||
|
|
||||||
$route = $utopia->getRoute();
|
$route = $utopia->getRoute();
|
||||||
|
|
||||||
|
|
@ -544,7 +544,7 @@ App::init()
|
||||||
// from overwriting the events that are supposed to be triggered in the shutdown hook.
|
// from overwriting the events that are supposed to be triggered in the shutdown hook.
|
||||||
$queueForEventsClone = new Event($publisher);
|
$queueForEventsClone = new Event($publisher);
|
||||||
$queueForFunctions = new Func($publisherFunctions);
|
$queueForFunctions = new Func($publisherFunctions);
|
||||||
$queueForWebhooks = new Webhook($publisher);
|
$queueForWebhooks = new Webhook($publisherWebhooks);
|
||||||
$queueForRealtime = new Realtime();
|
$queueForRealtime = new Realtime();
|
||||||
|
|
||||||
$dbForProject
|
$dbForProject
|
||||||
|
|
|
||||||
|
|
@ -84,25 +84,28 @@ App::setResource('localeCodes', function () {
|
||||||
App::setResource('publisher', function (Group $pools) {
|
App::setResource('publisher', function (Group $pools) {
|
||||||
return new BrokerPool(publisher: $pools->get('publisher'));
|
return new BrokerPool(publisher: $pools->get('publisher'));
|
||||||
}, ['pools']);
|
}, ['pools']);
|
||||||
App::setResource('publisherDatabases', function (BrokerPool $publisher) {
|
App::setResource('publisherDatabases', function (Publisher $publisher) {
|
||||||
return $publisher;
|
return $publisher;
|
||||||
}, ['publisher']);
|
}, ['publisher']);
|
||||||
App::setResource('publisherFunctions', function (BrokerPool $publisher) {
|
App::setResource('publisherFunctions', function (Publisher $publisher) {
|
||||||
return $publisher;
|
return $publisher;
|
||||||
}, ['publisher']);
|
}, ['publisher']);
|
||||||
App::setResource('publisherMigrations', function (BrokerPool $publisher) {
|
App::setResource('publisherMigrations', function (Publisher $publisher) {
|
||||||
return $publisher;
|
return $publisher;
|
||||||
}, ['publisher']);
|
}, ['publisher']);
|
||||||
App::setResource('publisherStatsUsage', function (BrokerPool $publisher) {
|
App::setResource('publisherStatsUsage', function (Publisher $publisher) {
|
||||||
return $publisher;
|
return $publisher;
|
||||||
}, ['publisher']);
|
}, ['publisher']);
|
||||||
App::setResource('publisherMails', function (BrokerPool $publisher) {
|
App::setResource('publisherMails', function (Publisher $publisher) {
|
||||||
return $publisher;
|
return $publisher;
|
||||||
}, ['publisher']);
|
}, ['publisher']);
|
||||||
App::setResource('publisherDeletes', function (BrokerPool $publisher) {
|
App::setResource('publisherDeletes', function (Publisher $publisher) {
|
||||||
return $publisher;
|
return $publisher;
|
||||||
}, ['publisher']);
|
}, ['publisher']);
|
||||||
App::setResource('publisherMessaging', function (BrokerPool $publisher) {
|
App::setResource('publisherMessaging', function (Publisher $publisher) {
|
||||||
|
return $publisher;
|
||||||
|
}, ['publisher']);
|
||||||
|
App::setResource('publisherWebhooks', function (Publisher $publisher) {
|
||||||
return $publisher;
|
return $publisher;
|
||||||
}, ['publisher']);
|
}, ['publisher']);
|
||||||
App::setResource('queueForMessaging', function (Publisher $publisher) {
|
App::setResource('queueForMessaging', function (Publisher $publisher) {
|
||||||
|
|
|
||||||
12
composer.lock
generated
12
composer.lock
generated
|
|
@ -4109,16 +4109,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/migration",
|
"name": "utopia-php/migration",
|
||||||
"version": "1.0.1",
|
"version": "1.0.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/utopia-php/migration.git",
|
"url": "https://github.com/utopia-php/migration.git",
|
||||||
"reference": "38171023efd3abe650d2abc5ac65f5df52311da6"
|
"reference": "0e4499d9dd2c90c2be188cc5fb7a32d9a892b569"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/38171023efd3abe650d2abc5ac65f5df52311da6",
|
"url": "https://api.github.com/repos/utopia-php/migration/zipball/0e4499d9dd2c90c2be188cc5fb7a32d9a892b569",
|
||||||
"reference": "38171023efd3abe650d2abc5ac65f5df52311da6",
|
"reference": "0e4499d9dd2c90c2be188cc5fb7a32d9a892b569",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -4159,9 +4159,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/utopia-php/migration/issues",
|
"issues": "https://github.com/utopia-php/migration/issues",
|
||||||
"source": "https://github.com/utopia-php/migration/tree/1.0.1"
|
"source": "https://github.com/utopia-php/migration/tree/1.0.0"
|
||||||
},
|
},
|
||||||
"time": "2025-08-28T13:41:25+00:00"
|
"time": "2025-08-13T09:15:53+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/orchestration",
|
"name": "utopia-php/orchestration",
|
||||||
|
|
|
||||||
|
|
@ -85,11 +85,4 @@ class StatsUsage extends Event
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reset(): Event
|
|
||||||
{
|
|
||||||
$this->metrics = [];
|
|
||||||
parent::reset();
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ class Exception extends \Exception
|
||||||
public const AVATAR_IMAGE_NOT_FOUND = 'avatar_image_not_found';
|
public const AVATAR_IMAGE_NOT_FOUND = 'avatar_image_not_found';
|
||||||
public const AVATAR_REMOTE_URL_FAILED = 'avatar_remote_url_failed';
|
public const AVATAR_REMOTE_URL_FAILED = 'avatar_remote_url_failed';
|
||||||
public const AVATAR_ICON_NOT_FOUND = 'avatar_icon_not_found';
|
public const AVATAR_ICON_NOT_FOUND = 'avatar_icon_not_found';
|
||||||
|
public const AVATAR_SVG_SANITIZATION_FAILED = 'avatar_svg_sanitization_failed';
|
||||||
|
|
||||||
/** Storage */
|
/** Storage */
|
||||||
public const STORAGE_FILE_ALREADY_EXISTS = 'storage_file_already_exists';
|
public const STORAGE_FILE_ALREADY_EXISTS = 'storage_file_already_exists';
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,18 @@ abstract class Action extends AppwriteAction
|
||||||
$this->context = ROWS;
|
$this->context = ROWS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the same helper method to ensure consistency
|
|
||||||
$contextId = '$' . $this->getCollectionsEventsContext() . 'Id';
|
$contextId = '$' . $this->getCollectionsEventsContext() . 'Id';
|
||||||
$this->removableAttributes = ['$databaseId', $contextId, '$sequence'];
|
$this->removableAttributes = [
|
||||||
|
'*' => [
|
||||||
|
'$sequence',
|
||||||
|
'$databaseId',
|
||||||
|
$contextId,
|
||||||
|
],
|
||||||
|
'privileged' => [
|
||||||
|
'$createdAt',
|
||||||
|
'$updatedAt',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
return parent::setHttpPath($path);
|
return parent::setHttpPath($path);
|
||||||
}
|
}
|
||||||
|
|
@ -200,11 +209,19 @@ abstract class Action extends AppwriteAction
|
||||||
* Remove configured removable attributes from a document.
|
* Remove configured removable attributes from a document.
|
||||||
* Used for relationship path handling to remove API-specific attributes.
|
* Used for relationship path handling to remove API-specific attributes.
|
||||||
*/
|
*/
|
||||||
protected function removeReadonlyAttributes(Document $document): void
|
protected function removeReadonlyAttributes(
|
||||||
{
|
Document|array $document,
|
||||||
foreach ($this->removableAttributes as $attribute) {
|
bool $privileged = false,
|
||||||
$document->removeAttribute($attribute);
|
): Document|array {
|
||||||
|
foreach ($this->removableAttributes['*'] as $attribute) {
|
||||||
|
unset($document[$attribute]);
|
||||||
}
|
}
|
||||||
|
if (!$privileged) {
|
||||||
|
foreach ($this->removableAttributes['privileged'] ?? [] as $attribute) {
|
||||||
|
unset($document[$attribute]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $document;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -127,8 +127,7 @@ class Update extends Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove sequence if set
|
$data = $this->removeReadonlyAttributes($data, privileged: true);
|
||||||
unset($document['$sequence']);
|
|
||||||
|
|
||||||
$documents = [];
|
$documents = [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ class Upsert extends Action
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($documents as $key => $document) {
|
foreach ($documents as $key => $document) {
|
||||||
|
$document = $this->removeReadonlyAttributes($document, privileged: true);
|
||||||
$documents[$key] = new Document($document);
|
$documents[$key] = new Document($document);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,7 @@ class Create extends Action
|
||||||
|
|
||||||
$operations = 0;
|
$operations = 0;
|
||||||
|
|
||||||
$checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database, &$operations) {
|
$checkPermissions = function (Document $collection, Document $document, string $permission) use ($isAPIKey, $isPrivilegedUser, &$checkPermissions, $dbForProject, $database, &$operations) {
|
||||||
$operations++;
|
$operations++;
|
||||||
|
|
||||||
$documentSecurity = $collection->getAttribute('documentSecurity', false);
|
$documentSecurity = $collection->getAttribute('documentSecurity', false);
|
||||||
|
|
@ -307,6 +307,8 @@ class Create extends Action
|
||||||
$relation = new Document($relation);
|
$relation = new Document($relation);
|
||||||
}
|
}
|
||||||
if ($relation instanceof Document) {
|
if ($relation instanceof Document) {
|
||||||
|
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||||
|
|
||||||
$current = Authorization::skip(
|
$current = Authorization::skip(
|
||||||
fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId())
|
fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId())
|
||||||
);
|
);
|
||||||
|
|
@ -318,7 +320,6 @@ class Create extends Action
|
||||||
$relation['$id'] = ID::unique();
|
$relation['$id'] = ID::unique();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->removeReadonlyAttributes($relation);
|
|
||||||
$relation->setAttribute('$collection', $relatedCollection->getId());
|
$relation->setAttribute('$collection', $relatedCollection->getId());
|
||||||
$type = Database::PERMISSION_UPDATE;
|
$type = Database::PERMISSION_UPDATE;
|
||||||
}
|
}
|
||||||
|
|
@ -351,27 +352,12 @@ class Create extends Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove sequence if set
|
|
||||||
unset($document['$sequence']);
|
|
||||||
|
|
||||||
// Assign a unique ID if needed, otherwise use the provided ID.
|
// Assign a unique ID if needed, otherwise use the provided ID.
|
||||||
$document['$id'] = $sourceId === 'unique()' ? ID::unique() : $sourceId;
|
$document['$id'] = $sourceId === 'unique()' ? ID::unique() : $sourceId;
|
||||||
|
$document = $this->removeReadonlyAttributes($document, $isAPIKey || $isPrivilegedUser);
|
||||||
// Allowing to add createdAt and updatedAt timestamps if server side(api key
|
|
||||||
if (!$isAPIKey && !$isPrivilegedUser) {
|
|
||||||
if (isset($document['$createdAt'])) {
|
|
||||||
throw new Exception($this->getInvalidStructureException(), 'Attribute "$createdAt" can not be modified. Please use a server SDK with an API key to modify server attributes.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($document['$updatedAt'])) {
|
|
||||||
throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$document = new Document($document);
|
$document = new Document($document);
|
||||||
$setPermissions($document, $permissions);
|
$setPermissions($document, $permissions);
|
||||||
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);
|
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);
|
||||||
|
|
||||||
return $document;
|
return $document;
|
||||||
}, $documents);
|
}, $documents);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ class Delete extends Action
|
||||||
}
|
}
|
||||||
|
|
||||||
$collectionsCache = [];
|
$collectionsCache = [];
|
||||||
|
|
||||||
$this->processDocument(
|
$this->processDocument(
|
||||||
database: $database,
|
database: $database,
|
||||||
collection: $collection,
|
collection: $collection,
|
||||||
|
|
|
||||||
|
|
@ -109,16 +109,6 @@ class Update extends Action
|
||||||
throw new Exception($this->getParentNotFoundException());
|
throw new Exception($this->getParentNotFoundException());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allowing to add createdAt and updatedAt timestamps if server side(api key)
|
|
||||||
if (!$isAPIKey && !$isPrivilegedUser) {
|
|
||||||
if (isset($data['$createdAt'])) {
|
|
||||||
throw new Exception($this->getInvalidStructureException(), 'Attribute "$createdAt" can not be modified. Please use a server SDK with an API key to modify server attributes.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($data['$updatedAt'])) {
|
|
||||||
throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Read permission should not be required for update
|
// Read permission should not be required for update
|
||||||
/** @var Document $document */
|
/** @var Document $document */
|
||||||
$document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
$document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
||||||
|
|
@ -159,17 +149,14 @@ class Update extends Action
|
||||||
$permissions = $document->getPermissions() ?? [];
|
$permissions = $document->getPermissions() ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove sequence if set
|
|
||||||
unset($document['$sequence']);
|
|
||||||
|
|
||||||
$data['$id'] = $documentId;
|
$data['$id'] = $documentId;
|
||||||
$data['$permissions'] = $permissions;
|
$data['$permissions'] = $permissions;
|
||||||
|
$data = $this->removeReadonlyAttributes($data, $isAPIKey || $isPrivilegedUser);
|
||||||
$newDocument = new Document($data);
|
$newDocument = new Document($data);
|
||||||
|
|
||||||
$operations = 0;
|
$operations = 0;
|
||||||
|
|
||||||
$setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, &$operations) {
|
$setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations) {
|
||||||
|
|
||||||
$operations++;
|
$operations++;
|
||||||
|
|
||||||
$relationships = \array_filter(
|
$relationships = \array_filter(
|
||||||
|
|
@ -208,11 +195,13 @@ class Update extends Action
|
||||||
$relation = new Document($relation);
|
$relation = new Document($relation);
|
||||||
}
|
}
|
||||||
if ($relation instanceof Document) {
|
if ($relation instanceof Document) {
|
||||||
|
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||||
|
|
||||||
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument(
|
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument(
|
||||||
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(),
|
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(),
|
||||||
$relation->getId()
|
$relation->getId()
|
||||||
));
|
));
|
||||||
$this->removeReadonlyAttributes($relation);
|
|
||||||
// Attribute $collection is required for Utopia.
|
// Attribute $collection is required for Utopia.
|
||||||
$relation->setAttribute(
|
$relation->setAttribute(
|
||||||
'$collection',
|
'$collection',
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,8 @@ class Upsert extends Action
|
||||||
];
|
];
|
||||||
|
|
||||||
$permissions = Permission::aggregate($permissions, $allowedPermissions);
|
$permissions = Permission::aggregate($permissions, $allowedPermissions);
|
||||||
// if no permission, upsert permission from the old document if present (update scenario) else add default permission (create scenario)
|
|
||||||
|
// If no permission, upsert permission from the old document if present (update scenario) else add default permission (create scenario)
|
||||||
if (\is_null($permissions)) {
|
if (\is_null($permissions)) {
|
||||||
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
||||||
if ($oldDocument->isEmpty()) {
|
if ($oldDocument->isEmpty()) {
|
||||||
|
|
@ -157,24 +158,14 @@ class Upsert extends Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Allowing to add createdAt and updatedAt timestamps if server side(api key)
|
|
||||||
if (!$isAPIKey && !$isPrivilegedUser) {
|
|
||||||
if (isset($data['$createdAt'])) {
|
|
||||||
throw new Exception($this->getInvalidStructureException(), 'Attribute "$createdAt" can not be modified. Please use a server SDK with an API key to modify server attributes.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($data['$updatedAt'])) {
|
|
||||||
throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['$id'] = $documentId;
|
$data['$id'] = $documentId;
|
||||||
$data['$permissions'] = $permissions ?? [];
|
$data['$permissions'] = $permissions ?? [];
|
||||||
|
$data = $this->removeReadonlyAttributes($data, $isAPIKey || $isPrivilegedUser);
|
||||||
$newDocument = new Document($data);
|
$newDocument = new Document($data);
|
||||||
$operations = 0;
|
$operations = 0;
|
||||||
|
|
||||||
$setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, &$operations) {
|
$setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations) {
|
||||||
|
|
||||||
$operations++;
|
$operations++;
|
||||||
|
|
||||||
$relationships = \array_filter(
|
$relationships = \array_filter(
|
||||||
|
|
@ -213,11 +204,13 @@ class Upsert extends Action
|
||||||
$relation = new Document($relation);
|
$relation = new Document($relation);
|
||||||
}
|
}
|
||||||
if ($relation instanceof Document) {
|
if ($relation instanceof Document) {
|
||||||
|
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||||
|
|
||||||
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument(
|
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument(
|
||||||
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(),
|
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(),
|
||||||
$relation->getId()
|
$relation->getId()
|
||||||
));
|
));
|
||||||
$this->removeReadonlyAttributes($relation);
|
|
||||||
// Attribute $collection is required for Utopia.
|
// Attribute $collection is required for Utopia.
|
||||||
$relation->setAttribute(
|
$relation->setAttribute(
|
||||||
'$collection',
|
'$collection',
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ class XList extends Action
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check which removable attributes are explicitly requested
|
// Check which removable attributes are explicitly requested
|
||||||
foreach ($this->removableAttributes as $attribute) {
|
foreach ($this->removableAttributes['*'] as $attribute) {
|
||||||
if (\in_array($attribute, $values, true)) {
|
if (\in_array($attribute, $values, true)) {
|
||||||
$requestedAttributes[$attribute] = true;
|
$requestedAttributes[$attribute] = true;
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +186,7 @@ class XList extends Action
|
||||||
if (!$hasWildcard) {
|
if (!$hasWildcard) {
|
||||||
foreach ($documents as $document) {
|
foreach ($documents as $document) {
|
||||||
// Remove attributes that are not explicitly requested
|
// Remove attributes that are not explicitly requested
|
||||||
foreach ($this->removableAttributes as $attribute) {
|
foreach ($this->removableAttributes['*'] as $attribute) {
|
||||||
if (!isset($requestedAttributes[$attribute])) {
|
if (!isset($requestedAttributes[$attribute])) {
|
||||||
$document->removeAttribute($attribute);
|
$document->removeAttribute($attribute);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ namespace Appwrite\Platform\Workers;
|
||||||
|
|
||||||
use Ahc\Jwt\JWT;
|
use Ahc\Jwt\JWT;
|
||||||
use Appwrite\Event\Realtime;
|
use Appwrite\Event\Realtime;
|
||||||
use Appwrite\Event\StatsUsage;
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Utopia\CLI\Console;
|
use Utopia\CLI\Console;
|
||||||
use Utopia\Config\Config;
|
use Utopia\Config\Config;
|
||||||
|
|
@ -14,14 +13,9 @@ use Utopia\Database\Exception\Authorization;
|
||||||
use Utopia\Database\Exception\Conflict;
|
use Utopia\Database\Exception\Conflict;
|
||||||
use Utopia\Database\Exception\Restricted;
|
use Utopia\Database\Exception\Restricted;
|
||||||
use Utopia\Database\Exception\Structure;
|
use Utopia\Database\Exception\Structure;
|
||||||
use Utopia\Database\Validator\Authorization as AuthorizationValidator;
|
|
||||||
use Utopia\Migration\Destination;
|
use Utopia\Migration\Destination;
|
||||||
use Utopia\Migration\Destinations\Appwrite as DestinationAppwrite;
|
use Utopia\Migration\Destinations\Appwrite as DestinationAppwrite;
|
||||||
use Utopia\Migration\Exception as MigrationException;
|
use Utopia\Migration\Exception as MigrationException;
|
||||||
use Utopia\Migration\Resource;
|
|
||||||
use Utopia\Migration\Resources\Database\Database as ResourceDatabase;
|
|
||||||
use Utopia\Migration\Resources\Database\Row as ResourceRow;
|
|
||||||
use Utopia\Migration\Resources\Database\Table as ResourceTable;
|
|
||||||
use Utopia\Migration\Source;
|
use Utopia\Migration\Source;
|
||||||
use Utopia\Migration\Sources\Appwrite as SourceAppwrite;
|
use Utopia\Migration\Sources\Appwrite as SourceAppwrite;
|
||||||
use Utopia\Migration\Sources\CSV;
|
use Utopia\Migration\Sources\CSV;
|
||||||
|
|
@ -51,7 +45,6 @@ class Migrations extends Action
|
||||||
*/
|
*/
|
||||||
protected array $sourceReport = [];
|
protected array $sourceReport = [];
|
||||||
|
|
||||||
private string $source;
|
|
||||||
/**
|
/**
|
||||||
* @var callable
|
* @var callable
|
||||||
*/
|
*/
|
||||||
|
|
@ -76,14 +69,13 @@ class Migrations extends Action
|
||||||
->inject('logError')
|
->inject('logError')
|
||||||
->inject('queueForRealtime')
|
->inject('queueForRealtime')
|
||||||
->inject('deviceForImports')
|
->inject('deviceForImports')
|
||||||
->inject('queueForStatsUsage')
|
|
||||||
->callback($this->action(...));
|
->callback($this->action(...));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function action(Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError, Realtime $queueForRealtime, Device $deviceForImports, StatsUsage $queueForStatsUsage): void
|
public function action(Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError, Realtime $queueForRealtime, Device $deviceForImports): void
|
||||||
{
|
{
|
||||||
$payload = $message->getPayload() ?? [];
|
$payload = $message->getPayload() ?? [];
|
||||||
$this->deviceForImports = $deviceForImports;
|
$this->deviceForImports = $deviceForImports;
|
||||||
|
|
@ -111,7 +103,7 @@ class Migrations extends Action
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->processMigration($migration, $queueForRealtime, $queueForStatsUsage);
|
$this->processMigration($migration, $queueForRealtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -212,7 +204,6 @@ class Migrations extends Action
|
||||||
// set the errors back without trace
|
// set the errors back without trace
|
||||||
$clonedMigrationDocument->setAttribute('errors', $errorMessages);
|
$clonedMigrationDocument->setAttribute('errors', $errorMessages);
|
||||||
|
|
||||||
|
|
||||||
/** Trigger Realtime Events */
|
/** Trigger Realtime Events */
|
||||||
$queueForRealtime
|
$queueForRealtime
|
||||||
->setProject($project)
|
->setProject($project)
|
||||||
|
|
@ -275,7 +266,7 @@ class Migrations extends Action
|
||||||
* @throws \Utopia\Database\Exception
|
* @throws \Utopia\Database\Exception
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
protected function processMigration(Document $migration, Realtime $queueForRealtime, StatsUsage $queueForStatsUsage): void
|
protected function processMigration(Document $migration, Realtime $queueForRealtime): void
|
||||||
{
|
{
|
||||||
$project = $this->project;
|
$project = $this->project;
|
||||||
$projectDocument = $this->dbForPlatform->getDocument('projects', $project->getId());
|
$projectDocument = $this->dbForPlatform->getDocument('projects', $project->getId());
|
||||||
|
|
@ -309,7 +300,6 @@ class Migrations extends Action
|
||||||
$destination
|
$destination
|
||||||
);
|
);
|
||||||
|
|
||||||
$aggregatedResources = [];
|
|
||||||
/** Start Transfer */
|
/** Start Transfer */
|
||||||
if (empty($source->getErrors())) {
|
if (empty($source->getErrors())) {
|
||||||
$migration->setAttribute('stage', 'migrating');
|
$migration->setAttribute('stage', 'migrating');
|
||||||
|
|
@ -317,40 +307,9 @@ class Migrations extends Action
|
||||||
|
|
||||||
$transfer->run(
|
$transfer->run(
|
||||||
$migration->getAttribute('resources'),
|
$migration->getAttribute('resources'),
|
||||||
function ($resources) use ($migration, $transfer, $projectDocument, $queueForRealtime, &$aggregatedResources) {
|
function () use ($migration, $transfer, $projectDocument, $queueForRealtime) {
|
||||||
$migration->setAttribute('resourceData', json_encode($transfer->getCache()));
|
$migration->setAttribute('resourceData', json_encode($transfer->getCache()));
|
||||||
$migration->setAttribute('statusCounters', json_encode($transfer->getStatusCounters()));
|
$migration->setAttribute('statusCounters', json_encode($transfer->getStatusCounters()));
|
||||||
|
|
||||||
if (!empty($resources)) {
|
|
||||||
/**
|
|
||||||
* @var Resource $resource
|
|
||||||
*/
|
|
||||||
$resource = $resources[0];
|
|
||||||
$count = count($resources);
|
|
||||||
$databaseId = null;
|
|
||||||
$tableId = null;
|
|
||||||
switch ($resource->getName()) {
|
|
||||||
case ResourceTable::getName():
|
|
||||||
/** @var ResourceTable $resource */
|
|
||||||
$databaseId = $resource->getDatabase()->getSequence();
|
|
||||||
break;
|
|
||||||
case ResourceRow::getName():
|
|
||||||
/** @var ResourceRow $resource */
|
|
||||||
$table = $resource->getTable();
|
|
||||||
$databaseId = $table->getDatabase()->getSequence();
|
|
||||||
$tableId = $table->getSequence();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$aggregatedResources[] = [
|
|
||||||
'name' => $resource->getName(),
|
|
||||||
'count' => $count,
|
|
||||||
'databaseId' => $databaseId,
|
|
||||||
'tableId' => $tableId
|
|
||||||
];
|
|
||||||
|
|
||||||
}
|
|
||||||
$this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
|
$this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
|
||||||
},
|
},
|
||||||
$migration->getAttribute('resourceId'),
|
$migration->getAttribute('resourceId'),
|
||||||
|
|
@ -452,71 +411,9 @@ class Migrations extends Action
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($migration->getAttribute('status', '') === 'completed') {
|
if ($migration->getAttribute('status', '') === 'completed') {
|
||||||
foreach ($aggregatedResources as $resource) {
|
|
||||||
$this->processMigrationResourceStats(
|
|
||||||
$resource,
|
|
||||||
$queueForStatsUsage,
|
|
||||||
$projectDocument,
|
|
||||||
$migration->getAttribute('source'),
|
|
||||||
$migration->getAttribute('resourceId')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$destination?->success();
|
$destination?->success();
|
||||||
$source?->success();
|
$source?->success();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function processMigrationResourceStats(array $resources, StatsUsage $queueForStatsUsage, Document $projectDocument, string $source, ?string $resourceId)
|
|
||||||
{
|
|
||||||
$resourceName = $resources['name'];
|
|
||||||
$count = $resources['count'];
|
|
||||||
$databaseInternalId = $resources['databaseId'];
|
|
||||||
$tableInternalId = $resources['tableId'];
|
|
||||||
|
|
||||||
if ($source === CSV::getName()) {
|
|
||||||
[$databaseId, $tableId] = explode(':', $resourceId);
|
|
||||||
$database = AuthorizationValidator::skip(fn () => $this->dbForProject->getDocument('databases', $databaseId));
|
|
||||||
$table = AuthorizationValidator::skip(fn () => $this->dbForProject->getDocument('database_' . $database->getSequence(), $tableId));
|
|
||||||
$databaseInternalId = (int) $database->getSequence();
|
|
||||||
$tableInternalId = (int) $table->getSequence();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($resourceName) {
|
|
||||||
case ResourceDatabase::getName():
|
|
||||||
$queueForStatsUsage->addMetric(METRIC_DATABASES, $count);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ResourceTable::getName():
|
|
||||||
$queueForStatsUsage
|
|
||||||
->addMetric(METRIC_COLLECTIONS, $count)
|
|
||||||
->addMetric(
|
|
||||||
str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS),
|
|
||||||
$count
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ResourceRow::getName():
|
|
||||||
$queueForStatsUsage
|
|
||||||
->addMetric(
|
|
||||||
str_replace(
|
|
||||||
['{databaseInternalId}','{collectionInternalId}'],
|
|
||||||
[$databaseInternalId, $tableInternalId],
|
|
||||||
METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS
|
|
||||||
),
|
|
||||||
$count
|
|
||||||
)
|
|
||||||
->addMetric(
|
|
||||||
str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS),
|
|
||||||
$count
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$queueForStatsUsage->setProject($projectDocument)->trigger();
|
|
||||||
$queueForStatsUsage->reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,18 +41,21 @@ class Row extends Any
|
||||||
'description' => 'Row automatically incrementing ID.',
|
'description' => 'Row automatically incrementing ID.',
|
||||||
'default' => 0,
|
'default' => 0,
|
||||||
'example' => 1,
|
'example' => 1,
|
||||||
|
'readOnly' => true,
|
||||||
])
|
])
|
||||||
->addRule('$tableId', [
|
->addRule('$tableId', [
|
||||||
'type' => self::TYPE_STRING,
|
'type' => self::TYPE_STRING,
|
||||||
'description' => 'Table ID.',
|
'description' => 'Table ID.',
|
||||||
'default' => '',
|
'default' => '',
|
||||||
'example' => '5e5ea5c15117e',
|
'example' => '5e5ea5c15117e',
|
||||||
|
'readOnly' => true,
|
||||||
])
|
])
|
||||||
->addRule('$databaseId', [
|
->addRule('$databaseId', [
|
||||||
'type' => self::TYPE_STRING,
|
'type' => self::TYPE_STRING,
|
||||||
'description' => 'Database ID.',
|
'description' => 'Database ID.',
|
||||||
'default' => '',
|
'default' => '',
|
||||||
'example' => '5e5ea5c15117e',
|
'example' => '5e5ea5c15117e',
|
||||||
|
'readOnly' => true,
|
||||||
])
|
])
|
||||||
->addRule('$createdAt', [
|
->addRule('$createdAt', [
|
||||||
'type' => self::TYPE_DATETIME,
|
'type' => self::TYPE_DATETIME,
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,20 @@ class Client
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Response Format
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return self $this
|
||||||
|
*/
|
||||||
|
public function setResponseFormat(string $value): self
|
||||||
|
{
|
||||||
|
$this->addHeader('X-Appwrite-Response-Format', $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bool $status true
|
* @param bool $status true
|
||||||
* @return self $this
|
* @return self $this
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use Appwrite\Tests\Retryable;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Tests\E2E\Client;
|
use Tests\E2E\Client;
|
||||||
use Utopia\Database\Helpers\ID;
|
use Utopia\Database\Helpers\ID;
|
||||||
|
use Utopia\System\System;
|
||||||
|
|
||||||
abstract class Scope extends TestCase
|
abstract class Scope extends TestCase
|
||||||
{
|
{
|
||||||
|
|
@ -23,6 +24,17 @@ abstract class Scope extends TestCase
|
||||||
{
|
{
|
||||||
$this->client = new Client();
|
$this->client = new Client();
|
||||||
$this->client->setEndpoint($this->endpoint);
|
$this->client->setEndpoint($this->endpoint);
|
||||||
|
|
||||||
|
$format = System::getEnv('_APP_E2E_RESPONSE_FORMAT');
|
||||||
|
if (!empty($format)) {
|
||||||
|
if (
|
||||||
|
!\preg_match('/^\d+\.\d+\.\d+$/', $format) ||
|
||||||
|
!\version_compare($format, APP_VERSION_STABLE, '<=')
|
||||||
|
) {
|
||||||
|
throw new \Exception('E2E response format must be ' . APP_VERSION_STABLE . ' or lower.');
|
||||||
|
}
|
||||||
|
$this->client->setResponseFormat($format);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
|
|
|
||||||
|
|
@ -1697,6 +1697,7 @@ trait DatabasesBase
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @depends testCreateIndexes
|
* @depends testCreateIndexes
|
||||||
*/
|
*/
|
||||||
|
|
@ -2211,6 +2212,55 @@ trait DatabasesBase
|
||||||
$this->assertArrayHasKey('$permissions', $library3['body']);
|
$this->assertArrayHasKey('$permissions', $library3['body']);
|
||||||
$this->assertCount(3, $library3['body']['$permissions']);
|
$this->assertCount(3, $library3['body']['$permissions']);
|
||||||
$this->assertNotEmpty($library3['body']['$permissions']);
|
$this->assertNotEmpty($library3['body']['$permissions']);
|
||||||
|
|
||||||
|
// Readonly attributes are ignored
|
||||||
|
$personNoPerm = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/' . $newPersonId, array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'data' => [
|
||||||
|
'$id' => 'some-other-id',
|
||||||
|
'$collectionId' => 'some-other-collection',
|
||||||
|
'$databaseId' => 'some-other-database',
|
||||||
|
'$createdAt' => '2024-01-01T00:00:00Z',
|
||||||
|
'$updatedAt' => '2024-01-01T00:00:00Z',
|
||||||
|
'library' => [
|
||||||
|
'$id' => 'library3',
|
||||||
|
'libraryName' => 'Library 3',
|
||||||
|
'$createdAt' => '2024-01-01T00:00:00Z',
|
||||||
|
'$updatedAt' => '2024-01-01T00:00:00Z',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$update = $personNoPerm;
|
||||||
|
$update['body']['$id'] = 'random';
|
||||||
|
$update['body']['$sequence'] = 123;
|
||||||
|
$update['body']['$databaseId'] = 'random';
|
||||||
|
$update['body']['$collectionId'] = 'random';
|
||||||
|
$update['body']['$createdAt'] = '2024-01-01T00:00:00.000+00:00';
|
||||||
|
$update['body']['$updatedAt'] = '2024-01-01T00:00:00.000+00:00';
|
||||||
|
|
||||||
|
$upserted = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/' . $newPersonId, array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'data' => $update['body']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $upserted['headers']['status-code']);
|
||||||
|
$this->assertEquals($personNoPerm['body']['$id'], $upserted['body']['$id']);
|
||||||
|
$this->assertEquals($personNoPerm['body']['$collectionId'], $upserted['body']['$collectionId']);
|
||||||
|
$this->assertEquals($personNoPerm['body']['$databaseId'], $upserted['body']['$databaseId']);
|
||||||
|
$this->assertEquals($personNoPerm['body']['$sequence'], $upserted['body']['$sequence']);
|
||||||
|
|
||||||
|
if ($this->getSide() === 'client') {
|
||||||
|
$this->assertEquals($personNoPerm['body']['$createdAt'], $upserted['body']['$createdAt']);
|
||||||
|
$this->assertNotEquals('2024-01-01T00:00:00.000+00:00', $upserted['body']['$updatedAt']);
|
||||||
|
} else {
|
||||||
|
$this->assertEquals('2024-01-01T00:00:00.000+00:00', $upserted['body']['$createdAt']);
|
||||||
|
$this->assertEquals('2024-01-01T00:00:00.000+00:00', $upserted['body']['$updatedAt']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3000,6 +3050,37 @@ trait DatabasesBase
|
||||||
|
|
||||||
$this->assertEquals(200, $response['headers']['status-code']);
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
|
|
||||||
|
// Test readonly attributes are ignored
|
||||||
|
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $id, array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-timestamp' => DateTime::formatTz(DateTime::now()),
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'data' => [
|
||||||
|
'$id' => 'newId',
|
||||||
|
'$sequence' => 9999,
|
||||||
|
'$collectionId' => 'newCollectionId',
|
||||||
|
'$databaseId' => 'newDatabaseId',
|
||||||
|
'$createdAt' => '2024-01-01T00:00:00.000+00:00',
|
||||||
|
'$updatedAt' => '2024-01-01T00:00:00.000+00:00',
|
||||||
|
'title' => 'Thor: Ragnarok',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
|
$this->assertEquals($id, $response['body']['$id']);
|
||||||
|
$this->assertEquals($data['moviesId'], $response['body']['$collectionId']);
|
||||||
|
$this->assertEquals($databaseId, $response['body']['$databaseId']);
|
||||||
|
$this->assertNotEquals(9999, $response['body']['$sequence']);
|
||||||
|
|
||||||
|
if ($this->getSide() === 'client') {
|
||||||
|
$this->assertNotEquals('2024-01-01T00:00:00.000+00:00', $response['body']['$createdAt']);
|
||||||
|
$this->assertNotEquals('2024-01-01T00:00:00.000+00:00', $response['body']['$updatedAt']);
|
||||||
|
} else {
|
||||||
|
$this->assertEquals('2024-01-01T00:00:00.000+00:00', $response['body']['$createdAt']);
|
||||||
|
$this->assertEquals('2024-01-01T00:00:00.000+00:00', $response['body']['$updatedAt']);
|
||||||
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4260,7 +4341,9 @@ trait DatabasesBase
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
if ($this->getSide() === 'client') {
|
if ($this->getSide() === 'client') {
|
||||||
$this->assertEquals($document['headers']['status-code'], 400);
|
$this->assertEquals($document['body']['title'], 'Again Updated Date Test');
|
||||||
|
$this->assertNotEquals($document['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040'));
|
||||||
|
$this->assertNotEquals($document['body']['$updatedAt'], DateTime::formatTz('2022-08-01 13:09:23.050'));
|
||||||
} else {
|
} else {
|
||||||
$this->assertEquals($document['body']['title'], 'Again Updated Date Test');
|
$this->assertEquals($document['body']['title'], 'Again Updated Date Test');
|
||||||
$this->assertEquals($document['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040'));
|
$this->assertEquals($document['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040'));
|
||||||
|
|
|
||||||
|
|
@ -889,157 +889,4 @@ class DatabasesCustomClientTest extends Scope
|
||||||
|
|
||||||
$this->assertEquals(200, $response['headers']['status-code']);
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
}
|
}
|
||||||
public function testModifyCreatedAtUpdatedAtSingleDocument(): void
|
|
||||||
{
|
|
||||||
$database = $this->client->call(Client::METHOD_POST, '/databases', [
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
||||||
], [
|
|
||||||
'databaseId' => ID::unique(),
|
|
||||||
'name' => 'Test Database'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$databaseId = $database['body']['$id'];
|
|
||||||
|
|
||||||
$table = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
||||||
]), [
|
|
||||||
'collectionId' => ID::unique(),
|
|
||||||
'name' => 'Test Table',
|
|
||||||
'documentsecurity' => true,
|
|
||||||
'permissions' => [
|
|
||||||
Permission::create(Role::user($this->getUser()['$id'])),
|
|
||||||
Permission::read(Role::user($this->getUser()['$id'])),
|
|
||||||
Permission::update(Role::user($this->getUser()['$id'])),
|
|
||||||
Permission::delete(Role::user($this->getUser()['$id'])),
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$collectionId = $table['body']['$id'];
|
|
||||||
|
|
||||||
// Create string column
|
|
||||||
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
||||||
]), [
|
|
||||||
'key' => 'title',
|
|
||||||
'size' => 256,
|
|
||||||
'required' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
sleep(1);
|
|
||||||
|
|
||||||
// Test 1: Try to create document with $createdAt - should return 400
|
|
||||||
$response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'documentId' => ID::unique(),
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Test Movie',
|
|
||||||
'$createdAt' => '2000-01-01T10:00:00.000+00:00'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
|
||||||
|
|
||||||
// Test 2: Try to create document with $updatedAt - should return 400
|
|
||||||
$response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'documentId' => ID::unique(),
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Test Movie',
|
|
||||||
'$updatedAt' => '2000-01-01T10:00:00.000+00:00'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
|
||||||
|
|
||||||
// Test 3: Try to create document with both $createdAt and $updatedAt - should return 400
|
|
||||||
$response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'documentId' => ID::unique(),
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Test Movie',
|
|
||||||
'$createdAt' => '2000-01-01T10:00:00.000+00:00',
|
|
||||||
'$updatedAt' => '2000-01-01T10:00:00.000+00:00'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
|
||||||
|
|
||||||
// Test 4: Create a valid document first
|
|
||||||
$validRow = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'documentId' => ID::unique(),
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Valid Movie'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(201, $validRow['headers']['status-code']);
|
|
||||||
$documentId = $validRow['body']['$id'];
|
|
||||||
|
|
||||||
// Test 5: Try to update document with $createdAt - should return 400
|
|
||||||
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Updated Movie',
|
|
||||||
'$createdAt' => '2000-01-01T10:00:00.000+00:00'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
|
||||||
|
|
||||||
// Test 6: Try to update document with $updatedAt - should return 400
|
|
||||||
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Updated Movie',
|
|
||||||
'$updatedAt' => '2000-01-01T10:00:00.000+00:00'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
|
||||||
|
|
||||||
// Test 7: Try to update document with both $createdAt and $updatedAt - should return 400
|
|
||||||
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Updated Movie',
|
|
||||||
'$createdAt' => '2000-01-01T10:00:00.000+00:00',
|
|
||||||
'$updatedAt' => '2000-01-01T10:00:00.000+00:00'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4269,9 +4269,10 @@ trait DatabasesBase
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($this->getSide() === 'client') {
|
if ($this->getSide() === 'client') {
|
||||||
$this->assertEquals($row['headers']['status-code'], 400);
|
|
||||||
} else {
|
|
||||||
$this->assertEquals($row['body']['title'], 'Again Updated Date Test');
|
$this->assertEquals($row['body']['title'], 'Again Updated Date Test');
|
||||||
|
$this->assertNotEquals($row['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040'));
|
||||||
|
$this->assertNotEquals($row['body']['$updatedAt'], DateTime::formatTz('2022-08-01 13:09:23.050'));
|
||||||
|
} else {
|
||||||
$this->assertEquals($row['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040'));
|
$this->assertEquals($row['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040'));
|
||||||
$this->assertEquals($row['body']['$updatedAt'], DateTime::formatTz('2022-08-01 13:09:23.050'));
|
$this->assertEquals($row['body']['$updatedAt'], DateTime::formatTz('2022-08-01 13:09:23.050'));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -890,158 +890,4 @@ class DatabasesCustomClientTest extends Scope
|
||||||
|
|
||||||
$this->assertEquals(200, $response['headers']['status-code']);
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testModifyCreatedAtUpdatedAtSingleRow(): void
|
|
||||||
{
|
|
||||||
$database = $this->client->call(Client::METHOD_POST, '/tablesdb', [
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
||||||
], [
|
|
||||||
'databaseId' => ID::unique(),
|
|
||||||
'name' => 'Test Database'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$databaseId = $database['body']['$id'];
|
|
||||||
|
|
||||||
$table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
||||||
]), [
|
|
||||||
'tableId' => ID::unique(),
|
|
||||||
'name' => 'Test Table',
|
|
||||||
'rowSecurity' => true,
|
|
||||||
'permissions' => [
|
|
||||||
Permission::create(Role::user($this->getUser()['$id'])),
|
|
||||||
Permission::read(Role::user($this->getUser()['$id'])),
|
|
||||||
Permission::update(Role::user($this->getUser()['$id'])),
|
|
||||||
Permission::delete(Role::user($this->getUser()['$id'])),
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$tableId = $table['body']['$id'];
|
|
||||||
|
|
||||||
// Create string column
|
|
||||||
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
||||||
]), [
|
|
||||||
'key' => 'title',
|
|
||||||
'size' => 256,
|
|
||||||
'required' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
sleep(1);
|
|
||||||
|
|
||||||
// Test 1: Try to create row with $createdAt - should return 400
|
|
||||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'rowId' => ID::unique(),
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Test Movie',
|
|
||||||
'$createdAt' => '2000-01-01T10:00:00.000+00:00'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
|
||||||
|
|
||||||
// Test 2: Try to create row with $updatedAt - should return 400
|
|
||||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'rowId' => ID::unique(),
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Test Movie',
|
|
||||||
'$updatedAt' => '2000-01-01T10:00:00.000+00:00'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
|
||||||
|
|
||||||
// Test 3: Try to create row with both $createdAt and $updatedAt - should return 400
|
|
||||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'rowId' => ID::unique(),
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Test Movie',
|
|
||||||
'$createdAt' => '2000-01-01T10:00:00.000+00:00',
|
|
||||||
'$updatedAt' => '2000-01-01T10:00:00.000+00:00'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
|
||||||
|
|
||||||
// Test 4: Create a valid row first
|
|
||||||
$validRow = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'rowId' => ID::unique(),
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Valid Movie'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(201, $validRow['headers']['status-code']);
|
|
||||||
$rowId = $validRow['body']['$id'];
|
|
||||||
|
|
||||||
// Test 5: Try to update row with $createdAt - should return 400
|
|
||||||
$response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Updated Movie',
|
|
||||||
'$createdAt' => '2000-01-01T10:00:00.000+00:00'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
|
||||||
|
|
||||||
// Test 6: Try to update row with $updatedAt - should return 400
|
|
||||||
$response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Updated Movie',
|
|
||||||
'$updatedAt' => '2000-01-01T10:00:00.000+00:00'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
|
||||||
|
|
||||||
// Test 7: Try to update row with both $createdAt and $updatedAt - should return 400
|
|
||||||
$response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
], $this->getHeaders()), [
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Updated Movie',
|
|
||||||
'$createdAt' => '2000-01-01T10:00:00.000+00:00',
|
|
||||||
'$updatedAt' => '2000-01-01T10:00:00.000+00:00'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
$this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $tableId, array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId, array_merge([
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
|
||||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue