Merge branch '1.8.x' into add-param-examples

This commit is contained in:
Chirag Aggarwal 2025-09-04 20:48:28 +05:30
commit d2b681e95c
24 changed files with 15904 additions and 1090 deletions

View file

@ -5745,7 +5745,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 448,
"weight": 460,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@ -5820,7 +5820,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 446,
"weight": 458,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@ -5936,7 +5936,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 447,
"weight": 459,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@ -7491,7 +7491,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 420,
"weight": 432,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@ -7579,7 +7579,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 412,
"weight": 424,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@ -7724,7 +7724,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 413,
"weight": 425,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@ -7822,7 +7822,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 416,
"weight": 428,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@ -7961,7 +7961,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 414,
"weight": 426,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@ -8063,7 +8063,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 418,
"weight": 430,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@ -8150,7 +8150,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 423,
"weight": 435,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@ -8268,7 +8268,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 422,
"weight": 434,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -5745,7 +5745,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 448,
"weight": 460,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@ -5820,7 +5820,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 446,
"weight": 458,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@ -5936,7 +5936,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 447,
"weight": 459,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@ -7491,7 +7491,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 420,
"weight": 432,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@ -7579,7 +7579,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 412,
"weight": 424,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@ -7724,7 +7724,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 413,
"weight": 425,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@ -7822,7 +7822,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 416,
"weight": 428,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@ -7961,7 +7961,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 414,
"weight": 426,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@ -8063,7 +8063,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 418,
"weight": 430,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@ -8150,7 +8150,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 423,
"weight": 435,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@ -8268,7 +8268,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 422,
"weight": 434,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -5845,7 +5845,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 448,
"weight": 460,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@ -5918,7 +5918,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 446,
"weight": 458,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@ -6035,7 +6035,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 447,
"weight": 459,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@ -7573,7 +7573,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 420,
"weight": 432,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@ -7657,7 +7657,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 412,
"weight": 424,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@ -7799,7 +7799,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 413,
"weight": 425,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@ -7891,7 +7891,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 416,
"weight": 428,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@ -8025,7 +8025,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 414,
"weight": 426,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@ -8124,7 +8124,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 418,
"weight": 430,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@ -8206,7 +8206,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 423,
"weight": 435,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@ -8317,7 +8317,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 422,
"weight": 434,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -5845,7 +5845,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 448,
"weight": 460,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@ -5918,7 +5918,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 446,
"weight": 458,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@ -6035,7 +6035,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 447,
"weight": 459,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@ -7573,7 +7573,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 420,
"weight": 432,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@ -7657,7 +7657,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 412,
"weight": 424,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@ -7799,7 +7799,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 413,
"weight": 425,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@ -7891,7 +7891,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 416,
"weight": 428,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@ -8025,7 +8025,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 414,
"weight": 426,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@ -8124,7 +8124,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 418,
"weight": 430,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@ -8206,7 +8206,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 423,
"weight": 435,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@ -8317,7 +8317,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 422,
"weight": 434,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -36,6 +36,7 @@ use Utopia\Database\Exception\Authorization as AuthorizationException;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@ -282,7 +283,13 @@ App::get('/v1/teams/:teamId/prefs')
$prefs = $team->getAttribute('prefs', []);
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
try {
$prefs = new Document($prefs);
} catch (StructureException $e) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
}
$response->dynamic($prefs, Response::MODEL_PREFERENCES);
});
App::put('/v1/teams/:teamId')
@ -357,6 +364,11 @@ App::put('/v1/teams/:teamId/prefs')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $teamId, array $prefs, Response $response, Database $dbForProject, Event $queueForEvents) {
try {
$prefs = new Document($prefs);
} catch (StructureException $e) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
}
$team = $dbForProject->getDocument('teams', $teamId);
@ -364,11 +376,13 @@ App::put('/v1/teams/:teamId/prefs')
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$team = $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('prefs', $prefs));
$team = $dbForProject->updateDocument('teams', $team->getId(), new Document([
'prefs' => $prefs->getArrayCopy()
]));
$queueForEvents->setParam('teamId', $team->getId());
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
$response->dynamic($prefs, Response::MODEL_PREFERENCES);
});
App::delete('/v1/teams/:teamId')

