mirror of
https://github.com/appwrite/appwrite
synced 2026-05-06 06:48:22 +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
|
||||
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:
|
||||
check_database_changes:
|
||||
|
|
@ -100,7 +108,10 @@ jobs:
|
|||
run: docker compose exec -T appwrite vars
|
||||
|
||||
- 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:
|
||||
name: E2E General Test
|
||||
|
|
@ -132,7 +143,10 @@ jobs:
|
|||
done
|
||||
|
||||
- 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
|
||||
if: failure()
|
||||
|
|
@ -208,6 +222,7 @@ jobs:
|
|||
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/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
|
||||
|
||||
- name: Failure Logs
|
||||
|
|
@ -296,6 +311,7 @@ jobs:
|
|||
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/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
|
||||
|
||||
- name: Failure Logs
|
||||
|
|
@ -337,6 +353,7 @@ jobs:
|
|||
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=devKeys
|
||||
|
||||
- name: Failure Logs
|
||||
|
|
@ -392,6 +409,7 @@ jobs:
|
|||
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=devKeys
|
||||
|
||||
- name: Failure Logs
|
||||
|
|
@ -434,6 +452,7 @@ jobs:
|
|||
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
|
||||
|
||||
- name: Failure Logs
|
||||
|
|
@ -490,6 +509,7 @@ jobs:
|
|||
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
|
||||
|
||||
- name: Failure Logs
|
||||
|
|
@ -498,4 +518,4 @@ jobs:
|
|||
echo "=== Appwrite Worker Builds Logs ==="
|
||||
docker compose logs appwrite-worker-builds
|
||||
echo "=== OpenRuntimes Executor Logs ==="
|
||||
docker compose logs openruntimes-executor
|
||||
docker compose logs openruntimes-executor
|
||||
|
|
@ -435,6 +435,11 @@ return [
|
|||
'description' => 'The requested favicon could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::AVATAR_SVG_SANITIZATION_FAILED => [
|
||||
'name' => Exception::AVATAR_SVG_SANITIZATION_FAILED,
|
||||
'description' => 'SVG sanitization failed.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Storage */
|
||||
Exception::STORAGE_FILE_ALREADY_EXISTS => [
|
||||
|
|
|
|||
|
|
@ -9948,17 +9948,20 @@
|
|||
"type": "integer",
|
||||
"description": "Row automatically incrementing ID.",
|
||||
"x-example": 1,
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"readOnly": true
|
||||
},
|
||||
"$tableId": {
|
||||
"type": "string",
|
||||
"description": "Table ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$databaseId": {
|
||||
"type": "string",
|
||||
"description": "Database ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
|
|
|
|||
|
|
@ -47120,17 +47120,20 @@
|
|||
"type": "integer",
|
||||
"description": "Row automatically incrementing ID.",
|
||||
"x-example": 1,
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"readOnly": true
|
||||
},
|
||||
"$tableId": {
|
||||
"type": "string",
|
||||
"description": "Table ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$databaseId": {
|
||||
"type": "string",
|
||||
"description": "Database ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
|
|
|
|||
|
|
@ -35978,17 +35978,20 @@
|
|||
"type": "integer",
|
||||
"description": "Row automatically incrementing ID.",
|
||||
"x-example": 1,
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"readOnly": true
|
||||
},
|
||||
"$tableId": {
|
||||
"type": "string",
|
||||
"description": "Table ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$databaseId": {
|
||||
"type": "string",
|
||||
"description": "Database ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
|
|
|
|||
|
|
@ -9948,17 +9948,20 @@
|
|||
"type": "integer",
|
||||
"description": "Row automatically incrementing ID.",
|
||||
"x-example": 1,
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"readOnly": true
|
||||
},
|
||||
"$tableId": {
|
||||
"type": "string",
|
||||
"description": "Table ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$databaseId": {
|
||||
"type": "string",
|
||||
"description": "Database ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
|
|
|
|||
|
|
@ -47120,17 +47120,20 @@
|
|||
"type": "integer",
|
||||
"description": "Row automatically incrementing ID.",
|
||||
"x-example": 1,
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"readOnly": true
|
||||
},
|
||||
"$tableId": {
|
||||
"type": "string",
|
||||
"description": "Table ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$databaseId": {
|
||||
"type": "string",
|
||||
"description": "Database ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
|
|
|
|||
|
|
@ -35978,17 +35978,20 @@
|
|||
"type": "integer",
|
||||
"description": "Row automatically incrementing ID.",
|
||||
"x-example": 1,
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"readOnly": true
|
||||
},
|
||||
"$tableId": {
|
||||
"type": "string",
|
||||
"description": "Table ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$databaseId": {
|
||||
"type": "string",
|
||||
"description": "Database ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
|
|
|
|||
|
|
@ -9944,17 +9944,20 @@
|
|||
"type": "integer",
|
||||
"description": "Row automatically incrementing ID.",
|
||||
"x-example": 1,
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"readOnly": true
|
||||
},
|
||||
"$tableId": {
|
||||
"type": "string",
|
||||
"description": "Table ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$databaseId": {
|
||||
"type": "string",
|
||||
"description": "Database ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
|
|
|
|||
|
|
@ -47166,17 +47166,20 @@
|
|||
"type": "integer",
|
||||
"description": "Row automatically incrementing ID.",
|
||||
"x-example": 1,
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"readOnly": true
|
||||
},
|
||||
"$tableId": {
|
||||
"type": "string",
|
||||
"description": "Table ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$databaseId": {
|
||||
"type": "string",
|
||||
"description": "Database ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
|
|
|
|||
|
|
@ -36115,17 +36115,20 @@
|
|||
"type": "integer",
|
||||
"description": "Row automatically incrementing ID.",
|
||||
"x-example": 1,
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"readOnly": true
|
||||
},
|
||||
"$tableId": {
|
||||
"type": "string",
|
||||
"description": "Table ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$databaseId": {
|
||||
"type": "string",
|
||||
"description": "Database ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
|
|
|
|||
|
|
@ -9944,17 +9944,20 @@
|
|||
"type": "integer",
|
||||
"description": "Row automatically incrementing ID.",
|
||||
"x-example": 1,
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"readOnly": true
|
||||
},
|
||||
"$tableId": {
|
||||
"type": "string",
|
||||
"description": "Table ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$databaseId": {
|
||||
"type": "string",
|
||||
"description": "Database ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
|
|
|
|||
|
|
@ -47166,17 +47166,20 @@
|
|||
"type": "integer",
|
||||
"description": "Row automatically incrementing ID.",
|
||||
"x-example": 1,
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"readOnly": true
|
||||
},
|
||||
"$tableId": {
|
||||
"type": "string",
|
||||
"description": "Table ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$databaseId": {
|
||||
"type": "string",
|
||||
"description": "Database ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
|
|
|
|||
|
|
@ -36115,17 +36115,20 @@
|
|||
"type": "integer",
|
||||
"description": "Row automatically incrementing ID.",
|
||||
"x-example": 1,
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"readOnly": true
|
||||
},
|
||||
"$tableId": {
|
||||
"type": "string",
|
||||
"description": "Table ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$databaseId": {
|
||||
"type": "string",
|
||||
"description": "Database ID.",
|
||||
"x-example": "5e5ea5c15117e"
|
||||
"x-example": "5e5ea5c15117e",
|
||||
"readOnly": true
|
||||
},
|
||||
"$createdAt": {
|
||||
"type": "string",
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ App::get('/v1/avatars/favicon')
|
|||
$sanitizer->minify(true);
|
||||
$cleanSvg = $sanitizer->sanitize($data);
|
||||
if ($cleanSvg === false) {
|
||||
throw new \Exception('SVG sanitization failed');
|
||||
throw new Exception(Exception::AVATAR_SVG_SANITIZATION_FAILED);
|
||||
}
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
|
|
|
|||
|
|
@ -3011,7 +3011,7 @@ App::post('/v1/messaging/messages/email')
|
|||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->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()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
|
|
@ -3184,7 +3184,7 @@ App::post('/v1/messaging/messages/sms')
|
|||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->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()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
|
|
@ -3319,7 +3319,7 @@ App::post('/v1/messaging/messages/push')
|
|||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->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()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
|
|
|
|||
|
|
@ -1060,26 +1060,6 @@ App::init()
|
|||
$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)) {
|
||||
$response->addHeader('X-Appwrite-Warning', implode(';', $warnings));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ use Utopia\Database\DateTime;
|
|||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Queue\Broker\Pool as BrokerPool;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
|
|
@ -416,6 +415,7 @@ App::init()
|
|||
->inject('user')
|
||||
->inject('publisher')
|
||||
->inject('publisherFunctions')
|
||||
->inject('publisherWebhooks')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForAudits')
|
||||
|
|
@ -431,7 +431,7 @@ App::init()
|
|||
->inject('plan')
|
||||
->inject('devKey')
|
||||
->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();
|
||||
|
||||
|
|
@ -544,7 +544,7 @@ App::init()
|
|||
// from overwriting the events that are supposed to be triggered in the shutdown hook.
|
||||
$queueForEventsClone = new Event($publisher);
|
||||
$queueForFunctions = new Func($publisherFunctions);
|
||||
$queueForWebhooks = new Webhook($publisher);
|
||||
$queueForWebhooks = new Webhook($publisherWebhooks);
|
||||
$queueForRealtime = new Realtime();
|
||||
|
||||
$dbForProject
|
||||
|
|
|
|||
|
|
@ -84,25 +84,28 @@ App::setResource('localeCodes', function () {
|
|||
App::setResource('publisher', function (Group $pools) {
|
||||
return new BrokerPool(publisher: $pools->get('publisher'));
|
||||
}, ['pools']);
|
||||
App::setResource('publisherDatabases', function (BrokerPool $publisher) {
|
||||
App::setResource('publisherDatabases', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
App::setResource('publisherFunctions', function (BrokerPool $publisher) {
|
||||
App::setResource('publisherFunctions', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
App::setResource('publisherMigrations', function (BrokerPool $publisher) {
|
||||
App::setResource('publisherMigrations', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
App::setResource('publisherStatsUsage', function (BrokerPool $publisher) {
|
||||
App::setResource('publisherStatsUsage', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
App::setResource('publisherMails', function (BrokerPool $publisher) {
|
||||
App::setResource('publisherMails', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
App::setResource('publisherDeletes', function (BrokerPool $publisher) {
|
||||
App::setResource('publisherDeletes', function (Publisher $publisher) {
|
||||
return $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;
|
||||
}, ['publisher']);
|
||||
App::setResource('queueForMessaging', function (Publisher $publisher) {
|
||||
|
|
|
|||
12
composer.lock
generated
12
composer.lock
generated
|
|
@ -4109,16 +4109,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "38171023efd3abe650d2abc5ac65f5df52311da6"
|
||||
"reference": "0e4499d9dd2c90c2be188cc5fb7a32d9a892b569"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/38171023efd3abe650d2abc5ac65f5df52311da6",
|
||||
"reference": "38171023efd3abe650d2abc5ac65f5df52311da6",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/0e4499d9dd2c90c2be188cc5fb7a32d9a892b569",
|
||||
"reference": "0e4499d9dd2c90c2be188cc5fb7a32d9a892b569",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -4159,9 +4159,9 @@
|
|||
],
|
||||
"support": {
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -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_REMOTE_URL_FAILED = 'avatar_remote_url_failed';
|
||||
public const AVATAR_ICON_NOT_FOUND = 'avatar_icon_not_found';
|
||||
public const AVATAR_SVG_SANITIZATION_FAILED = 'avatar_svg_sanitization_failed';
|
||||
|
||||
/** Storage */
|
||||
public const STORAGE_FILE_ALREADY_EXISTS = 'storage_file_already_exists';
|
||||
|
|
|
|||
|
|
@ -27,9 +27,18 @@ abstract class Action extends AppwriteAction
|
|||
$this->context = ROWS;
|
||||
}
|
||||
|
||||
// Use the same helper method to ensure consistency
|
||||
$contextId = '$' . $this->getCollectionsEventsContext() . 'Id';
|
||||
$this->removableAttributes = ['$databaseId', $contextId, '$sequence'];
|
||||
$this->removableAttributes = [
|
||||
'*' => [
|
||||
'$sequence',
|
||||
'$databaseId',
|
||||
$contextId,
|
||||
],
|
||||
'privileged' => [
|
||||
'$createdAt',
|
||||
'$updatedAt',
|
||||
],
|
||||
];
|
||||
|
||||
return parent::setHttpPath($path);
|
||||
}
|
||||
|
|
@ -200,11 +209,19 @@ abstract class Action extends AppwriteAction
|
|||
* Remove configured removable attributes from a document.
|
||||
* Used for relationship path handling to remove API-specific attributes.
|
||||
*/
|
||||
protected function removeReadonlyAttributes(Document $document): void
|
||||
{
|
||||
foreach ($this->removableAttributes as $attribute) {
|
||||
$document->removeAttribute($attribute);
|
||||
protected function removeReadonlyAttributes(
|
||||
Document|array $document,
|
||||
bool $privileged = false,
|
||||
): 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
|
||||
unset($document['$sequence']);
|
||||
$data = $this->removeReadonlyAttributes($data, privileged: true);
|
||||
|
||||
$documents = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ class Upsert extends Action
|
|||
}
|
||||
|
||||
foreach ($documents as $key => $document) {
|
||||
$document = $this->removeReadonlyAttributes($document, privileged: true);
|
||||
$documents[$key] = new Document($document);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ class Create extends Action
|
|||
|
||||
$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++;
|
||||
|
||||
$documentSecurity = $collection->getAttribute('documentSecurity', false);
|
||||
|
|
@ -307,6 +307,8 @@ class Create extends Action
|
|||
$relation = new Document($relation);
|
||||
}
|
||||
if ($relation instanceof Document) {
|
||||
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||
|
||||
$current = Authorization::skip(
|
||||
fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId())
|
||||
);
|
||||
|
|
@ -318,7 +320,6 @@ class Create extends Action
|
|||
$relation['$id'] = ID::unique();
|
||||
}
|
||||
} else {
|
||||
$this->removeReadonlyAttributes($relation);
|
||||
$relation->setAttribute('$collection', $relatedCollection->getId());
|
||||
$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.
|
||||
$document['$id'] = $sourceId === 'unique()' ? ID::unique() : $sourceId;
|
||||
|
||||
// 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 = $this->removeReadonlyAttributes($document, $isAPIKey || $isPrivilegedUser);
|
||||
$document = new Document($document);
|
||||
$setPermissions($document, $permissions);
|
||||
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);
|
||||
|
||||
return $document;
|
||||
}, $documents);
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ class Delete extends Action
|
|||
}
|
||||
|
||||
$collectionsCache = [];
|
||||
|
||||
$this->processDocument(
|
||||
database: $database,
|
||||
collection: $collection,
|
||||
|
|
|
|||
|
|
@ -109,16 +109,6 @@ class Update extends Action
|
|||
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
|
||||
/** @var Document $document */
|
||||
$document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
||||
|
|
@ -159,17 +149,14 @@ class Update extends Action
|
|||
$permissions = $document->getPermissions() ?? [];
|
||||
}
|
||||
|
||||
// Remove sequence if set
|
||||
unset($document['$sequence']);
|
||||
|
||||
$data['$id'] = $documentId;
|
||||
$data['$permissions'] = $permissions;
|
||||
$data = $this->removeReadonlyAttributes($data, $isAPIKey || $isPrivilegedUser);
|
||||
$newDocument = new Document($data);
|
||||
|
||||
$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++;
|
||||
|
||||
$relationships = \array_filter(
|
||||
|
|
@ -208,11 +195,13 @@ class Update extends Action
|
|||
$relation = new Document($relation);
|
||||
}
|
||||
if ($relation instanceof Document) {
|
||||
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||
|
||||
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(),
|
||||
$relation->getId()
|
||||
));
|
||||
$this->removeReadonlyAttributes($relation);
|
||||
|
||||
// Attribute $collection is required for Utopia.
|
||||
$relation->setAttribute(
|
||||
'$collection',
|
||||
|
|
|
|||
|
|
@ -121,7 +121,8 @@ class Upsert extends Action
|
|||
];
|
||||
|
||||
$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)) {
|
||||
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
||||
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['$permissions'] = $permissions ?? [];
|
||||
$data = $this->removeReadonlyAttributes($data, $isAPIKey || $isPrivilegedUser);
|
||||
$newDocument = new Document($data);
|
||||
$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++;
|
||||
|
||||
$relationships = \array_filter(
|
||||
|
|
@ -213,11 +204,13 @@ class Upsert extends Action
|
|||
$relation = new Document($relation);
|
||||
}
|
||||
if ($relation instanceof Document) {
|
||||
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||
|
||||
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(),
|
||||
$relation->getId()
|
||||
));
|
||||
$this->removeReadonlyAttributes($relation);
|
||||
|
||||
// Attribute $collection is required for Utopia.
|
||||
$relation->setAttribute(
|
||||
'$collection',
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ class XList extends Action
|
|||
}
|
||||
|
||||
// Check which removable attributes are explicitly requested
|
||||
foreach ($this->removableAttributes as $attribute) {
|
||||
foreach ($this->removableAttributes['*'] as $attribute) {
|
||||
if (\in_array($attribute, $values, true)) {
|
||||
$requestedAttributes[$attribute] = true;
|
||||
}
|
||||
|
|
@ -186,7 +186,7 @@ class XList extends Action
|
|||
if (!$hasWildcard) {
|
||||
foreach ($documents as $document) {
|
||||
// Remove attributes that are not explicitly requested
|
||||
foreach ($this->removableAttributes as $attribute) {
|
||||
foreach ($this->removableAttributes['*'] as $attribute) {
|
||||
if (!isset($requestedAttributes[$attribute])) {
|
||||
$document->removeAttribute($attribute);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ namespace Appwrite\Platform\Workers;
|
|||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Exception;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
|
|
@ -14,14 +13,9 @@ use Utopia\Database\Exception\Authorization;
|
|||
use Utopia\Database\Exception\Conflict;
|
||||
use Utopia\Database\Exception\Restricted;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Validator\Authorization as AuthorizationValidator;
|
||||
use Utopia\Migration\Destination;
|
||||
use Utopia\Migration\Destinations\Appwrite as DestinationAppwrite;
|
||||
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\Sources\Appwrite as SourceAppwrite;
|
||||
use Utopia\Migration\Sources\CSV;
|
||||
|
|
@ -51,7 +45,6 @@ class Migrations extends Action
|
|||
*/
|
||||
protected array $sourceReport = [];
|
||||
|
||||
private string $source;
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
|
|
@ -76,14 +69,13 @@ class Migrations extends Action
|
|||
->inject('logError')
|
||||
->inject('queueForRealtime')
|
||||
->inject('deviceForImports')
|
||||
->inject('queueForStatsUsage')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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() ?? [];
|
||||
$this->deviceForImports = $deviceForImports;
|
||||
|
|
@ -111,7 +103,7 @@ class Migrations extends Action
|
|||
return;
|
||||
}
|
||||
|
||||
$this->processMigration($migration, $queueForRealtime, $queueForStatsUsage);
|
||||
$this->processMigration($migration, $queueForRealtime);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -212,7 +204,6 @@ class Migrations extends Action
|
|||
// set the errors back without trace
|
||||
$clonedMigrationDocument->setAttribute('errors', $errorMessages);
|
||||
|
||||
|
||||
/** Trigger Realtime Events */
|
||||
$queueForRealtime
|
||||
->setProject($project)
|
||||
|
|
@ -275,7 +266,7 @@ class Migrations extends Action
|
|||
* @throws \Utopia\Database\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function processMigration(Document $migration, Realtime $queueForRealtime, StatsUsage $queueForStatsUsage): void
|
||||
protected function processMigration(Document $migration, Realtime $queueForRealtime): void
|
||||
{
|
||||
$project = $this->project;
|
||||
$projectDocument = $this->dbForPlatform->getDocument('projects', $project->getId());
|
||||
|
|
@ -309,7 +300,6 @@ class Migrations extends Action
|
|||
$destination
|
||||
);
|
||||
|
||||
$aggregatedResources = [];
|
||||
/** Start Transfer */
|
||||
if (empty($source->getErrors())) {
|
||||
$migration->setAttribute('stage', 'migrating');
|
||||
|
|
@ -317,40 +307,9 @@ class Migrations extends Action
|
|||
|
||||
$transfer->run(
|
||||
$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('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);
|
||||
},
|
||||
$migration->getAttribute('resourceId'),
|
||||
|
|
@ -452,71 +411,9 @@ class Migrations extends Action
|
|||
}
|
||||
|
||||
if ($migration->getAttribute('status', '') === 'completed') {
|
||||
foreach ($aggregatedResources as $resource) {
|
||||
$this->processMigrationResourceStats(
|
||||
$resource,
|
||||
$queueForStatsUsage,
|
||||
$projectDocument,
|
||||
$migration->getAttribute('source'),
|
||||
$migration->getAttribute('resourceId')
|
||||
);
|
||||
}
|
||||
$destination?->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.',
|
||||
'default' => 0,
|
||||
'example' => 1,
|
||||
'readOnly' => true,
|
||||
])
|
||||
->addRule('$tableId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Table ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c15117e',
|
||||
'readOnly' => true,
|
||||
])
|
||||
->addRule('$databaseId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Database ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c15117e',
|
||||
'readOnly' => true,
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
|
|
|
|||
|
|
@ -108,6 +108,20 @@ class Client
|
|||
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
|
||||
* @return self $this
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use Appwrite\Tests\Retryable;
|
|||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\System\System;
|
||||
|
||||
abstract class Scope extends TestCase
|
||||
{
|
||||
|
|
@ -23,6 +24,17 @@ abstract class Scope extends TestCase
|
|||
{
|
||||
$this->client = new Client();
|
||||
$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
|
||||
|
|
|
|||
|
|
@ -1697,6 +1697,7 @@ trait DatabasesBase
|
|||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @depends testCreateIndexes
|
||||
*/
|
||||
|
|
@ -2211,6 +2212,55 @@ trait DatabasesBase
|
|||
$this->assertArrayHasKey('$permissions', $library3['body']);
|
||||
$this->assertCount(3, $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']);
|
||||
|
||||
// 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 [];
|
||||
}
|
||||
|
||||
|
|
@ -4260,7 +4341,9 @@ trait DatabasesBase
|
|||
]
|
||||
]);
|
||||
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 {
|
||||
$this->assertEquals($document['body']['title'], 'Again Updated Date Test');
|
||||
$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']);
|
||||
}
|
||||
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') {
|
||||
$this->assertEquals($row['headers']['status-code'], 400);
|
||||
} else {
|
||||
$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']['$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']);
|
||||
}
|
||||
|
||||
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