updated endpoints

This commit is contained in:
ArnabChatterjee20k
2025-09-17 13:54:27 +05:30
parent bfacb900e2
commit b452bd0d44
14 changed files with 921 additions and 30 deletions
+1 -1
View File
@@ -52,7 +52,7 @@
"utopia-php/cache": "0.13.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "1.*",
"utopia-php/database": "dev-dat-677 as 1.4.9",
"utopia-php/detector": "0.1.*",
"utopia-php/domains": "0.8.*",
"utopia-php/dns": "0.3.*",
Generated
+30 -24
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7553e976312b0423cc31544abb91caec",
"content-hash": "bf86eeb3175aac99688369676b4d3f84",
"packages": [
{
"name": "adhocore/jwt",
@@ -756,24 +756,21 @@
},
{
"name": "google/protobuf",
"version": "v4.32.0",
"version": "v4.32.1",
"source": {
"type": "git",
"url": "https://github.com/protocolbuffers/protobuf-php.git",
"reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646"
"reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646",
"reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb",
"reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb",
"shasum": ""
},
"require": {
"php": ">=8.1.0"
},
"provide": {
"ext-protobuf": "*"
},
"require-dev": {
"phpunit/phpunit": ">=5.0.0 <8.5.27"
},
@@ -797,9 +794,9 @@
"proto"
],
"support": {
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.0"
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.1"
},
"time": "2025-08-14T20:00:33+00:00"
"time": "2025-09-14T05:14:52+00:00"
},
{
"name": "league/csv",
@@ -3638,16 +3635,16 @@
},
{
"name": "utopia-php/database",
"version": "1.4.8",
"version": "dev-dat-677",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "dbecdf89fde33a5f81ec19f4f97fe0c3715dc83a"
"reference": "e95802fb07225a1616e5a242c379e8910e2f724a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/dbecdf89fde33a5f81ec19f4f97fe0c3715dc83a",
"reference": "dbecdf89fde33a5f81ec19f4f97fe0c3715dc83a",
"url": "https://api.github.com/repos/utopia-php/database/zipball/e95802fb07225a1616e5a242c379e8910e2f724a",
"reference": "e95802fb07225a1616e5a242c379e8910e2f724a",
"shasum": ""
},
"require": {
@@ -3688,9 +3685,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/1.4.8"
"source": "https://github.com/utopia-php/database/tree/dat-677"
},
"time": "2025-09-12T03:35:59+00:00"
"time": "2025-09-16T18:08:23+00:00"
},
{
"name": "utopia-php/detector",
@@ -6236,16 +6233,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.6.26",
"version": "9.6.27",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "a0139ea157533454f611038326f3020b3051f129"
"reference": "0a9aa4440b6a9528cf360071502628d717af3e0a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a0139ea157533454f611038326f3020b3051f129",
"reference": "a0139ea157533454f611038326f3020b3051f129",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0a9aa4440b6a9528cf360071502628d717af3e0a",
"reference": "0a9aa4440b6a9528cf360071502628d717af3e0a",
"shasum": ""
},
"require": {
@@ -6319,7 +6316,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.26"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.27"
},
"funding": [
{
@@ -6343,7 +6340,7 @@
"type": "tidelift"
}
],
"time": "2025-09-11T06:17:45+00:00"
"time": "2025-09-14T06:18:03+00:00"
},
{
"name": "psr/cache",
@@ -8510,9 +8507,18 @@
"time": "2024-03-07T20:33:40+00:00"
}
],
"aliases": [],
"aliases": [
{
"package": "utopia-php/database",
"version": "dev-dat-677",
"alias": "1.4.9",
"alias_normalized": "1.4.9.0"
}
],
"minimum-stability": "stable",
"stability-flags": {},
"stability-flags": {
"utopia-php/database": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@@ -37,6 +37,8 @@ abstract class Action extends AppwriteAction
'privileged' => [
'$createdAt',
'$updatedAt',
'$createdBy',
'$updatedBy'
],
];
@@ -23,6 +23,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Numeric;
use Utopia\Database\Document;
class Decrement extends Action
{
@@ -77,12 +78,13 @@ class Decrement extends Action
->param('min', null, new Numeric(), 'Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.', true)
->inject('response')
->inject('dbForProject')
->inject('user')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage): void
{
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
@@ -114,6 +116,12 @@ class Decrement extends Action
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage());
}
// Set $updatedBy field to current user ID if available
$userId = $user->getId();
if (!empty($userId)) {
$document->setAttribute('$updatedBy', $userId);
}
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
@@ -23,6 +23,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Numeric;
use Utopia\Database\Document;
class Increment extends Action
{
@@ -77,12 +78,13 @@ class Increment extends Action
->param('max', null, new Numeric(), 'Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.', true)
->inject('response')
->inject('dbForProject')
->inject('user')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage): void
{
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
@@ -114,6 +116,12 @@ class Increment extends Action
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage());
}
// Set $updatedBy field to current user ID if available
$userId = $user->getId();
if (!empty($userId)) {
$document->setAttribute('$updatedBy', $userId);
}
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
@@ -113,7 +113,7 @@ class Upsert extends Action
try {
$modified = $dbForProject->withPreserveDates(function () use ($dbForProject, $database, $collection, $documents, $plan, &$upserted) {
return $dbForProject->createOrUpdateDocuments(
return $dbForProject->upsertDocuments(
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
$documents,
onNext: function (Document $document) use ($plan, &$upserted) {
@@ -243,7 +243,7 @@ class Upsert extends Action
$upserted = [];
try {
$dbForProject->withPreserveDates(function () use (&$upserted, $dbForProject, $database, $collection, $newDocument) {
return $dbForProject->createOrUpdateDocuments(
return $dbForProject->upsertDocuments(
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
[$newDocument],
onNext: function (Document $document) use (&$upserted) {
@@ -435,7 +435,7 @@ class StatsResources extends Action
$message = 'Stats writeDocuments project: ' . $project->getId() . '(' . $project->getSequence() . ')';
try {
$dbForLogs->createOrUpdateDocuments(
$dbForLogs->upsertDocuments(
'stats',
$this->documents
);
@@ -69,6 +69,18 @@ class Document extends Any
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$createdBy', [
'type' => self::TYPE_STRING,
'description' => 'User ID of the user who created the document.',
'default' => null,
'example' => '5e5ea5c16897e',
])
->addRule('$updatedBy', [
'type' => self::TYPE_STRING,
'description' => 'User ID of the user who updated the document.',
'default' => null,
'example' => '5e5ea5c16897e',
])
->addRule('$permissions', [
'type' => self::TYPE_STRING,
'description' => 'Document permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).',
+6
View File
@@ -7,6 +7,8 @@ use Appwrite\Tests\Retryable;
use PHPUnit\Framework\TestCase;
use Tests\E2E\Client;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\System\System;
abstract class Scope extends TestCase
@@ -40,6 +42,10 @@ abstract class Scope extends TestCase
protected function tearDown(): void
{
$this->client = null;
// Clean up Authorization context to prevent state contamination between tests
Authorization::cleanRoles();
Authorization::setRole(Role::any()->toString());
}
protected function getLastEmail(int $limit = 1): array
@@ -4354,6 +4354,189 @@ trait DatabasesBase
return $data;
}
public function testCreatedByUpdatedBy(): void
{
// Setup: Create database for createdBy/updatedBy tests
$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'
]);
$this->assertEquals(201, $database['headers']['status-code']);
$databaseId = $database['body']['$id'];
// Setup: Create collection for createdBy/updatedBy tests
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'collectionId' => ID::unique(),
'name' => 'CreatedBy UpdatedBy Collection',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$collectionId = $collection['body']['$id'];
// Setup: Add attributes
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'key' => 'title',
'size' => 255,
'required' => true,
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'key' => 'releaseYear',
'required' => false,
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'key' => 'count',
'required' => false,
]);
// Wait for attributes to be created
sleep(2);
$headers = $this->getSide() === 'client' ? array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()): ['x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']];
// Test 1: Create document - verify $createdBy and $updatedBy behavior
$document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', $headers, [
'documentId' => ID::unique(),
'data' => [
'title' => 'CreatedBy UpdatedBy Test',
'releaseYear' => 2024
]
]);
$this->assertEquals(201, $document['headers']['status-code']);
$this->assertEquals('CreatedBy UpdatedBy Test', $document['body']['title']);
$documentId = $document['body']['$id'];
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $document['body']['$createdBy']);
$this->assertEquals($user['$id'], $document['body']['$updatedBy']);
\sleep(1);
// Test 2: Update document - verify $updatedBy behavior
$updatedDocument = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, $headers, [
'data' => [
'title' => 'Updated CreatedBy UpdatedBy Test',
]
]);
$this->assertEquals(200, $updatedDocument['headers']['status-code']);
$this->assertEquals('Updated CreatedBy UpdatedBy Test', $updatedDocument['body']['title']);
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $updatedDocument['body']['$createdBy']);
$this->assertEquals($user['$id'], $updatedDocument['body']['$updatedBy']);
// Test 4: Upsert operation
$upsertDocument = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, $headers, [
'data' => [
'title' => 'Upserted Document',
'releaseYear' => 2025
]
]);
$this->assertEquals(200, $upsertDocument['headers']['status-code']);
$this->assertEquals('Upserted Document', $upsertDocument['body']['title']);
// Client side: $createdBy should remain original user, $updatedBy should be current user
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $upsertDocument['body']['$createdBy']);
$this->assertEquals($user['$id'], $upsertDocument['body']['$updatedBy']);
// Test 5: Create new document with upsert
$newUpsertId = ID::unique();
$newUpsertDocument = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $newUpsertId, $headers, [
'data' => [
'title' => 'New Upserted Document',
'releaseYear' => 2026
]
]);
$this->assertEquals(200, $newUpsertDocument['headers']['status-code']);
$this->assertEquals('New Upserted Document', $newUpsertDocument['body']['title']);
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $newUpsertDocument['body']['$createdBy']);
$this->assertEquals($user['$id'], $newUpsertDocument['body']['$updatedBy']);
// Test 6: Increment/Decrement operations
$countDocument = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', $headers, [
'documentId' => ID::unique(),
'data' => [
'title' => 'Count Test Document',
'count' => 10
]
]);
$this->assertEquals(201, $countDocument['headers']['status-code']);
$countDocumentId = $countDocument['body']['$id'];
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $countDocument['body']['$createdBy']);
$this->assertEquals($user['$id'], $countDocument['body']['$updatedBy']);
// Test increment operation
$incrementResponse = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $countDocumentId . '/count/increment', $headers, [
'value' => 5
]);
$this->assertEquals(200, $incrementResponse['headers']['status-code']);
$this->assertEquals(15, $incrementResponse['body']['count']);
// Client side: $createdBy should remain unchanged, $updatedBy should be current user
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $incrementResponse['body']['$createdBy']);
$this->assertEquals($user['$id'], $incrementResponse['body']['$updatedBy']);
// Test decrement operation
$decrementResponse = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $countDocumentId . '/count/decrement', $headers, [
'value' => 3
]);
$this->assertEquals(200, $decrementResponse['headers']['status-code']);
$this->assertEquals(12, $decrementResponse['body']['count']);
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $decrementResponse['body']['$createdBy']);
$this->assertEquals($user['$id'], $decrementResponse['body']['$updatedBy']);
// Cleanup
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, $headers);
}
public function testUpdatePermissionsWithEmptyPayload(): array
{
// Create Database
@@ -6729,4 +6729,246 @@ class DatabasesCustomServerTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey']
]));
}
public function testCreatedByUpdatedByModify(): void
{
// Create database
$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' => 'CreatedBy UpdatedBy Test DB'
]);
$this->assertEquals(201, $database['headers']['status-code']);
$databaseId = $database['body']['$id'];
// Create collection
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'collectionId' => ID::unique(),
'name' => 'CreatedBy UpdatedBy Collection',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$collectionId = $collection['body']['$id'];
// Add attributes
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'key' => 'title',
'size' => 255,
'required' => true,
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'key' => 'count',
'required' => false,
]);
sleep(2);
// Test 1: Create document with manual $createdBy and $updatedBy
$document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'documentId' => ID::unique(),
'data' => [
'title' => 'Server Side Test',
'count' => 10,
'$createdBy' => 'user-123',
'$updatedBy' => 'user-456'
]
]);
$this->assertEquals(201, $document['headers']['status-code']);
$this->assertEquals('user-123', $document['body']['$createdBy']);
$this->assertEquals('user-456', $document['body']['$updatedBy']);
$documentId = $document['body']['$id'];
// Test 2: Update document with different $updatedBy
$updatedDocument = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'data' => [
'title' => 'Updated Server Side Test',
'$updatedBy' => 'user-789'
]
]);
$this->assertEquals(200, $updatedDocument['headers']['status-code']);
$this->assertEquals('user-123', $updatedDocument['body']['$createdBy']); // Should remain unchanged
$this->assertEquals('user-789', $updatedDocument['body']['$updatedBy']); // Should be updated
// Test 3: Bulk create with mixed $createdBy and $updatedBy values
// bulk1: both createdBy and updatedBy set
// bulk2: only createdBy set, updatedBy should be null
// bulk3: neither set, both should be null
$bulkDocuments = [
[
'$id' => 'bulk1',
'title' => 'Bulk Document 1',
'count' => 1,
'$createdBy' => 'bulk-user-1',
'$updatedBy' => 'bulk-user-1'
],
[
'$id' => 'bulk2',
'title' => 'Bulk Document 2',
'count' => 2,
'$createdBy' => 'bulk-user-2'
],
[
'$id' => 'bulk3',
'title' => 'Bulk Document 3',
'count' => 3
]
];
$bulkCreateResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'documents' => $bulkDocuments
]);
$this->assertEquals(201, $bulkCreateResponse['headers']['status-code']);
$this->assertCount(3, $bulkCreateResponse['body']['documents']);
// Verify bulk create results
$this->assertEquals('bulk-user-1', $bulkCreateResponse['body']['documents'][0]['$createdBy']);
$this->assertEquals('bulk-user-1', $bulkCreateResponse['body']['documents'][0]['$updatedBy']);
$this->assertEquals('bulk-user-2', $bulkCreateResponse['body']['documents'][1]['$createdBy']);
// Test 4: Bulk update with $updatedBy
$bulkUpdateResponse = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'data' => [
'title' => 'Bulk Updated',
'$updatedBy' => 'bulk-updater'
],
'queries' => [Query::startsWith('$id', 'bulk')->toString()]
]);
$this->assertEquals(200, $bulkUpdateResponse['headers']['status-code']);
$this->assertCount(3, $bulkUpdateResponse['body']['documents']);
// Verify bulk update results - $createdBy should remain unchanged, $updatedBy should be updated
foreach ($bulkUpdateResponse['body']['documents'] as $doc) {
$this->assertEquals('bulk-updater', $doc['$updatedBy']);
$this->assertEquals('Bulk Updated', $doc['title']);
// $createdBy should remain as originally set
if ($doc['$id'] === 'bulk1') {
$this->assertEquals('bulk-user-1', $doc['$createdBy']);
} elseif ($doc['$id'] === 'bulk2') {
$this->assertEquals('bulk-user-2', $doc['$createdBy']);
}
}
// Test 5: Bulk upsert with $createdBy and $updatedBy
// bulk1: existing document - createdBy should be ignored, updatedBy should be updated
// new-upsert: new document - both should be set as specified
$upsertDocuments = [
[
'$id' => 'bulk1', // Existing document
'title' => 'Upserted Document 1',
'count' => 100,
'$createdBy' => 'should-not-change',
'$updatedBy' => 'upsert-updater-1'
],
[
'$id' => 'new-upsert',
'title' => 'New Upserted Document',
'count' => 200,
'$createdBy' => 'new-creator',
'$updatedBy' => 'new-updater'
]
];
$bulkUpsertResponse = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'documents' => $upsertDocuments
]);
$this->assertEquals(200, $bulkUpsertResponse['headers']['status-code']);
$this->assertCount(2, $bulkUpsertResponse['body']['documents']);
// Verify upsert results
foreach ($bulkUpsertResponse['body']['documents'] as $doc) {
if ($doc['$id'] === 'bulk1') {
$this->assertEquals('should-not-change', $doc['$createdBy']);
$this->assertEquals('upsert-updater-1', $doc['$updatedBy']);
} elseif ($doc['$id'] === 'new-upsert') {
$this->assertEquals('new-creator', $doc['$createdBy']);
$this->assertEquals('new-updater', $doc['$updatedBy']);
}
}
// Test 6: Increment operation
// making requests via root
$incrementResponse = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId . '/count/increment', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'value' => 5
]);
$this->assertEquals(200, $incrementResponse['headers']['status-code']);
$this->assertEquals(15, $incrementResponse['body']['count']);
$this->assertEquals('user-123', $incrementResponse['body']['$createdBy']);
$this->assertEquals($this->getRoot()['$id'], $incrementResponse['body']['$updatedBy']);
// Test 7: Decrement operation
$decrementResponse = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId . '/count/decrement', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'value' => 3
]);
$this->assertEquals(200, $decrementResponse['headers']['status-code']);
$this->assertEquals(12, $decrementResponse['body']['count']);
$this->assertEquals('user-123', $decrementResponse['body']['$createdBy']);
$this->assertEquals($this->getRoot()['$id'], $decrementResponse['body']['$updatedBy']);
// Cleanup
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]);
}
}
@@ -8828,4 +8828,186 @@ trait DatabasesBase
$this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}", $headers);
}
public function testCreatedByUpdatedBy(): void
{
// Setup: Create database for createdBy/updatedBy tests
$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'
]);
$this->assertEquals(201, $database['headers']['status-code']);
$databaseId = $database['body']['$id'];
// Setup: Create table for createdBy/updatedBy tests
$table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'tableId' => ID::unique(),
'name' => 'CreatedBy UpdatedBy Table',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
]);
$this->assertEquals(201, $table['headers']['status-code']);
$tableId = $table['body']['$id'];
// Setup: Add columns
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'key' => 'title',
'size' => 255,
'required' => true,
]);
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'key' => 'releaseYear',
'required' => false,
]);
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'key' => 'count',
'required' => false,
]);
// Wait for columns to be created
sleep(2);
$headers = array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders());
// Test 1: Create row - verify $createdBy and $updatedBy behavior
$row = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', $headers, [
'rowId' => ID::unique(),
'data' => [
'title' => 'CreatedBy UpdatedBy Test',
'releaseYear' => 2024
]
]);
$this->assertEquals(201, $row['headers']['status-code']);
$this->assertEquals('CreatedBy UpdatedBy Test', $row['body']['title']);
$rowId = $row['body']['$id'];
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $row['body']['$createdBy']);
$this->assertEquals($user['$id'], $row['body']['$updatedBy']);
\sleep(1);
// Test 2: Update row - verify $updatedBy behavior
$updatedRow = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, $headers, [
'data' => [
'title' => 'Updated CreatedBy UpdatedBy Test',
]
]);
$this->assertEquals(200, $updatedRow['headers']['status-code']);
$this->assertEquals('Updated CreatedBy UpdatedBy Test', $updatedRow['body']['title']);
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $updatedRow['body']['$createdBy']);
$this->assertEquals($user['$id'], $updatedRow['body']['$updatedBy']);
// Test 4: Upsert operation
$upsertRow = $this->client->call(Client::METHOD_PUT, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, $headers, [
'data' => [
'title' => 'Upserted Row',
'releaseYear' => 2025
]
]);
$this->assertEquals(200, $upsertRow['headers']['status-code']);
$this->assertEquals('Upserted Row', $upsertRow['body']['title']);
// Client side: $createdBy should remain original user, $updatedBy should be current user
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $upsertRow['body']['$createdBy']);
$this->assertEquals($user['$id'], $upsertRow['body']['$updatedBy']);
// Test 5: Create new row with upsert
$newUpsertId = ID::unique();
$newUpsertRow = $this->client->call(Client::METHOD_PUT, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $newUpsertId, $headers, [
'data' => [
'title' => 'New Upserted Row',
'releaseYear' => 2026
]
]);
$this->assertEquals(200, $newUpsertRow['headers']['status-code']);
$this->assertEquals('New Upserted Row', $newUpsertRow['body']['title']);
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $newUpsertRow['body']['$createdBy']);
$this->assertEquals($user['$id'], $newUpsertRow['body']['$updatedBy']);
// Test 6: Increment/Decrement operations
$countRow = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', $headers, [
'rowId' => ID::unique(),
'data' => [
'title' => 'Count Test Row',
'count' => 10
]
]);
$this->assertEquals(201, $countRow['headers']['status-code']);
$countRowId = $countRow['body']['$id'];
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $countRow['body']['$createdBy']);
$this->assertEquals($user['$id'], $countRow['body']['$updatedBy']);
// Test increment operation
$incrementResponse = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $countRowId . '/count/increment', $headers, [
'value' => 5
]);
$this->assertEquals(200, $incrementResponse['headers']['status-code']);
$this->assertEquals(15, $incrementResponse['body']['count']);
// Client side: $createdBy should remain unchanged, $updatedBy should be current user
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $incrementResponse['body']['$createdBy']);
$this->assertEquals($user['$id'], $incrementResponse['body']['$updatedBy']);
// Test decrement operation
$decrementResponse = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $countRowId . '/count/decrement', $headers, [
'value' => 3
]);
$this->assertEquals(200, $decrementResponse['headers']['status-code']);
$this->assertEquals(12, $decrementResponse['body']['count']);
$user = $this->getSide() === 'client' ?$this->getUser() : $this->getRoot();
$this->assertEquals($user['$id'], $decrementResponse['body']['$createdBy']);
$this->assertEquals($user['$id'], $decrementResponse['body']['$updatedBy']);
// Cleanup
$this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId, $headers);
}
}
@@ -6697,4 +6697,246 @@ class DatabasesCustomServerTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey']
]));
}
public function testCreatedByUpdatedByModify(): void
{
// Create database
$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' => 'CreatedBy UpdatedBy Test DB'
]);
$this->assertEquals(201, $database['headers']['status-code']);
$databaseId = $database['body']['$id'];
// Create table
$table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'tableId' => ID::unique(),
'name' => 'CreatedBy UpdatedBy Table',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
]);
$this->assertEquals(201, $table['headers']['status-code']);
$tableId = $table['body']['$id'];
// Add columns
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'key' => 'title',
'size' => 255,
'required' => true,
]);
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'key' => 'count',
'required' => false,
]);
sleep(2);
// Test 1: Create row with manual $createdBy and $updatedBy
$row = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'rowId' => ID::unique(),
'data' => [
'title' => 'Server Side Test',
'count' => 10,
'$createdBy' => 'user-123',
'$updatedBy' => 'user-456'
]
]);
$this->assertEquals(201, $row['headers']['status-code']);
$this->assertEquals('user-123', $row['body']['$createdBy']);
$this->assertEquals('user-456', $row['body']['$updatedBy']);
$rowId = $row['body']['$id'];
// Test 2: Update row with different $updatedBy
$updatedRow = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'data' => [
'title' => 'Updated Server Side Test',
'$updatedBy' => 'user-789'
]
]);
$this->assertEquals(200, $updatedRow['headers']['status-code']);
$this->assertEquals('user-123', $updatedRow['body']['$createdBy']); // Should remain unchanged
$this->assertEquals('user-789', $updatedRow['body']['$updatedBy']); // Should be updated
// Test 3: Bulk create with mixed $createdBy and $updatedBy values
// bulk1: both createdBy and updatedBy set
// bulk2: only createdBy set, updatedBy should be null
// bulk3: neither set, both should be null
$bulkRows = [
[
'$id' => 'bulk1',
'title' => 'Bulk Row 1',
'count' => 1,
'$createdBy' => 'bulk-user-1',
'$updatedBy' => 'bulk-user-1'
],
[
'$id' => 'bulk2',
'title' => 'Bulk Row 2',
'count' => 2,
'$createdBy' => 'bulk-user-2'
],
[
'$id' => 'bulk3',
'title' => 'Bulk Row 3',
'count' => 3
]
];
$bulkCreateResponse = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'rows' => $bulkRows
]);
$this->assertEquals(201, $bulkCreateResponse['headers']['status-code']);
$this->assertCount(3, $bulkCreateResponse['body']['rows']);
// Verify bulk create results
$this->assertEquals('bulk-user-1', $bulkCreateResponse['body']['rows'][0]['$createdBy']);
$this->assertEquals('bulk-user-1', $bulkCreateResponse['body']['rows'][0]['$updatedBy']);
$this->assertEquals('bulk-user-2', $bulkCreateResponse['body']['rows'][1]['$createdBy']);
// Test 4: Bulk update with $updatedBy
$bulkUpdateResponse = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'data' => [
'title' => 'Bulk Updated',
'$updatedBy' => 'bulk-updater'
],
'queries' => [Query::startsWith('$id', 'bulk')->toString()]
]);
$this->assertEquals(200, $bulkUpdateResponse['headers']['status-code']);
$this->assertCount(3, $bulkUpdateResponse['body']['rows']);
// Verify bulk update results - $createdBy should remain unchanged, $updatedBy should be updated
foreach ($bulkUpdateResponse['body']['rows'] as $doc) {
$this->assertEquals('bulk-updater', $doc['$updatedBy']);
$this->assertEquals('Bulk Updated', $doc['title']);
// $createdBy should remain as originally set
if ($doc['$id'] === 'bulk1') {
$this->assertEquals('bulk-user-1', $doc['$createdBy']);
} elseif ($doc['$id'] === 'bulk2') {
$this->assertEquals('bulk-user-2', $doc['$createdBy']);
}
}
// Test 5: Bulk upsert with $createdBy and $updatedBy
// bulk1: existing row - createdBy should be ignored, updatedBy should be updated
// new-upsert: new row - both should be set as specified
$upsertRows = [
[
'$id' => 'bulk1', // Existing row
'title' => 'Upserted Row 1',
'count' => 100,
'$createdBy' => 'should-not-change',
'$updatedBy' => 'upsert-updater-1'
],
[
'$id' => 'new-upsert',
'title' => 'New Upserted Row',
'count' => 200,
'$createdBy' => 'new-creator',
'$updatedBy' => 'new-updater'
]
];
$bulkUpsertResponse = $this->client->call(Client::METHOD_PUT, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'rows' => $upsertRows
]);
$this->assertEquals(200, $bulkUpsertResponse['headers']['status-code']);
$this->assertCount(2, $bulkUpsertResponse['body']['rows']);
// Verify upsert results
foreach ($bulkUpsertResponse['body']['rows'] as $doc) {
if ($doc['$id'] === 'bulk1') {
$this->assertEquals('should-not-change', $doc['$createdBy']);
$this->assertEquals('upsert-updater-1', $doc['$updatedBy']);
} elseif ($doc['$id'] === 'new-upsert') {
$this->assertEquals('new-creator', $doc['$createdBy']);
$this->assertEquals('new-updater', $doc['$updatedBy']);
}
}
// Test 6: Increment operation
// making requests via root
$incrementResponse = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId . '/count/increment', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'value' => 5
]);
$this->assertEquals(200, $incrementResponse['headers']['status-code']);
$this->assertEquals(15, $incrementResponse['body']['count']);
$this->assertEquals('user-123', $incrementResponse['body']['$createdBy']);
$this->assertEquals($this->getRoot()['$id'], $incrementResponse['body']['$updatedBy']);
// Test 7: Decrement operation
$decrementResponse = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId . '/count/decrement', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'value' => 3
]);
$this->assertEquals(200, $decrementResponse['headers']['status-code']);
$this->assertEquals(12, $decrementResponse['body']['count']);
$this->assertEquals('user-123', $decrementResponse['body']['$createdBy']);
$this->assertEquals($this->getRoot()['$id'], $decrementResponse['body']['$updatedBy']);
// Cleanup
$this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]);
}
}