View file

@ -15,6 +15,7 @@ use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Appwrite\Vcs\Comment;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@ -135,11 +136,39 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
if (!$latestComment->isEmpty()) {
$latestCommentId = $latestComment->getAttribute('providerCommentId', '');
$comment = new Comment();
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
$retries = 0;
$lockAcquired = false;
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
while ($retries < 9) {
$retries++;
try {
$dbForPlatform->createDocument('vcsCommentLocks', new Document([
'$id' => $latestCommentId
]));
$lockAcquired = true;
break;
} catch (\Throwable $err) {
if ($retries >= 9) {
Console::warning("Error creating vcs comment lock for " . $latestCommentId . ": " . $err->getMessage());
}
\sleep(1);
}
}
if ($lockAcquired) {
// Wrap in try/finally to ensure lock file gets deleted
try {
$comment = new Comment();
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
} finally {
$dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId);
}
}
} else {
$comment = new Comment();
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
@ -177,11 +206,40 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
foreach ($latestComments as $comment) {
$latestCommentId = $comment->getAttribute('providerCommentId', '');
$comment = new Comment();
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
$retries = 0;
$lockAcquired = false;
while ($retries < 9) {
$retries++;
try {
$dbForPlatform->createDocument('vcsCommentLocks', new Document([
'$id' => $latestCommentId
]));
$lockAcquired = true;
break;
} catch (\Throwable $err) {
if ($retries >= 9) {
Console::warning("Error creating vcs comment lock for " . $latestCommentId . ": " . $err->getMessage());
}
\sleep(1);
}
}
if ($lockAcquired) {
// Wrap in try/finally to ensure lock file gets deleted
try {
$comment = new Comment();
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
} finally {
$dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId);
}
}
}
}
@ -271,6 +329,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$domain = ID::unique() . "." . $sitesDomain;
$ruleId = md5($domain);
$previewRuleId = $ruleId;
Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
@ -362,6 +421,48 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
}
if ($resource->getCollection() === 'sites' && !empty($latestCommentId) && !empty($previewRuleId)) {
$retries = 0;
$lockAcquired = false;
while ($retries < 9) {
$retries++;
try {
$dbForPlatform->createDocument('vcsCommentLocks', new Document([
'$id' => $latestCommentId
]));
$lockAcquired = true;
break;
} catch (\Throwable $err) {
if ($retries >= 9) {
Console::warning("Error creating vcs comment lock for " . $latestCommentId . ": " . $err->getMessage());
}
\sleep(1);
}
}
if ($lockAcquired) {
// Wrap in try/finally to ensure lock file gets deleted
try {
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId));
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$previewUrl = !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '';
if (!empty($previewUrl)) {
$comment = new Comment();
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, $previewUrl);
$github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment());
}
} finally {
$dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId);
}
}
}
if (!empty($providerCommitHash) && $resource->getAttribute('providerSilentMode', false) === false) {
$resourceName = $resource->getAttribute('name');
$projectName = $project->getAttribute('name');

12
composer.lock generated
View file

@ -3650,16 +3650,16 @@
},
{
"name": "utopia-php/database",
"version": "1.3.1",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "fcd166b715a14cfea11f7a9c47d4c0076bedcecd"
"reference": "d32bd6160d55cab0cbe4b070e1c56e4c2a03c7a0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/fcd166b715a14cfea11f7a9c47d4c0076bedcecd",
"reference": "fcd166b715a14cfea11f7a9c47d4c0076bedcecd",
"url": "https://api.github.com/repos/utopia-php/database/zipball/d32bd6160d55cab0cbe4b070e1c56e4c2a03c7a0",
"reference": "d32bd6160d55cab0cbe4b070e1c56e4c2a03c7a0",
"shasum": ""
},
"require": {
@ -3700,9 +3700,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/1.3.1"
"source": "https://github.com/utopia-php/database/tree/1.4.0"
},
"time": "2025-09-03T15:50:41+00:00"
"time": "2025-09-04T11:45:26+00:00"
},
{
"name": "utopia-php/detector",

View file

@ -57,8 +57,7 @@ class Mapper
'datetime' => Type::string(),
'json' => Types::json(),
'none' => Types::json(),
'any' => Types::json(),
'spatial' => Types::json(),
'any' => Types::json()
];
foreach ($defaults as $type => $default) {

View file

@ -15,7 +15,6 @@ abstract class Model
public const TYPE_DATETIME_EXAMPLE = '2020-10-15T06:38:00.000+00:00';
public const TYPE_RELATIONSHIP = 'relationship';
public const TYPE_PAYLOAD = 'payload';
public const TYPE_SPATIAL = 'spatial';
/**
* @var bool

View file

@ -12,7 +12,7 @@ class AttributeLine extends Attribute
$this
->addRule('default', [
'type' => self::TYPE_SPATIAL,
'type' => self::TYPE_JSON,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'required' => false,

View file

@ -12,7 +12,7 @@ class AttributePoint extends Attribute
$this
->addRule('default', [
'type' => self::TYPE_SPATIAL,
'type' => self::TYPE_JSON,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'required' => false,

View file

@ -12,7 +12,7 @@ class AttributePolygon extends Attribute
$this
->addRule('default', [
'type' => self::TYPE_SPATIAL,
'type' => self::TYPE_JSON,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'required' => false,

View file

@ -12,7 +12,7 @@ class ColumnLine extends Column
$this
->addRule('default', [
'type' => self::TYPE_SPATIAL,
'type' => self::TYPE_JSON,
'description' => 'Default value for column when not provided. Cannot be set when column is required.',
'default' => null,
'required' => false,

View file

@ -12,7 +12,7 @@ class ColumnPoint extends Column
$this
->addRule('default', [
'type' => self::TYPE_SPATIAL,
'type' => self::TYPE_JSON,
'description' => 'Default value for column when not provided. Cannot be set when column is required.',
'default' => null,
'required' => false,

View file

@ -12,7 +12,7 @@ class ColumnPolygon extends Column
$this
->addRule('default', [
'type' => self::TYPE_SPATIAL,
'type' => self::TYPE_JSON,
'description' => 'Default value for column when not provided. Cannot be set when column is required.',
'default' => null,
'required' => false,

View file

@ -6343,6 +6343,192 @@ trait DatabasesBase
$this->assertEquals('Latest Event', $rows['body']['rows'][0]['name']);
}
/**
* @throws \Utopia\Database\Exception
* @throws \Utopia\Database\Exception\Query
*/
public function testCreatedBetween(): 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' => 'CreatedBetween test'
]);
$this->assertNotEmpty($database['body']['$id']);
$this->assertEquals(201, $database['headers']['status-code']);
$this->assertEquals('CreatedBetween test', $database['body']['name']);
$databaseId = $database['body']['$id'];
// Create Collection
$articles = $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' => 'Articles',
'rowSecurity' => true,
'permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
],
]);
$this->assertEquals(201, $articles['headers']['status-code']);
$this->assertEquals($articles['body']['name'], 'Articles');
// Create Attributes
$title = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $articles['body']['$id'] . '/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,
]);
$this->assertEquals(202, $title['headers']['status-code']);
$content = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $articles['body']['$id'] . '/columns/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'content',
'size' => 5000,
'required' => true,
]);
$this->assertEquals(202, $content['headers']['status-code']);
// Wait for attributes to be available
sleep(2);
// Create first article
$row1 = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $articles['body']['$id'] . '/rows', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rowId' => ID::unique(),
'data' => [
'title' => 'First Article',
'content' => 'This is the first article content',
],
'permissions' => [
Permission::read(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(201, $row1['headers']['status-code']);
$firstArticleCreatedAt = $row1['body']['$createdAt'];
// Sleep to ensure different timestamps
sleep(1);
// Create second article
$row2 = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $articles['body']['$id'] . '/rows', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rowId' => ID::unique(),
'data' => [
'title' => 'Second Article',
'content' => 'This is the second article content',
],
'permissions' => [
Permission::read(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(201, $row2['headers']['status-code']);
$secondArticleCreatedAt = $row2['body']['$createdAt'];
// Sleep again
sleep(1);
// Create third article
$row3 = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $articles['body']['$id'] . '/rows', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rowId' => ID::unique(),
'data' => [
'title' => 'Third Article',
'content' => 'This is the third article content',
],
'permissions' => [
Permission::read(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(201, $row3['headers']['status-code']);
$thirdArticleCreatedAt = $row3['body']['$createdAt'];
// Sleep again
sleep(1);
// Create fourth article
$row4 = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $articles['body']['$id'] . '/rows', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rowId' => ID::unique(),
'data' => [
'title' => 'Fourth Article',
'content' => 'This is the fourth article content',
],
'permissions' => [
Permission::read(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(201, $row4['headers']['status-code']);
// Test createdBetween query - should return articles created between first and third (inclusive)
$rows = $this->client->call(
Client::METHOD_GET,
'/tablesdb/' . $databaseId . '/tables/' . $articles['body']['$id'] . '/rows',
array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()),
[
'queries' => [
Query::createdBetween($firstArticleCreatedAt, $thirdArticleCreatedAt)->toString(),
],
]
);
$this->assertEquals(200, $rows['headers']['status-code']);
$this->assertCount(3, $rows['body']['rows']);
// Verify the returned articles are the correct ones
$titles = array_column($rows['body']['rows'], 'title');
$this->assertContains('First Article', $titles);
$this->assertContains('Second Article', $titles);
$this->assertContains('Third Article', $titles);
$this->assertNotContains('Fourth Article', $titles);
// Test createdBetween query - should return only the second article when using its timestamp for both bounds
$rows = $this->client->call(
Client::METHOD_GET,
'/tablesdb/' . $databaseId . '/tables/' . $articles['body']['$id'] . '/rows',
array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()),
[
'queries' => [
Query::createdBetween($secondArticleCreatedAt, $secondArticleCreatedAt)->toString(),
],
]
);
$this->assertEquals(200, $rows['headers']['status-code']);
$this->assertCount(1, $rows['body']['rows']);
$this->assertEquals('Second Article', $rows['body']['rows'][0]['title']);
}
/**
* @throws \Utopia\Database\Exception
* @throws \Utopia\Database\Exception\Query
@ -6689,6 +6875,249 @@ trait DatabasesBase
$this->assertEquals('delivered', $rows['body']['rows'][0]['status']);
}
/**
* @throws \Utopia\Database\Exception
* @throws \Utopia\Database\Exception\Query
*/
public function testUpdatedBetween(): 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' => 'UpdatedBetween test'
]);
$this->assertNotEmpty($database['body']['$id']);
$this->assertEquals(201, $database['headers']['status-code']);
$this->assertEquals('UpdatedBetween test', $database['body']['name']);
$databaseId = $database['body']['$id'];
// Create Collection
$products = $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' => 'Products',
'rowSecurity' => true,
'permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
],
]);
$this->assertEquals(201, $products['headers']['status-code']);
$this->assertEquals($products['body']['name'], 'Products');
// Create Attributes
$name = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $products['body']['$id'] . '/columns/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
$this->assertEquals(202, $name['headers']['status-code']);
$price = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $products['body']['$id'] . '/columns/float', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'price',
'required' => true,
]);
$this->assertEquals(202, $price['headers']['status-code']);
// Wait for attributes to be available
sleep(2);
// Create first product
$row1 = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $products['body']['$id'] . '/rows', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rowId' => ID::unique(),
'data' => [
'name' => 'Product A',
'price' => 99.99,
],
'permissions' => [
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(201, $row1['headers']['status-code']);
// Sleep to ensure different timestamps
sleep(1);
// Create second product
$row2 = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $products['body']['$id'] . '/rows', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rowId' => ID::unique(),
'data' => [
'name' => 'Product B',
'price' => 149.99,
],
'permissions' => [
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(201, $row2['headers']['status-code']);
// Sleep again
sleep(1);
// Create third product
$row3 = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $products['body']['$id'] . '/rows', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rowId' => ID::unique(),
'data' => [
'name' => 'Product C',
'price' => 199.99,
],
'permissions' => [
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(201, $row3['headers']['status-code']);
// Sleep again
sleep(1);
// Create fourth product
$row4 = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $products['body']['$id'] . '/rows', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rowId' => ID::unique(),
'data' => [
'name' => 'Product D',
'price' => 249.99,
],
'permissions' => [
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(201, $row4['headers']['status-code']);
// Now update products in sequence to get different updatedAt timestamps
sleep(1);
// Update first product
$update1 = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $products['body']['$id'] . '/rows/' . $row1['body']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'price' => 89.99,
]
]);
$this->assertEquals(200, $update1['headers']['status-code']);
$firstProductUpdatedAt = $update1['body']['$updatedAt'];
sleep(1);
// Update second product
$update2 = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $products['body']['$id'] . '/rows/' . $row2['body']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'price' => 139.99,
]
]);
$this->assertEquals(200, $update2['headers']['status-code']);
$secondProductUpdatedAt = $update2['body']['$updatedAt'];
sleep(1);
// Update third product
$update3 = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $products['body']['$id'] . '/rows/' . $row3['body']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'price' => 189.99,
]
]);
$this->assertEquals(200, $update3['headers']['status-code']);
$thirdProductUpdatedAt = $update3['body']['$updatedAt'];
sleep(1);
// Update fourth product
$update4 = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $products['body']['$id'] . '/rows/' . $row4['body']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'price' => 239.99,
]
]);
$this->assertEquals(200, $update4['headers']['status-code']);
// Test updatedBetween query - should return products updated between first and third (inclusive)
$rows = $this->client->call(
Client::METHOD_GET,
'/tablesdb/' . $databaseId . '/tables/' . $products['body']['$id'] . '/rows',
array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()),
[
'queries' => [
Query::updatedBetween($firstProductUpdatedAt, $thirdProductUpdatedAt)->toString(),
],
]
);
$this->assertEquals(200, $rows['headers']['status-code']);
$this->assertCount(3, $rows['body']['rows']);
// Verify the returned products are the correct ones
$names = array_column($rows['body']['rows'], 'name');
$this->assertContains('Product A', $names);
$this->assertContains('Product B', $names);
$this->assertContains('Product C', $names);
$this->assertNotContains('Product D', $names);
// Test updatedBetween query - should return only the second product when using its timestamp for both bounds
$rows = $this->client->call(
Client::METHOD_GET,
'/tablesdb/' . $databaseId . '/tables/' . $products['body']['$id'] . '/rows',
array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()),
[
'queries' => [
Query::updatedBetween($secondProductUpdatedAt, $secondProductUpdatedAt)->toString(),
],
]
);
$this->assertEquals(200, $rows['headers']['status-code']);
$this->assertCount(1, $rows['body']['rows']);
$this->assertEquals('Product B', $rows['body']['rows'][0]['name']);
$this->assertEquals(139.99, $rows['body']['rows'][0]['price']);
}
/**
* @depends testCreateDatabase
* @param array $data