diff --git a/.env b/.env index 9abfa756e1..0df9cb42f4 100644 --- a/.env +++ b/.env @@ -39,7 +39,7 @@ _APP_REDIS_HOST=redis _APP_REDIS_PORT=6379 _APP_REDIS_PASS= _APP_REDIS_USER= -COMPOSE_PROFILES=mariadb,mongodb,postgresql +COMPOSE_PROFILES=mongodb _APP_DB_ADAPTER=mongodb _APP_DB_HOST=mongodb _APP_DB_PORT=27017 @@ -47,15 +47,6 @@ _APP_DB_SCHEMA=appwrite _APP_DB_USER=user _APP_DB_PASS=password _APP_DB_ROOT_PASS=rootsecretpassword -_APP_DB_ADAPTER_DOCUMENTSDB=mongodb -_APP_DB_HOST_DOCUMENTSDB=mongodb -_APP_DB_PORT_DOCUMENTSDB=27017 -_APP_DB_ADAPTER_VECTORSDB=postgresql -_APP_DB_HOST_VECTORSDB=postgresql -_APP_DB_PORT_VECTORSDB=5432 -_APP_EMBEDDING_MODELS=embeddinggemma -_APP_EMBEDDING_ENDPOINT='http://ollama:11434/api/embed' -_APP_EMBEDDING_TIMEOUT=30000 _APP_STORAGE_DEVICE=Local _APP_STORAGE_S3_ACCESS_KEY= _APP_STORAGE_S3_SECRET= diff --git a/app/config/collections.php b/app/config/collections.php index 3af20ff2ac..a74e079dce 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -4,7 +4,6 @@ $common = include __DIR__ . '/collections/common.php'; $projects = include __DIR__ . '/collections/projects.php'; $databases = include __DIR__ . '/collections/databases.php'; -$vectorsdb = include __DIR__ . '/collections/vectorsdb.php'; $platform = include __DIR__ . '/collections/platform.php'; $logs = include __DIR__ . '/collections/logs.php'; @@ -27,7 +26,6 @@ unset($common['files']); $collections = [ 'buckets' => $buckets, 'databases' => $databases, - 'vectorsdb' => $vectorsdb, 'projects' => array_merge_recursive($projects, $common), 'console' => array_merge_recursive($platform, $common), 'logs' => $logs, diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php index b41e8f0fd5..55dceb9b40 100644 --- a/app/config/collections/projects.php +++ b/app/config/collections/projects.php @@ -61,15 +61,6 @@ return [ 'array' => false, 'filters' => [], ], - [ - '$id' => ID::custom('database'), - 'type' => Database::VAR_STRING, - 'size' => 128, - 'required' => false, - 'signed' => true, - 'array' => false, - 'filters' => [], - ] ], 'indexes' => [ [ diff --git a/app/config/collections/vectorsdb.php b/app/config/collections/vectorsdb.php deleted file mode 100644 index 817863cffa..0000000000 --- a/app/config/collections/vectorsdb.php +++ /dev/null @@ -1,165 +0,0 @@ - [ - '$collection' => ID::custom('databases'), - '$id' => ID::custom('collections'), - 'name' => 'Collections', - 'attributes' => [ - [ - '$id' => ID::custom('databaseInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('databaseId'), - 'type' => Database::VAR_STRING, - 'signed' => true, - 'size' => Database::LENGTH_KEY, - 'format' => '', - 'filters' => [], - 'required' => true, - 'default' => null, - 'array' => false, - ], - [ - '$id' => ID::custom('name'), - 'type' => Database::VAR_STRING, - 'size' => 256, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('dimension'), - 'type' => Database::VAR_INTEGER, - 'size' => 0, - 'required' => true, - 'signed' => false, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('enabled'), - 'type' => Database::VAR_BOOLEAN, - 'signed' => true, - 'size' => 0, - 'format' => '', - 'filters' => [], - 'required' => true, - 'default' => null, - 'array' => false, - ], - [ - '$id' => ID::custom('documentSecurity'), - 'type' => Database::VAR_BOOLEAN, - 'signed' => true, - 'size' => 0, - 'format' => '', - 'filters' => [], - 'required' => true, - 'default' => null, - 'array' => false, - ], - [ - '$id' => ID::custom('attributes'), - 'type' => Database::VAR_STRING, - 'size' => 1000000, - 'required' => false, - 'signed' => true, - 'array' => false, - 'filters' => ['subQueryAttributes'], - ], - [ - '$id' => ID::custom('indexes'), - 'type' => Database::VAR_STRING, - 'size' => 1000000, - 'required' => false, - 'signed' => true, - 'array' => false, - 'filters' => ['subQueryIndexes'], - ], - [ - '$id' => ID::custom('search'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - ], - 'defaultAttributes' => [ - [ - '$id' => ID::custom('embeddings'), - 'type' => Database::VAR_VECTOR, - 'required' => true, - 'signed' => false, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('metadata'), - 'type' => Database::VAR_OBJECT, - 'default' => [], - 'required' => false, - 'size' => 0, - 'signed' => false, - 'array' => false, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_fulltext_search'), - 'type' => Database::INDEX_FULLTEXT, - 'attributes' => ['search'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => ID::custom('_key_name'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['name'], - 'lengths' => [256], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_enabled'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['enabled'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_documentSecurity'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['documentSecurity'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - ], - 'defaultIndexes' => [ - // not creating default indexes on the embeddings as it depends on the type of query users using the most - [ - '$id' => ID::custom('_key_metadata'), - 'type' => Database::INDEX_OBJECT, - 'attributes' => ['metadata'], - 'lengths' => [], - 'orders' => [], - ], - ] - ] -]; diff --git a/app/config/errors.php b/app/config/errors.php index ec2593d207..278dbb3458 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -1206,11 +1206,6 @@ return [ 'description' => 'Migration is already in progress. You can check the status of the migration in your Appwrite Console\'s "Settings" > "Migrations".', 'code' => 409, ], - Exception::MIGRATION_DATABASE_TYPE_UNSUPPORTED => [ - 'name' => Exception::MIGRATION_DATABASE_TYPE_UNSUPPORTED, - 'description' => 'The specified database type is not supported for CSV import or export operations.', - 'code' => 400, - ], /** Realtime */ Exception::REALTIME_MESSAGE_FORMAT_INVALID => [ diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index 5a87293b49..bfb73189b5 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -43,16 +43,6 @@ use Utopia\Validator\WhiteList; include_once __DIR__ . '/../shared/api.php'; -function getDatabaseTransferResourceServices(string $databaseType) -{ - return match($databaseType) { - DATABASE_TYPE_LEGACY, - DATABASE_TYPE_TABLESDB => Transfer::GROUP_DATABASES_TABLES_DB, - DATABASE_TYPE_VECTORSDB => Transfer::GROUP_DATABASES_VECTOR_DB, - DATABASE_TYPE_DOCUMENTSDB => Transfer::GROUP_DATABASES_DOCUMENTS_DB - }; -} - Http::post('/v1/migrations/appwrite') ->groups(['api', 'migrations']) ->desc('Create Appwrite migration') @@ -437,16 +427,8 @@ Http::post('/v1/migrations/csv/imports') throw new \Exception('Unable to copy file'); } - // getting databasetype - $resources = explode(':', $resourceId); - $databaseId = $resources[0]; - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $databaseType = $database->getAttribute('type'); - if (!in_array($databaseType, CSV_ALLOWED_DATABASE_TYPES)) { - throw new Exception(Exception::MIGRATION_DATABASE_TYPE_UNSUPPORTED, 'Database type not supported for csv'); - } $fileSize = $deviceForMigrations->getFileSize($newPath); - $resources = Transfer::extractServices([getDatabaseTransferResourceServices($databaseType)]); + $resources = Transfer::extractServices([Transfer::GROUP_DATABASES]); $migration = $dbForProject->createDocument('migrations', new Document([ '$id' => $migrationId, @@ -575,23 +557,13 @@ Http::post('/v1/migrations/csv/exports') throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); } - // getting databasetype - $resources = explode(':', $resourceId); - $databaseId = $resources[0]; - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $databaseType = $database->getAttribute('type'); - if (!in_array($databaseType, CSV_ALLOWED_DATABASE_TYPES)) { - throw new Exception(Exception::MIGRATION_DATABASE_TYPE_UNSUPPORTED, 'Database type not supported for csv'); - } - $resources = Transfer::extractServices([getDatabaseTransferResourceServices($databaseType)]); - $migration = $dbForProject->createDocument('migrations', new Document([ '$id' => ID::unique(), 'status' => 'pending', 'stage' => 'init', 'source' => Appwrite::getName(), 'destination' => CSV::getName(), - 'resources' => $resources, + 'resources' => Transfer::extractServices([Transfer::GROUP_DATABASES]), 'resourceId' => $resourceId, 'resourceType' => Resource::TYPE_DATABASE, 'statusCounters' => '{}', @@ -741,11 +713,12 @@ Http::get('/v1/migrations/appwrite/report') ->param('projectID', '', new Text(512), "Source's Project ID") ->param('key', '', new Text(512), "Source's API Key") ->inject('response') - ->inject('getDatabasesDB') - ->action(function (array $resources, string $endpoint, string $projectID, string $key, Response $response, callable $getDatabasesDB) { - + ->inject('dbForProject') + ->inject('project') + ->inject('user') + ->action(function (array $resources, string $endpoint, string $projectID, string $key, Response $response) { try { - $appwrite = new Appwrite($projectID, $endpoint, $key, $getDatabasesDB); + $appwrite = new Appwrite($projectID, $endpoint, $key); $report = $appwrite->report($resources); } catch (\Throwable $e) { throw new Exception( diff --git a/app/controllers/api/project.php b/app/controllers/api/project.php index b79180c91c..d24519e3fb 100644 --- a/app/controllers/api/project.php +++ b/app/controllers/api/project.php @@ -62,33 +62,16 @@ Http::get('/v1/project/usage') METRIC_EXECUTIONS_MB_SECONDS, METRIC_BUILDS_MB_SECONDS, METRIC_DOCUMENTS, - METRIC_DOCUMENTS_DOCUMENTSDB, METRIC_DATABASES, - METRIC_DATABASES_DOCUMENTSDB, METRIC_USERS, METRIC_BUCKETS, METRIC_FILES_STORAGE, METRIC_DATABASES_STORAGE, - METRIC_DATABASES_STORAGE_DOCUMENTSDB, METRIC_DEPLOYMENTS_STORAGE, METRIC_BUILDS_STORAGE, METRIC_DATABASES_OPERATIONS_READS, - METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB, METRIC_DATABASES_OPERATIONS_WRITES, - METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB, METRIC_FILES_IMAGES_TRANSFORMED, - // VectorsDB totals - METRIC_DATABASES_VECTORSDB, - METRIC_COLLECTIONS_VECTORSDB, - METRIC_DOCUMENTS_VECTORSDB, - METRIC_DATABASES_STORAGE_VECTORSDB, - METRIC_DATABASES_OPERATIONS_READS_VECTORSDB, - METRIC_DATABASES_OPERATIONS_WRITES_VECTORSDB, - // Embeddings totals - METRIC_EMBEDDINGS_TEXT, - METRIC_EMBEDDINGS_TEXT_TOTAL_TOKENS, - METRIC_EMBEDDINGS_TEXT_TOTAL_DURATION, - METRIC_EMBEDDINGS_TEXT_TOTAL_ERROR ], 'period' => [ METRIC_NETWORK_REQUESTS, @@ -97,26 +80,11 @@ Http::get('/v1/project/usage') METRIC_USERS, METRIC_EXECUTIONS, METRIC_DATABASES_STORAGE, - METRIC_DATABASES_STORAGE_DOCUMENTSDB, METRIC_EXECUTIONS_MB_SECONDS, METRIC_BUILDS_MB_SECONDS, METRIC_DATABASES_OPERATIONS_READS, - METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB, METRIC_DATABASES_OPERATIONS_WRITES, - METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB, METRIC_FILES_IMAGES_TRANSFORMED, - // VectorsDB time series - METRIC_DATABASES_VECTORSDB, - METRIC_COLLECTIONS_VECTORSDB, - METRIC_DOCUMENTS_VECTORSDB, - METRIC_DATABASES_STORAGE_VECTORSDB, - METRIC_DATABASES_OPERATIONS_READS_VECTORSDB, - METRIC_DATABASES_OPERATIONS_WRITES_VECTORSDB, - // Embeddings time series - METRIC_EMBEDDINGS_TEXT, - METRIC_EMBEDDINGS_TEXT_TOTAL_TOKENS, - METRIC_EMBEDDINGS_TEXT_TOTAL_DURATION, - METRIC_EMBEDDINGS_TEXT_TOTAL_ERROR ] ]; @@ -389,11 +357,8 @@ Http::get('/v1/project/usage') 'buildsMbSecondsTotal' => $total[METRIC_BUILDS_MB_SECONDS], 'documentsTotal' => $total[METRIC_DOCUMENTS], 'rowsTotal' => $total[METRIC_DOCUMENTS], - 'documentsdbDocumentsTotal' => $total[METRIC_DOCUMENTS_DOCUMENTSDB], 'databasesTotal' => $total[METRIC_DATABASES], - 'documentsdbTotal' => $total[METRIC_DATABASES_DOCUMENTSDB], 'databasesStorageTotal' => $total[METRIC_DATABASES_STORAGE], - 'documentsdbDatabasesStorageTotal' => $total[METRIC_DATABASES_STORAGE_DOCUMENTSDB], 'usersTotal' => $total[METRIC_USERS], 'bucketsTotal' => $total[METRIC_BUCKETS], 'filesStorageTotal' => $total[METRIC_FILES_STORAGE], @@ -402,27 +367,10 @@ Http::get('/v1/project/usage') 'deploymentsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE], 'databasesReadsTotal' => $total[METRIC_DATABASES_OPERATIONS_READS], 'databasesWritesTotal' => $total[METRIC_DATABASES_OPERATIONS_WRITES], - 'documentsdbDatabasesReadsTotal' => $total[METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB], - 'documentsdbDatabasesWritesTotal' => $total[METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB], - 'vectorsdbDatabasesTotal' => $total[METRIC_DATABASES_VECTORSDB] ?? 0, - 'vectorsdbCollectionsTotal' => $total[METRIC_COLLECTIONS_VECTORSDB] ?? 0, - 'vectorsdbDocumentsTotal' => $total[METRIC_DOCUMENTS_VECTORSDB] ?? 0, - 'vectorsdbDatabasesStorageTotal' => $total[METRIC_DATABASES_STORAGE_VECTORSDB] ?? 0, - 'vectorsdbDatabasesReadsTotal' => $total[METRIC_DATABASES_OPERATIONS_READS_VECTORSDB] ?? 0, - 'vectorsdbDatabasesWritesTotal' => $total[METRIC_DATABASES_OPERATIONS_WRITES_VECTORSDB] ?? 0, 'executionsBreakdown' => $executionsBreakdown, 'bucketsBreakdown' => $bucketsBreakdown, 'databasesReads' => $usage[METRIC_DATABASES_OPERATIONS_READS], 'databasesWrites' => $usage[METRIC_DATABASES_OPERATIONS_WRITES], - 'documentsdbDatabasesReads' => $usage[METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB], - 'documentsdbDatabasesWrites' => $usage[METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB], - 'documentsdbDatabasesStorage' => $usage[METRIC_DATABASES_STORAGE_DOCUMENTSDB], - 'vectorsdbDatabases' => $usage[METRIC_DATABASES_VECTORSDB] ?? [], - 'vectorsdbCollections' => $usage[METRIC_COLLECTIONS_VECTORSDB] ?? [], - 'vectorsdbDocuments' => $usage[METRIC_DOCUMENTS_VECTORSDB] ?? [], - 'vectorsdbDatabasesStorage' => $usage[METRIC_DATABASES_STORAGE_VECTORSDB] ?? [], - 'vectorsdbDatabasesReads' => $usage[METRIC_DATABASES_OPERATIONS_READS_VECTORSDB] ?? [], - 'vectorsdbDatabasesWrites' => $usage[METRIC_DATABASES_OPERATIONS_WRITES_VECTORSDB] ?? [], 'databasesStorageBreakdown' => $databasesStorageBreakdown, 'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown, 'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown, @@ -432,14 +380,6 @@ Http::get('/v1/project/usage') 'authPhoneCountryBreakdown' => $authPhoneCountryBreakdown, 'imageTransformations' => $usage[METRIC_FILES_IMAGES_TRANSFORMED], 'imageTransformationsTotal' => $total[METRIC_FILES_IMAGES_TRANSFORMED], - 'embeddingsText' => $usage[METRIC_EMBEDDINGS_TEXT] ?? [], - 'embeddingsTextTokens' => $usage[METRIC_EMBEDDINGS_TEXT_TOTAL_TOKENS] ?? [], - 'embeddingsTextDuration' => $usage[METRIC_EMBEDDINGS_TEXT_TOTAL_DURATION] ?? [], - 'embeddingsTextErrors' => $usage[METRIC_EMBEDDINGS_TEXT_TOTAL_ERROR] ?? [], - 'embeddingsTextTotal' => $total[METRIC_EMBEDDINGS_TEXT] ?? 0, - 'embeddingsTextTokensTotal' => $total[METRIC_EMBEDDINGS_TEXT_TOTAL_TOKENS] ?? 0, - 'embeddingsTextDurationTotal' => $total[METRIC_EMBEDDINGS_TEXT_TOTAL_DURATION] ?? 0, - 'embeddingsTextErrorsTotal' => $total[METRIC_EMBEDDINGS_TEXT_TOTAL_ERROR] ?? 0, ]), Response::MODEL_USAGE_PROJECT); }); diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 24a1b28cdd..cef64ffdeb 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -4,7 +4,6 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Validator\MockNumber; use Appwrite\Event\Delete; use Appwrite\Event\Mail; -use Appwrite\Event\Validator\Event; use Appwrite\Extend\Exception; use Appwrite\Network\Platform; use Appwrite\Network\Validator\Email; @@ -30,7 +29,6 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\UID; -use Utopia\Domains\Validator\PublicDomain; use Utopia\Http\Http; use Utopia\Locale\Locale; use Utopia\System\System; @@ -38,11 +36,9 @@ use Utopia\Validator\ArrayList; use Utopia\Validator\Boolean; use Utopia\Validator\Hostname; use Utopia\Validator\Integer; -use Utopia\Validator\Multiple; use Utopia\Validator\Nullable; use Utopia\Validator\Range; use Utopia\Validator\Text; -use Utopia\Validator\URL; use Utopia\Validator\WhiteList; Http::init() @@ -773,312 +769,6 @@ Http::delete('/v1/projects/:projectId') $response->noContent(); }); -// Webhooks - -Http::post('/v1/projects/:projectId/webhooks') - ->desc('Create webhook') - ->groups(['api', 'projects']) - ->label('scope', 'projects.write') - ->label('sdk', new Method( - namespace: 'projects', - group: 'webhooks', - name: 'createWebhook', - description: '/docs/references/projects/create-webhook.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_CREATED, - model: Response::MODEL_WEBHOOK, - ) - ] - )) - ->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform']) - ->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.') - ->param('enabled', true, new Boolean(true), 'Enable or disable a webhook.', true) - ->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.') - ->param('url', '', fn ($request) => new Multiple([new URL(['http', 'https']), new PublicDomain()], Multiple::TYPE_STRING), 'Webhook URL.', false, ['request']) - ->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.') - ->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true) - ->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true) - ->inject('response') - ->inject('dbForPlatform') - ->action(function (string $projectId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForPlatform) { - - $project = $dbForPlatform->getDocument('projects', $projectId); - - if ($project->isEmpty()) { - throw new Exception(Exception::PROJECT_NOT_FOUND); - } - - $security = (bool) filter_var($security, FILTER_VALIDATE_BOOLEAN); - - $webhook = new Document([ - '$id' => ID::unique(), - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'projectInternalId' => $project->getSequence(), - 'projectId' => $project->getId(), - 'name' => $name, - 'events' => $events, - 'url' => $url, - 'security' => $security, - 'httpUser' => $httpUser, - 'httpPass' => $httpPass, - 'signatureKey' => \bin2hex(\random_bytes(64)), - 'enabled' => $enabled, - ]); - - $webhook = $dbForPlatform->createDocument('webhooks', $webhook); - - $dbForPlatform->purgeCachedDocument('projects', $project->getId()); - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($webhook, Response::MODEL_WEBHOOK); - }); - -Http::get('/v1/projects/:projectId/webhooks') - ->desc('List webhooks') - ->groups(['api', 'projects']) - ->label('scope', 'projects.read') - ->label('sdk', new Method( - namespace: 'projects', - group: 'webhooks', - name: 'listWebhooks', - description: '/docs/references/projects/list-webhooks.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_WEBHOOK_LIST, - ) - ] - )) - ->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform']) - ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) - ->inject('response') - ->inject('dbForPlatform') - ->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) { - - $project = $dbForPlatform->getDocument('projects', $projectId); - - if ($project->isEmpty()) { - throw new Exception(Exception::PROJECT_NOT_FOUND); - } - - $webhooks = $dbForPlatform->find('webhooks', [ - Query::equal('projectInternalId', [$project->getSequence()]), - Query::limit(5000), - ]); - - $response->dynamic(new Document([ - 'webhooks' => $webhooks, - 'total' => $includeTotal ? count($webhooks) : 0, - ]), Response::MODEL_WEBHOOK_LIST); - }); - -Http::get('/v1/projects/:projectId/webhooks/:webhookId') - ->desc('Get webhook') - ->groups(['api', 'projects']) - ->label('scope', 'projects.read') - ->label('sdk', new Method( - namespace: 'projects', - group: 'webhooks', - name: 'getWebhook', - description: '/docs/references/projects/get-webhook.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_WEBHOOK, - ) - ] - )) - ->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform']) - ->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook unique ID.', false, ['dbForPlatform']) - ->inject('response') - ->inject('dbForPlatform') - ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForPlatform) { - - $project = $dbForPlatform->getDocument('projects', $projectId); - - if ($project->isEmpty()) { - throw new Exception(Exception::PROJECT_NOT_FOUND); - } - - $webhook = $dbForPlatform->findOne('webhooks', [ - Query::equal('$id', [$webhookId]), - Query::equal('projectInternalId', [$project->getSequence()]), - ]); - - if ($webhook->isEmpty()) { - throw new Exception(Exception::WEBHOOK_NOT_FOUND); - } - - $response->dynamic($webhook, Response::MODEL_WEBHOOK); - }); - -Http::put('/v1/projects/:projectId/webhooks/:webhookId') - ->desc('Update webhook') - ->groups(['api', 'projects']) - ->label('scope', 'projects.write') - ->label('sdk', new Method( - namespace: 'projects', - group: 'webhooks', - name: 'updateWebhook', - description: '/docs/references/projects/update-webhook.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_WEBHOOK, - ) - ] - )) - ->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform']) - ->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook unique ID.', false, ['dbForPlatform']) - ->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.') - ->param('enabled', true, new Boolean(true), 'Enable or disable a webhook.', true) - ->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.') - ->param('url', '', fn ($request) => new Multiple([new URL(['http', 'https']), new PublicDomain()], Multiple::TYPE_STRING), 'Webhook URL.', false, ['request']) - ->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.') - ->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true) - ->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true) - ->inject('response') - ->inject('dbForPlatform') - ->action(function (string $projectId, string $webhookId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForPlatform) { - - $project = $dbForPlatform->getDocument('projects', $projectId); - - if ($project->isEmpty()) { - throw new Exception(Exception::PROJECT_NOT_FOUND); - } - - $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - - $webhook = $dbForPlatform->findOne('webhooks', [ - Query::equal('$id', [$webhookId]), - Query::equal('projectInternalId', [$project->getSequence()]), - ]); - - if ($webhook->isEmpty()) { - throw new Exception(Exception::WEBHOOK_NOT_FOUND); - } - - $webhook - ->setAttribute('name', $name) - ->setAttribute('events', $events) - ->setAttribute('url', $url) - ->setAttribute('security', $security) - ->setAttribute('httpUser', $httpUser) - ->setAttribute('httpPass', $httpPass) - ->setAttribute('enabled', $enabled); - - if ($enabled) { - $webhook->setAttribute('attempts', 0); - } - - $dbForPlatform->updateDocument('webhooks', $webhook->getId(), $webhook); - $dbForPlatform->purgeCachedDocument('projects', $project->getId()); - - $response->dynamic($webhook, Response::MODEL_WEBHOOK); - }); - -Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') - ->desc('Update webhook signature key') - ->groups(['api', 'projects']) - ->label('scope', 'projects.write') - ->label('sdk', new Method( - namespace: 'projects', - group: 'webhooks', - name: 'updateWebhookSignature', - description: '/docs/references/projects/update-webhook-signature.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_WEBHOOK, - ) - ] - )) - ->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform']) - ->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook unique ID.', false, ['dbForPlatform']) - ->inject('response') - ->inject('dbForPlatform') - ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForPlatform) { - - $project = $dbForPlatform->getDocument('projects', $projectId); - - if ($project->isEmpty()) { - throw new Exception(Exception::PROJECT_NOT_FOUND); - } - - $webhook = $dbForPlatform->findOne('webhooks', [ - Query::equal('$id', [$webhookId]), - Query::equal('projectInternalId', [$project->getSequence()]), - ]); - - if ($webhook->isEmpty()) { - throw new Exception(Exception::WEBHOOK_NOT_FOUND); - } - - $webhook->setAttribute('signatureKey', \bin2hex(\random_bytes(64))); - - $dbForPlatform->updateDocument('webhooks', $webhook->getId(), $webhook); - $dbForPlatform->purgeCachedDocument('projects', $project->getId()); - - $response->dynamic($webhook, Response::MODEL_WEBHOOK); - }); - -Http::delete('/v1/projects/:projectId/webhooks/:webhookId') - ->desc('Delete webhook') - ->groups(['api', 'projects']) - ->label('scope', 'projects.write') - ->label('sdk', new Method( - namespace: 'projects', - group: 'webhooks', - name: 'deleteWebhook', - description: '/docs/references/projects/delete-webhook.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_NOCONTENT, - model: Response::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - )) - ->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform']) - ->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook unique ID.', false, ['dbForPlatform']) - ->inject('response') - ->inject('dbForPlatform') - ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForPlatform) { - - $project = $dbForPlatform->getDocument('projects', $projectId); - - if ($project->isEmpty()) { - throw new Exception(Exception::PROJECT_NOT_FOUND); - } - - $webhook = $dbForPlatform->findOne('webhooks', [ - Query::equal('$id', [$webhookId]), - Query::equal('projectInternalId', [$project->getSequence()]), - ]); - - if ($webhook->isEmpty()) { - throw new Exception(Exception::WEBHOOK_NOT_FOUND); - } - - $dbForPlatform->deleteDocument('webhooks', $webhook->getId()); - - $dbForPlatform->purgeCachedDocument('projects', $project->getId()); - - $response->noContent(); - }); - // Keys Http::post('/v1/projects/:projectId/keys') diff --git a/app/controllers/general.php b/app/controllers/general.php index 1a099c4bde..341ac59f7a 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -888,7 +888,7 @@ Http::init() $dbForProject = $getProjectDB($project); $request->addFilter(new RequestV20($dbForProject, $route->getPathValues($request))); } - if (version_compare($requestFormat, '1.9.0', '<')) { + if (version_compare($requestFormat, '1.8.2', '<')) { $request->addFilter(new RequestV21()); } } diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index fc8b9b589b..acc9c2dc9a 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -470,12 +470,6 @@ Http::init() ->action(function (Http $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Context $usage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) { $route = $utopia->getRoute(); - $path = $route->getMatchedPath(); - $databaseType = match (true) { - str_contains($path, '/documentsdb') => DATABASE_TYPE_DOCUMENTSDB, - str_contains($path, '/vectorsdb') => DATABASE_TYPE_VECTORSDB, - default => '', - }; if ( array_key_exists('rest', $project->getAttribute('apis', [])) diff --git a/app/http.php b/app/http.php index 517a1aa544..1302940856 100644 --- a/app/http.php +++ b/app/http.php @@ -196,8 +196,6 @@ include __DIR__ . '/controllers/general.php'; function createDatabase(Http $app, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void { - $max = 15; - $sleep = 2; $max = 15; $sleep = 2; $attempts = 0; @@ -411,29 +409,13 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $tot }); $projectCollections = $collections['projects']; - $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); $sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', '')); $sharedTablesV2 = \array_diff($sharedTables, $sharedTablesV1); - $documentsSharedTables = \explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES', '')); - $documentsSharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES_V1', '')); - $documentsSharedTablesV2 = \array_diff($documentsSharedTables, $documentsSharedTablesV1); - - $vectorSharedTables = \explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES', '')); - $vectorSharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES_V1', '')); - $vectorSharedTablesV2 = \array_diff($vectorSharedTables, $vectorSharedTablesV1); - $cache = $app->getResource('cache'); - // All shared tables V2 pools that need project metadata collections - $sharedTablesV2All = \array_values(\array_unique(\array_filter([ - ...$sharedTablesV2, - ...$documentsSharedTablesV2, - ...$vectorSharedTablesV2, - ]))); - - foreach ($sharedTablesV2All as $hostname) { + foreach ($sharedTablesV2 as $hostname) { Span::init('database.setup'); Span::add('database.hostname', $hostname); diff --git a/app/init/constants.php b/app/init/constants.php index 8fdd6d1a51..c578bdbf9a 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -288,45 +288,6 @@ const METRIC_DATABASES_OPERATIONS_READS = 'databases.operations.reads'; const METRIC_DATABASE_ID_OPERATIONS_READS = '{databaseInternalId}.databases.operations.reads'; const METRIC_DATABASES_OPERATIONS_WRITES = 'databases.operations.writes'; const METRIC_DATABASE_ID_OPERATIONS_WRITES = '{databaseInternalId}.databases.operations.writes'; - -// documentsdb -const METRIC_DATABASES_DOCUMENTSDB = 'documentsdb.databases'; -const METRIC_COLLECTIONS_DOCUMENTSDB = 'documentsdb.collections'; -const METRIC_DATABASES_STORAGE_DOCUMENTSDB = 'documentsdb.databases.storage'; -const METRIC_DATABASE_ID_COLLECTIONS_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.collections'; -const METRIC_DATABASE_ID_STORAGE_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.databases.storage'; -const METRIC_DOCUMENTS_DOCUMENTSDB = 'documentsdb.documents'; -const METRIC_DATABASE_ID_DOCUMENTS_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.documents'; -const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.{collectionInternalId}.documents'; -const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.{collectionInternalId}.databases.storage'; -const METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB = 'documentsdb.databases.operations.reads'; -const METRIC_DATABASE_ID_OPERATIONS_READS_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.databases.operations.reads'; -const METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB = 'documentsdb.databases.operations.writes'; -const METRIC_DATABASE_ID_OPERATIONS_WRITES_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.databases.operations.writes'; - -// vectorsdb -const METRIC_DATABASES_VECTORSDB = 'vectorsdb.databases'; -const METRIC_COLLECTIONS_VECTORSDB = 'vectorsdb.collections'; -const METRIC_DATABASES_STORAGE_VECTORSDB = 'vectorsdb.databases.storage'; -const METRIC_DATABASE_ID_COLLECTIONS_VECTORSDB = 'vectorsdb.{databaseInternalId}.collections'; -const METRIC_DATABASE_ID_STORAGE_VECTORSDB = 'vectorsdb.{databaseInternalId}.databases.storage'; -const METRIC_DOCUMENTS_VECTORSDB = 'vectorsdb.documents'; -const METRIC_DATABASE_ID_DOCUMENTS_VECTORSDB = 'vectorsdb.{databaseInternalId}.documents'; -const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS_VECTORSDB = 'vectorsdb.{databaseInternalId}.{collectionInternalId}.documents'; -const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE_VECTORSDB = 'vectorsdb.{databaseInternalId}.{collectionInternalId}.databases.storage'; -const METRIC_DATABASES_OPERATIONS_READS_VECTORSDB = 'vectorsdb.databases.operations.reads'; -const METRIC_DATABASE_ID_OPERATIONS_READS_VECTORSDB = 'vectorsdb.{databaseInternalId}.databases.operations.reads'; -const METRIC_DATABASES_OPERATIONS_WRITES_VECTORSDB = 'vectorsdb.databases.operations.writes'; -const METRIC_DATABASE_ID_OPERATIONS_WRITES_VECTORSDB = 'vectorsdb.{databaseInternalId}.databases.operations.writes'; -const METRIC_EMBEDDINGS_TEXT = 'embeddings.text'; -const METRIC_EMBEDDINGS_MODEL_TEXT = 'embeddings.text.{embeddingModel}'; -const METRIC_EMBEDDINGS_TEXT_TOTAL_ERROR = 'embeddings.text.totalErrors'; -const METRIC_EMBEDDINGS_MODEL_TEXT_TOTAL_ERROR = 'embeddings.text.{embeddingModel}.totalErrors'; -const METRIC_EMBEDDINGS_TEXT_TOTAL_DURATION = 'embeddings.text.totalDuration'; -const METRIC_EMBEDDINGS_MODEL_TEXT_TOTAL_DURATION = 'embeddings.text.{embeddingModel}.totalDuration'; -const METRIC_EMBEDDINGS_TEXT_TOTAL_TOKENS = 'embeddings.text.totalTokens'; -const METRIC_EMBEDDINGS_MODEL_TEXT_TOTAL_TOKENS = 'embeddings.text.{embeddingModel}.totalTokens'; - const METRIC_BUCKETS = 'buckets'; const METRIC_FILES = 'files'; const METRIC_FILES_STORAGE = 'files.storage'; @@ -419,7 +380,6 @@ const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers'; const RESOURCE_TYPE_MESSAGES = 'messages'; const RESOURCE_TYPE_EXECUTIONS = 'executions'; const RESOURCE_TYPE_VCS = 'vcs'; -const RESOURCE_TYPE_EMBEDDINGS_TEXT = 'embeddingsText'; // Resource types for Tokens const TOKENS_RESOURCE_TYPE_FILES = 'files'; @@ -441,16 +401,3 @@ const CACHE_RECONNECT_RETRY_DELAY = 1000; // Project status const PROJECT_STATUS_ACTIVE = 'active'; - -// Database types -const DATABASE_TYPE_LEGACY = 'legacy'; -const DATABASE_TYPE_TABLESDB = 'tablesdb'; -const DATABASE_TYPE_DOCUMENTSDB = 'documentsdb'; -const DATABASE_TYPE_VECTORSDB = 'vectorsdb'; - -// CSV import/export allowed database types -const CSV_ALLOWED_DATABASE_TYPES = [ - DATABASE_TYPE_LEGACY, - DATABASE_TYPE_TABLESDB, - DATABASE_TYPE_VECTORSDB -]; diff --git a/app/init/models.php b/app/init/models.php index bf6d67dd95..6c90f08199 100644 --- a/app/init/models.php +++ b/app/init/models.php @@ -22,7 +22,6 @@ use Appwrite\Utopia\Response\Model\AttributeLine; use Appwrite\Utopia\Response\Model\AttributeList; use Appwrite\Utopia\Response\Model\AttributeLongtext; use Appwrite\Utopia\Response\Model\AttributeMediumtext; -use Appwrite\Utopia\Response\Model\AttributeObject; use Appwrite\Utopia\Response\Model\AttributePoint; use Appwrite\Utopia\Response\Model\AttributePolygon; use Appwrite\Utopia\Response\Model\AttributeRelationship; @@ -30,7 +29,6 @@ use Appwrite\Utopia\Response\Model\AttributeString; use Appwrite\Utopia\Response\Model\AttributeText; use Appwrite\Utopia\Response\Model\AttributeURL; use Appwrite\Utopia\Response\Model\AttributeVarchar; -use Appwrite\Utopia\Response\Model\AttributeVector; use Appwrite\Utopia\Response\Model\AuthProvider; use Appwrite\Utopia\Response\Model\BaseList; use Appwrite\Utopia\Response\Model\Branch; @@ -67,7 +65,6 @@ use Appwrite\Utopia\Response\Model\DetectionRuntime; use Appwrite\Utopia\Response\Model\DetectionVariable; use Appwrite\Utopia\Response\Model\DevKey; use Appwrite\Utopia\Response\Model\Document as ModelDocument; -use Appwrite\Utopia\Response\Model\Embedding; use Appwrite\Utopia\Response\Model\Error; use Appwrite\Utopia\Response\Model\ErrorDev; use Appwrite\Utopia\Response\Model\Execution; @@ -139,8 +136,6 @@ use Appwrite\Utopia\Response\Model\UsageBuckets; use Appwrite\Utopia\Response\Model\UsageCollection; use Appwrite\Utopia\Response\Model\UsageDatabase; use Appwrite\Utopia\Response\Model\UsageDatabases; -use Appwrite\Utopia\Response\Model\UsageDocumentsDB; -use Appwrite\Utopia\Response\Model\UsageDocumentsDBs; use Appwrite\Utopia\Response\Model\UsageFunction; use Appwrite\Utopia\Response\Model\UsageFunctions; use Appwrite\Utopia\Response\Model\UsageProject; @@ -149,12 +144,9 @@ use Appwrite\Utopia\Response\Model\UsageSites; use Appwrite\Utopia\Response\Model\UsageStorage; use Appwrite\Utopia\Response\Model\UsageTable; use Appwrite\Utopia\Response\Model\UsageUsers; -use Appwrite\Utopia\Response\Model\UsageVectorsDB; -use Appwrite\Utopia\Response\Model\UsageVectorsDBs; use Appwrite\Utopia\Response\Model\User; use Appwrite\Utopia\Response\Model\Variable; use Appwrite\Utopia\Response\Model\VcsContent; -use Appwrite\Utopia\Response\Model\VectorsDBCollection; use Appwrite\Utopia\Response\Model\Webhook; // General @@ -219,12 +211,9 @@ Response::setModel(new BaseList('Migrations List', Response::MODEL_MIGRATION_LIS Response::setModel(new BaseList('Migrations Firebase Projects List', Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', Response::MODEL_MIGRATION_FIREBASE_PROJECT)); Response::setModel(new BaseList('Specifications List', Response::MODEL_SPECIFICATION_LIST, 'specifications', Response::MODEL_SPECIFICATION)); Response::setModel(new BaseList('VCS Content List', Response::MODEL_VCS_CONTENT_LIST, 'contents', Response::MODEL_VCS_CONTENT)); -Response::setModel(new BaseList('VectorsDB Collections List', Response::MODEL_VECTORSDB_COLLECTION_LIST, 'collections', Response::MODEL_VECTORSDB_COLLECTION)); -Response::setModel(new BaseList('Embedding list', Response::MODEL_EMBEDDING_LIST, 'embeddings', Response::MODEL_EMBEDDING)); // Entities Response::setModel(new Database()); -Response::setModel(new Embedding()); // Collection API Models Response::setModel(new Collection()); @@ -248,17 +237,6 @@ Response::setModel(new AttributeText()); Response::setModel(new AttributeMediumtext()); Response::setModel(new AttributeLongtext()); -// DocumentsDB API Models -Response::setModel(new UsageDocumentsDBs()); -Response::setModel(new UsageDocumentsDB()); - -// VectorsDB API Models -Response::setModel(new VectorsDBCollection()); -Response::setModel(new AttributeObject()); -Response::setModel(new AttributeVector()); -Response::setModel(new UsageVectorsDBs()); -Response::setModel(new UsageVectorsDB()); - // Table API Models Response::setModel(new Table()); Response::setModel(new Column()); diff --git a/app/init/registers.php b/app/init/registers.php index 7c2f822fdd..7b68c2af9a 100644 --- a/app/init/registers.php +++ b/app/init/registers.php @@ -160,6 +160,7 @@ $register->set('pools', function () { 'pass' => System::getEnv('_APP_DB_PASS', ''), 'path' => System::getEnv('_APP_DB_SCHEMA', ''), ]); + $fallbackForRedis = 'redis_main=' . AppwriteURL::unparse([ 'scheme' => 'redis', 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'), @@ -168,23 +169,6 @@ $register->set('pools', function () { 'pass' => System::getEnv('_APP_REDIS_PASS', ''), ]); - $fallbackForDocumentsDB = 'db_main=' . AppwriteURL::unparse([ - 'scheme' => System::getEnv('_APP_DB_ADAPTER_DOCUMENTSDB', 'mongodb'), - 'host' => System::getEnv('_APP_DB_HOST_DOCUMENTSDB', 'mongodb'), - 'port' => System::getEnv('_APP_DB_PORT_DOCUMENTSDB', '27017'), - 'user' => System::getEnv('_APP_DB_USER', ''), - 'pass' => System::getEnv('_APP_DB_PASS', ''), - 'path' => System::getEnv('_APP_DB_SCHEMA', ''), - ]); - $fallbackForVectorsDB = 'db_main=' . AppwriteURL::unparse([ - 'scheme' => System::getEnv('_APP_DB_ADAPTER_VECTORSDB', 'postgresql'), - 'host' => System::getEnv('_APP_DB_HOST_VECTORSDB', 'postgresql'), - 'port' => System::getEnv('_APP_DB_PORT_VECTORSDB', '5432'), - 'user' => System::getEnv('_APP_DB_USER', ''), - 'pass' => System::getEnv('_APP_DB_PASS', ''), - 'path' => System::getEnv('_APP_DB_SCHEMA', ''), - ]); - $connections = [ 'console' => [ 'type' => 'database', @@ -196,25 +180,13 @@ $register->set('pools', function () { 'type' => 'database', 'dsns' => $fallbackForDB, 'multiple' => true, - 'schemes' => ['mongodb','mariadb', 'mysql','postgresql'], - ], - 'documentsdb' => [ - 'type' => 'database', - 'dsns' => System::getEnv('_APP_CONNECTIONS_DATABASE_DOCUMENTSDB', $fallbackForDocumentsDB), - 'multiple' => true, - 'schemes' => ['mongodb'], - ], - 'vectorsdb' => [ - 'type' => 'database', - 'dsns' => System::getEnv('_APP_CONNECTIONS_DATABASE_VECTORSDB', $fallbackForVectorsDB), - 'multiple' => true, - 'schemes' => ['postgresql'], + 'schemes' => ['mariadb', 'mongodb', 'mysql', 'postgresql'], ], 'logs' => [ 'type' => 'database', 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_LOGS', $fallbackForDB), 'multiple' => false, - 'schemes' => ['mongodb','mariadb', 'mysql','postgresql'], + 'schemes' => ['mariadb', 'mongodb', 'mysql', 'postgresql'], ], 'publisher' => [ 'type' => 'publisher', diff --git a/app/init/resources.php b/app/init/resources.php index 6ada4059d6..2ed9e88e90 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -32,8 +32,6 @@ use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Executor\Executor; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; -use Utopia\Agents\Adapters\Ollama; -use Utopia\Agents\Agent; use Utopia\Audit\Adapter\Database as AdapterDatabase; use Utopia\Audit\Audit; use Utopia\Auth\Hashes\Argon2; @@ -478,7 +476,7 @@ Http::setResource('user', function (string $mode, Document $project, Document $c return $user; }, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']); -Http::setResource('project', function ($dbForPlatform, $request, $console, $authorization) { +Http::setResource('project', function ($dbForPlatform, $request, $console, $authorization, Http $utopia) { /** @var Appwrite\Utopia\Request $request */ /** @var Utopia\Database\Database $dbForPlatform */ /** @var Utopia\Database\Document $console */ @@ -488,6 +486,20 @@ Http::setResource('project', function ($dbForPlatform, $request, $console, $auth $projectId = $request->getHeader('x-appwrite-project', ''); } + // Backwards compatibility for new services, originally project resources + // These endpoints moved from /v1/projects/:projectId/ to /v1/ + // When accessed via the old alias path, extract projectId from the URI + $deprecatedProjectPathPrefix = '/v1/projects/'; + $route = $utopia->match($request); + if (!empty($route)) { + $isDeprecatedAlias = \str_starts_with($request->getURI(), $deprecatedProjectPathPrefix) && + !\str_starts_with($route->getPath(), $deprecatedProjectPathPrefix); + + if ($isDeprecatedAlias) { + $projectId = \explode('/', $request->getURI(), 5)[3] ?? ''; + } + } + if (empty($projectId) || $projectId === 'console') { return $console; } @@ -495,7 +507,7 @@ Http::setResource('project', function ($dbForPlatform, $request, $console, $auth $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId)); return $project; -}, ['dbForPlatform', 'request', 'console', 'authorization']); +}, ['dbForPlatform', 'request', 'console', 'authorization', 'utopia']); Http::setResource('session', function (User $user, Store $store, Token $proofForToken) { if ($user->isEmpty()) { @@ -557,7 +569,7 @@ Http::setResource('authorization', function () { return new Authorization(); }, []); -Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Response $response, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, UsageContext $usage, Authorization $authorization, Request $request) { +Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Response $response, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, UsageContext $usage, Authorization $authorization) { if ($project->isEmpty() || $project->getId() === 'console') { return $dbForPlatform; } @@ -663,31 +675,7 @@ Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatfor $dbForProject->getCache()->purge($cacheKey); }; - /** - * Prefix metrics with database type when applicable. - * Avoids prefixing for legacy and tablesdb types to preserve historical metrics. - */ - $getDatabaseTypePrefixedMetric = function (string $databaseType, string $metric): string { - if ( - $databaseType === '' || - $databaseType === DATABASE_TYPE_LEGACY || - $databaseType === DATABASE_TYPE_TABLESDB - ) { - return $metric; - } - - return $databaseType . '.' . $metric; - }; - - // Determine database type from request path, similar to api.php - $path = $request->getURI(); - $databaseType = match (true) { - str_contains($path, '/documentsdb') => DATABASE_TYPE_DOCUMENTSDB, - str_contains($path, '/vectorsdb') => DATABASE_TYPE_VECTORSDB, - default => '', - }; - - $usageDatabaseListener = function (string $event, Document $document, UsageContext $usage) use ($getDatabaseTypePrefixedMetric, $databaseType) { + $usageDatabaseListener = function (string $event, Document $document, UsageContext $usage) { $value = 1; switch ($event) { @@ -719,8 +707,7 @@ Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatfor $usage->addMetric(METRIC_SESSIONS, $value); // per project break; case $document->getCollection() === 'databases': // databases - $metric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DATABASES); - $usage->addMetric($metric, $value); // per project + $usage->addMetric(METRIC_DATABASES, $value); // per project if ($event === Database::EVENT_DOCUMENT_DELETE) { $usage->addReduce($document); @@ -729,11 +716,9 @@ Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatfor case str_starts_with($document->getCollection(), 'database_') && ! str_contains($document->getCollection(), 'collection'): // collections $parts = explode('_', $document->getCollection()); $databaseInternalId = $parts[1] ?? 0; - $collectionMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_COLLECTIONS); - $databaseIdCollectionMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DATABASE_ID_COLLECTIONS); $usage - ->addMetric($collectionMetric, $value) // per project - ->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdCollectionMetric), $value); + ->addMetric(METRIC_COLLECTIONS, $value) // per project + ->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value); if ($event === Database::EVENT_DOCUMENT_DELETE) { $usage->addReduce($document); @@ -743,13 +728,10 @@ Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatfor $parts = explode('_', $document->getCollection()); $databaseInternalId = $parts[1] ?? 0; $collectionInternalId = $parts[3] ?? 0; - $documentsMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DOCUMENTS); - $databaseIdDocumentsMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DATABASE_ID_DOCUMENTS); - $databaseIdCollectionIdDocumentsMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS); $usage - ->addMetric($documentsMetric, $value) // per project - ->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database - ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection + ->addMetric(METRIC_DOCUMENTS, $value) // per project + ->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS), $value) // per database + ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), $value); // per collection break; case $document->getCollection() === 'buckets': // buckets $usage->addMetric(METRIC_BUCKETS, $value); // per project @@ -824,7 +806,7 @@ Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatfor ->on(Database::EVENT_DOCUMENT_DELETE, 'purge-function-events-cache', fn ($event, $document) => $functionsEventsCacheListener($event, $document, $project, $database)); return $database; -}, ['pools', 'dbForPlatform', 'cache', 'project', 'response', 'publisher', 'publisherFunctions', 'publisherWebhooks', 'queueForEvents', 'queueForFunctions', 'queueForWebhooks', 'queueForRealtime', 'usage', 'authorization', 'request']); +}, ['pools', 'dbForPlatform', 'cache', 'project', 'response', 'publisher', 'publisherFunctions', 'publisherWebhooks', 'queueForEvents', 'queueForFunctions', 'queueForWebhooks', 'queueForRealtime', 'usage', 'authorization']); Http::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) { @@ -845,138 +827,6 @@ Http::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authori return $database; }, ['pools', 'cache', 'authorization']); -Http::setResource('getDatabasesDB', function (Group $pools, Cache $cache, Document $project, Request $request, UsageContext $usage, Authorization $authorization) { - - return function (Document $database) use ($pools, $cache, $project, $request, $usage, $authorization): Database { - $databaseDSN = $database->getAttribute('database', $project->getAttribute('database', '')); - $databaseType = $database->getAttribute('type', ''); - - try { - $databaseDSN = new DSN($databaseDSN); - } catch (\InvalidArgumentException) { - // for old databases migrated through patch script - // databaseDSN determines the adapter - $databaseDSN = new DSN('mysql://'.$databaseDSN); - } - try { - $dsn = new DSN($project->getAttribute('database')); - } catch (\InvalidArgumentException) { - // TODO: Temporary until all projects are using shared tables - $dsn = new DSN('mysql://' . $project->getAttribute('database')); - } - - $pool = $pools->get($databaseDSN->getHost()); - - $adapter = new DatabasePool($pool); - $database = new Database($adapter, $cache); - $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); - - $database - ->setDatabase(APP_DATABASE) - ->setAuthorization($authorization) - ->setMetadata('host', \gethostname()) - ->setMetadata('project', $project->getId()) - ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API) - ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); - // inside pools authorization needs to be set first - $database->getAdapter()->setSupportForAttributes($databaseType !== DOCUMENTSDB); - if (\in_array($dsn->getHost(), $sharedTables)) { - $database - ->setSharedTables(true) - ->setTenant((int)$project->getSequence()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getSequence()); - } - $timeout = \intval($request->getHeader('x-appwrite-timeout')); - if (!empty($timeout) && Http::isDevelopment()) { - $database->setTimeout($timeout); - } - - // Register database event listeners for usage stats collection - $documentsMetric = METRIC_DOCUMENTS; - $databaseIdDocumentsMetric = METRIC_DATABASE_ID_DOCUMENTS; - $databaseIdCollectionIdDocumentsMetric = METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS; - if ($databaseType !== DATABASE_TYPE_LEGACY && $databaseType !== DATABASE_TYPE_TABLESDB) { - $documentsMetric = $databaseType. '.' .$documentsMetric; - $databaseIdDocumentsMetric = $databaseType. '.' .$databaseIdDocumentsMetric; - $databaseIdCollectionIdDocumentsMetric = $databaseType . '.' .$databaseIdCollectionIdDocumentsMetric; - } - $database - ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', function ($event, $document) use ($usage, $documentsMetric, $databaseIdDocumentsMetric, $databaseIdCollectionIdDocumentsMetric) { - $value = 1; - - if (str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_')) { - $parts = explode('_', $document->getCollection()); - $databaseInternalId = $parts[1] ?? 0; - $collectionInternalId = $parts[3] ?? 0; - $usage - ->addMetric($documentsMetric, $value) // per project - ->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database - ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection - } - }) - ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', function ($event, $document) use ($usage, $documentsMetric, $databaseIdDocumentsMetric, $databaseIdCollectionIdDocumentsMetric) { - $value = -1; - - if (str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_')) { - $parts = explode('_', $document->getCollection()); - $databaseInternalId = $parts[1] ?? 0; - $collectionInternalId = $parts[3] ?? 0; - $usage - ->addMetric($documentsMetric, $value) // per project - ->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database - ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection - } - }) - ->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', function ($event, $document) use ($usage, $documentsMetric, $databaseIdDocumentsMetric, $databaseIdCollectionIdDocumentsMetric) { - $value = $document->getAttribute('modified', 0); - - if (str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_')) { - $parts = explode('_', $document->getCollection()); - $databaseInternalId = $parts[1] ?? 0; - $collectionInternalId = $parts[3] ?? 0; - $usage - ->addMetric($documentsMetric, $value) // per project - ->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database - ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection - } - }) - ->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', function ($event, $document) use ($usage, $documentsMetric, $databaseIdDocumentsMetric, $databaseIdCollectionIdDocumentsMetric) { - $value = -1 * $document->getAttribute('modified', 0); - - if (str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_')) { - $parts = explode('_', $document->getCollection()); - $databaseInternalId = $parts[1] ?? 0; - $collectionInternalId = $parts[3] ?? 0; - $usage - ->addMetric($documentsMetric, $value) // per project - ->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database - ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection - } - }) - ->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', function ($event, $document) use ($usage, $documentsMetric, $databaseIdDocumentsMetric, $databaseIdCollectionIdDocumentsMetric) { - $value = $document->getAttribute('created', 0); - - if (str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_')) { - $parts = explode('_', $document->getCollection()); - $databaseInternalId = $parts[1] ?? 0; - $collectionInternalId = $parts[3] ?? 0; - $usage - ->addMetric($documentsMetric, $value) // per project - ->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database - ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection - } - }); - - return $database; - }; - -}, ['pools','cache','project','request','usage','authorization']); - Http::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) { $databases = []; @@ -1236,16 +1086,21 @@ function getDevice(string $root, string $connection = ''): Device } } -Http::setResource('mode', function ($request) { - /** @var Appwrite\Utopia\Request $request */ - +Http::setResource('mode', function (Request $request, Document $project) { /** * Defines the mode for the request: * - 'default' => Requests for Client and Server Side * - 'admin' => Request from the Console on non-console projects */ - return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); -}, ['request']); + $mode = $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); + + $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', '')); + if (!empty($projectId) && $project->getId() !== $projectId) { + $mode = APP_MODE_ADMIN; + } + + return $mode; +}, ['request', 'project']); Http::setResource('geodb', function ($register) { /** @var Utopia\Registry\Registry $register */ @@ -1605,9 +1460,9 @@ Http::setResource('resourceToken', function ($project, $dbForProject, $request, return new Document([]); }, ['project', 'dbForProject', 'request', 'authorization']); -Http::setResource('transactionState', function (Database $dbForProject, Authorization $authorization, callable $getDatabasesDB) { - return new TransactionState($dbForProject, $authorization, $getDatabasesDB); -}, ['dbForProject', 'authorization', 'getDatabasesDB']); +Http::setResource('transactionState', function (Database $dbForProject, Authorization $authorization) { + return new TransactionState($dbForProject, $authorization); +}, ['dbForProject', 'authorization']); Http::setResource('executionsRetentionCount', function (Document $project, array $plan) { if ($project->getId() === 'console' || empty($plan)) { @@ -1616,10 +1471,3 @@ Http::setResource('executionsRetentionCount', function (Document $project, array return (int) ($plan['executionsRetentionCount'] ?? 100); }, ['project', 'plan']); - -Http::setResource('embeddingAgent', function ($register) { - $adapter = new Ollama(); - $adapter->setEndpoint(System::getEnv('_APP_EMBEDDING_ENDPOINT', 'http://ollama:11434/api/embed')); - $adapter->setTimeout((int) System::getEnv('_APP_EMBEDDING_TIMEOUT', '30000')); - return new Agent($adapter); -}, ['register']); diff --git a/app/worker.php b/app/worker.php index 71446ee94f..840231f16c 100644 --- a/app/worker.php +++ b/app/worker.php @@ -220,60 +220,6 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authoriza }; }, ['pools', 'cache', 'authorization']); -Server::setResource('getDatabasesDB', function (Cache $cache, Registry $register, Document $project, Authorization $authorization) { - return function (Document $database, ?Document $projectDocument = null) use ($cache, $register, $project, $authorization): Database { - $projectDocument ??= $project; - $databaseDSN = $database->getAttribute('database', $project->getAttribute('database', '')); - $databaseType = $database->getAttribute('type', ''); - - // Backwards‑compatibility: older or seeded legacy databases may not have a DSN stored - // in the "database" attribute. In that case, fall back to the project's database DSN. - if ($databaseDSN === '') { - $databaseDSN = $projectDocument->getAttribute('database', ''); - } - - try { - $databaseDSN = new DSN($databaseDSN); - } catch (\InvalidArgumentException) { - $databaseDSN = new DSN('mysql://'.$databaseDSN); - } - - try { - $dsn = new DSN($projectDocument->getAttribute('database')); - } catch (\InvalidArgumentException) { - // Temporary fallback until all projects use shared tables - $dsn = new DSN('mysql://' . $projectDocument->getAttribute('database')); - } - - $pools = $register->get('pools'); - $pool = $pools->get($databaseDSN->getHost()); - - $adapter = new DatabasePool($pool); - $database = new Database($adapter, $cache); - $database - ->setDatabase(APP_DATABASE) - ->setAuthorization($authorization); - $database->getAdapter()->setSupportForAttributes($databaseType !== DOCUMENTSDB); - - $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); - - if (\in_array($dsn->getHost(), $sharedTables, true)) { - $database - ->setSharedTables(true) - ->setTenant((int) $projectDocument->getSequence()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $projectDocument->getSequence()); - } - - $database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER); - return $database; - }; -}, ['cache', 'register', 'project', 'authorization']); - Server::setResource('abuseRetention', function () { return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day }); diff --git a/composer.json b/composer.json index c92991a1c0..7181428123 100644 --- a/composer.json +++ b/composer.json @@ -52,6 +52,7 @@ "appwrite/php-runtimes": "0.19.*", "appwrite/php-clamav": "2.0.*", "utopia-php/abuse": "1.2.*", + "utopia-php/agents": "1.2.*", "utopia-php/analytics": "0.15.*", "utopia-php/audit": "2.2.*", "utopia-php/auth": "0.5.*", @@ -73,7 +74,7 @@ "utopia-php/locale": "0.8.*", "utopia-php/logger": "0.6.*", "utopia-php/messaging": "0.20.*", - "utopia-php/migration": "1.8.*", + "utopia-php/migration": "1.7.*", "utopia-php/platform": "0.7.*", "utopia-php/pools": "1.*", "utopia-php/span": "1.1.*", @@ -97,6 +98,12 @@ "enshrined/svg-sanitize": "0.22.*", "utopia-php/di": "0.1.0" }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/utopia-php/database" + } + ], "require-dev": { "ext-fileinfo": "*", "appwrite/sdk-generator": "*", @@ -119,6 +126,7 @@ }, "config": { "platform": { + "php": "8.3" }, "allow-plugins": { "php-http/discovery": true, diff --git a/composer.lock b/composer.lock index 616a131bcb..a2eb498e47 100644 --- a/composer.lock +++ b/composer.lock @@ -4549,16 +4549,16 @@ }, { "name": "utopia-php/migration", - "version": "1.8.3", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "8633523b3343d492427331b6eec53f020f6ab7a7" + "reference": "97583ae502e40621ea91a71de19d053c5ae2e558" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/8633523b3343d492427331b6eec53f020f6ab7a7", - "reference": "8633523b3343d492427331b6eec53f020f6ab7a7", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/97583ae502e40621ea91a71de19d053c5ae2e558", + "reference": "97583ae502e40621ea91a71de19d053c5ae2e558", "shasum": "" }, "require": { @@ -4598,9 +4598,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.8.3" + "source": "https://github.com/utopia-php/migration/tree/1.7.0" }, - "time": "2026-03-19T09:18:47+00:00" + "time": "2026-03-10T06:36:27+00:00" }, { "name": "utopia-php/mongo", @@ -8495,5 +8495,8 @@ "platform-dev": { "ext-fileinfo": "*" }, + "platform-overrides": { + "php": "8.3" + }, "plugin-api-version": "2.9.0" } diff --git a/docker-compose.yml b/docker-compose.yml index bf4832282a..7d64dfa867 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -112,8 +112,6 @@ services: condition: service_healthy coredns: condition: service_started - ollama: - condition: service_started entrypoint: - php - -e @@ -161,12 +159,6 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS - - _APP_DB_ADAPTER_VECTORSDB - - _APP_DB_HOST_VECTORSDB - - _APP_DB_PORT_VECTORSDB - - _APP_DB_SCHEMA_VECTORSDB - - _APP_DB_USER_VECTORSDB - - _APP_DB_PASS_VECTORSDB - _APP_SMTP_HOST - _APP_SMTP_PORT - _APP_SMTP_SECURE @@ -303,7 +295,6 @@ services: depends_on: - ${_APP_DB_HOST:-mongodb} - redis - - ollama environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -320,12 +311,6 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS - - _APP_DB_ADAPTER_VECTORSDB - - _APP_DB_HOST_VECTORSDB - - _APP_DB_PORT_VECTORSDB - - _APP_DB_SCHEMA_VECTORSDB - - _APP_DB_USER_VECTORSDB - - _APP_DB_PASS_VECTORSDB - _APP_USAGE_STATS - _APP_LOGGING_CONFIG - _APP_LOGGING_CONFIG_REALTIME @@ -345,7 +330,6 @@ services: depends_on: - redis - ${_APP_DB_HOST:-mongodb} - - ollama environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -379,7 +363,6 @@ services: - ${_APP_DB_HOST:-mongodb} - request-catcher-sms - request-catcher-webhook - - ollama environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -410,7 +393,6 @@ services: depends_on: - redis - ${_APP_DB_HOST:-mongodb} - - ollama volumes: - appwrite-uploads:/storage/uploads:rw - appwrite-cache:/storage/cache:rw @@ -420,7 +402,6 @@ services: - appwrite-certificates:/storage/certificates:rw - ./app:/usr/src/code/app - ./src:/usr/src/code/src - environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -477,11 +458,9 @@ services: volumes: - ./app:/usr/src/code/app - ./src:/usr/src/code/src - depends_on: - redis - ${_APP_DB_HOST:-mongodb} - - ollama environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -497,12 +476,6 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS - - _APP_DB_ADAPTER_VECTORSDB - - _APP_DB_HOST_VECTORSDB - - _APP_DB_PORT_VECTORSDB - - _APP_DB_SCHEMA_VECTORSDB - - _APP_DB_USER_VECTORSDB - - _APP_DB_PASS_VECTORSDB - _APP_LOGGING_CONFIG - _APP_WORKERS_NUM - _APP_QUEUE_NAME @@ -524,7 +497,6 @@ services: depends_on: - redis - ${_APP_DB_HOST:-mongodb} - - ollama environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -657,7 +629,6 @@ services: depends_on: - redis - ${_APP_DB_HOST:-mongodb} - - ollama volumes: - appwrite-config:/storage/config:rw - appwrite-certificates:/storage/certificates:rw @@ -877,7 +848,6 @@ services: - ./app:/usr/src/code/app - ./src:/usr/src/code/src - ./tests:/usr/src/code/tests - depends_on: - ${_APP_DB_HOST:-mongodb} environment: @@ -1074,7 +1044,6 @@ services: depends_on: - redis - ${_APP_DB_HOST:-mongodb} - - ollama environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -1108,7 +1077,6 @@ services: depends_on: - ${_APP_DB_HOST:-mongodb} - redis - - ollama environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -1139,7 +1107,6 @@ services: depends_on: - ${_APP_DB_HOST:-mongodb} - redis - - ollama environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -1170,7 +1137,6 @@ services: depends_on: - ${_APP_DB_HOST:-mongodb} - redis - - ollama environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -1262,6 +1228,7 @@ services: start_period: 5s mariadb: + profiles: ["mariadb"] image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p container_name: appwrite-mariadb <<: *x-logging @@ -1285,6 +1252,7 @@ services: retries: 12 mongodb: + profiles: ["mongodb"] image: mongo:8.2.5 container_name: appwrite-mongodb <<: *x-logging @@ -1320,55 +1288,32 @@ services: retries: 10 start_period: 30s - appwrite-mongo-express: - image: mongo-express - container_name: appwrite-mongo-express - networks: - - appwrite - ports: - - "8082:8081" - environment: - ME_CONFIG_MONGODB_URL: "mongodb://root:${_APP_DB_ROOT_PASS}@appwrite-mongodb:27017/?replicaSet=rs0&directConnection=true" - ME_CONFIG_BASICAUTH_USERNAME: ${_APP_DB_USER} - ME_CONFIG_BASICAUTH_PASSWORD: ${_APP_DB_PASS} - depends_on: - - mongodb + postgresql: - image: appwrite/postgres:0.1.0 + profiles: ["postgresql"] + build: + context: ./tests/resources/postgresql + args: + POSTGRES_VERSION: 17 container_name: appwrite-postgresql <<: *x-logging networks: - appwrite volumes: - - appwrite-postgresql:/var/lib/postgresql/18/data:rw + - appwrite-postgresql:/var/lib/postgresql:rw ports: - "5432:5432" environment: - POSTGRES_DB=${_APP_DB_SCHEMA} - POSTGRES_USER=${_APP_DB_USER} - POSTGRES_PASSWORD=${_APP_DB_PASS} + command: "postgres" healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${_APP_DB_USER} -d ${_APP_DB_SCHEMA}"] + test: ["CMD-SHELL", "pg_isready -U ${_APP_DB_USER}"] interval: 5s timeout: 5s - retries: 10 - start_period: 10s - command: "postgres" - - ollama: - image: appwrite/ollama:0.1.1 - container_name: ollama - ports: - - "11434:11434" - restart: unless-stopped - environment: - MODELS: ${_APP_EMBEDDING_MODELS:-embeddinggemma} - OLLAMA_KEEP_ALIVE: 24h - volumes: - - appwrite-models:/root/.ollama - networks: - - appwrite + retries: 12 redis: image: redis:7.4.7-alpine @@ -1491,4 +1436,3 @@ volumes: appwrite-sites: appwrite-builds: appwrite-config: - appwrite-models: \ No newline at end of file diff --git a/docs/references/documentsdb/create-collection.md b/docs/references/documentsdb/create-collection.md deleted file mode 100644 index c6293a4c38..0000000000 --- a/docs/references/documentsdb/create-collection.md +++ /dev/null @@ -1 +0,0 @@ -Create a new Collection. Before using this route, you should create a new database resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. \ No newline at end of file diff --git a/docs/references/documentsdb/create-document.md b/docs/references/documentsdb/create-document.md deleted file mode 100644 index 197744b4a0..0000000000 --- a/docs/references/documentsdb/create-document.md +++ /dev/null @@ -1 +0,0 @@ -Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. \ No newline at end of file diff --git a/docs/references/documentsdb/create-documents.md b/docs/references/documentsdb/create-documents.md deleted file mode 100644 index 9f4a4a1396..0000000000 --- a/docs/references/documentsdb/create-documents.md +++ /dev/null @@ -1 +0,0 @@ -Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. \ No newline at end of file diff --git a/docs/references/documentsdb/create-index.md b/docs/references/documentsdb/create-index.md deleted file mode 100644 index 164b754161..0000000000 --- a/docs/references/documentsdb/create-index.md +++ /dev/null @@ -1,2 +0,0 @@ -Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request. -Attributes can be `key`, `fulltext`, and `unique`. \ No newline at end of file diff --git a/docs/references/documentsdb/create.md b/docs/references/documentsdb/create.md deleted file mode 100644 index b608485341..0000000000 --- a/docs/references/documentsdb/create.md +++ /dev/null @@ -1 +0,0 @@ -Create a new Database. diff --git a/docs/references/documentsdb/decrement-document-attribute.md b/docs/references/documentsdb/decrement-document-attribute.md deleted file mode 100644 index b7b32d6148..0000000000 --- a/docs/references/documentsdb/decrement-document-attribute.md +++ /dev/null @@ -1 +0,0 @@ -Decrement a specific column of a row by a given value. \ No newline at end of file diff --git a/docs/references/documentsdb/delete-collection.md b/docs/references/documentsdb/delete-collection.md deleted file mode 100644 index 90f7aa6aa5..0000000000 --- a/docs/references/documentsdb/delete-collection.md +++ /dev/null @@ -1 +0,0 @@ -Delete a collection by its unique ID. Only users with write permissions have access to delete this resource. \ No newline at end of file diff --git a/docs/references/documentsdb/delete-document.md b/docs/references/documentsdb/delete-document.md deleted file mode 100644 index 36fbf6802d..0000000000 --- a/docs/references/documentsdb/delete-document.md +++ /dev/null @@ -1 +0,0 @@ -Delete a document by its unique ID. \ No newline at end of file diff --git a/docs/references/documentsdb/delete-documents.md b/docs/references/documentsdb/delete-documents.md deleted file mode 100644 index a7b05503de..0000000000 --- a/docs/references/documentsdb/delete-documents.md +++ /dev/null @@ -1 +0,0 @@ -Bulk delete documents using queries, if no queries are passed then all documents are deleted. \ No newline at end of file diff --git a/docs/references/documentsdb/delete-index.md b/docs/references/documentsdb/delete-index.md deleted file mode 100644 index c5b8f49e5f..0000000000 --- a/docs/references/documentsdb/delete-index.md +++ /dev/null @@ -1 +0,0 @@ -Delete an index. \ No newline at end of file diff --git a/docs/references/documentsdb/delete.md b/docs/references/documentsdb/delete.md deleted file mode 100644 index 605fa290d3..0000000000 --- a/docs/references/documentsdb/delete.md +++ /dev/null @@ -1 +0,0 @@ -Delete a database by its unique ID. Only API keys with with databases.write scope can delete a database. \ No newline at end of file diff --git a/docs/references/documentsdb/get-collection-logs.md b/docs/references/documentsdb/get-collection-logs.md deleted file mode 100644 index 8578cef03c..0000000000 --- a/docs/references/documentsdb/get-collection-logs.md +++ /dev/null @@ -1 +0,0 @@ -Get the collection activity logs list by its unique ID. \ No newline at end of file diff --git a/docs/references/documentsdb/get-collection-usage.md b/docs/references/documentsdb/get-collection-usage.md deleted file mode 100644 index 48682a075f..0000000000 --- a/docs/references/documentsdb/get-collection-usage.md +++ /dev/null @@ -1 +0,0 @@ -Get usage metrics and statistics for a collection. Returning the total number of documents. The response includes both current totals and historical data over time. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, range defaults to 30 days. \ No newline at end of file diff --git a/docs/references/documentsdb/get-collection.md b/docs/references/documentsdb/get-collection.md deleted file mode 100644 index 97b39e8474..0000000000 --- a/docs/references/documentsdb/get-collection.md +++ /dev/null @@ -1 +0,0 @@ -Get a collection by its unique ID. This endpoint response returns a JSON object with the collection metadata. \ No newline at end of file diff --git a/docs/references/documentsdb/get-database-usage.md b/docs/references/documentsdb/get-database-usage.md deleted file mode 100644 index 2c2628a464..0000000000 --- a/docs/references/documentsdb/get-database-usage.md +++ /dev/null @@ -1 +0,0 @@ -Get usage metrics and statistics for a database. You can view the total number of collections, documents, and storage usage. The response includes both current totals and historical data over time. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, range defaults to 30 days. \ No newline at end of file diff --git a/docs/references/documentsdb/get-document-logs.md b/docs/references/documentsdb/get-document-logs.md deleted file mode 100644 index 9b96df5ad4..0000000000 --- a/docs/references/documentsdb/get-document-logs.md +++ /dev/null @@ -1 +0,0 @@ -Get the document activity logs list by its unique ID. \ No newline at end of file diff --git a/docs/references/documentsdb/get-document.md b/docs/references/documentsdb/get-document.md deleted file mode 100644 index 4e4d76bec0..0000000000 --- a/docs/references/documentsdb/get-document.md +++ /dev/null @@ -1 +0,0 @@ -Get a document by its unique ID. This endpoint response returns a JSON object with the document data. \ No newline at end of file diff --git a/docs/references/documentsdb/get-index.md b/docs/references/documentsdb/get-index.md deleted file mode 100644 index cdea5b4f27..0000000000 --- a/docs/references/documentsdb/get-index.md +++ /dev/null @@ -1 +0,0 @@ -Get index by ID. \ No newline at end of file diff --git a/docs/references/documentsdb/get-logs.md b/docs/references/documentsdb/get-logs.md deleted file mode 100644 index 8e49da4603..0000000000 --- a/docs/references/documentsdb/get-logs.md +++ /dev/null @@ -1 +0,0 @@ -Get the database activity logs list by its unique ID. \ No newline at end of file diff --git a/docs/references/documentsdb/get.md b/docs/references/documentsdb/get.md deleted file mode 100644 index 24183f6f6b..0000000000 --- a/docs/references/documentsdb/get.md +++ /dev/null @@ -1 +0,0 @@ -Get a database by its unique ID. This endpoint response returns a JSON object with the database metadata. \ No newline at end of file diff --git a/docs/references/documentsdb/increment-document-attribute.md b/docs/references/documentsdb/increment-document-attribute.md deleted file mode 100644 index 7a19b3fbc7..0000000000 --- a/docs/references/documentsdb/increment-document-attribute.md +++ /dev/null @@ -1 +0,0 @@ -Increment a specific column of a row by a given value. \ No newline at end of file diff --git a/docs/references/documentsdb/list-attributes.md b/docs/references/documentsdb/list-attributes.md deleted file mode 100644 index 72ad6d727f..0000000000 --- a/docs/references/documentsdb/list-attributes.md +++ /dev/null @@ -1 +0,0 @@ -List attributes in the collection. \ No newline at end of file diff --git a/docs/references/documentsdb/list-collections.md b/docs/references/documentsdb/list-collections.md deleted file mode 100644 index e437674915..0000000000 --- a/docs/references/documentsdb/list-collections.md +++ /dev/null @@ -1 +0,0 @@ -Get a list of all collections that belong to the provided databaseId. You can use the search parameter to filter your results. \ No newline at end of file diff --git a/docs/references/documentsdb/list-documents.md b/docs/references/documentsdb/list-documents.md deleted file mode 100644 index 4e2ae91792..0000000000 --- a/docs/references/documentsdb/list-documents.md +++ /dev/null @@ -1 +0,0 @@ -Get a list of all the user's documents in a given collection. You can use the query params to filter your results. \ No newline at end of file diff --git a/docs/references/documentsdb/list-indexes.md b/docs/references/documentsdb/list-indexes.md deleted file mode 100644 index a8c687fb2b..0000000000 --- a/docs/references/documentsdb/list-indexes.md +++ /dev/null @@ -1 +0,0 @@ -List indexes in the collection. \ No newline at end of file diff --git a/docs/references/documentsdb/list-usage.md b/docs/references/documentsdb/list-usage.md deleted file mode 100644 index a88e76680e..0000000000 --- a/docs/references/documentsdb/list-usage.md +++ /dev/null @@ -1 +0,0 @@ -List usage metrics and statistics for all databases in the project. You can view the total number of databases, collections, documents, and storage usage. The response includes both current totals and historical data over time. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, range defaults to 30 days. \ No newline at end of file diff --git a/docs/references/documentsdb/list.md b/docs/references/documentsdb/list.md deleted file mode 100644 index d93fb9d7a8..0000000000 --- a/docs/references/documentsdb/list.md +++ /dev/null @@ -1 +0,0 @@ -Get a list of all databases from the current Appwrite project. You can use the search parameter to filter your results. \ No newline at end of file diff --git a/docs/references/documentsdb/update-collection.md b/docs/references/documentsdb/update-collection.md deleted file mode 100644 index b8f6bef997..0000000000 --- a/docs/references/documentsdb/update-collection.md +++ /dev/null @@ -1 +0,0 @@ -Update a collection by its unique ID. \ No newline at end of file diff --git a/docs/references/documentsdb/update-document.md b/docs/references/documentsdb/update-document.md deleted file mode 100644 index 526f3971d1..0000000000 --- a/docs/references/documentsdb/update-document.md +++ /dev/null @@ -1 +0,0 @@ -Update a document by its unique ID. Using the patch method you can pass only specific fields that will get updated. \ No newline at end of file diff --git a/docs/references/documentsdb/update-documents.md b/docs/references/documentsdb/update-documents.md deleted file mode 100644 index 5f560c6435..0000000000 --- a/docs/references/documentsdb/update-documents.md +++ /dev/null @@ -1 +0,0 @@ -Update all documents that match your queries, if no queries are submitted then all documents are updated. You can pass only specific fields to be updated. \ No newline at end of file diff --git a/docs/references/documentsdb/update.md b/docs/references/documentsdb/update.md deleted file mode 100644 index 4e99bf2e07..0000000000 --- a/docs/references/documentsdb/update.md +++ /dev/null @@ -1 +0,0 @@ -Update a database by its unique ID. \ No newline at end of file diff --git a/docs/references/documentsdb/upsert-document.md b/docs/references/documentsdb/upsert-document.md deleted file mode 100644 index f1b68d13d5..0000000000 --- a/docs/references/documentsdb/upsert-document.md +++ /dev/null @@ -1 +0,0 @@ -Create or update a Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. \ No newline at end of file diff --git a/docs/references/documentsdb/upsert-documents.md b/docs/references/documentsdb/upsert-documents.md deleted file mode 100644 index 4feb473076..0000000000 --- a/docs/references/documentsdb/upsert-documents.md +++ /dev/null @@ -1 +0,0 @@ -Create or update Documents. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. diff --git a/mongo-init-replicaset.sh b/mongo-init-replicaset.sh old mode 100644 new mode 100755 diff --git a/mongo-init.js b/mongo-init.js index bc06ba5b23..edff6cc499 100644 --- a/mongo-init.js +++ b/mongo-init.js @@ -15,4 +15,4 @@ adminDb.createUser({ roles: [ { role: 'readWrite', db: database } ] -}); \ No newline at end of file +}); diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ee9e50ef62..9bdf1f03c6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -210,6 +210,12 @@ parameters: count: 1 path: src/Appwrite/Auth/Validator/PersonalData.php + - + message: '#^PHPDoc tag @var above a method has no effect\.$#' + identifier: varTag.misplaced + count: 1 + path: src/Appwrite/Databases/TransactionState.php + - message: '#^PHPDoc tag @param has invalid value \(DeviceDetector\)\: Unexpected token "\\n ", expected variable at offset 32 on line 2$#' identifier: phpDoc.parseError @@ -1235,11 +1241,55 @@ parameters: identifier: return.phpDocType count: 1 path: src/Appwrite/Utopia/Response/Model/User.php + + - + message: '#^Attribute class Tests\\E2E\\General\\Retry does not exist\.$#' + identifier: attribute.notFound + count: 1 + path: tests/e2e/General/UsageTest.php + - message: '#^Unsafe access to private property Tests\\E2E\\Services\\Databases\\Legacy\\DatabasesStringTypesTest\:\:\$setupCache through static\:\:\.$#' identifier: staticClassAccess.privateProperty count: 4 path: tests/e2e/Services/Databases/Legacy/DatabasesStringTypesTest.php + + - + message: '#^Variable \$library might not be defined\.$#' + identifier: variable.undefined + count: 1 + path: tests/e2e/Services/Databases/LegacyConsoleClientTest.php + + - + message: '#^Variable \$person might not be defined\.$#' + identifier: variable.undefined + count: 4 + path: tests/e2e/Services/Databases/LegacyConsoleClientTest.php + + - + message: '#^Variable \$library might not be defined\.$#' + identifier: variable.undefined + count: 1 + path: tests/e2e/Services/Databases/LegacyCustomClientTest.php + + - + message: '#^Variable \$person might not be defined\.$#' + identifier: variable.undefined + count: 4 + path: tests/e2e/Services/Databases/LegacyCustomClientTest.php + + - + message: '#^Variable \$library might not be defined\.$#' + identifier: variable.undefined + count: 1 + path: tests/e2e/Services/Databases/LegacyCustomServerTest.php + + - + message: '#^Variable \$person might not be defined\.$#' + identifier: variable.undefined + count: 4 + path: tests/e2e/Services/Databases/LegacyCustomServerTest.php + - message: '#^Call to an undefined method Tests\\E2E\\Services\\Databases\\Permissions\\LegacyPermissionsGuestTest\:\:getIndexUrl\(\)\.$#' identifier: method.notFound @@ -1623,6 +1673,43 @@ parameters: identifier: method.notFound count: 1 path: tests/e2e/Services/TablesDB/Permissions/TablesDBPermissionsTeamTest.php + + - + message: '#^Variable \$library might not be defined\.$#' + identifier: variable.undefined + count: 1 + path: tests/e2e/Services/TablesDB/TablesDBConsoleClientTest.php + + - + message: '#^Variable \$person might not be defined\.$#' + identifier: variable.undefined + count: 4 + path: tests/e2e/Services/TablesDB/TablesDBConsoleClientTest.php + + - + message: '#^Variable \$library might not be defined\.$#' + identifier: variable.undefined + count: 1 + path: tests/e2e/Services/TablesDB/TablesDBCustomClientTest.php + + - + message: '#^Variable \$person might not be defined\.$#' + identifier: variable.undefined + count: 4 + path: tests/e2e/Services/TablesDB/TablesDBCustomClientTest.php + + - + message: '#^Variable \$library might not be defined\.$#' + identifier: variable.undefined + count: 1 + path: tests/e2e/Services/TablesDB/TablesDBCustomServerTest.php + + - + message: '#^Variable \$person might not be defined\.$#' + identifier: variable.undefined + count: 4 + path: tests/e2e/Services/TablesDB/TablesDBCustomServerTest.php + - message: '#^Unsafe access to private property Tests\\E2E\\Services\\Tokens\\TokensConsoleClientTest\:\:\$bucketAndFileData through static\:\:\.$#' identifier: staticClassAccess.privateProperty diff --git a/src/Appwrite/Databases/TransactionState.php b/src/Appwrite/Databases/TransactionState.php index 71bd8799c7..8e098774e6 100644 --- a/src/Appwrite/Databases/TransactionState.php +++ b/src/Appwrite/Databases/TransactionState.php @@ -21,23 +21,17 @@ class TransactionState { private Database $dbForProject; private Authorization $authorization; - /** - * @var callable(Document $database): Database - */ - private mixed $getDatabasesDB; - - public function __construct(Database $dbForProject, Authorization $authorization, callable $getDatabasesDB) + /** @var Authorization $authorization */ + public function __construct(Database $dbForProject, Authorization $authorization) { $this->dbForProject = $dbForProject; $this->authorization = $authorization; - $this->getDatabasesDB = $getDatabasesDB; } /** * Get a document with transaction-aware logic * - * @param Document $database Target database document * @param string $collectionId Collection ID * @param string $documentId Document ID * @param string|null $transactionId Optional transaction ID @@ -48,15 +42,13 @@ class TransactionState * @throws Timeout */ public function getDocument( - Document $database, string $collectionId, string $documentId, ?string $transactionId = null, array $queries = [] ): Document { - $dbForDatabases = ($this->getDatabasesDB)($database); if ($transactionId === null) { - return $dbForDatabases->getDocument($collectionId, $documentId, $queries); + return $this->dbForProject->getDocument($collectionId, $documentId, $queries); } $state = $this->getTransactionState($transactionId); @@ -74,7 +66,7 @@ class TransactionState if ($docState['action'] === 'update' || $docState['action'] === 'upsert') { // Merge with committed version - $committedDoc = $dbForDatabases->getDocument($collectionId, $documentId, $queries); + $committedDoc = $this->dbForProject->getDocument($collectionId, $documentId, $queries); if (!$committedDoc->isEmpty()) { foreach ($docState['document']->getAttributes() as $key => $value) { if ($key !== '$id') { @@ -88,13 +80,13 @@ class TransactionState } } } - return $dbForDatabases->getDocument($collectionId, $documentId, $queries); + + return $this->dbForProject->getDocument($collectionId, $documentId, $queries); } /** * List documents with transaction-aware logic * - * @param Document $database Target database document * @param string $collectionId Collection ID * @param string|null $transactionId Optional transaction ID * @param array $queries Optional query filters @@ -104,19 +96,17 @@ class TransactionState * @throws Timeout */ public function listDocuments( - Document $database, string $collectionId, ?string $transactionId = null, array $queries = [] ): array { - $dbForDatabases = ($this->getDatabasesDB)($database); // If no transaction, use normal database retrieval if ($transactionId === null) { - return $dbForDatabases->find($collectionId, $queries); + return $this->dbForProject->find($collectionId, $queries); } $state = $this->getTransactionState($transactionId); - $committedDocs = $dbForDatabases->find($collectionId, $queries); + $committedDocs = $this->dbForProject->find($collectionId, $queries); $documentMap = []; // Build map of committed documents @@ -157,7 +147,6 @@ class TransactionState /** * Count documents with transaction-aware logic * - * @param Document $database Target database document * @param string $collectionId Collection ID * @param string|null $transactionId Optional transaction ID * @param array $queries Optional query filters @@ -167,23 +156,23 @@ class TransactionState * @throws Timeout */ public function countDocuments( - Document $database, string $collectionId, ?string $transactionId = null, array $queries = [] ): int { - $dbForDatabases = ($this->getDatabasesDB)($database); if ($transactionId === null) { - return $dbForDatabases->count($collectionId, $queries, APP_LIMIT_COUNT); + return $this->dbForProject->count($collectionId, $queries, APP_LIMIT_COUNT); } $state = $this->getTransactionState($transactionId); - $baseCount = $dbForDatabases->count($collectionId, $queries, APP_LIMIT_COUNT); + + $baseCount = $this->dbForProject->count($collectionId, $queries, APP_LIMIT_COUNT); if (!isset($state[$collectionId])) { return $baseCount; } - $committedDocs = $dbForDatabases->find($collectionId, $queries); + + $committedDocs = $this->dbForProject->find($collectionId, $queries); $committedDocIds = []; foreach ($committedDocs as $doc) { $committedDocIds[$doc->getId()] = true; @@ -225,19 +214,17 @@ class TransactionState /** * Check if a document exists with transaction-aware logic * - * @param Document $database Target database document * @param string $collectionId Collection ID * @param string $documentId Document ID * @param string|null $transactionId Optional transaction ID * @return bool True if document exists */ public function documentExists( - Document $database, string $collectionId, string $documentId, ?string $transactionId = null ): bool { - $doc = $this->getDocument($database, $collectionId, $documentId, $transactionId); + $doc = $this->getDocument($collectionId, $documentId, $transactionId); return !$doc->isEmpty(); } diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index bf6339f8a0..ba633b4478 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -519,7 +519,6 @@ class Event * @param string $pattern * @param array $params * @param ?Document $database - * @param ?Document $database * @return array * @throws \InvalidArgumentException */ @@ -534,7 +533,7 @@ class Event $parsed = self::parseEventPattern($pattern); // to switch the resource types from databases to the required prefix // eg; all databases events get fired with databases. prefix which mainly depicts legacy type - // so a projection from databases to the actual prefix(documentsdb, vectorsdb,etc) + // so a projection from databases to the actual prefix if ((str_contains($pattern, 'databases.') && $database && $database->getAttribute('type') !== 'legacy')) { $parsed = self::getDatabaseTypeEvents($database, $parsed); } @@ -696,6 +695,7 @@ class Event ) ) { $pairedEvents = []; + foreach ($events as $event) { $pairedEvents[] = $event; // tablesdb needs databases event with tables and collections @@ -745,13 +745,6 @@ class Event 'attributes' => 'columns', ]; break; - case 'documentsdb': - case 'vectorsdb': - // sending the type itself(eg: documentsdb, vectorsdb) - $eventMap = [ - 'databases' => $database->getAttribute('type') - ]; - break; } foreach ($event as $eventKey => $eventValue) { if (isset($eventMap[$eventValue])) { diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index f7c76d3800..a54edf7074 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -340,7 +340,6 @@ class Exception extends \Exception public const string MIGRATION_ALREADY_EXISTS = 'migration_already_exists'; public const string MIGRATION_IN_PROGRESS = 'migration_in_progress'; public const string MIGRATION_PROVIDER_ERROR = 'migration_provider_error'; - public const string MIGRATION_DATABASE_TYPE_UNSUPPORTED = 'migration_database_type_unsupported'; /** Realtime */ public const string REALTIME_MESSAGE_FORMAT_INVALID = 'realtime_message_format_invalid'; diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index 7a2b6fe19a..85ae4fde25 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -492,8 +492,6 @@ class Realtime extends MessagingAdapter break; case 'databases': case 'tablesdb': - case 'documentsdb': - case 'vectorsdb': $resource = $parts[4] ?? ''; if (in_array($resource, ['columns', 'attributes', 'indexes'])) { $channels[] = 'console'; @@ -513,20 +511,12 @@ class Realtime extends MessagingAdapter $resourceId = $tableId ?: $collectionId; $channels = []; - switch ($parts[0]) { - case 'databases': - case 'tablesdb': - // sending legacy + tablesdb events to both legacy and tablesdb - $channels = array_values(array_unique(array_merge( - self::getDatabaseChannels('legacy', $database->getId(), $resourceId, $payload->getId(), 'databases'), - self::getDatabaseChannels('tablesdb', $database->getId(), $resourceId, $payload->getId(), 'databases'), - self::getDatabaseChannels('tablesdb', $database->getId(), $resourceId, $payload->getId()) - ))); - break; - default: - // only prefixed events - $channels = array_values(self::getDatabaseChannels($parts[0], $database->getId(), $resourceId, $payload->getId())); - } + // sending legacy + tablesdb events to both legacy and tablesdb + $channels = array_values(array_unique(array_merge( + self::getDatabaseChannels('legacy', $database->getId(), $resourceId, $payload->getId(), 'databases'), + self::getDatabaseChannels('tablesdb', $database->getId(), $resourceId, $payload->getId(), 'databases'), + self::getDatabaseChannels('tablesdb', $database->getId(), $resourceId, $payload->getId()) + ))); $roles = $collection->getAttribute('documentSecurity', false) ? \array_merge($collection->getRead(), $payload->getRead()) @@ -592,7 +582,6 @@ class Realtime extends MessagingAdapter * @param string $resourceId The collection/table ID * @param string $payloadId The document/row ID * @param string $prefixOverride Override the channel prefix when different API types share the same terminology but need different prefixes - * (e.g., 'databases' and 'documentsdb' use same terminology but need different prefixes) * @return array Array of channel names */ private static function getDatabaseChannels( @@ -626,13 +615,6 @@ class Realtime extends MessagingAdapter $channels[] = "{$basePrefix}.{$databaseId}.tables.{$resourceId}.rows.{$payloadId}"; break; - case 'documentsdb': - case 'vectorsdb': - $channels[] = 'documents'; - $channels[] = "{$basePrefix}.{$databaseId}.collections.{$resourceId}.documents"; - $channels[] = "{$basePrefix}.{$databaseId}.collections.{$resourceId}.documents.{$payloadId}"; - break; - default: $basePrefix = 'databases'; $channels[] = 'documents'; @@ -641,7 +623,6 @@ class Realtime extends MessagingAdapter break; } - return $channels; } } diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index a4f73eb5f2..072ca6e995 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -91,6 +91,7 @@ abstract class Migration '1.7.4' => 'V22', '1.8.0' => 'V23', '1.8.1' => 'V23', + '1.8.2' => 'V23', '1.9.0' => 'V24', ]; diff --git a/src/Appwrite/Network/Platform.php b/src/Appwrite/Network/Platform.php index ea64ff98c1..1cf5de91d1 100644 --- a/src/Appwrite/Network/Platform.php +++ b/src/Appwrite/Network/Platform.php @@ -35,6 +35,7 @@ class Platform public const SCHEME_ANDROID = 'appwrite-android'; public const SCHEME_WINDOWS = 'appwrite-windows'; public const SCHEME_LINUX = 'appwrite-linux'; + public const SCHEME_TAURI = 'tauri'; /** * @var array Map scheme types to user-friendly platform names. @@ -53,6 +54,7 @@ class Platform self::SCHEME_FIREFOX_EXTENSION => 'Web (Firefox Extension)', self::SCHEME_SAFARI_EXTENSION => 'Web (Safari Extension)', self::SCHEME_EDGE_EXTENSION => 'Web (Edge Extension)', + self::SCHEME_TAURI => 'Web (Tauri)', ]; /** diff --git a/src/Appwrite/Network/Validator/Origin.php b/src/Appwrite/Network/Validator/Origin.php index 8b9974e990..3c4e8a254a 100644 --- a/src/Appwrite/Network/Validator/Origin.php +++ b/src/Appwrite/Network/Validator/Origin.php @@ -69,6 +69,7 @@ class Origin extends Validator Platform::SCHEME_FIREFOX_EXTENSION, Platform::SCHEME_SAFARI_EXTENSION, Platform::SCHEME_EDGE_EXTENSION, + Platform::SCHEME_TAURI, ]; if (in_array($this->scheme, $webPlatforms, true)) { $validator = new Hostname($this->allowedHostnames); diff --git a/src/Appwrite/Platform/Modules/Databases/Constants.php b/src/Appwrite/Platform/Modules/Databases/Constants.php index edc6b09cf0..cfc297c3f4 100644 --- a/src/Appwrite/Platform/Modules/Databases/Constants.php +++ b/src/Appwrite/Platform/Modules/Databases/Constants.php @@ -22,11 +22,3 @@ const INDEX = 'index'; const DOCUMENTS = 'document'; const ATTRIBUTES = 'attribute'; const COLLECTIONS = 'collection'; - -const LEGACY = 'legacy'; -const TABLESDB = 'tablesdb'; -const DOCUMENTSDB = 'documentsdb'; -const VECTORSDB = 'vectorsdb'; - -const MIN_VECTOR_DIMENSION = 1; -const MAX_VECTOR_DIMENSION = 16000; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Action.php index b2417871ed..728e732cc5 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Action.php @@ -10,7 +10,7 @@ use Utopia\Database\Operator; class Action extends AppwriteAction { - private string $context = DATABASE_TYPE_LEGACY; + private string $context = 'legacy'; public function getDatabaseType(): string { @@ -20,13 +20,7 @@ class Action extends AppwriteAction public function setHttpPath(string $path): AppwriteAction { if (\str_contains($path, '/tablesdb')) { - $this->context = DATABASE_TYPE_TABLESDB; - } - if (\str_contains($path, '/documentsdb')) { - $this->context = DATABASE_TYPE_DOCUMENTSDB; - } - if (\str_contains($path, '/vectorsdb')) { - $this->context = DATABASE_TYPE_VECTORSDB; + $this->context = 'tablesdb'; } return parent::setHttpPath($path); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Action.php index 2f541936a8..f49d07ec4c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Action.php @@ -15,8 +15,6 @@ abstract class Action extends UtopiaAction */ private ?string $context = COLLECTIONS; - private ?string $databaseType = LEGACY; - /** * Get the response model used in the SDK and HTTP responses. */ @@ -26,9 +24,6 @@ abstract class Action extends UtopiaAction { if (\str_contains($path, '/tablesdb')) { $this->context = TABLES; - $this->databaseType = TABLESDB; - } elseif (\str_contains($path, '/vectorsdb')) { - $this->databaseType = VECTORSDB; } return parent::setHttpPath($path); } @@ -41,14 +36,6 @@ abstract class Action extends UtopiaAction return $this->context; } - /** - * Get the current API database type. - */ - protected function getDatabaseType(): string - { - return $this->databaseType; - } - /** * Get the key used in event parameters (e.g., 'collectionId' or 'tableId'). */ diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php index fd309a413c..216ec07e05 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php @@ -84,13 +84,12 @@ class Create extends Action ->param('indexes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC/DESC, optional), and lengths (array of integers, optional).', true) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('authorization') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, array $attributes, array $indexes, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Event $queueForEvents, Authorization $authorization): void + public function action(string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, array $attributes, array $indexes, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void { $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); @@ -122,18 +121,12 @@ class Create extends Action throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]); } - /** - * @var Database $dbForDatabases - */ - $dbForDatabases = $getDatabasesDB($database); - $collectionKey = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); $databaseKey = 'database_' . $database->getSequence(); $attributesValidator = new AttributesValidator( APP_LIMIT_ARRAY_PARAMS_SIZE, - $dbForDatabases->getAdapter()->getSupportForSpatialAttributes(), - $dbForDatabases->getAdapter()->getSupportForAttributes() + $dbForProject->getAdapter()->getSupportForSpatialAttributes() ); if (!$attributesValidator->isValid($attributes)) { @@ -162,7 +155,7 @@ class Create extends Action } // Validate indexes - $indexesValidator = new IndexesValidator($dbForDatabases->getLimitForIndexes()); + $indexesValidator = new IndexesValidator($dbForProject->getLimitForIndexes()); if (!$indexesValidator->isValid($indexes)) { $dbForProject->deleteDocument($databaseKey, $collection->getId()); throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $indexesValidator->getDescription()); @@ -185,23 +178,21 @@ class Create extends Action $indexValidator = new IndexValidator( $collectionAttributes, [], - $dbForDatabases->getAdapter()->getMaxIndexLength(), - $dbForDatabases->getAdapter()->getInternalIndexesKeys(), - $dbForDatabases->getAdapter()->getSupportForIndexArray(), - $dbForDatabases->getAdapter()->getSupportForSpatialIndexNull(), - $dbForDatabases->getAdapter()->getSupportForSpatialIndexOrder(), - $dbForDatabases->getAdapter()->getSupportForVectors(), - $dbForDatabases->getAdapter()->getSupportForAttributes(), - $dbForDatabases->getAdapter()->getSupportForMultipleFulltextIndexes(), - $dbForDatabases->getAdapter()->getSupportForIdenticalIndexes(), - $dbForDatabases->getAdapter()->getSupportForObjectIndexes(), - $dbForDatabases->getAdapter()->getSupportForTrigramIndex(), - $dbForDatabases->getAdapter()->getSupportForSpatialAttributes(), - $dbForDatabases->getAdapter()->getSupportForIndex(), - $dbForDatabases->getAdapter()->getSupportForUniqueIndex(), - $dbForDatabases->getAdapter()->getSupportForFulltextIndex(), - $dbForDatabases->getAdapter()->getSupportForTTLIndexes(), - $dbForDatabases->getAdapter()->getSupportForObject(), + $dbForProject->getAdapter()->getMaxIndexLength(), + $dbForProject->getAdapter()->getInternalIndexesKeys(), + $dbForProject->getAdapter()->getSupportForIndexArray(), + $dbForProject->getAdapter()->getSupportForSpatialIndexNull(), + $dbForProject->getAdapter()->getSupportForSpatialIndexOrder(), + $dbForProject->getAdapter()->getSupportForVectors(), + $dbForProject->getAdapter()->getSupportForAttributes(), + $dbForProject->getAdapter()->getSupportForMultipleFulltextIndexes(), + $dbForProject->getAdapter()->getSupportForIdenticalIndexes(), + $dbForProject->getAdapter()->getSupportForObjectIndexes(), + $dbForProject->getAdapter()->getSupportForTrigramIndex(), + $dbForProject->getAdapter()->getSupportForSpatialAttributes(), + $dbForProject->getAdapter()->getSupportForIndex(), + $dbForProject->getAdapter()->getSupportForUniqueIndex(), + $dbForProject->getAdapter()->getSupportForFulltextIndex(), ); foreach ($collectionIndexes as $indexDoc) { @@ -212,7 +203,7 @@ class Create extends Action } try { - $dbForDatabases->createCollection( + $dbForProject->createCollection( id: $collectionKey, attributes: $collectionAttributes, indexes: $collectionIndexes, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Delete.php index 7a5b73f7db..7f194aa93d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Delete.php @@ -62,14 +62,13 @@ class Delete extends Action ->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID.', false, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForDatabase') ->inject('queueForEvents') ->inject('authorization') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void + public function action(string $databaseId, string $collectionId, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void { $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { @@ -86,8 +85,7 @@ class Delete extends Action throw new Exception(Exception::GENERAL_SERVER_ERROR, "Failed to remove $type from DB"); } - $dbForDatabases = $getDatabasesDB($database); - $dbForDatabases->purgeCachedCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence()); + $dbForProject->purgeCachedCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence()); $queueForDatabase ->setType(DATABASE_TYPE_DELETE_COLLECTION) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php index 0bd4a2e080..39146508fb 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php @@ -17,7 +17,6 @@ abstract class Action extends DatabasesAction * @var string|null The current context (either 'row' or 'document') */ private ?string $context = DOCUMENTS; - private ?string $databaseType = DATABASE_TYPE_LEGACY; /** * Get the response model used in the SDK and HTTP responses. @@ -28,10 +27,6 @@ abstract class Action extends DatabasesAction { if (str_contains($path, '/tablesdb/')) { $this->context = ROWS; - } elseif (str_contains($path, '/documentsdb/')) { - $this->databaseType = DATABASE_TYPE_DOCUMENTSDB; - } elseif (str_contains($path, '/vectorsdb/')) { - $this->databaseType = DATABASE_TYPE_VECTORSDB; } $contextId = '$' . $this->getCollectionsEventsContext() . 'Id'; @@ -50,39 +45,6 @@ abstract class Action extends DatabasesAction return parent::setHttpPath($path); } - protected function getDatabasesOperationReadMetric(): string - { - if ($this->databaseType === DATABASE_TYPE_LEGACY || $this->databaseType === DATABASE_TYPE_TABLESDB) { - return METRIC_DATABASES_OPERATIONS_READS; - } - return $this->databaseType.'.'.METRIC_DATABASES_OPERATIONS_READS; - } - - protected function getDatabasesIdOperationReadMetric(): string - { - if ($this->databaseType === DATABASE_TYPE_LEGACY || $this->databaseType === DATABASE_TYPE_TABLESDB) { - return METRIC_DATABASE_ID_OPERATIONS_READS; - } - return $this->databaseType.'.'.METRIC_DATABASE_ID_OPERATIONS_READS; - } - - protected function getDatabasesOperationWriteMetric(): string - { - if ($this->databaseType === DATABASE_TYPE_LEGACY || $this->databaseType === DATABASE_TYPE_TABLESDB) { - return METRIC_DATABASES_OPERATIONS_WRITES; - } - return $this->databaseType.'.'.METRIC_DATABASES_OPERATIONS_WRITES; - - } - - protected function getDatabasesIdOperationWriteMetric(): string - { - if ($this->databaseType === DATABASE_TYPE_LEGACY || $this->databaseType === DATABASE_TYPE_TABLESDB) { - return METRIC_DATABASE_ID_OPERATIONS_WRITES; - } - return $this->databaseType.'.'.METRIC_DATABASE_ID_OPERATIONS_WRITES; - } - /** * Get the plural of the given name. * diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php index 8d31e19753..54557eaac0 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php @@ -82,7 +82,6 @@ class Decrement extends Action ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('usage') ->inject('plan') @@ -90,7 +89,7 @@ class Decrement extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Event $queueForEvents, Context $usage, array $plan, Authorization $authorization): void + public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Context $usage, array $plan, Authorization $authorization): void { $isAPIKey = User::isApp($authorization->getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles()); @@ -171,9 +170,8 @@ class Decrement extends Action return; } - $dbForDatabases = $getDatabasesDB($database); try { - $document = $dbForDatabases->decreaseDocumentAttribute( + $document = $dbForProject->decreaseDocumentAttribute( collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), id: $documentId, attribute: $attribute, @@ -203,8 +201,8 @@ class Decrement extends Action ); $usage - ->addMetric($this->getDatabasesOperationWriteMetric(), 1) - ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), 1); + ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1) + ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1); $queueForEvents ->setParam('databaseId', $databaseId) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php index 9de5f83154..b9c19b2d06 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php @@ -82,7 +82,6 @@ class Increment extends Action ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('usage') ->inject('plan') @@ -90,7 +89,7 @@ class Increment extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Event $queueForEvents, Context $usage, array $plan, Authorization $authorization): void + public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Context $usage, array $plan, Authorization $authorization): void { $isAPIKey = User::isApp($authorization->getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles()); @@ -171,9 +170,8 @@ class Increment extends Action return; } - $dbForDatabases = $getDatabasesDB($database); try { - $document = $dbForDatabases->increaseDocumentAttribute( + $document = $dbForProject->increaseDocumentAttribute( collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), id: $documentId, attribute: $attribute, @@ -203,8 +201,8 @@ class Increment extends Action ); $usage - ->addMetric($this->getDatabasesOperationWriteMetric(), 1) - ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), 1); + ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1) + ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1); $queueForEvents ->setParam('databaseId', $databaseId) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php index 267a54adb0..f45b126f16 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php @@ -76,7 +76,6 @@ class Delete extends Action ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('usage') ->inject('queueForEvents') ->inject('queueForRealtime') @@ -87,7 +86,7 @@ class Delete extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Context $usage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, EventProcessor $eventProcessor): void + public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Context $usage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, EventProcessor $eventProcessor): void { $database = $dbForProject->getDocument('databases', $databaseId); if ($database->isEmpty()) { @@ -164,11 +163,10 @@ class Delete extends Action return; } - $dbForDatabases = $getDatabasesDB($database); $documents = []; try { - $modified = $dbForDatabases->deleteDocuments( + $modified = $dbForProject->deleteDocuments( 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $queries, onNext: function (Document $document) use ($plan, &$documents) { @@ -191,12 +189,12 @@ class Delete extends Action } $usage - ->addMetric($this->getDatabasesOperationWriteMetric(), \max(1, $modified)) - ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), \max(1, $modified)); + ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $modified)) + ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $modified)); $response->dynamic(new Document([ 'total' => $modified, - $this->getSDKGroup() => $documents + $this->getSDKGroup() => $documents, ]), $this->getResponseModel()); $this->triggerBulk( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index da3adf1192..000b59ff07 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -80,7 +80,6 @@ class Update extends Action ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('usage') ->inject('queueForEvents') ->inject('queueForRealtime') @@ -91,7 +90,7 @@ class Update extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string|array $data, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Context $usage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, EventProcessor $eventProcessor): void + public function action(string $databaseId, string $collectionId, string|array $data, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Context $usage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, EventProcessor $eventProcessor): void { $data = \is_string($data) ? \json_decode($data, true) @@ -190,12 +189,11 @@ class Update extends Action return; } - $dbForDatabases = $getDatabasesDB($database); $documents = []; try { - $modified = $dbForDatabases->withPreserveDates(function () use ($plan, &$documents, $dbForDatabases, $database, $collection, $data, $queries) { - return $dbForDatabases->updateDocuments( + $modified = $dbForProject->withPreserveDates(function () use ($plan, &$documents, $dbForProject, $database, $collection, $data, $queries) { + return $dbForProject->updateDocuments( 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), new Document($data), $queries, @@ -222,8 +220,8 @@ class Update extends Action } $usage - ->addMetric($this->getDatabasesOperationWriteMetric(), \max(1, $modified)) - ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), \max(1, $modified)); + ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $modified)) + ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $modified)); $response->dynamic(new Document([ 'total' => $modified, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index 050227b4b9..564b5ee7b6 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -58,7 +58,7 @@ class Upsert extends Action group: $this->getSDKGroup(), name: self::getName(), description: '/docs/references/databases/upsert-documents.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], + auth: [AuthType::ADMIN, AuthType::KEY], responses: [ new SDKResponse( code: SwooleResponse::STATUS_CODE_CREATED, @@ -78,7 +78,6 @@ class Upsert extends Action ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('usage') ->inject('queueForEvents') ->inject('queueForRealtime') @@ -89,7 +88,7 @@ class Upsert extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Context $usage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, EventProcessor $eventProcessor): void + public function action(string $databaseId, string $collectionId, array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Context $usage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, EventProcessor $eventProcessor): void { $database = $dbForProject->getDocument('databases', $databaseId); if ($database->isEmpty()) { @@ -166,12 +165,11 @@ class Upsert extends Action return; } - $dbForDatabases = $getDatabasesDB($database); $upserted = []; try { - $modified = $dbForDatabases->withPreserveDates(function () use ($dbForDatabases, $database, $collection, $documents, $plan, &$upserted) { - return $dbForDatabases->upsertDocuments( + $modified = $dbForProject->withPreserveDates(function () use ($dbForProject, $database, $collection, $documents, $plan, &$upserted) { + return $dbForProject->upsertDocuments( 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documents, onNext: function (Document $document) use ($plan, &$upserted) { @@ -197,8 +195,8 @@ class Upsert extends Action } $usage - ->addMetric($this->getDatabasesOperationWriteMetric(), \max(1, $modified)) - ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), \max(1, $modified)); + ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $modified)) + ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $modified)); $response->dynamic(new Document([ 'total' => $modified, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php index 08c3b047be..0bbe7c75cf 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php @@ -85,7 +85,7 @@ class Create extends Action new Parameter('documentId', optional: false), new Parameter('data', optional: false), new Parameter('permissions', optional: true), - new Parameter('transactionId', optional: true) + new Parameter('transactionId', optional: true), ], deprecated: new Deprecated( since: '1.8.0', @@ -110,7 +110,7 @@ class Create extends Action new Parameter('databaseId', optional: false), new Parameter('collectionId', optional: false), new Parameter('documents', optional: false), - new Parameter('transactionId', optional: true) + new Parameter('transactionId', optional: true), ], deprecated: new Deprecated( since: '1.8.0', @@ -127,7 +127,6 @@ class Create extends Action ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('user') ->inject('queueForEvents') ->inject('usage') @@ -139,7 +138,7 @@ class Create extends Action ->inject('eventProcessor') ->callback($this->action(...)); } - public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Document $user, Event $queueForEvents, Context $usage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, Authorization $authorization, EventProcessor $eventProcessor): void + public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, Context $usage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, Authorization $authorization, EventProcessor $eventProcessor): void { $data = \is_string($data) ? \json_decode($data, true) @@ -448,12 +447,11 @@ class Create extends Action return; } - $dbForDatabases = $getDatabasesDB($database); try { $created = []; - $dbForDatabases->withPreserveDates( - function () use (&$created, $dbForDatabases, $database, $collection, $documents) { - $dbForDatabases->createDocuments( + $dbForProject->withPreserveDates( + function () use (&$created, $dbForProject, $database, $collection, $documents) { + $dbForProject->createDocuments( 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documents, onNext: function ($doc) use (&$created) { @@ -492,15 +490,15 @@ class Create extends Action } $usage - ->addMetric($this->getDatabasesOperationWriteMetric(), \max(1, $operations)) - ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), \max(1, $operations)); // per collection + ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations)) + ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); // per collection $response->setStatusCode(SwooleResponse::STATUS_CODE_CREATED); if ($isBulk) { $response->dynamic(new Document([ 'total' => count($created), - $this->getSDKGroup() => $created + $this->getSdkGroup() => $created ]), $this->getBulkResponseModel()); $this->triggerBulk( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index 9931109c49..0996fa24ab 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -79,7 +79,6 @@ class Delete extends Action ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('usage') ->inject('transactionState') @@ -96,7 +95,6 @@ class Delete extends Action ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, - callable $getDatabasesDB, Event $queueForEvents, Context $usage, TransactionState $transactionState, @@ -118,15 +116,14 @@ class Delete extends Action throw new Exception($this->getParentNotFoundException(), params: [$collectionId]); } - $dbForDatabases = $getDatabasesDB($database); // Read permission should not be required for delete $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); if ($transactionId !== null) { // Use transaction-aware document retrieval to see changes from same transaction - $document = $transactionState->getDocument($database, $collectionTableId, $documentId, $transactionId); + $document = $transactionState->getDocument($collectionTableId, $documentId, $transactionId); } else { - $document = $authorization->skip(fn () => $dbForDatabases->getDocument($collectionTableId, $documentId)); + $document = $authorization->skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId)); } if ($document->isEmpty()) { @@ -190,8 +187,8 @@ class Delete extends Action } try { - $dbForDatabases->withRequestTimestamp($requestTimestamp, function () use ($dbForDatabases, $database, $collection, $documentId) { - $dbForDatabases->deleteDocument( + $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) { + $dbForProject->deleteDocument( 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId ); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php index d84eb75a0f..10de481072 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php @@ -68,14 +68,13 @@ class Get extends Action ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID to read uncommitted changes within the transaction.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('usage') ->inject('transactionState') ->inject('authorization') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Context $usage, TransactionState $transactionState, Authorization $authorization): void + public function action(string $databaseId, string $collectionId, string $documentId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Context $usage, TransactionState $transactionState, Authorization $authorization): void { $isAPIKey = User::isApp($authorization->getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles()); @@ -87,7 +86,6 @@ class Get extends Action $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId)); - $dbForDatabases = $getDatabasesDB($database); if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception($this->getParentNotFoundException(), params: [$collectionId]); } @@ -101,17 +99,14 @@ class Get extends Action try { $selects = Query::groupByType($queries)['selections'] ?? []; $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); - $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); // Use transaction-aware document retrieval if transactionId is provided if ($transactionId !== null) { - $document = $transactionState->getDocument($database, $collectionTableId, $documentId, $transactionId, $queries); - } elseif (! empty($selects)) { - // has selects, allow relationship on documents! - $document = $dbForDatabases->getDocument($collectionTableId, $documentId, $queries); + $document = $transactionState->getDocument($collectionTableId, $documentId, $transactionId, $queries); + } elseif (!empty($selects)) { + $document = $dbForProject->getDocument($collectionTableId, $documentId, $queries); } else { - // has no selects, disable relationship looping on documents! - $document = $dbForDatabases->skipRelationships(fn () => $dbForDatabases->getDocument($collectionTableId, $documentId, $queries)); + $document = $dbForProject->skipRelationships(fn () => $dbForProject->getDocument($collectionTableId, $documentId, $queries)); } } catch (QueryException $e) { throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); @@ -134,8 +129,8 @@ class Get extends Action ); $usage - ->addMetric($this->getDatabasesOperationReadMetric(), max($operations, 1)) - ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationReadMetric()), $operations); + ->addMetric(METRIC_DATABASES_OPERATIONS_READS, max($operations, 1)) + ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations); $response->addHeader('X-Debug-Operations', $operations); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php index 4588e3666b..2e838329cb 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php @@ -70,7 +70,6 @@ class XList extends Action ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('locale') ->inject('geodb') ->inject('authorization') @@ -78,7 +77,7 @@ class XList extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, array $queries, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Locale $locale, Reader $geodb, Authorization $authorization, Audit $audit): void + public function action(string $databaseId, string $collectionId, string $documentId, array $queries, UtopiaResponse $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization, Audit $audit): void { $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { @@ -90,8 +89,7 @@ class XList extends Action throw new Exception($this->getParentNotFoundException(), params: [$collectionId]); } - $dbForDatabases = $getDatabasesDB($database); - $document = $dbForDatabases->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId); + $document = $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId); if ($document->isEmpty()) { throw new Exception($this->getNotFoundException(), params: [$documentId]); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php index f006ad7f59..ca7935dfbd 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php @@ -83,7 +83,6 @@ class Update extends Action ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('usage') ->inject('transactionState') @@ -92,7 +91,7 @@ class Update extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Event $queueForEvents, Context $usage, TransactionState $transactionState, array $plan, Authorization $authorization): void + public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Context $usage, TransactionState $transactionState, array $plan, Authorization $authorization): void { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -119,16 +118,15 @@ class Update extends Action $data = $this->parseOperators($data, $collection); } - $dbForDatabases = $getDatabasesDB($database); // Read permission should not be required for update /** @var Document $document */ $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); if ($transactionId !== null) { // Use transaction-aware document retrieval to see changes from same transaction - $document = $transactionState->getDocument($database, $collectionTableId, $documentId, $transactionId); + $document = $transactionState->getDocument($collectionTableId, $documentId, $transactionId); } else { - $document = $authorization->skip(fn () => $dbForDatabases->getDocument($collectionTableId, $documentId)); + $document = $authorization->skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId)); } if ($document->isEmpty()) { @@ -249,8 +247,8 @@ class Update extends Action $setCollection($collection, $newDocument); $usage - ->addMetric($this->getDatabasesOperationWriteMetric(), max($operations, 1)) - ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), $operations); + ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1)) + ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations); // Handle transaction staging if ($transactionId !== null) { @@ -321,9 +319,9 @@ class Update extends Action try { - $document = $dbForDatabases->withRequestTimestamp( + $document = $dbForProject->withRequestTimestamp( $requestTimestamp, - fn () => $dbForDatabases->withPreserveDates(fn () => $dbForDatabases->updateDocument( + fn () => $dbForProject->withPreserveDates(fn () => $dbForProject->updateDocument( 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $document->getId(), $newDocument diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 0dfc64f392..dc6655dfd3 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -87,7 +87,6 @@ class Upsert extends Action ->inject('response') ->inject('user') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('usage') ->inject('transactionState') @@ -96,7 +95,7 @@ class Upsert extends Action ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, callable $getDatabasesDB, Event $queueForEvents, Context $usage, TransactionState $transactionState, array $plan, Authorization $authorization): void + public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, Event $queueForEvents, Context $usage, TransactionState $transactionState, array $plan, Authorization $authorization): void { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -125,7 +124,6 @@ class Upsert extends Action $data = $this->parseOperators($data, $collection); } - $dbForDatabases = $getDatabasesDB($database); $allowedPermissions = [ Database::PERMISSION_READ, Database::PERMISSION_UPDATE, @@ -136,15 +134,13 @@ class Upsert extends Action $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); - $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); - // If no permission, upsert permission from the old document if present (update scenario) else add default permission (create scenario) if (\is_null($permissions)) { if ($transactionId !== null) { // Use transaction-aware document retrieval to see changes from same transaction - $oldDocument = $transactionState->getDocument($database, $collectionTableId, $documentId, $transactionId); + $oldDocument = $transactionState->getDocument($collectionTableId, $documentId, $transactionId); } else { - $oldDocument = $authorization->skip(fn () => $dbForDatabases->getDocument($collectionTableId, $documentId)); + $oldDocument = $authorization->skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId)); } if ($oldDocument->isEmpty()) { if (!empty($user->getId())) { @@ -186,7 +182,7 @@ class Upsert extends Action $newDocument = new Document($data); $operations = 0; - $setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $dbForDatabases, $database, &$operations, $authorization) { + $setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations, $authorization) { $operations++; $relationships = \array_filter( @@ -230,7 +226,7 @@ class Upsert extends Action if ($relation instanceof Document) { $relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser); - $oldDocument = $authorization->skip(fn () => $dbForDatabases->getDocument( + $oldDocument = $authorization->skip(fn () => $dbForProject->getDocument( 'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId() )); @@ -261,8 +257,8 @@ class Upsert extends Action $setCollection($collection, $newDocument); $usage - ->addMetric($this->getDatabasesOperationWriteMetric(), \max(1, $operations)) - ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), \max(1, $operations)); + ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations)) + ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); // Handle transaction staging if ($transactionId !== null) { @@ -331,8 +327,8 @@ class Upsert extends Action $upserted = []; try { - $dbForDatabases->withPreserveDates(function () use (&$upserted, $dbForDatabases, $database, $collection, $newDocument) { - return $dbForDatabases->upsertDocuments( + $dbForProject->withPreserveDates(function () use (&$upserted, $dbForProject, $database, $collection, $newDocument) { + return $dbForProject->upsertDocuments( 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), [$newDocument], onNext: function (Document $document) use (&$upserted) { @@ -355,9 +351,9 @@ class Upsert extends Action if (empty($upserted[0])) { if ($transactionId !== null) { // For transactions, get the document with transaction changes applied - $upserted[0] = $transactionState->getDocument($database, $collectionTableId, $documentId, $transactionId); + $upserted[0] = $transactionState->getDocument($collectionTableId, $documentId, $transactionId); } else { - $upserted[0] = $dbForDatabases->getDocument($collectionTableId, $documentId); + $upserted[0] = $dbForProject->getDocument($collectionTableId, $documentId); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php index bc9d30c6f2..a7d77d8a93 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php @@ -76,14 +76,13 @@ class XList extends Action ->inject('response') ->inject('dbForProject') ->inject('user') - ->inject('getDatabasesDB') ->inject('usage') ->inject('transactionState') ->inject('authorization') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, bool $includeTotal, int $ttl, UtopiaResponse $response, Database $dbForProject, Document $user, callable $getDatabasesDB, Context $usage, TransactionState $transactionState, Authorization $authorization): void + public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, bool $includeTotal, int $ttl, UtopiaResponse $response, Database $dbForProject, Document $user, Context $usage, TransactionState $transactionState, Authorization $authorization): void { $isAPIKey = User::isApp($authorization->getRoles()); $isPrivilegedUser = User::isPrivileged($authorization->getRoles()); @@ -104,7 +103,6 @@ class XList extends Action throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); } - $dbForDatabases = $getDatabasesDB($database); $cursor = Query::getCursorQueries($queries, false); $cursor = \reset($cursor); @@ -116,7 +114,7 @@ class XList extends Action $documentId = $cursor->getValue(); - $cursorDocument = $authorization->skip(fn () => $dbForDatabases->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId)); + $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId)); if ($cursorDocument->isEmpty()) { $type = ucfirst($this->getContext()); @@ -129,10 +127,11 @@ class XList extends Action try { $selectQueries = Query::groupByType($queries)['selections'] ?? []; $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); + // Use transaction-aware document retrieval if transactionId is provided if ($transactionId !== null) { - $documents = $transactionState->listDocuments($database, $collectionTableId, $transactionId, $queries); - $total = $includeTotal ? $transactionState->countDocuments($database, $collectionTableId, $transactionId, $queries) : 0; + $documents = $transactionState->listDocuments($collectionTableId, $transactionId, $queries); + $total = $includeTotal ? $transactionState->countDocuments($collectionTableId, $transactionId, $queries) : 0; } elseif (! empty($selectQueries)) { if ((int)$ttl > 0) { @@ -171,7 +170,7 @@ class XList extends Action }, $cachedDocuments); $documentsCacheHit = true; } else { - $documents = $dbForDatabases->find($collectionTableId, $queries); + $documents = $dbForProject->find($collectionTableId, $queries); // Convert Document objects to arrays for caching $documentsArray = \array_map(function ($doc) { @@ -197,15 +196,15 @@ class XList extends Action } else { // has selects, allow relationship on documents - $documents = $dbForDatabases->find($collectionTableId, $queries); - $total = $includeTotal ? $dbForDatabases->count($collectionTableId, $queries, APP_LIMIT_COUNT) : 0; + $documents = $dbForProject->find($collectionTableId, $queries); + $total = $includeTotal ? $dbForProject->count($collectionTableId, $queries, APP_LIMIT_COUNT) : 0; } } else { // has no selects, disable relationship loading on documents /* @type Document[] $documents */ - $documents = $dbForDatabases->skipRelationships(fn () => $dbForDatabases->find($collectionTableId, $queries)); - $total = $includeTotal ? $dbForDatabases->count($collectionTableId, $queries, APP_LIMIT_COUNT) : 0; + $documents = $dbForProject->skipRelationships(fn () => $dbForProject->find($collectionTableId, $queries)); + $total = $includeTotal ? $dbForProject->count($collectionTableId, $queries, APP_LIMIT_COUNT) : 0; } } catch (OrderException $e) { $documents = $this->isCollectionsAPI() ? 'documents' : 'rows'; @@ -233,8 +232,8 @@ class XList extends Action } $usage - ->addMetric($this->getDatabasesOperationReadMetric(), max($operations, 1)) - ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationReadMetric()), $operations); + ->addMetric(METRIC_DATABASES_OPERATIONS_READS, max($operations, 1)) + ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations); $response->dynamic(new Document([ 'total' => $total, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php index 7e073c95d4..fd785f3609 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php @@ -77,14 +77,13 @@ class Create extends Action ->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForDatabase') ->inject('queueForEvents') ->inject('authorization') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, array $lengths, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void + public function action(string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, array $lengths, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void { $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); @@ -104,9 +103,7 @@ class Create extends Action Query::equal('databaseInternalId', [$db->getSequence()]) ], 61); - $dbForDatabases = $getDatabasesDB($db); - - $limit = $dbForDatabases->getLimitForIndexes(); + $limit = $dbForProject->getLimitForIndexes(); if ($count >= $limit) { throw new Exception($this->getLimitException(), params: [$collectionId]); @@ -148,35 +145,32 @@ class Create extends Action ]; $contextType = $this->getParentContext(); - if ($dbForDatabases->getAdapter()->getSupportForAttributes()) { - foreach ($attributes as $i => $attribute) { - // find attribute metadata in collection document - $attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key')); + foreach ($attributes as $i => $attribute) { + $attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key')); - if ($attributeIndex === false) { - throw new Exception($this->getParentUnknownException(), params: [$attribute]); - } + if ($attributeIndex === false) { + throw new Exception($this->getParentUnknownException(), params: [$attribute]); + } - $attributeStatus = $oldAttributes[$attributeIndex]['status']; - $attributeType = $oldAttributes[$attributeIndex]['type']; - $attributeArray = $oldAttributes[$attributeIndex]['array'] ?? false; + $attributeStatus = $oldAttributes[$attributeIndex]['status']; + $attributeType = $oldAttributes[$attributeIndex]['type']; + $attributeArray = $oldAttributes[$attributeIndex]['array'] ?? false; - if ($attributeType === Database::VAR_RELATIONSHIP) { - throw new Exception($this->getParentInvalidTypeException(), "Cannot create an index for a relationship $contextType: " . $oldAttributes[$attributeIndex]['key']); - } + if ($attributeType === Database::VAR_RELATIONSHIP) { + throw new Exception($this->getParentInvalidTypeException(), "Cannot create an index for a relationship $contextType: " . $oldAttributes[$attributeIndex]['key']); + } - if ($attributeStatus !== 'available') { - throw new Exception($this->getParentNotAvailableException(), params: [$oldAttributes[$attributeIndex]['key']]); - } + if ($attributeStatus !== 'available') { + throw new Exception($this->getParentNotAvailableException(), params: [$oldAttributes[$attributeIndex]['key']]); + } - if (empty($lengths[$i])) { - $lengths[$i] = null; - } + if (empty($lengths[$i])) { + $lengths[$i] = null; + } - if ($attributeArray === true) { - // Because of a bug in MySQL, we cannot create indexes on array attributes for now, otherwise queries break. - throw new Exception(Exception::INDEX_INVALID, 'Creating indexes on array attributes is not currently supported.'); - } + if ($attributeArray === true) { + // Because of a bug in MySQL, we cannot create indexes on array attributes for now, otherwise queries break. + throw new Exception(Exception::INDEX_INVALID, 'Creating indexes on array attributes is not currently supported.'); } } @@ -197,23 +191,21 @@ class Create extends Action $validator = new IndexValidator( $collection->getAttribute('attributes'), $collection->getAttribute('indexes'), - $dbForDatabases->getAdapter()->getMaxIndexLength(), - $dbForDatabases->getAdapter()->getInternalIndexesKeys(), - $dbForDatabases->getAdapter()->getSupportForIndexArray(), - $dbForDatabases->getAdapter()->getSupportForSpatialIndexNull(), - $dbForDatabases->getAdapter()->getSupportForSpatialIndexOrder(), - $dbForDatabases->getAdapter()->getSupportForVectors(), - $dbForDatabases->getAdapter()->getSupportForAttributes(), - $dbForDatabases->getAdapter()->getSupportForMultipleFulltextIndexes(), - $dbForDatabases->getAdapter()->getSupportForIdenticalIndexes(), - $dbForDatabases->getAdapter()->getSupportForObjectIndexes(), - $dbForDatabases->getAdapter()->getSupportForTrigramIndex(), - $dbForDatabases->getAdapter()->getSupportForSpatialAttributes(), - $dbForDatabases->getAdapter()->getSupportForIndex(), - $dbForDatabases->getAdapter()->getSupportForUniqueIndex(), - $dbForDatabases->getAdapter()->getSupportForFulltextIndex(), - $dbForDatabases->getAdapter()->getSupportForTTLIndexes(), - $dbForDatabases->getAdapter()->getSupportForObject() + $dbForProject->getAdapter()->getMaxIndexLength(), + $dbForProject->getAdapter()->getInternalIndexesKeys(), + $dbForProject->getAdapter()->getSupportForIndexArray(), + $dbForProject->getAdapter()->getSupportForSpatialIndexNull(), + $dbForProject->getAdapter()->getSupportForSpatialIndexOrder(), + $dbForProject->getAdapter()->getSupportForVectors(), + $dbForProject->getAdapter()->getSupportForAttributes(), + $dbForProject->getAdapter()->getSupportForMultipleFulltextIndexes(), + $dbForProject->getAdapter()->getSupportForIdenticalIndexes(), + $dbForProject->getAdapter()->getSupportForObjectIndexes(), + $dbForProject->getAdapter()->getSupportForTrigramIndex(), + $dbForProject->getAdapter()->getSupportForSpatialAttributes(), + $dbForProject->getAdapter()->getSupportForIndex(), + $dbForProject->getAdapter()->getSupportForUniqueIndex(), + $dbForProject->getAdapter()->getSupportForFulltextIndex(), ); if (!$validator->isValid($index)) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php index 5d9d425d71..f34fd82997 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php @@ -70,13 +70,12 @@ class Update extends Action ->param('enabled', true, new Boolean(), 'Is collection enabled? When set to \'disabled\', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.', true) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('authorization') ->callback($this->action(...)); } - public function action(string $databaseId, string $collectionId, ?string $name, ?array $permissions, bool $documentSecurity, bool $enabled, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Event $queueForEvents, Authorization $authorization): void + public function action(string $databaseId, string $collectionId, ?string $name, ?array $permissions, bool $documentSecurity, bool $enabled, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void { $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { @@ -111,8 +110,7 @@ class Update extends Action ->setAttribute('search', \implode(' ', [$collectionId, $searchName])) ); - $dbForDatabases = $getDatabasesDB($database); - $dbForDatabases->updateCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $permissions, $documentSecurity); + $dbForProject->updateCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $permissions, $documentSecurity); $queueForEvents ->setContext('database', $database) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Usage/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Usage/Get.php index 37213f1061..de20d058c4 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Usage/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Usage/Get.php @@ -31,11 +31,6 @@ class Get extends Action return UtopiaResponse::MODEL_USAGE_COLLECTION; } - protected function getMetric(): string - { - return METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS; - } - public function __construct() { $this @@ -69,16 +64,14 @@ class Get extends Action ->inject('response') ->inject('dbForProject') ->inject('authorization') - ->inject('getDatabasesDB') ->callback($this->action(...)); } - public function action(string $databaseId, string $range, string $collectionId, UtopiaResponse $response, Database $dbForProject, Authorization $authorization, callable $getDatabasesDB): void + public function action(string $databaseId, string $range, string $collectionId, UtopiaResponse $response, Database $dbForProject, Authorization $authorization): void { $database = $dbForProject->getDocument('databases', $databaseId); $collectionDocument = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId); - $dbForDatabases = $getDatabasesDB($database); - $collection = $dbForDatabases->getCollection('database_' . $database->getSequence() . '_collection_' . $collectionDocument->getSequence()); + $collection = $dbForProject->getCollection('database_' . $database->getSequence() . '_collection_' . $collectionDocument->getSequence()); if ($collection->isEmpty()) { throw new Exception($this->getNotFoundException(), params: [$collectionId]); @@ -88,7 +81,7 @@ class Get extends Action $stats = $usage = []; $days = $periods[$range]; $metrics = [ - str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getSequence(), $collectionDocument->getSequence()], $this->getMetric()), + str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getSequence(), $collectionDocument->getSequence()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), ]; $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Create.php index 3585bc4477..c2786b9f26 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Create.php @@ -19,9 +19,7 @@ use Utopia\Database\Exception\Index as IndexException; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\Structure as StructureException; use Utopia\Database\Helpers\ID; -use Utopia\DSN\DSN; use Utopia\Http\Adapter\Swoole\Response as SwooleResponse; -use Utopia\System\System; use Utopia\Validator\Boolean; use Utopia\Validator\Text; @@ -32,121 +30,6 @@ class Create extends Action return 'createDatabase'; } - protected function getDatabaseDSN(Document $project): string - { - // TODO: use database worker for for creating the v2 schema if not present - // it is considered that the v2 metadata schema is already created during server start in the http.php - return $this->constructDatabaseDSNFromProjectDatabase($this->getDatabaseType(), $project->getAttribute('region'), $project->getAttribute('database')); - } - - private function constructDatabaseDSNFromProjectDatabase(string $databasetype, $region, ?string $dsn = null): string - { - $databases = []; - $databaseKeys = []; - /** - * @var string|null $databaseOverride - */ - $databaseOverride = ''; - $dbScheme = ''; - $databaseSharedTables = []; - $databaseSharedTablesV1 = []; - $databaseSharedTablesV2 = []; - $projectSharedTables = []; - $projectSharedTablesV1 = []; - $projectSharedTablesV2 = []; - - switch ($databasetype) { - case DOCUMENTSDB: - $databases = Config::getParam('pools-documentsdb', []); - $databaseKeys = System::getEnv('_APP_DATABASE_DOCUMENTSDB_KEYS', ''); - $databaseOverride = System::getEnv('_APP_DATABASE_DOCUMENTSDB_OVERRIDE'); - $dbScheme = System::getEnv('_APP_DB_HOST_DOCUMENTSDB', 'mongodb'); - $databaseSharedTables = \explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES', '')); - $databaseSharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES_V1', '')); - break; - case VECTORSDB: - $databases = Config::getParam('pools-vectorsdb', []); - $databaseKeys = System::getEnv('_APP_DATABASE_VECTORSDB_KEYS', ''); - $databaseOverride = System::getEnv('_APP_DATABASE_VECTORSDB_OVERRIDE'); - $dbScheme = System::getEnv('_APP_DB_HOST_VECTORSDB', 'postgresql'); - $databaseSharedTables = \explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES', '')); - $databaseSharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES_V1', '')); - break; - default: - // legacy/tablesdb - // it is already created during create project - return $dsn; - } - - $isSharedTablesV1 = false; - $isSharedTablesV2 = false; - - if (!empty($dsn)) { - try { - $parsedDsn = new DSN($dsn); - $dsnHost = $parsedDsn->getHost(); - } catch (\InvalidArgumentException) { - $dsnHost = $dsn; - } - - $projectSharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); - $projectSharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', '')); - $projectSharedTablesV2 = \array_diff($projectSharedTables, $projectSharedTablesV1); - $isSharedTablesV1 = \in_array($dsnHost, $projectSharedTablesV1); - $isSharedTablesV2 = \in_array($dsnHost, $projectSharedTablesV2); - } - - if ($region !== 'default') { - $keys = explode(',', $databaseKeys); - $databases = array_filter($keys, function ($value) use ($region) { - return str_contains($value, $region); - }); - } - $databaseSharedTablesV2 = \array_diff($databaseSharedTables, $databaseSharedTablesV1); - - $index = \array_search($databaseOverride, $databases); - if ($index !== false) { - $selectedDsn = $databases[$index]; - } else { - if (!empty($dsn)) { - $beforeFilter = \array_values($databases); - if ($isSharedTablesV1) { - $databases = array_filter($databases, fn ($value) => \in_array($value, $databaseSharedTablesV1)); - } elseif ($isSharedTablesV2) { - $databases = array_filter($databases, fn ($value) => \in_array($value, $databaseSharedTablesV2)); - } else { - $databases = array_filter($databases, fn ($value) => !\in_array($value, $databaseSharedTables)); - } - } - $selectedDsn = !empty($databases) ? $databases[array_rand($databases)] : ''; - } - - if (\in_array($selectedDsn, $databaseSharedTables)) { - $schema = 'appwrite'; - $database = 'appwrite'; - $namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''); - $selectedDsn = $schema . '://' . $selectedDsn . '?database=' . $database; - - if (!empty($namespace)) { - $selectedDsn .= '&namespace=' . $namespace; - } - } - try { - new DSN($selectedDsn); - } catch (\InvalidArgumentException) { - $selectedDsn = $dbScheme.'://' . $selectedDsn; - } - - return $selectedDsn; - } - - protected function getDatabaseCollection() - { - return match ($this->getDatabaseType()) { - 'vectorsdb' => (Config::getParam('collections', [])['vectorsdb'] ?? [])['collections'] ?? [], - default => (Config::getParam('collections', [])['databases'] ?? [])['collections'] ?? [], - }; - } public function __construct() { $this @@ -182,15 +65,13 @@ class Create extends Action ->param('databaseId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject']) ->param('name', '', new Text(128), 'Database name. Max length: 128 chars.') ->param('enabled', true, new Boolean(), 'Is the database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.', true) - ->inject('project') ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->callback($this->action(...)); } - public function action(string $databaseId, string $name, bool $enabled, Document $project, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Event $queueForEvents): void + public function action(string $databaseId, string $name, bool $enabled, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void { $databaseId = $databaseId == 'unique()' ? ID::unique() : $databaseId; @@ -201,7 +82,6 @@ class Create extends Action 'enabled' => $enabled, 'search' => implode(' ', [$databaseId, $name]), 'type' => $this->getDatabaseType(), - 'database' => $this->getDatabaseDSN($project) ])); } catch (DuplicateException) { throw new Exception(Exception::DATABASE_ALREADY_EXISTS, params: [$databaseId]); @@ -211,7 +91,7 @@ class Create extends Action $database = $dbForProject->getDocument('databases', $databaseId); - $collections = $this->getDatabaseCollection(); + $collections = (Config::getParam('collections', [])['databases'] ?? [])['collections'] ?? []; if (empty($collections)) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'The "collections" collection is not configured.'); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Action.php index f3edf010d4..e2a4491736 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Action.php @@ -10,45 +10,11 @@ abstract class Action extends DatabasesAction * The current API context (either 'table' or 'collection'). */ private ?string $context = COLLECTIONS; - private ?string $databaseType = LEGACY; - - public function getDatabaseType(): string - { - return $this->databaseType; - } - - protected function getDatabasesOperationWriteMetric(): string - { - if ($this->databaseType === LEGACY || $this->databaseType === TABLESDB) { - return METRIC_DATABASES_OPERATIONS_WRITES; - } - return $this->databaseType.'.'.METRIC_DATABASES_OPERATIONS_WRITES; - - } - protected function getDatabasesIdOperationWriteMetric(): string - { - if ($this->databaseType === LEGACY || $this->databaseType === TABLESDB) { - return METRIC_DATABASE_ID_OPERATIONS_WRITES; - } - return $this->databaseType.'.'.METRIC_DATABASE_ID_OPERATIONS_WRITES; - } public function setHttpPath(string $path): DatabasesAction { - switch (true) { - case str_contains($path, '/tablesdb'): - $this->context = TABLES; - $this->databaseType = TABLESDB; - break; - - case str_contains($path, '/documentsdb'): - $this->context = COLLECTIONS; - $this->databaseType = DOCUMENTSDB; - break; - case str_contains($path, '/vectorsdb'): - $this->context = COLLECTIONS; - $this->databaseType = VECTORSDB; - break; + if (\str_contains($path, '/tablesdb')) { + $this->context = TABLES; } return parent::setHttpPath($path); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php index 26457cc4a0..eebb3a77d5 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Operations/Create.php @@ -148,7 +148,7 @@ class Create extends Action $collectionKey = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); $isDependant = isset($dependants[$collectionKey][$documentId]); - $document = $transactionState->getDocument($database, $collectionKey, $documentId, $transactionId); + $document = $transactionState->getDocument($collectionKey, $documentId, $transactionId); if ($document->isEmpty() && !$isDependant && $operation['action'] !== 'upsert') { throw new Exception(Exception::DOCUMENT_NOT_FOUND, params: [$documentId]); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php index 5e88eee500..9a5a63ea91 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php @@ -67,10 +67,8 @@ class Update extends Action ->param('transactionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Transaction ID.', false, ['dbForProject']) ->param('commit', false, new Boolean(), 'Commit transaction?', true) ->param('rollback', false, new Boolean(), 'Rollback transaction?', true) - ->inject('project') ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('user') ->inject('transactionState') ->inject('queueForDeletes') @@ -90,7 +88,6 @@ class Update extends Action * @param bool $rollback * @param UtopiaResponse $response * @param Database $dbForProject - * @param callable $getDatabasesDB * @param Document $user * @param TransactionState $transactionState * @param Delete $queueForDeletes @@ -109,7 +106,7 @@ class Update extends Action * @throws Structure * @throws \Utopia\Http\Exception */ - public function action(string $transactionId, bool $commit, bool $rollback, Document $project, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Document $user, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, Context $usage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, Authorization $authorization, EventProcessor $eventProcessor): void + public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, Document $user, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, Context $usage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, Authorization $authorization, EventProcessor $eventProcessor): void { if (!$commit && !$rollback) { throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Either commit or rollback must be true'); @@ -138,52 +135,14 @@ class Update extends Action } if ($commit) { + $operations = []; $totalOperations = 0; $databaseOperations = []; $currentDocumentId = null; - $firstOperation = $authorization->skip(fn () => $dbForProject->findOne('transactionLogs', [ - Query::equal('transactionInternalId', [$transaction->getSequence()]), - Query::orderAsc(), - ])); - - if ($firstOperation->isEmpty()) { - $transaction = $authorization->skip(fn () => $dbForProject->updateDocument( - 'transactions', - $transactionId, - new Document(['status' => 'committed']) - )); - - $queueForDeletes - ->setType(DELETE_TYPE_DOCUMENT) - ->setDocument($transaction); - - $response - ->setStatusCode(SwooleResponse::STATUS_CODE_OK) - ->dynamic($transaction, $this->getResponseModel()); - - return; - } - - $databaseDoc = null; - switch ($this->getDatabaseType()) { - case DATABASE_TYPE_DOCUMENTSDB: - case DATABASE_TYPE_VECTORSDB: - $databaseDoc = $authorization->skip(fn () => $dbForProject->findOne('databases', [ - Query::equal('$sequence', [$firstOperation['databaseInternalId']]) - ])); - break; - default: - // Legacy/tablesdb: use project-level database - $databaseDoc = new Document(['database' => $project->getAttribute('database')]); - break; - } - - $dbForDatabases = $getDatabasesDB($databaseDoc); - try { - $dbForDatabases->withTransaction(function () use ($dbForDatabases, $dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, &$currentDocumentId, $queueForEvents, $usage, $queueForRealtime, $queueForFunctions, $queueForWebhooks, $authorization) { + $dbForProject->withTransaction(function () use ($dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, &$currentDocumentId, $queueForEvents, $usage, $queueForRealtime, $queueForFunctions, $queueForWebhooks, $authorization) { $authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([ 'status' => 'committing', ]))); @@ -223,7 +182,7 @@ class Update extends Action } if ($action === 'delete' && $documentId && empty($data)) { - $doc = $dbForDatabases->getDocument($collectionId, $documentId); + $doc = $dbForProject->getDocument($collectionId, $documentId); if (!$doc->isEmpty()) { $operation['data'] = $doc->getArrayCopy(); $data = $operation['data']; @@ -237,40 +196,40 @@ class Update extends Action switch ($action) { case 'create': - $this->handleCreateOperation($dbForDatabases, $collectionId, $documentId, $data, $createdAt, $state); + $this->handleCreateOperation($dbForProject, $collectionId, $documentId, $data, $createdAt, $state); break; case 'update': - $this->handleUpdateOperation($dbForDatabases, $collectionId, $documentId, $data, $createdAt, $state); + $this->handleUpdateOperation($dbForProject, $collectionId, $documentId, $data, $createdAt, $state); break; case 'upsert': - $this->handleUpsertOperation($dbForDatabases, $collectionId, $documentId, $data, $createdAt, $state); + $this->handleUpsertOperation($dbForProject, $collectionId, $documentId, $data, $createdAt, $state); break; case 'delete': - $this->handleDeleteOperation($dbForDatabases, $collectionId, $documentId, $createdAt, $state); + $this->handleDeleteOperation($dbForProject, $collectionId, $documentId, $createdAt, $state); break; case 'increment': - $this->handleIncrementOperation($dbForDatabases, $collectionId, $documentId, $data, $createdAt, $state); + $this->handleIncrementOperation($dbForProject, $collectionId, $documentId, $data, $createdAt, $state); break; case 'decrement': - $this->handleDecrementOperation($dbForDatabases, $collectionId, $documentId, $data, $createdAt, $state); + $this->handleDecrementOperation($dbForProject, $collectionId, $documentId, $data, $createdAt, $state); break; case 'bulkCreate': - $count = $this->handleBulkCreateOperation($dbForDatabases, $collectionId, $data, $createdAt, $state); + $count = $this->handleBulkCreateOperation($dbForProject, $collectionId, $data, $createdAt, $state); $totalOperations += $count; $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + $count; break; case 'bulkUpdate': - $count = $this->handleBulkUpdateOperation($dbForDatabases, $transactionState, $collectionId, $data, $createdAt, $state); + $count = $this->handleBulkUpdateOperation($dbForProject, $transactionState, $collectionId, $data, $createdAt, $state); $totalOperations += $count; $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + $count; break; case 'bulkUpsert': - $count = $this->handleBulkUpsertOperation($dbForDatabases, $transactionState, $collectionId, $data, $createdAt, $state); + $count = $this->handleBulkUpsertOperation($dbForProject, $transactionState, $collectionId, $data, $createdAt, $state); $totalOperations += $count; $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + $count; break; case 'bulkDelete': - $count = $this->handleBulkDeleteOperation($dbForDatabases, $transactionState, $collectionId, $data, $createdAt, $state); + $count = $this->handleBulkDeleteOperation($dbForProject, $transactionState, $collectionId, $data, $createdAt, $state); $totalOperations += $count; $databaseOperations[$databaseInternalId] = ($databaseOperations[$databaseInternalId] ?? 0) + $count; break; @@ -320,16 +279,15 @@ class Update extends Action throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); } - $usage->addMetric($this->getDatabasesOperationWriteMetric(), $totalOperations); + $usage->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $totalOperations); foreach ($databaseOperations as $sequence => $count) { $usage->addMetric( - str_replace('{databaseInternalId}', $sequence, $this->getDatabasesIdOperationWriteMetric()), + str_replace('{databaseInternalId}', $sequence, METRIC_DATABASE_ID_OPERATIONS_WRITES), $count ); } - $dbCache = []; foreach ($operations as $operation) { $databaseInternalId = $operation['databaseInternalId']; $collectionInternalId = $operation['collectionInternalId']; @@ -342,16 +300,6 @@ class Update extends Action $data = $data->getArrayCopy(); } - // using a dbCache so only one time database is set with databaseInternalId - if (!isset($dbCache[$databaseInternalId])) { - $databaseDoc = $authorization->skip(fn () => $dbForProject->findOne('databases', [ - Query::equal('$sequence', [$databaseInternalId]) - ])); - $dbCache[$databaseInternalId] = $getDatabasesDB($databaseDoc); - } - - $dbForDatabases = $dbCache[$databaseInternalId]; - $database = $authorization->skip(fn () => $dbForProject->findOne('databases', [ Query::equal('$sequence', [$databaseInternalId]) ])); @@ -381,7 +329,7 @@ class Update extends Action $eventAction = 'create'; $docId = $documentId ?? $data['$id'] ?? null; if ($docId) { - $doc = $dbForDatabases->getDocument($collectionId, $docId); + $doc = $dbForProject->getDocument($collectionId, $docId); if (!$doc->isEmpty()) { $documentsToTrigger[] = $doc; } @@ -392,7 +340,7 @@ class Update extends Action case 'decrement': $eventAction = 'update'; if ($documentId) { - $doc = $dbForDatabases->getDocument($collectionId, $documentId); + $doc = $dbForProject->getDocument($collectionId, $documentId); if (!$doc->isEmpty()) { $documentsToTrigger[] = $doc; } @@ -408,7 +356,7 @@ class Update extends Action $eventAction = 'update'; $docId = $documentId ?? $data['$id'] ?? null; if ($docId) { - $doc = $dbForDatabases->getDocument($collectionId, $docId); + $doc = $dbForProject->getDocument($collectionId, $docId); if (!$doc->isEmpty()) { $documentsToTrigger[] = $doc; } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/Get.php index 18e6fd7a8b..6f90e77e2b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/Get.php @@ -26,43 +26,6 @@ class Get extends Action return 'getDatabaseUsage'; } - protected $databaseType = DATABASE_TYPE_LEGACY; - - public function setHttpPath(string $path): Action - { - $this->databaseType = match (true) { - str_contains($path, '/documentsdb') => DATABASE_TYPE_DOCUMENTSDB, - str_contains($path, '/vectorsdb') => DATABASE_TYPE_VECTORSDB, - default => DATABASE_TYPE_LEGACY, - }; - - return parent::setHttpPath($path); - } - - protected function getMetrics(): array - { - $metrics = [ - METRIC_DATABASE_ID_COLLECTIONS, - METRIC_DATABASE_ID_DOCUMENTS, - METRIC_DATABASE_ID_STORAGE, - METRIC_DATABASE_ID_OPERATIONS_READS, - METRIC_DATABASE_ID_OPERATIONS_WRITES - ]; - if ($this->databaseType === DATABASE_TYPE_LEGACY || $this->databaseType === DATABASE_TYPE_TABLESDB) { - return $metrics; - } - - return array_map( - fn ($metric) => "{$this->databaseType}.{$metric}", - $metrics - ); - } - - protected function getResponseModel(): string - { - return UtopiaResponse::MODEL_USAGE_DATABASE; - } - public function __construct() { $this @@ -111,10 +74,13 @@ class Get extends Action $periods = Config::getParam('usage', []); $stats = $usage = []; $days = $periods[$range]; - $metrics = array_map( - fn ($metric) => str_replace('{databaseInternalId}', $database->getSequence(), $metric), - $this->getMetrics() - ); + $metrics = [ + str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_COLLECTIONS), + str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_DOCUMENTS), + str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_STORAGE), + str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_READS), + str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES) + ]; $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $metric) { @@ -176,6 +142,6 @@ class Get extends Action 'storage' => $usage[$metrics[2]]['data'], 'databaseReads' => $usage[$metrics[3]]['data'], 'databaseWrites' => $usage[$metrics[4]]['data'], - ]), $this->getResponseModel()); + ]), UtopiaResponse::MODEL_USAGE_DATABASE); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/XList.php index b8cb774a3e..db5ad21358 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/XList.php @@ -24,43 +24,6 @@ class XList extends Action return 'listDatabaseUsage'; } - protected $databaseType = DATABASE_TYPE_LEGACY; - - public function setHttpPath(string $path): Action - { - $this->databaseType = match (true) { - str_contains($path, '/documentsdb') => DATABASE_TYPE_DOCUMENTSDB, - str_contains($path, '/vectorsdb') => DATABASE_TYPE_VECTORSDB, - default => DATABASE_TYPE_LEGACY, - }; - - return parent::setHttpPath($path); - } - - protected function getMetrics(): array - { - $metrics = [ - METRIC_DATABASES, - METRIC_COLLECTIONS, - METRIC_DOCUMENTS, - METRIC_DATABASES_STORAGE, - METRIC_DATABASES_OPERATIONS_READS, - METRIC_DATABASES_OPERATIONS_WRITES, - ]; - if ($this->databaseType === DATABASE_TYPE_LEGACY || $this->databaseType === DATABASE_TYPE_TABLESDB) { - return $metrics; - } - return array_map( - fn ($metric) => "{$this->databaseType}.{$metric}", - $metrics - ); - } - - protected function getResponseModel(): string - { - return UtopiaResponse::MODEL_USAGE_DATABASES; - } - public function __construct() { $this @@ -103,7 +66,14 @@ class XList extends Action $periods = Config::getParam('usage', []); $stats = $usage = []; $days = $periods[$range]; - $metrics = $this->getMetrics(); + $metrics = [ + METRIC_DATABASES, + METRIC_COLLECTIONS, + METRIC_DOCUMENTS, + METRIC_DATABASES_STORAGE, + METRIC_DATABASES_OPERATIONS_READS, + METRIC_DATABASES_OPERATIONS_WRITES, + ]; $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $metric) { @@ -166,6 +136,6 @@ class XList extends Action 'storage' => $usage[$metrics[3]]['data'], 'databasesReads' => $usage[$metrics[4]]['data'], 'databasesWrites' => $usage[$metrics[5]]['data'], - ]), $this->getResponseModel()); + ]), UtopiaResponse::MODEL_USAGE_DATABASES); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php index e0ffeb8149..8627fa49c5 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php @@ -17,6 +17,7 @@ use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Query; use Utopia\Database\Validator\Query\Cursor; use Utopia\Http\Adapter\Swoole\Response as SwooleResponse; +use Utopia\Platform\Action; use Utopia\Validator\Boolean; use Utopia\Validator\Text; @@ -91,8 +92,6 @@ class XList extends Action $cursor->setValue($cursorDocument); } - $queries[] = Query::equal('type', [$this->getDatabaseType()]); - try { $databases = $dbForProject->find('databases', $queries); $total = $includeTotal ? $dbForProject->count('databases', $queries, APP_LIMIT_COUNT) : 0; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Create.php deleted file mode 100644 index d1e91addf7..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Create.php +++ /dev/null @@ -1,75 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/documentsdb/:databaseId/collections') - ->desc('Create collection') - ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].collections.[collectionId].create') - ->label('scope', 'collections.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'collections.create') - ->label('audits.resource', 'database/{request.databaseId}/collection/{response.$id}') - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'collections', - name: 'createCollection', - description: '/docs/references/documentsdb/create-collection.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject']) - ->param('collectionId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject']) - ->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.') - ->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE)), 'An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('documentSecurity', false, new Boolean(true), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('enabled', true, new Boolean(), 'Is collection enabled? When set to \'disabled\', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.', true) - ->param('attributes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.', true) - ->param('indexes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC/DESC, optional), and lengths (array of integers, optional).', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Delete.php deleted file mode 100644 index d698b40203..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Delete.php +++ /dev/null @@ -1,62 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId') - ->desc('Delete collection') - ->groups(['api', 'database', 'schema']) - ->label('scope', 'collections.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].collections.[collectionId].delete') - ->label('audits.event', 'collection.delete') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'collections', - name: 'deleteCollection', - description: '/docs/references/documentsdb/delete-collection.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_NOCONTENT, - model: UtopiaResponse::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForDatabase') - ->inject('queueForEvents') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Attribute/Decrement.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Attribute/Decrement.php deleted file mode 100644 index de3acdc96a..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Attribute/Decrement.php +++ /dev/null @@ -1,73 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents/:documentId/:attribute/decrement') - ->desc('Decrement document attribute') - ->groups(['api', 'database']) - ->label('event', 'documentsdb.[databaseId].collections.[collectionId].documents.[documentId].update') - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'documents.update') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'decrementDocumentAttribute', - description: '/docs/references/documentsdb/decrement-document-attribute.md', - auth: [AuthType::SESSION, AuthType::JWT, AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documentId', '', new UID(), 'Document ID.') - ->param('attribute', '', new Key(), 'Attribute key.') - ->param('value', 1, new Numeric(), 'Value to decrement the attribute by. The value must be a number.', true) - ->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) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->inject('usage') - ->inject('plan') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Attribute/Increment.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Attribute/Increment.php deleted file mode 100644 index 8664bb09ec..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Attribute/Increment.php +++ /dev/null @@ -1,73 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents/:documentId/:attribute/increment') - ->desc('Increment document attribute') - ->groups(['api', 'database']) - ->label('event', 'documentsdb.[databaseId].collections.[collectionId].documents.[documentId].update') - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'documents.update') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'incrementDocumentAttribute', - description: '/docs/references/documentsdb/increment-document-attribute.md', - auth: [AuthType::SESSION, AuthType::JWT, AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documentId', '', new UID(), 'Document ID.') - ->param('attribute', '', new Key(), 'Attribute key.') - ->param('value', 1, new Numeric(), 'Value to increment the attribute by. The value must be a number.', true) - ->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) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->inject('usage') - ->inject('plan') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Bulk/Delete.php deleted file mode 100644 index 09ad9a5741..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Bulk/Delete.php +++ /dev/null @@ -1,72 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents') - ->desc('Delete documents') - ->groups(['api', 'database']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'documents.delete') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'deleteDocuments', - description: '/docs/references/documentsdb/delete-documents.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('usage') - ->inject('queueForEvents') - ->inject('queueForRealtime') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') - ->inject('plan') - ->inject('eventProcessor') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Bulk/Update.php deleted file mode 100644 index c723f1bc30..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Bulk/Update.php +++ /dev/null @@ -1,74 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents') - ->desc('Update documents') - ->groups(['api', 'database']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'documents.update') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'updateDocuments', - description: '/docs/references/documentsdb/update-documents.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true) - ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('usage') - ->inject('queueForEvents') - ->inject('queueForRealtime') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') - ->inject('plan') - ->inject('eventProcessor') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Bulk/Upsert.php deleted file mode 100644 index d5b62ec903..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Bulk/Upsert.php +++ /dev/null @@ -1,74 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PUT) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents') - ->desc('Upsert documents') - ->groups(['api', 'database']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'document.create') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', [ - new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'upsertDocuments', - description: '/docs/references/documentsdb/upsert-documents.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON, - ) - ]) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of document data as JSON objects. May contain partial documents.', false, ['plan']) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('usage') - ->inject('queueForEvents') - ->inject('queueForRealtime') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') - ->inject('plan') - ->inject('eventProcessor') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Create.php deleted file mode 100644 index 039a05ff50..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Create.php +++ /dev/null @@ -1,116 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents') - ->desc('Create document') - ->groups(['api', 'database']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'document.create') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', [ - new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'createDocument', - desc: 'Create document', - description: '/docs/references/documentsdb/create-document.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON, - parameters: [ - new Parameter('databaseId', optional: false), - new Parameter('collectionId', optional: false), - new Parameter('documentId', optional: false), - new Parameter('data', optional: false), - new Parameter('permissions', optional: true), - ] - ), - new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'createDocuments', - desc: 'Create documents', - description: '/docs/references/documentsdb/create-documents.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: $this->getBulkResponseModel(), - ) - ], - contentType: ContentType::JSON, - parameters: [ - new Parameter('databaseId', optional: false), - new Parameter('collectionId', optional: false), - new Parameter('documents', optional: false), - ] - ) - ]) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true) - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.') - ->param('data', [], new JSON(), 'Document data as JSON object.', true, example: '{"username":"walter.obrien","email":"walter.obrien@example.com","fullName":"Walter O\'Brien","age":30,"isAdmin":false}') - ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of documents data as JSON objects.', true, ['plan']) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('user') - ->inject('queueForEvents') - ->inject('usage') - ->inject('queueForRealtime') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') - ->inject('plan') - ->inject('authorization') - ->inject('eventProcessor') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Delete.php deleted file mode 100644 index 86749f8a3d..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Delete.php +++ /dev/null @@ -1,76 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents/:documentId') - ->desc('Delete document') - ->groups(['api', 'database']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].delete') - ->label('audits.event', 'document.delete') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{request.documentId}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'deleteDocument', - description: '/docs/references/documentsdb/delete-document.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_NOCONTENT, - model: UtopiaResponse::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('documentId', '', new UID(), 'Document ID.') - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('requestTimestamp') - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->inject('usage') - ->inject('transactionState') - ->inject('plan') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Get.php deleted file mode 100644 index 4dd1f6f6b3..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Get.php +++ /dev/null @@ -1,64 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents/:documentId') - ->desc('Get document') - ->groups(['api', 'database']) - ->label('scope', 'documents.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'getDocument', - description: '/docs/references/documentsdb/get-document.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('documentId', '', new UID(), 'Document ID.') - ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) - ->param('transactionId', null, new UID(), 'Transaction ID to read uncommitted changes within the transaction.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('usage') - ->inject('transactionState') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Logs/XList.php deleted file mode 100644 index cc7fe41555..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Logs/XList.php +++ /dev/null @@ -1,59 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents/:documentId/logs') - ->desc('List document logs') - ->groups(['api', 'database']) - ->label('scope', 'documents.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'logs', - name: 'listDocumentLogs', - description: '/docs/references/documentsdb/get-document-logs.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON, - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documentId', '', new UID(), 'Document ID.') - ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('locale') - ->inject('geodb') - ->inject('authorization') - ->inject('audit') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Update.php deleted file mode 100644 index b5c612c155..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Update.php +++ /dev/null @@ -1,75 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents/:documentId') - ->desc('Update document') - ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update') - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'document.update') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{response.$id}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'updateDocument', - description: '/docs/references/documentsdb/update-document.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documentId', '', new UID(), 'Document ID.') - ->param('data', [], new JSON(), 'Document data as JSON object. Include only fields and value pairs to be updated.', true) - ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('requestTimestamp') - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->inject('usage') - ->inject('transactionState') - ->inject('plan') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Upsert.php deleted file mode 100644 index 448c2d44bc..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Upsert.php +++ /dev/null @@ -1,78 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PUT) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents/:documentId') - ->desc('Upsert a document') - ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].upsert') - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'document.upsert') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{response.$id}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', [ - new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'upsertDocument', - description: '/docs/references/documentsdb/upsert-document.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - ), - ]) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documentId', '', new UID(), 'Document ID.') - ->param('data', [], new JSON(), 'Document data as JSON object. Include all required fields of the document to be created or updated.', true) - ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('requestTimestamp') - ->inject('response') - ->inject('user') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->inject('usage') - ->inject('transactionState') - ->inject('plan') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/XList.php deleted file mode 100644 index 9e0d0b10d9..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/XList.php +++ /dev/null @@ -1,68 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents') - ->desc('List documents') - ->groups(['api', 'database']) - ->label('scope', 'documents.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'listDocuments', - description: '/docs/references/documentsdb/list-documents.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) - ->param('transactionId', null, new UID(), 'Transaction ID to read uncommitted changes within the transaction.', true) - ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) - ->param('ttl', 0, new Range(min: 0, max: 86400), 'TTL (seconds) for cached responses when caching is enabled for select queries. Must be between 0 and 86400 (24 hours).', true) - ->inject('response') - ->inject('dbForProject') - ->inject('user') - ->inject('getDatabasesDB') - ->inject('usage') - ->inject('transactionState') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Get.php deleted file mode 100644 index 53120dd636..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Get.php +++ /dev/null @@ -1,56 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId') - ->desc('Get collection') - ->groups(['api', 'database']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'collections', - name: 'getCollection', - description: '/docs/references/documentsdb/get-collection.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/Create.php deleted file mode 100644 index 3aee3ebcb1..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/Create.php +++ /dev/null @@ -1,73 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/indexes') - ->desc('Create index') - ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].create') - ->label('scope', 'collections.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'index.create') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'createIndex', - description: '/docs/references/documentsdb/create-index.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_ACCEPTED, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject']) - ->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).', false, ['dbForProject']) - ->param('key', null, fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Index Key.', false, ['dbForProject']) - ->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL]), 'Index type.') - ->param('attributes', null, fn (Database $dbForProject) => new ArrayList(new Key(true, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.', false, ['dbForProject']) - ->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true) - ->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForDatabase') - ->inject('queueForEvents') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/Delete.php deleted file mode 100644 index d4464f171d..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/Delete.php +++ /dev/null @@ -1,67 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/indexes/:key') - ->desc('Delete index') - ->groups(['api', 'database']) - ->label('scope', 'collections.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].update') - ->label('audits.event', 'index.delete') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'deleteIndex', // getName needs to be different from parent action to avoid conflict in path name - description: '/docs/references/documentsdb/delete-index.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_NOCONTENT, - model: UtopiaResponse::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('key', '', new Key(), 'Index Key.') - ->inject('response') - ->inject('dbForProject') - ->inject('queueForDatabase') - ->inject('queueForEvents') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/Get.php deleted file mode 100644 index 7fa75b6ed9..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/Get.php +++ /dev/null @@ -1,58 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/indexes/:key') - ->desc('Get index') - ->groups(['api', 'database']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'getIndex', // getName needs to be different from parent action to avoid conflict in path name - description: '/docs/references/documentsdb/get-index.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('key', null, new Key(), 'Index Key.') - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/XList.php deleted file mode 100644 index 1e16155f76..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/XList.php +++ /dev/null @@ -1,60 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/indexes') - ->desc('List indexes') - ->groups(['api', 'database']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'listIndexes', // getName needs to be different from parent action to avoid conflict in path name - description: '/docs/references/documentsdb/list-indexes.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('queries', [], new Indexes(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Indexes::ALLOWED_ATTRIBUTES), true) - ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Logs/XList.php deleted file mode 100644 index 51695ea165..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Logs/XList.php +++ /dev/null @@ -1,58 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/logs') - ->desc('List collection logs') - ->groups(['api', 'database']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: $this->getSdkGroup(), - name: 'listCollectionLogs', - description: '/docs/references/documentsdb/get-collection-logs.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject']) - ->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID.', false, ['dbForProject']) - ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) - ->inject('response') - ->inject('dbForProject') - ->inject('locale') - ->inject('geodb') - ->inject('authorization') - ->inject('audit') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Update.php deleted file mode 100644 index 052970fec4..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Update.php +++ /dev/null @@ -1,68 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PUT) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId') - ->desc('Update collection') - ->groups(['api', 'database', 'schema']) - ->label('scope', 'collections.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].collections.[collectionId].update') - ->label('audits.event', 'collection.update') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'collections', - name: 'updateCollection', - description: '/docs/references/documentsdb/update-collection.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_COLLECTION, - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('name', null, new Text(128), 'Collection name. Max length: 128 chars.') - ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('documentSecurity', false, new Boolean(true), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('enabled', true, new Boolean(), 'Is collection enabled? When set to \'disabled\', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Usage/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Usage/Get.php deleted file mode 100644 index 51dd3c381d..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Usage/Get.php +++ /dev/null @@ -1,65 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/usage') - ->desc('Get collection usage stats') - ->groups(['api', 'database', 'usage']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: null, - name: 'getCollectionUsage', - description: '/docs/references/documentsdb/get-collection-usage.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON, - )) - ->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject']) - ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) - ->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID.', false, ['dbForProject']) - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->inject('getDatabasesDB') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/XList.php deleted file mode 100644 index 638244145b..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/XList.php +++ /dev/null @@ -1,61 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/:databaseId/collections') - ->desc('List collections') - ->groups(['api', 'database']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'collections', - name: 'listCollections', - description: '/docs/references/documentsdb/list-collections.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('queries', [], new Collections(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Collections::ALLOWED_ATTRIBUTES), true) - ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) - ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Create.php deleted file mode 100644 index f9b425b3e6..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Create.php +++ /dev/null @@ -1,60 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/documentsdb') - ->desc('Create database') - ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].create') - ->label('scope', 'databases.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'database.create') - ->label('audits.resource', 'database/{response.$id}') - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'documentsdb', - name: 'create', - description: '/docs/references/documentsdb/create.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: UtopiaResponse::MODEL_DATABASE, - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject']) - ->param('name', '', new Text(128), 'Database name. Max length: 128 chars.') - ->param('enabled', true, new Boolean(), 'Is the database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.', true) - ->inject('project') - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Delete.php deleted file mode 100644 index 1708656c98..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Delete.php +++ /dev/null @@ -1,56 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) - ->setHttpPath('/v1/documentsdb/:databaseId') - ->desc('Delete database') - ->groups(['api', 'database', 'schema']) - ->label('scope', 'databases.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].delete') - ->label('audits.event', 'database.delete') - ->label('audits.resource', 'database/{request.databaseId}') - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'documentsdb', - name: 'delete', - description: '/docs/references/documentsdb/delete.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_NOCONTENT, - model: UtopiaResponse::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - )) - ->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject']) - ->inject('response') - ->inject('dbForProject') - ->inject('queueForDatabase') - ->inject('queueForEvents') - ->inject('usage') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Get.php deleted file mode 100644 index 309a3b867e..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Get.php +++ /dev/null @@ -1,50 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/:databaseId') - ->desc('Get database') - ->groups(['api', 'database']) - ->label('scope', 'databases.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'documentsdb', - name: 'get', - description: '/docs/references/documentsdb/get.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_DATABASE, - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject']) - ->inject('response') - ->inject('dbForProject') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Logs/XList.php deleted file mode 100644 index 8afb0fd1ef..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Logs/XList.php +++ /dev/null @@ -1,60 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/:databaseId/logs') - ->desc('List database logs') - ->groups(['api', 'database']) - ->label('scope', 'databases.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', [ - new Method( - namespace: 'documentsDB', - group: 'logs', - name: 'listDatabaseLogs', - description: '/docs/references/documentsdb/get-logs.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_LOG_LIST, - ) - ], - contentType: ContentType::JSON - ), - ]) - ->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject']) - ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) - ->inject('response') - ->inject('dbForProject') - ->inject('locale') - ->inject('geodb') - ->inject('authorization') - ->inject('audit') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Create.php deleted file mode 100644 index 9341779dcd..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Create.php +++ /dev/null @@ -1,56 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/documentsdb/transactions') - ->desc('Create transaction') - ->groups(['api', 'database', 'transactions']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'transactions', - name: 'createTransaction', - description: '/docs/references/documentsdb/create-transaction.md', - auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: UtopiaResponse::MODEL_TRANSACTION, - ) - ], - contentType: ContentType::JSON - )) - ->param('ttl', APP_DATABASE_TXN_TTL_DEFAULT, new Range(min: APP_DATABASE_TXN_TTL_MIN, max: APP_DATABASE_TXN_TTL_MAX), 'Seconds before the transaction expires.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('user') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Delete.php deleted file mode 100644 index 036f2e9600..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Delete.php +++ /dev/null @@ -1,55 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) - ->setHttpPath('/v1/documentsdb/transactions/:transactionId') - ->desc('Delete transaction') - ->groups(['api', 'database', 'transactions']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'transactions', - name: 'deleteTransaction', - description: '/docs/references/documentsdb/delete-transaction.md', - auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_NOCONTENT, - model: UtopiaResponse::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - )) - ->param('transactionId', '', new UID(), 'Transaction ID.') - ->inject('response') - ->inject('dbForProject') - ->inject('queueForDeletes') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Get.php deleted file mode 100644 index 7def4f0b9a..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Get.php +++ /dev/null @@ -1,54 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/transactions/:transactionId') - ->desc('Get transaction') - ->groups(['api', 'database', 'transactions']) - ->label('scope', 'documents.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'transactions', - name: 'getTransaction', - description: '/docs/references/documentsdb/get-transaction.md', - auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_TRANSACTION, - ) - ], - contentType: ContentType::JSON - )) - ->param('transactionId', '', new UID(), 'Transaction ID.') - ->inject('response') - ->inject('dbForProject') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Operations/Create.php deleted file mode 100644 index bc15d440d1..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Operations/Create.php +++ /dev/null @@ -1,60 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/documentsdb/transactions/:transactionId/operations') - ->desc('Create operations') - ->groups(['api', 'database', 'transactions']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'transactions', - name: 'createOperations', - description: '/docs/references/documentsdb/create-operations.md', - auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: UtopiaResponse::MODEL_TRANSACTION, - ) - ], - contentType: ContentType::JSON - )) - ->param('transactionId', '', new UID(), 'Transaction ID.') - ->param('operations', [], new ArrayList(new Operation(type: 'documentsdb')), 'Array of staged operations.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('transactionState') - ->inject('plan') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Update.php deleted file mode 100644 index b4c0c2ffab..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/Update.php +++ /dev/null @@ -1,69 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/documentsdb/transactions/:transactionId') - ->desc('Update transaction') - ->groups(['api', 'database', 'transactions']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'transactions', - name: 'updateTransaction', - description: '/docs/references/documentsdb/update-transaction.md', - auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_TRANSACTION, - ) - ], - contentType: ContentType::JSON - )) - ->param('transactionId', '', new UID(), 'Transaction ID.') - ->param('commit', false, new Boolean(), 'Commit transaction?', true) - ->param('rollback', false, new Boolean(), 'Rollback transaction?', true) - ->inject('project') - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('user') - ->inject('transactionState') - ->inject('queueForDeletes') - ->inject('queueForEvents') - ->inject('usage') - ->inject('queueForRealtime') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') - ->inject('authorization') - ->inject('eventProcessor') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/XList.php deleted file mode 100644 index b216ce6a4a..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Transactions/XList.php +++ /dev/null @@ -1,54 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/transactions') - ->desc('List transactions') - ->groups(['api', 'database', 'transactions']) - ->label('scope', 'documents.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'transactions', - name: 'listTransactions', - description: '/docs/references/documentsdb/list-transactions.md', - auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_TRANSACTION_LIST, - ) - ], - contentType: ContentType::JSON - )) - ->param('queries', [], new Transactions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries).', true) - ->inject('response') - ->inject('dbForProject') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Update.php deleted file mode 100644 index 4bf5747b54..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Update.php +++ /dev/null @@ -1,58 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PUT) - ->setHttpPath('/v1/documentsdb/:databaseId') - ->desc('Update database') - ->groups(['api', 'database', 'schema']) - ->label('scope', 'databases.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].update') - ->label('audits.event', 'database.update') - ->label('audits.resource', 'database/{response.$id}') - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'documentsdb', - name: 'update', - description: '/docs/references/documentsdb/update.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_DATABASE, - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject']) - ->param('name', null, new Text(128), 'Database name. Max length: 128 chars.') - ->param('enabled', true, new Boolean(), 'Is database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('queueForEvents') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Usage/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Usage/Get.php deleted file mode 100644 index 8373b6bc20..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Usage/Get.php +++ /dev/null @@ -1,60 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/:databaseId/usage') - ->desc('Get DocumentsDB usage stats') - ->groups(['api', 'database', 'usage']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', [ - new Method( - namespace: 'documentsDB', - group: null, - name: 'getUsage', - description: '/docs/references/documentsdb/get-database-usage.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_USAGE_DOCUMENTSDB, - ) - ], - contentType: ContentType::JSON, - ), - ]) - ->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject']) - ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Usage/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Usage/XList.php deleted file mode 100644 index 16535765ca..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Usage/XList.php +++ /dev/null @@ -1,56 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb/usage') - ->desc('Get DocumentsDB usage stats') - ->groups(['api', 'database', 'usage']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', [ - new Method( - namespace: 'documentsDB', - group: null, - name: 'listUsage', - description: '/docs/references/documentsdb/list-usage.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_USAGE_DATABASES, - ) - ], - contentType: ContentType::JSON - ), - ]) - ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) - ->inject('response') - ->inject('dbForProject') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/XList.php deleted file mode 100644 index 13814b37e2..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/XList.php +++ /dev/null @@ -1,53 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/documentsdb') - ->desc('List databases') - ->groups(['api', 'database']) - ->label('scope', 'databases.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'documentsDB', - group: 'documentsdb', - name: 'list', - description: '/docs/references/documentsdb/list.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_DATABASE_LIST, - ) - ], - contentType: ContentType::JSON - )) - ->param('queries', [], new Databases(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following columns: ' . implode(', ', Databases::ALLOWED_ATTRIBUTES), true) - ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) - ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) - ->inject('response') - ->inject('dbForProject') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Create.php index eb2293dc28..8aa6e1e28b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Create.php @@ -50,10 +50,8 @@ class Create extends DatabaseCreate ->param('databaseId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject']) ->param('name', '', new Text(128), 'Database name. Max length: 128 chars.') ->param('enabled', true, new Boolean(), 'Is the database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.', true) - ->inject('project') ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->callback($this->action(...)); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Create.php index 48f1136b09..9d32166a26 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Create.php @@ -67,7 +67,6 @@ class Create extends CollectionCreate ->param('indexes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of column keys), orders (array of ASC/DESC, optional), and lengths (array of integers, optional).', true) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('authorization') ->callback($this->action(...)); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Delete.php index 97c5465fe3..aa5b94c00f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Delete.php @@ -54,7 +54,6 @@ class Delete extends CollectionDelete ->param('tableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Table ID.', false, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForDatabase') ->inject('queueForEvents') ->inject('authorization') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Create.php index e683aafba1..8186e07d61 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Indexes/Create.php @@ -64,7 +64,6 @@ class Create extends IndexCreate ->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForDatabase') ->inject('queueForEvents') ->inject('authorization') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php index 37a3db01db..adaf83ccf1 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php @@ -61,7 +61,6 @@ class Delete extends DocumentsDelete ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('usage') ->inject('queueForEvents') ->inject('queueForRealtime') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Update.php index bb839b752e..d706d1f28b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Update.php @@ -63,7 +63,6 @@ class Update extends DocumentsUpdate ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('usage') ->inject('queueForEvents') ->inject('queueForRealtime') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Upsert.php index 364bf4a928..58da5064f9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Upsert.php @@ -63,7 +63,6 @@ class Upsert extends DocumentsUpsert ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('usage') ->inject('queueForEvents') ->inject('queueForRealtime') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Decrement.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Decrement.php index 2670cc00aa..e1e717e9b1 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Decrement.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Decrement.php @@ -65,7 +65,6 @@ class Decrement extends DecrementDocumentAttribute ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('usage') ->inject('plan') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Increment.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Increment.php index ca6589aa3a..0b20450254 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Increment.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Column/Increment.php @@ -65,7 +65,6 @@ class Increment extends IncrementDocumentAttribute ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('usage') ->inject('plan') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php index 26649accfb..fde8005d2b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php @@ -104,7 +104,6 @@ class Create extends DocumentCreate ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('user') ->inject('queueForEvents') ->inject('usage') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php index addc87f610..1845edc307 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php @@ -67,7 +67,6 @@ class Delete extends DocumentDelete ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('usage') ->inject('transactionState') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php index 48a24e9ec4..43b799e5b1 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Get.php @@ -57,7 +57,6 @@ class Get extends DocumentGet ->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID to read uncommitted changes within the transaction.', true, ['dbForProject']) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('usage') ->inject('transactionState') ->inject('authorization') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php index e1d821130f..a5f4787b05 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php @@ -50,7 +50,6 @@ class XList extends DocumentLogXList ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('locale') ->inject('geodb') ->inject('authorization') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Update.php index 99599bd169..c0d90f9531 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Update.php @@ -65,7 +65,6 @@ class Update extends DocumentUpdate ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('usage') ->inject('transactionState') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Upsert.php index 472a49cf64..7f0aa0ad7d 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Upsert.php @@ -68,7 +68,6 @@ class Upsert extends DocumentUpsert ->inject('response') ->inject('user') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('usage') ->inject('transactionState') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php index ca83b10aae..6e5dcd9370 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/XList.php @@ -61,7 +61,6 @@ class XList extends DocumentXList ->inject('response') ->inject('dbForProject') ->inject('user') - ->inject('getDatabasesDB') ->inject('usage') ->inject('transactionState') ->inject('authorization') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Update.php index 88b16d57f0..c525f97715 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Update.php @@ -62,7 +62,6 @@ class Update extends CollectionUpdate ->param('enabled', true, new Boolean(), 'Is table enabled? When set to \'disabled\', users cannot access the table but Server SDKs with and API key can still read and write to the table. No data is lost when this is toggled.', true) ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForEvents') ->inject('authorization') ->callback($this->action(...)); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Usage/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Usage/Get.php index 6976be014c..4261ceaab6 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Usage/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Usage/Get.php @@ -54,7 +54,6 @@ class Get extends CollectionUsageGet ->inject('response') ->inject('dbForProject') ->inject('authorization') - ->inject('getDatabasesDB') ->callback($this->action(...)); } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php index 872927d533..68ea2b8901 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php @@ -51,10 +51,8 @@ class Update extends TransactionsUpdate ->param('transactionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Transaction ID.', false, ['dbForProject']) ->param('commit', false, new Boolean(), 'Commit transaction?', true) ->param('rollback', false, new Boolean(), 'Rollback transaction?', true) - ->inject('project') ->inject('response') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('user') ->inject('transactionState') ->inject('queueForDeletes') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Create.php deleted file mode 100644 index b85a8b30b4..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Create.php +++ /dev/null @@ -1,208 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections') - ->desc('Create collection') - ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].collections.[collectionId].create') - ->label('scope', 'collections.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'collection.create') - ->label('audits.resource', 'database/{request.databaseId}/collection/{response.$id}') - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'collections', - name: 'createCollection', - description: '/docs/references/vectorsdb/create-collection.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject']) - ->param('collectionId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject']) - ->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.') - ->param('dimension', null, new Range(MIN_VECTOR_DIMENSION, MAX_VECTOR_DIMENSION), 'Embedding dimension.') - ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('documentSecurity', false, new Boolean(true), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('enabled', true, new Boolean(), 'Is collection enabled? When set to \'disabled\', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->inject('authorization') - ->callback($this->action(...)); - } - - public function action(string $databaseId, string $collectionId, string $name, int $dimension, ?array $permissions, bool $documentSecurity, bool $enabled, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Event $queueForEvents, Authorization $authorization): void - { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - - if ($database->isEmpty()) { - throw new Exception(Exception::DATABASE_NOT_FOUND); - } - - $collectionId = $collectionId === 'unique()' ? ID::unique() : $collectionId; - - // Map aggregate permissions into the multiple permissions they represent. - $permissions = Permission::aggregate($permissions) ?? []; - - try { - $collection = $dbForProject->createDocument('database_' . $database->getSequence(), new Document([ - '$id' => $collectionId, - 'databaseInternalId' => $database->getSequence(), - 'databaseId' => $databaseId, - '$permissions' => $permissions, - 'documentSecurity' => $documentSecurity, - 'enabled' => $enabled, - 'name' => $name, - 'dimension' => $dimension, - 'search' => \implode(' ', [$collectionId, $name]), - ])); - - } catch (DuplicateException) { - throw new Exception($this->getDuplicateException()); - } catch (LimitException) { - throw new Exception($this->getLimitException()); - } catch (NotFoundException) { - throw new Exception(Exception::DATABASE_NOT_FOUND); - } - /** @var Database $dbForDatabases */ - $dbForDatabases = $getDatabasesDB($database); - - $attributes = []; - $indexes = []; - $collections = (Config::getParam('collections', [])['vectorsdb'] ?? [])['collections'] ?? []; - foreach ($collections['defaultAttributes'] as $attribute) { - if ($attribute['$id'] === 'embeddings') { - $attribute['size'] = $dimension; - } - $attributes[] = new Document($attribute); - } - foreach ($collections['defaultIndexes'] as $index) { - $indexes[] = new Document($index); - } - try { - // passing null in creates only creates the metadata collection - if (!$dbForDatabases->exists(null, Database::METADATA)) { - $dbForDatabases->create(); - } - $dbForDatabases->createCollection( - id: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), - permissions: $permissions, - documentSecurity: $documentSecurity, - attributes:$attributes, - indexes:$indexes - ); - // Create attribute and indexes metadata documents in the attributes and indexes collections - // needed for the get and list calls - $attributeDocs = array_map(function ($attributeConfig) use ($database, $collection, $databaseId, $collectionId, $dimension) { - $key = \is_string($attributeConfig['$id']) ? $attributeConfig['$id'] : (string) $attributeConfig['$id']; - return new Document([ - '$id' => ID::custom($database->getSequence() . '_' . $collection->getSequence() . '_' . $key), - 'key' => $key, - 'databaseInternalId' => $database->getSequence(), - 'databaseId' => $databaseId, - 'collectionInternalId' => $collection->getSequence(), - 'collectionId' => $collectionId, - 'type' => $attributeConfig['type'], - 'status' => 'available', - 'size' => $dimension, - 'required' => $attributeConfig['required'] ?? false, - 'signed' => $attributeConfig['signed'] ?? false, - 'default' => $attributeConfig['default'] ?? null, - 'array' => $attributeConfig['array'] ?? false, - 'format' => $attributeConfig['format'] ?? '', - 'formatOptions' => $attributeConfig['formatOptions'] ?? [], - 'filters' => $attributeConfig['filters'] ?? [], - 'options' => $attributeConfig['options'] ?? [], - ]); - }, $collections['defaultAttributes']); - $dbForProject->createDocuments('attributes', $attributeDocs); - - $indexDocs = array_map(function ($indexConfig) use ($database, $collection, $databaseId, $collectionId) { - $key = \is_string($indexConfig['$id']) ? $indexConfig['$id'] : (string) $indexConfig['$id']; - - return new Document([ - '$id' => ID::custom($database->getSequence() . '_' . $collection->getSequence() . '_' . $key), - 'key' => $key, - 'status' => 'available', - 'databaseInternalId' => $database->getSequence(), - 'databaseId' => $databaseId, - 'collectionInternalId' => $collection->getSequence(), - 'collectionId' => $collectionId, - 'type' => $indexConfig['type'], - 'attributes' => $indexConfig['attributes'] ?? [], - 'lengths' => $indexConfig['lengths'] ?? [], - 'orders' => $indexConfig['orders'] ?? [], - ]); - }, $collections['defaultIndexes']); - - if (!empty($indexDocs)) { - $dbForProject->createDocuments('indexes', $indexDocs); - } - } catch (DuplicateException) { - throw new Exception($this->getDuplicateException()); - } catch (IndexException) { - throw new Exception($this->getInvalidIndexException()); - } catch (LimitException) { - throw new Exception($this->getLimitException()); - } - - $queueForEvents - ->setContext('database', $database) - ->setParam('databaseId', $databaseId) - ->setParam($this->getEventsParamKey(), $collection->getId()); - - $response - ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED) - ->dynamic($collection, $this->getResponseModel()); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Delete.php deleted file mode 100644 index f1188868aa..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Delete.php +++ /dev/null @@ -1,62 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId') - ->desc('Delete collection') - ->groups(['api', 'database', 'schema']) - ->label('scope', 'collections.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].collections.[collectionId].delete') - ->label('audits.event', 'collection.delete') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'collections', - name: 'deleteCollection', - description: '/docs/references/vectorsdb/delete-collection.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_NOCONTENT, - model: UtopiaResponse::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForDatabase') - ->inject('queueForEvents') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Bulk/Delete.php deleted file mode 100644 index a4d640b423..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Bulk/Delete.php +++ /dev/null @@ -1,72 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/documents') - ->desc('Delete documents') - ->groups(['api', 'database']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'documents.delete') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'deleteDocuments', - description: '/docs/references/vectorsdb/delete-documents.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('usage') - ->inject('queueForEvents') - ->inject('queueForRealtime') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') - ->inject('plan') - ->inject('eventProcessor') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Bulk/Update.php deleted file mode 100644 index 2784fa220a..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Bulk/Update.php +++ /dev/null @@ -1,74 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/documents') - ->desc('Update documents') - ->groups(['api', 'database']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'documents.update') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'updateDocuments', - description: '/docs/references/vectorsdb/update-documents.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true) - ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('usage') - ->inject('queueForEvents') - ->inject('queueForRealtime') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') - ->inject('plan') - ->inject('eventProcessor') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Bulk/Upsert.php deleted file mode 100644 index cfbf6c9158..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Bulk/Upsert.php +++ /dev/null @@ -1,74 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PUT) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/documents') - ->desc('Upsert documents') - ->groups(['api', 'database']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'document.create') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', [ - new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'upsertDocuments', - description: '/docs/references/vectorsdb/upsert-documents.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON, - ) - ]) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of document data as JSON objects. May contain partial documents.', false, ['plan']) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('usage') - ->inject('queueForEvents') - ->inject('queueForRealtime') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') - ->inject('plan') - ->inject('eventProcessor') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Create.php deleted file mode 100644 index 563b5f60ef..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Create.php +++ /dev/null @@ -1,116 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/documents') - ->desc('Create document') - ->groups(['api', 'database']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'document.create') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', [ - new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'createDocument', - desc: 'Create document', - description: '/docs/references/vectorsdb/create-document.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON, - parameters: [ - new Parameter('databaseId', optional: false), - new Parameter('collectionId', optional: false), - new Parameter('documentId', optional: false), - new Parameter('data', optional: false), - new Parameter('permissions', optional: true), - ] - ), - new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'createDocuments', - desc: 'Create documents', - description: '/docs/references/vectorsdb/create-documents.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: $this->getBulkResponseModel(), - ) - ], - contentType: ContentType::JSON, - parameters: [ - new Parameter('databaseId', optional: false), - new Parameter('collectionId', optional: false), - new Parameter('documents', optional: false), - ] - ) - ]) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true) - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.') - ->param('data', [], new JSON(), 'Document data as JSON object.', true, example: '{"embeddings": [0.12, -0.55, 0.88, 1.02], "metadata": {"key":"value"} }') - ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of documents data as JSON objects.', true, ['plan']) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('user') - ->inject('queueForEvents') - ->inject('usage') - ->inject('queueForRealtime') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') - ->inject('plan') - ->inject('authorization') - ->inject('eventProcessor') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Delete.php deleted file mode 100644 index eca6049970..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Delete.php +++ /dev/null @@ -1,76 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/documents/:documentId') - ->desc('Delete document') - ->groups(['api', 'database']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].delete') - ->label('audits.event', 'document.delete') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{request.documentId}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'deleteDocument', - description: '/docs/references/vectorsdb/delete-document.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_NOCONTENT, - model: UtopiaResponse::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('documentId', '', new UID(), 'Document ID.') - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('requestTimestamp') - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->inject('usage') - ->inject('transactionState') - ->inject('plan') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Get.php deleted file mode 100644 index 2a7090a01e..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Get.php +++ /dev/null @@ -1,64 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/documents/:documentId') - ->desc('Get document') - ->groups(['api', 'database']) - ->label('scope', 'documents.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'getDocument', - description: '/docs/references/vectorsdb/get-document.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('documentId', '', new UID(), 'Document ID.') - ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) - ->param('transactionId', null, new UID(), 'Transaction ID to read uncommitted changes within the transaction.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('usage') - ->inject('transactionState') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Logs/XList.php deleted file mode 100644 index dea9d30119..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Logs/XList.php +++ /dev/null @@ -1,59 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/documents/:documentId/logs') - ->desc('List document logs') - ->groups(['api', 'database']) - ->label('scope', 'documents.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'logs', - name: 'listDocumentLogs', - description: '/docs/references/vectorsdb/get-document-logs.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON, - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documentId', '', new UID(), 'Document ID.') - ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('locale') - ->inject('geodb') - ->inject('authorization') - ->inject('audit') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Update.php deleted file mode 100644 index 2624cc82e4..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Update.php +++ /dev/null @@ -1,75 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/documents/:documentId') - ->desc('Update document') - ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update') - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'document.update') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{response.$id}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'updateDocument', - description: '/docs/references/vectorsdb/update-document.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documentId', '', new UID(), 'Document ID.') - ->param('data', [], new JSON(), 'Document data as JSON object. Include only fields and value pairs to be updated.', true) - ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('requestTimestamp') - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->inject('usage') - ->inject('transactionState') - ->inject('plan') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Upsert.php deleted file mode 100644 index f8f17d33d9..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Upsert.php +++ /dev/null @@ -1,79 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PUT) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/documents/:documentId') - ->desc('Upsert a document') - ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].upsert') - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'document.upsert') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{response.$id}') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', [ - new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'upsertDocument', - description: '/docs/references/vectorsdb/upsert-document.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - ), - ]) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documentId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Document ID.', false, ['dbForProject']) - ->param('data', [], new JSON(), 'Document data as JSON object. Include all required fields of the document to be created or updated.', true) - ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true) - ->inject('requestTimestamp') - ->inject('response') - ->inject('user') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->inject('usage') - ->inject('transactionState') - ->inject('plan') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/XList.php deleted file mode 100644 index c9ed05ac02..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/XList.php +++ /dev/null @@ -1,68 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/documents') - ->desc('List documents') - ->groups(['api', 'database']) - ->label('scope', 'documents.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'listDocuments', - description: '/docs/references/vectorsdb/list-documents.md', - auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) - ->param('transactionId', null, new UID(), 'Transaction ID to read uncommitted changes within the transaction.', true) - ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) - ->param('ttl', 0, new Range(min: 0, max: 86400), 'TTL (seconds) for cached responses when caching is enabled for select queries. Must be between 0 and 86400 (24 hours).', true) - ->inject('response') - ->inject('dbForProject') - ->inject('user') - ->inject('getDatabasesDB') - ->inject('usage') - ->inject('transactionState') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Get.php deleted file mode 100644 index 9619bb5048..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Get.php +++ /dev/null @@ -1,56 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId') - ->desc('Get collection') - ->groups(['api', 'database']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'collections', - name: 'getCollection', - description: '/docs/references/vectorsdb/get-collection.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Indexes/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Indexes/Create.php deleted file mode 100644 index a535dd5724..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Indexes/Create.php +++ /dev/null @@ -1,73 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/indexes') - ->desc('Create index') - ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].tables.[tableId].indexes.[indexId].create') - ->label('scope', 'collections.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'index.create') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.tableId}') - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'createIndex', - description: '/docs/references/vectorsdb/create-index.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_ACCEPTED, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('key', null, new Key(), 'Index Key.') - ->param('type', null, new WhiteList([Database::INDEX_HNSW_EUCLIDEAN,Database::INDEX_HNSW_DOT, Database::INDEX_HNSW_COSINE, Database::INDEX_OBJECT, Database::INDEX_KEY, Database::INDEX_UNIQUE]), 'Index type.') - ->param('attributes', null, new ArrayList(new Key(true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.') - ->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true) - ->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForDatabase') - ->inject('queueForEvents') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Indexes/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Indexes/Delete.php deleted file mode 100644 index 5c7fc47ee0..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Indexes/Delete.php +++ /dev/null @@ -1,67 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/indexes/:key') - ->desc('Delete index') - ->groups(['api', 'database']) - ->label('scope', 'collections.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].update') - ->label('audits.event', 'index.delete') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'deleteIndex', // getName needs to be different from parent action to avoid conflict in path name - description: '/docs/references/vectorsdb/delete-index.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_NOCONTENT, - model: UtopiaResponse::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('key', '', new Key(), 'Index Key.') - ->inject('response') - ->inject('dbForProject') - ->inject('queueForDatabase') - ->inject('queueForEvents') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Indexes/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Indexes/Get.php deleted file mode 100644 index 4cf646acba..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Indexes/Get.php +++ /dev/null @@ -1,58 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/indexes/:key') - ->desc('Get index') - ->groups(['api', 'database']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'getIndex', // getName needs to be different from parent action to avoid conflict in path name - description: '/docs/references/vectorsdb/get-index.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('key', null, new Key(), 'Index Key.') - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Indexes/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Indexes/XList.php deleted file mode 100644 index acc46fb570..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Indexes/XList.php +++ /dev/null @@ -1,60 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/indexes') - ->desc('List indexes') - ->groups(['api', 'database']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'listIndexes', // getName needs to be different from parent action to avoid conflict in path name - description: '/docs/references/vectorsdb/list-indexes.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('queries', [], new Indexes(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Indexes::ALLOWED_ATTRIBUTES), true) - ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Logs/XList.php deleted file mode 100644 index cd0e45eb47..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Logs/XList.php +++ /dev/null @@ -1,57 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/logs') - ->desc('List collection logs') - ->groups(['api', 'database']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'listCollectionLogs', - description: '/docs/references/vectorsdb/get-collection-logs.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) - ->inject('response') - ->inject('dbForProject') - ->inject('locale') - ->inject('geodb') - ->inject('authorization') - ->inject('audit') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Update.php deleted file mode 100644 index f8ba767e7e..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Update.php +++ /dev/null @@ -1,117 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PUT) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId') - ->desc('Update collection') - ->groups(['api', 'database', 'schema']) - ->label('scope', 'collections.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].collections.[collectionId].update') - ->label('audits.event', 'collection.update') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'collections', - name: 'updateCollection', - description: '/docs/references/vectorsdb/update-collection.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_VECTORSDB_COLLECTION, - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('name', null, new Text(128), 'Collection name. Max length: 128 chars.') - ->param('dimension', null, new Range(MIN_VECTOR_DIMENSION, MAX_VECTOR_DIMENSION), 'Embedding dimensions.', true) - ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('documentSecurity', false, new Boolean(true), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) - ->param('enabled', true, new Boolean(), 'Is collection enabled? When set to \'disabled\', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->inject('authorization') - ->callback($this->action(...)); - } - - public function action(string $databaseId, string $collectionId, ?string $name, ?int $dimensions, ?array $permissions, bool $documentSecurity, ?bool $enabled, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Event $queueForEvents, Authorization $authorization): void - { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { - throw new Exception(Exception::DATABASE_NOT_FOUND); - } - - $collection = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId); - if ($collection->isEmpty()) { - throw new Exception($this->getNotFoundException()); - } - - $permissions ??= $collection->getPermissions(); - - // Map aggregate permissions into the multiple permissions they represent. - $permissions = Permission::aggregate($permissions); - - $enabled ??= $collection->getAttribute('enabled', true); - - $updated = $dbForProject->updateDocument( - 'database_' . $database->getSequence(), - $collectionId, - $collection - ->setAttribute('name', $name ?? $collection->getAttribute('name')) - ->setAttribute('dimension', $dimensions ?? $collection->getAttribute('dimension')) - ->setAttribute('$permissions', $permissions) - ->setAttribute('documentSecurity', $documentSecurity) - ->setAttribute('enabled', $enabled) - ->setAttribute('search', \implode(' ', [$collectionId, $name ?? $collection->getAttribute('name')])) - ); - - $dbForDatabases = $getDatabasesDB($database); - $dbForDatabases->updateCollection('database_' . $database->getSequence() . '_collection_' . $updated->getSequence(), $permissions, $documentSecurity); - - $queueForEvents - ->setContext('database', $database) - ->setParam('databaseId', $databaseId) - ->setParam($this->getEventsParamKey(), $updated->getId()); - - $response->dynamic($updated, $this->getResponseModel()); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Usage/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Usage/Get.php deleted file mode 100644 index 7e0f79a9f1..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Usage/Get.php +++ /dev/null @@ -1,64 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/usage') - ->desc('Get collection usage stats') - ->groups(['api', 'database', 'usage']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: null, - name: 'getCollectionUsage', - description: '/docs/references/vectorsdb/get-collection-usage.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON, - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) - ->param('collectionId', '', new UID(), 'Collection ID.') - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->inject('getDatabasesDB') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/XList.php deleted file mode 100644 index 7ba26b8b6a..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/XList.php +++ /dev/null @@ -1,61 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/:databaseId/collections') - ->desc('List collections') - ->groups(['api', 'database']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'collections', - name: 'listCollections', - description: '/docs/references/vectorsdb/list-collections.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getResponseModel(), - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('queries', [], new Collections(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Collections::ALLOWED_ATTRIBUTES), true) - ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) - ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Create.php deleted file mode 100644 index cc2914fc10..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Create.php +++ /dev/null @@ -1,59 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/vectorsdb') - ->desc('Create database') - ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].create') - ->label('scope', 'databases.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('audits.event', 'database.create') - ->label('audits.resource', 'database/{response.$id}') - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'vectorsdb', - name: 'create', - description: '/docs/references/vectorsdb/create.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: UtopiaResponse::MODEL_DATABASE, - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('name', '', new Text(128), 'Database name. Max length: 128 chars.') - ->param('enabled', true, new Boolean(), 'Is the database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.', true) - ->inject('project') - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('queueForEvents') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Delete.php deleted file mode 100644 index c9d36904a9..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Delete.php +++ /dev/null @@ -1,55 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) - ->setHttpPath('/v1/vectorsdb/:databaseId') - ->desc('Delete database') - ->groups(['api', 'database', 'schema']) - ->label('scope', 'databases.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].delete') - ->label('audits.event', 'database.delete') - ->label('audits.resource', 'database/{request.databaseId}') - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'vectorsdb', - name: 'delete', - description: '/docs/references/vectorsdb/delete.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_NOCONTENT, - model: UtopiaResponse::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->inject('response') - ->inject('dbForProject') - ->inject('queueForDatabase') - ->inject('queueForEvents') - ->inject('usage') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Embeddings/Text/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Embeddings/Text/Create.php deleted file mode 100644 index d9b378774b..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Embeddings/Text/Create.php +++ /dev/null @@ -1,152 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/vectorsdb/embeddings/text') - ->desc('Create Text Embeddings') - ->groups(['api', 'database']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_EMBEDDINGS_TEXT) - ->label('audits.event', 'embedding.create') - ->label('audits.resource', 'vectorsdb/embeddings/text') - ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') - ->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2) - ->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT) - ->label('sdk', [ - new Method( - namespace: 'vectorsDB', - group: $this->getSdkGroup(), - name: 'createTextEmbeddings', - desc: 'Create Text Embedding', - description: '/docs/references/vectorsdb/create-document.md', - auth: [AuthType::ADMIN, AuthType::KEY, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: $this->getBulkResponseModel(), - ) - ], - contentType: ContentType::JSON, - parameters: [ - new Parameter('texts', optional: false), - new Parameter('model', optional: true), - ] - ) - ]) - ->param('texts', [], fn (array $plan) => new ArrayList(new Text(0), $plan['databasesMaxEmbeddingTexts'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of text to generate embeddings.', false, ['plan']) - ->param('model', Ollama::MODEL_EMBEDDING_GEMMA, new WhiteList(Ollama::MODELS), 'The embedding model to use for generating vector embeddings.', true) - ->inject('response') - ->inject('project') - ->inject('embeddingAgent') - ->inject('usage') - ->inject('log') - ->inject('logger') - ->callback($this->action(...)); - } - - public function action(array $texts, string $model, UtopiaResponse $response, Document $project, Agent $embeddingAgent, Context $usage, Log $log, ?Logger $logger): void - { - $results = []; - $embeddingAgent->getAdapter()->setModel($model); - $dimension = $embeddingAgent->getAdapter()->getEmbeddingDimension(); - - $totalDuration = 0; - $totalTokens = 0; - $totalErrors = 0; - foreach ($texts as $text) { - $embedding = []; - $error = ''; - try { - $embedResult = $embeddingAgent->embed($text); - $embedding = $embedResult['embedding'] ?? []; - $totalDuration += $embedResult['totalDuration'] ?? 0; - $totalTokens += $embedResult['tokensProcessed'] ?? 0; - } catch (\Exception $e) { - $error = 'Error while generating embedding'; - $totalErrors += 1; - if ($logger) { - $log->setNamespace("http"); - $log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname())); - $log->setVersion(System::getEnv('_APP_VERSION', 'UNKNOWN')); - $log->setType(Log::TYPE_ERROR); - $log->setMessage($e->getMessage()); - - $log->addTag('embeddingModel', $model); - $log->addTag('code', $e->getCode()); - $log->addTag('projectId', $project->getId()); - - $log->addExtra('file', $e->getFile()); - $log->addExtra('line', $e->getLine()); - $log->addExtra('trace', $e->getTraceAsString()); - - $logger->addLog($log); - } - } - - $results[] = new Document([ - 'model' => $model, - 'dimension' => $dimension, - 'embedding' => $embedding, - 'error' => $error - ]); - } - $embeddings = new Document([ - 'embeddings' => $results, - 'total' => \count($results), - ]); - - $response - ->setStatusCode(SwooleResponse::STATUS_CODE_OK) - ->dynamic($embeddings, $this->getBulkResponseModel()); - - $usage - ->addMetric(METRIC_EMBEDDINGS_TEXT, \count($texts)) - ->addMetric(\str_replace('{embeddingModel}', $model, METRIC_EMBEDDINGS_MODEL_TEXT), \count($texts)) - ->addMetric(METRIC_EMBEDDINGS_TEXT_TOTAL_TOKENS, $totalTokens) - ->addMetric(\str_replace('{embeddingModel}', $model, METRIC_EMBEDDINGS_MODEL_TEXT_TOTAL_TOKENS), $totalTokens) - ->addMetric(METRIC_EMBEDDINGS_TEXT_TOTAL_DURATION, $totalDuration) - ->addMetric(\str_replace('{embeddingModel}', $model, METRIC_EMBEDDINGS_MODEL_TEXT_TOTAL_DURATION), $totalDuration) - ->addMetric(METRIC_EMBEDDINGS_TEXT_TOTAL_ERROR, $totalErrors) - ->addMetric(\str_replace('{embeddingModel}', $model, METRIC_EMBEDDINGS_MODEL_TEXT_TOTAL_ERROR), $totalErrors); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Get.php deleted file mode 100644 index a79632b105..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Get.php +++ /dev/null @@ -1,49 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/:databaseId') - ->desc('Get database') - ->groups(['api', 'database']) - ->label('scope', 'databases.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'vectorsdb', - name: 'get', - description: '/docs/references/vectorsdb/get.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_DATABASE, - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->inject('response') - ->inject('dbForProject') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Logs/XList.php deleted file mode 100644 index d8c1df5f04..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Logs/XList.php +++ /dev/null @@ -1,59 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/:databaseId/logs') - ->desc('List database logs') - ->groups(['api', 'database']) - ->label('scope', 'databases.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', [ - new Method( - namespace: 'vectorsDB', - group: 'logs', - name: 'listDatabaseLogs', - description: '/docs/references/vectorsdb/get-logs.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_LOG_LIST, - ) - ], - contentType: ContentType::JSON - ), - ]) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) - ->inject('response') - ->inject('dbForProject') - ->inject('locale') - ->inject('geodb') - ->inject('authorization') - ->inject('audit') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Create.php deleted file mode 100644 index cb67d3f7f1..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Create.php +++ /dev/null @@ -1,56 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/vectorsdb/transactions') - ->desc('Create transaction') - ->groups(['api', 'database', 'transactions']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'transactions', - name: 'createTransaction', - description: '/docs/references/vectorsdb/create-transaction.md', - auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: UtopiaResponse::MODEL_TRANSACTION, - ) - ], - contentType: ContentType::JSON - )) - ->param('ttl', APP_DATABASE_TXN_TTL_DEFAULT, new Range(min: APP_DATABASE_TXN_TTL_MIN, max: APP_DATABASE_TXN_TTL_MAX), 'Seconds before the transaction expires.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('user') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Delete.php deleted file mode 100644 index 0ac2caecba..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Delete.php +++ /dev/null @@ -1,55 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE) - ->setHttpPath('/v1/vectorsdb/transactions/:transactionId') - ->desc('Delete transaction') - ->groups(['api', 'database', 'transactions']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'transactions', - name: 'deleteTransaction', - description: '/docs/references/vectorsdb/delete-transaction.md', - auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_NOCONTENT, - model: UtopiaResponse::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - )) - ->param('transactionId', '', new UID(), 'Transaction ID.') - ->inject('response') - ->inject('dbForProject') - ->inject('queueForDeletes') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Get.php deleted file mode 100644 index fa4cc86cdd..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Get.php +++ /dev/null @@ -1,54 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/transactions/:transactionId') - ->desc('Get transaction') - ->groups(['api', 'database', 'transactions']) - ->label('scope', 'documents.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'transactions', - name: 'getTransaction', - description: '/docs/references/vectorsdb/get-transaction.md', - auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_TRANSACTION, - ) - ], - contentType: ContentType::JSON - )) - ->param('transactionId', '', new UID(), 'Transaction ID.') - ->inject('response') - ->inject('dbForProject') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Operations/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Operations/Create.php deleted file mode 100644 index 830c0c3fe1..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Operations/Create.php +++ /dev/null @@ -1,60 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/vectorsdb/transactions/:transactionId/operations') - ->desc('Create operations') - ->groups(['api', 'database', 'transactions']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'transactions', - name: 'createOperations', - description: '/docs/references/vectorsdb/create-operations.md', - auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_CREATED, - model: UtopiaResponse::MODEL_TRANSACTION, - ) - ], - contentType: ContentType::JSON - )) - ->param('transactionId', '', new UID(), 'Transaction ID.') - ->param('operations', [], new ArrayList(new Operation(type: 'documentsdb')), 'Array of staged operations.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('transactionState') - ->inject('plan') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Update.php deleted file mode 100644 index f4bd4d67f5..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/Update.php +++ /dev/null @@ -1,69 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/vectorsdb/transactions/:transactionId') - ->desc('Update transaction') - ->groups(['api', 'database', 'transactions']) - ->label('scope', 'documents.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'transactions', - name: 'updateTransaction', - description: '/docs/references/vectorsdb/update-transaction.md', - auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_TRANSACTION, - ) - ], - contentType: ContentType::JSON - )) - ->param('transactionId', '', new UID(), 'Transaction ID.') - ->param('commit', false, new Boolean(), 'Commit transaction?', true) - ->param('rollback', false, new Boolean(), 'Rollback transaction?', true) - ->inject('project') - ->inject('response') - ->inject('dbForProject') - ->inject('getDatabasesDB') - ->inject('user') - ->inject('transactionState') - ->inject('queueForDeletes') - ->inject('queueForEvents') - ->inject('usage') - ->inject('queueForRealtime') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') - ->inject('authorization') - ->inject('eventProcessor') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/XList.php deleted file mode 100644 index fb95667ffb..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Transactions/XList.php +++ /dev/null @@ -1,54 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/transactions') - ->desc('List transactions') - ->groups(['api', 'database', 'transactions']) - ->label('scope', 'documents.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'transactions', - name: 'listTransactions', - description: '/docs/references/vectorsdb/list-transactions.md', - auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_TRANSACTION_LIST, - ) - ], - contentType: ContentType::JSON - )) - ->param('queries', [], new Transactions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries).', true) - ->inject('response') - ->inject('dbForProject') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Update.php deleted file mode 100644 index 0b10d6d98b..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Update.php +++ /dev/null @@ -1,57 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_PUT) - ->setHttpPath('/v1/vectorsdb/:databaseId') - ->desc('Update database') - ->groups(['api', 'database', 'schema']) - ->label('scope', 'databases.write') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('event', 'databases.[databaseId].update') - ->label('audits.event', 'database.update') - ->label('audits.resource', 'database/{response.$id}') - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'vectorsdb', - name: 'update', - description: '/docs/references/vectorsdb/update.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_DATABASE, - ) - ], - contentType: ContentType::JSON - )) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('name', null, new Text(128), 'Database name. Max length: 128 chars.') - ->param('enabled', true, new Boolean(), 'Is database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('queueForEvents') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Usage/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Usage/Get.php deleted file mode 100644 index 051e2e39fa..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Usage/Get.php +++ /dev/null @@ -1,59 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/:databaseId/usage') - ->desc('Get VectorsDB usage stats') - ->groups(['api', 'database', 'usage']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', [ - new Method( - namespace: 'vectorsDB', - group: null, - name: 'getUsage', - description: '/docs/references/vectorsdb/get-database-usage.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_USAGE_VECTORSDB, - ) - ], - contentType: ContentType::JSON, - ), - ]) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Usage/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Usage/XList.php deleted file mode 100644 index d91a5963c4..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Usage/XList.php +++ /dev/null @@ -1,56 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb/usage') - ->desc('Get VectorsDB usage stats') - ->groups(['api', 'database', 'usage']) - ->label('scope', 'collections.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', [ - new Method( - namespace: 'vectorsDB', - group: null, - name: 'listUsage', - description: '/docs/references/vectorsdb/list-usage.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_USAGE_VECTORSDBS, - ) - ], - contentType: ContentType::JSON - ), - ]) - ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) - ->inject('response') - ->inject('dbForProject') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/XList.php deleted file mode 100644 index e18a89c6a4..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/XList.php +++ /dev/null @@ -1,53 +0,0 @@ -setHttpMethod(self::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/vectorsdb') - ->desc('List databases') - ->groups(['api', 'database']) - ->label('scope', 'databases.read') - ->label('resourceType', RESOURCE_TYPE_DATABASES) - ->label('sdk', new Method( - namespace: 'vectorsDB', - group: 'vectorsdb', - name: 'list', - description: '/docs/references/vectorsdb/list.md', - auth: [AuthType::ADMIN, AuthType::KEY], - responses: [ - new SDKResponse( - code: SwooleResponse::STATUS_CODE_OK, - model: UtopiaResponse::MODEL_DATABASE_LIST, - ) - ], - contentType: ContentType::JSON - )) - ->param('queries', [], new Databases(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following columns: ' . implode(', ', Databases::ALLOWED_ATTRIBUTES), true) - ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) - ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) - ->inject('response') - ->inject('dbForProject') - ->callback($this->action(...)); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Http.php b/src/Appwrite/Platform/Modules/Databases/Services/Http.php index 5146382b56..f683f537bc 100644 --- a/src/Appwrite/Platform/Modules/Databases/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Databases/Services/Http.php @@ -3,10 +3,8 @@ namespace Appwrite\Platform\Modules\Databases\Services; use Appwrite\Platform\Modules\Databases\Http\Init\Timeout; -use Appwrite\Platform\Modules\Databases\Services\Registry\DocumentsDB as DocumentsDBRegistry; use Appwrite\Platform\Modules\Databases\Services\Registry\Legacy as LegacyRegistry; -use Appwrite\Platform\Modules\Databases\Services\Registry\TablesDB as TablesDBDBRegistry; -use Appwrite\Platform\Modules\Databases\Services\Registry\VectorsDB as VectorsDBRegistry; +use Appwrite\Platform\Modules\Databases\Services\Registry\TablesDB as TablesDBRegistry; use Utopia\Platform\Service; class Http extends Service @@ -19,9 +17,7 @@ class Http extends Service foreach ([ LegacyRegistry::class, - TablesDBDBRegistry::class, - DocumentsDBRegistry::class, - VectorsDBRegistry::class + TablesDBRegistry::class, ] as $registrar) { new $registrar($this); } diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Registry/DocumentsDB.php b/src/Appwrite/Platform/Modules/Databases/Services/Registry/DocumentsDB.php deleted file mode 100644 index a1e3538cac..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Services/Registry/DocumentsDB.php +++ /dev/null @@ -1,109 +0,0 @@ -registerDatabaseActions($service); - $this->registerTableActions($service); - $this->registerIndexActions($service); - $this->registerRowActions($service); - $this->registerTransactionActions($service); - } - - private function registerDatabaseActions(Service $service): void - { - $service->addAction(CreateTablesDatabase::getName(), new CreateTablesDatabase()); - $service->addAction(GetTablesDatabase::getName(), new GetTablesDatabase()); - $service->addAction(UpdateTablesDatabase::getName(), new UpdateTablesDatabase()); - $service->addAction(DeleteTablesDatabase::getName(), new DeleteTablesDatabase()); - $service->addAction(ListTablesDatabase::getName(), new ListTablesDatabase()); - $service->addAction(GetTablesDatabaseUsage::getName(), new GetTablesDatabaseUsage()); - $service->addAction(ListTablesDatabaseUsage::getName(), new ListTablesDatabaseUsage()); - } - - private function registerTableActions(Service $service): void - { - $service->addAction(CreateTable::getName(), new CreateTable()); - $service->addAction(GetTable::getName(), new GetTable()); - $service->addAction(UpdateTable::getName(), new UpdateTable()); - $service->addAction(DeleteTable::getName(), new DeleteTable()); - $service->addAction(ListTables::getName(), new ListTables()); - $service->addAction(ListTableLogs::getName(), new ListTableLogs()); - $service->addAction(GetTableUsage::getName(), new GetTableUsage()); - } - - private function registerIndexActions(Service $service): void - { - $service->addAction(CreateColumnIndex::getName(), new CreateColumnIndex()); - $service->addAction(GetColumnIndex::getName(), new GetColumnIndex()); - $service->addAction(DeleteColumnIndex::getName(), new DeleteColumnIndex()); - $service->addAction(ListColumnIndexes::getName(), new ListColumnIndexes()); - } - - private function registerRowActions(Service $service): void - { - $service->addAction(CreateRow::getName(), new CreateRow()); - $service->addAction(GetRow::getName(), new GetRow()); - $service->addAction(UpdateRow::getName(), new UpdateRow()); - $service->addAction(UpdateRows::getName(), new UpdateRows()); - $service->addAction(UpsertRow::getName(), new UpsertRow()); - $service->addAction(UpsertRows::getName(), new UpsertRows()); - $service->addAction(DeleteRow::getName(), new DeleteRow()); - $service->addAction(DeleteRows::getName(), new DeleteRows()); - $service->addAction(ListRows::getName(), new ListRows()); - $service->addAction(ListRowLogs::getName(), new ListRowLogs()); - $service->addAction(IncrementRowColumn::getName(), new IncrementRowColumn()); - $service->addAction(DecrementRowColumn::getName(), new DecrementRowColumn()); - } - - private function registerTransactionActions(Service $service): void - { - $service->addAction(CreateTransaction::getName(), new CreateTransaction()); - $service->addAction(GetTransaction::getName(), new GetTransaction()); - $service->addAction(UpdateTransaction::getName(), new UpdateTransaction()); - $service->addAction(DeleteTransaction::getName(), new DeleteTransaction()); - $service->addAction(ListTransactions::getName(), new ListTransactions()); - $service->addAction(CreateOperations::getName(), new CreateOperations()); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Registry/VectorsDB.php b/src/Appwrite/Platform/Modules/Databases/Services/Registry/VectorsDB.php deleted file mode 100644 index 9496b1781a..0000000000 --- a/src/Appwrite/Platform/Modules/Databases/Services/Registry/VectorsDB.php +++ /dev/null @@ -1,110 +0,0 @@ -registerDatabaseActions($service); - $this->registerCollectionActions($service); - $this->registerIndexActions($service); - $this->registerDocumentActions($service); - $this->registerEmbeddingActions($service); - $this->registerTransactionActions($service); - } - - private function registerDatabaseActions(Service $service): void - { - $service->addAction(CreateVectorDatabase::getName(), new CreateVectorDatabase()); - $service->addAction(GetVectorDatabase::getName(), new GetVectorDatabase()); - $service->addAction(UpdateVectorDatabase::getName(), new UpdateVectorDatabase()); - $service->addAction(DeleteVectorDatabase::getName(), new DeleteVectorDatabase()); - $service->addAction(ListVectorDatabases::getName(), new ListVectorDatabases()); - $service->addAction(GetVectorDatabaseUsage::getName(), new GetVectorDatabaseUsage()); - $service->addAction(ListVectorDatabaseUsage::getName(), new ListVectorDatabaseUsage()); - } - - private function registerCollectionActions(Service $service): void - { - $service->addAction(CreateCollection::getName(), new CreateCollection()); - $service->addAction(GetCollection::getName(), new GetCollection()); - $service->addAction(UpdateCollection::getName(), new UpdateCollection()); - $service->addAction(DeleteCollection::getName(), new DeleteCollection()); - $service->addAction(ListCollections::getName(), new ListCollections()); - $service->addAction(ListCollectionLogs::getName(), new ListCollectionLogs()); - $service->addAction(GetCollectionUsage::getName(), new GetCollectionUsage()); - } - - private function registerIndexActions(Service $service): void - { - $service->addAction(CreateIndex::getName(), new CreateIndex()); - $service->addAction(GetIndex::getName(), new GetIndex()); - $service->addAction(DeleteIndex::getName(), new DeleteIndex()); - $service->addAction(ListIndexes::getName(), new ListIndexes()); - } - - private function registerDocumentActions(Service $service): void - { - $service->addAction(CreateDocument::getName(), new CreateDocument()); - $service->addAction(UpdateDocument::getName(), new UpdateDocument()); - $service->addAction(UpsertDocument::getName(), new UpsertDocument()); - $service->addAction(GetDocument::getName(), new GetDocument()); - $service->addAction(ListDocuments::getName(), new ListDocuments()); - $service->addAction(DeleteDocument::getName(), new DeleteDocument()); - $service->addAction(UpdateDocuments::getName(), new UpdateDocuments()); - $service->addAction(UpsertDocuments::getName(), new UpsertDocuments()); - $service->addAction(DeleteDocuments::getName(), new DeleteDocuments()); - } - - private function registerTransactionActions(Service $service): void - { - $service->addAction(CreateTransaction::getName(), new CreateTransaction()); - $service->addAction(GetTransaction::getName(), new GetTransaction()); - $service->addAction(UpdateTransaction::getName(), new UpdateTransaction()); - $service->addAction(DeleteTransaction::getName(), new DeleteTransaction()); - $service->addAction(ListTransactions::getName(), new ListTransactions()); - $service->addAction(CreateOperations::getName(), new CreateOperations()); - } - - private function registerEmbeddingActions(Service $service): void - { - $service->addAction(CreateTextEmbeddings::getName(), new CreateTextEmbeddings()); - } -} diff --git a/src/Appwrite/Platform/Modules/Databases/Workers/Databases.php b/src/Appwrite/Platform/Modules/Databases/Workers/Databases.php index 66ed3e0eab..60d70b7942 100644 --- a/src/Appwrite/Platform/Modules/Databases/Workers/Databases.php +++ b/src/Appwrite/Platform/Modules/Databases/Workers/Databases.php @@ -36,7 +36,6 @@ class Databases extends Action ->inject('project') ->inject('dbForPlatform') ->inject('dbForProject') - ->inject('getDatabasesDB') ->inject('queueForRealtime') ->inject('log') ->callback($this->action(...)); @@ -52,7 +51,7 @@ class Databases extends Action * @return void * @throws \Exception */ - public function action(Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, callable $getDatabasesDB, Realtime $queueForRealtime, Log $log): void + public function action(Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime, Log $log): void { $payload = $message->getPayload() ?? []; @@ -64,10 +63,7 @@ class Databases extends Action $document = new Document($payload['row'] ?? $payload['document'] ?? []); $collection = new Document($payload['table'] ?? $payload['collection'] ?? []); $database = new Document($payload['database'] ?? []); - /** - * @var Database $dbForDatabases - */ - $dbForDatabases = $getDatabasesDB($database); + $log->addTag('projectId', $project->getId()); $log->addTag('type', $type); @@ -78,12 +74,12 @@ class Databases extends Action $log->addTag('databaseId', $database->getId()); match (\strval($type)) { - DATABASE_TYPE_DELETE_DATABASE => $this->deleteDatabase($database, $dbForProject, $dbForDatabases), - DATABASE_TYPE_DELETE_COLLECTION => $this->deleteCollection($database, $collection, $dbForProject, $dbForDatabases), + DATABASE_TYPE_DELETE_DATABASE => $this->deleteDatabase($database, $dbForProject), + DATABASE_TYPE_DELETE_COLLECTION => $this->deleteCollection($database, $collection, $dbForProject), DATABASE_TYPE_CREATE_ATTRIBUTE => $this->createAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime), - DATABASE_TYPE_DELETE_ATTRIBUTE => $this->deleteAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $dbForDatabases, $queueForRealtime), - DATABASE_TYPE_CREATE_INDEX => $this->createIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $dbForDatabases, $queueForRealtime), - DATABASE_TYPE_DELETE_INDEX => $this->deleteIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $dbForDatabases, $queueForRealtime), + DATABASE_TYPE_DELETE_ATTRIBUTE => $this->deleteAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime), + DATABASE_TYPE_CREATE_INDEX => $this->createIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime), + DATABASE_TYPE_DELETE_INDEX => $this->deleteIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime), default => throw new Exception('No database operation for type: ' . \strval($type)), }; @@ -248,7 +244,6 @@ class Databases extends Action * @param Document $project * @param Database $dbForPlatform * @param Database $dbForProject - * @param Database $dbForDatabases * @param Realtime $queueForRealtime * @return void * @throws Authorization @@ -256,7 +251,7 @@ class Databases extends Action * @throws \Exception * @throws \Throwable **/ - private function deleteAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForDatabases, Database $dbForProject, Realtime $queueForRealtime): void + private function deleteAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void { if ($collection->isEmpty()) { throw new Exception('Missing collection/table'); @@ -391,7 +386,7 @@ class Databases extends Action } if ($exists) { // Delete the duplicate if created, else update in db - $this->deleteIndex($database, $collection, $index, $project, $dbForPlatform, $dbForProject, $dbForDatabases, $queueForRealtime); + $this->deleteIndex($database, $collection, $index, $project, $dbForPlatform, $dbForProject, $queueForRealtime); } else { $dbForProject->updateDocument('indexes', $index->getId(), new Document([ 'attributes' => $index->getAttribute('attributes'), @@ -420,7 +415,6 @@ class Databases extends Action * @param Document $project * @param Database $dbForPlatform * @param Database $dbForProject - * @param Database $dbForDatabases * @param Realtime $queueForRealtime * @return void * @throws Authorization @@ -429,7 +423,7 @@ class Databases extends Action * @throws DatabaseException * @throws \Throwable */ - private function createIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Database $dbForDatabases, Realtime $queueForRealtime): void + private function createIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void { if ($collection->isEmpty()) { throw new Exception('Missing collection/table'); @@ -449,7 +443,7 @@ class Databases extends Action $project = $dbForPlatform->getDocument('projects', $projectId); try { - if (!$dbForDatabases->createIndex('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $key, $type, $attributes, $lengths, $orders)) { + if (!$dbForProject->createIndex('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $key, $type, $attributes, $lengths, $orders)) { throw new DatabaseException('Failed to create Index'); } $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'available')); @@ -479,7 +473,6 @@ class Databases extends Action * @param Document $project * @param Database $dbForPlatform * @param Database $dbForProject - * @param Database $dbForDatabases * @param Realtime $queueForRealtime * @return void * @throws Authorization @@ -488,7 +481,7 @@ class Databases extends Action * @throws DatabaseException * @throws \Throwable */ - private function deleteIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Database $dbForDatabases, Realtime $queueForRealtime): void + private function deleteIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void { if ($collection->isEmpty()) { throw new Exception('Missing collection/table'); @@ -504,7 +497,7 @@ class Databases extends Action $project = $dbForPlatform->getDocument('projects', $projectId); try { - if ($status !== 'failed' && !$dbForDatabases->deleteIndex('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $key)) { + if ($status !== 'failed' && !$dbForProject->deleteIndex('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $key)) { throw new DatabaseException('Failed to delete index'); } $dbForProject->deleteDocument('indexes', $index->getId()); @@ -532,15 +525,14 @@ class Databases extends Action /** * @param Document $database - * @param Database $dbForProject - * @param Database $dbForDatabases + * @param $dbForProject * @return void * @throws Exception */ - protected function deleteDatabase(Document $database, Database $dbForProject, Database $dbForDatabases): void + protected function deleteDatabase(Document $database, $dbForProject): void { - $this->deleteByGroup('database_' . $database->getSequence(), [], $dbForProject, function ($collection) use ($database, $dbForProject, $dbForDatabases) { - $this->deleteCollection($database, $collection, $dbForProject, $dbForDatabases); + $this->deleteByGroup('database_' . $database->getSequence(), [], $dbForProject, function ($collection) use ($database, $dbForProject) { + $this->deleteCollection($database, $collection, $dbForProject); }); $dbForProject->deleteCollection('database_' . $database->getSequence()); @@ -550,7 +542,6 @@ class Databases extends Action * @param Document $database * @param Document $collection * @param Database $dbForProject - * @param Database $dbForDatabases * @return void * @throws Authorization * @throws Conflict @@ -559,7 +550,7 @@ class Databases extends Action * @throws Structure * @throws Exception */ - protected function deleteCollection(Document $database, Document $collection, Database $dbForProject, Database $dbForDatabases): void + protected function deleteCollection(Document $database, Document $collection, Database $dbForProject): void { if ($collection->isEmpty()) { throw new Exception('Missing collection/table'); @@ -569,7 +560,7 @@ class Databases extends Action $collectionInternalId = $collection->getSequence(); $databaseInternalId = $database->getSequence(); - $dbForDatabases->deleteCollection('database_' . $databaseInternalId . '_collection_' . $collection->getSequence()); + $dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $collection->getSequence()); /** * Related collections relating to current collection diff --git a/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Create.php b/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Create.php index 261571a37b..91daf33b2b 100644 --- a/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Create.php +++ b/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Create.php @@ -39,6 +39,7 @@ class Create extends Base $this ->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) ->setHttpPath('/v1/webhooks') + ->httpAlias('/v1/projects/:projectId/webhooks') ->desc('Create webhook') ->groups(['api', 'webhooks']) ->label('scope', 'webhooks.write') diff --git a/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Delete.php b/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Delete.php index c63a558b06..7730e9fc2c 100644 --- a/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Delete.php +++ b/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Delete.php @@ -32,6 +32,7 @@ class Delete extends Base $this ->setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE) ->setHttpPath('/v1/webhooks/:webhookId') + ->httpAlias('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Delete webhook') ->groups(['api', 'webhooks']) ->label('scope', 'webhooks.write') diff --git a/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Get.php b/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Get.php index 229db1924d..52ac455fc9 100644 --- a/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Get.php +++ b/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Get.php @@ -30,6 +30,7 @@ class Get extends Base $this ->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) ->setHttpPath('/v1/webhooks/:webhookId') + ->httpAlias('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Get webhook') ->groups(['api', 'webhooks']) ->label('scope', 'webhooks.read') diff --git a/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Signature/Update.php b/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Signature/Update.php index 7995192bee..9b2612863f 100644 --- a/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Signature/Update.php +++ b/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Signature/Update.php @@ -30,6 +30,7 @@ class Update extends Base { $this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) ->setHttpPath('/v1/webhooks/:webhookId/signature') + ->httpAlias('/v1/projects/:projectId/webhooks/:webhookId/signature') ->desc('Update webhook signature key') ->groups(['api', 'webhooks']) ->label('scope', 'webhooks.write') diff --git a/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Update.php b/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Update.php index abc2f2ef00..a1387c356c 100644 --- a/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Update.php +++ b/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/Update.php @@ -37,6 +37,7 @@ class Update extends Base { $this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT) ->setHttpPath('/v1/webhooks/:webhookId') + ->httpAlias('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Update webhook') ->groups(['api', 'webhooks']) ->label('scope', 'webhooks.write') diff --git a/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/XList.php b/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/XList.php index 35bf762ce1..fae95d7c5d 100644 --- a/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/XList.php +++ b/src/Appwrite/Platform/Modules/Webhooks/Http/Webhooks/XList.php @@ -34,6 +34,7 @@ class XList extends Base $this ->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) ->setHttpPath('/v1/webhooks') + ->httpAlias('/v1/projects/:projectId/webhooks') ->desc('List webhooks') ->groups(['api', 'webhooks']) ->label('scope', 'webhooks.read') diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 716969e67a..3065b2377f 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -54,7 +54,6 @@ class Deletes extends Action ->inject('project') ->inject('dbForPlatform') ->inject('getProjectDB') - ->inject('getDatabasesDB') ->inject('getLogsDB') ->inject('deviceForFiles') ->inject('deviceForFunctions') @@ -81,7 +80,6 @@ class Deletes extends Action Document $project, Database $dbForPlatform, callable $getProjectDB, - callable $getDatabasesDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, @@ -117,7 +115,7 @@ class Deletes extends Action case DELETE_TYPE_DOCUMENT: switch ($document->getCollection()) { case DELETE_TYPE_PROJECTS: - $this->deleteProject($dbForPlatform, $getProjectDB, $getDatabasesDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $document); + $this->deleteProject($dbForPlatform, $getProjectDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $document); break; case DELETE_TYPE_SITES: $this->deleteSite($dbForPlatform, $getProjectDB, $deviceForSites, $deviceForBuilds, $deviceForFiles, $document, $certificates, $project); @@ -152,7 +150,7 @@ class Deletes extends Action } break; case DELETE_TYPE_TEAM_PROJECTS: - $this->deleteProjectsByTeam($dbForPlatform, $getProjectDB, $getDatabasesDB, $certificates, $document); + $this->deleteProjectsByTeam($dbForPlatform, $getProjectDB, $certificates, $document); break; case DELETE_TYPE_EXECUTIONS: $this->deleteExecutionLogs($project, $getProjectDB, $executionRetention); @@ -222,50 +220,6 @@ class Deletes extends Action } } - private function cleanDatabase( - Document $databaseDoc, - callable $executionActionPerDatabase, - bool $projectTables, - array $projectCollectionIds - ): void { - $executionActionPerDatabase( - $databaseDoc, - fn (Database $dbForDatabases) => $this->cleanDatabaseCollections( - $dbForDatabases, - $projectTables, - $projectCollectionIds - ) - ); - } - - private function cleanDatabaseCollections( - Database $dbForDatabases, - bool $projectTables, - array $projectCollectionIds - ): void { - $dbForDatabases->foreach( - Database::METADATA, - function (Document $collection) use ($dbForDatabases, $projectTables, $projectCollectionIds) { - $collectionId = $collection->getId(); - - try { - if ($projectTables || !\in_array($collectionId, $projectCollectionIds, true)) { - $dbForDatabases->deleteCollection($collectionId); - return; - } - - $this->deleteByGroup( - $collectionId, - [Query::orderAsc()], - database: $dbForDatabases - ); - } catch (Throwable $e) { - Console::error('Error deleting ' . $collectionId . ' ' . $e->getMessage()); - } - } - ); - } - /** * @param Database $dbForPlatform * @param callable $getProjectDB @@ -593,7 +547,7 @@ class Deletes extends Action * @throws Structure * @throws Exception */ - protected function deleteProjectsByTeam(Database $dbForPlatform, callable $getProjectDB, callable $getDatabasesDB, CertificatesAdapter $certificates, Document $document): void + protected function deleteProjectsByTeam(Database $dbForPlatform, callable $getProjectDB, CertificatesAdapter $certificates, Document $document): void { $projects = $dbForPlatform->find('projects', [ @@ -608,7 +562,7 @@ class Deletes extends Action $deviceForBuilds = getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()); $deviceForCache = getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()); - $this->deleteProject($dbForPlatform, $getProjectDB, $getDatabasesDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $project); + $this->deleteProject($dbForPlatform, $getProjectDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $project); $dbForPlatform->deleteDocument('projects', $project->getId()); } } @@ -626,7 +580,7 @@ class Deletes extends Action * @throws Authorization * @throws DatabaseException */ - protected function deleteProject(Database $dbForPlatform, callable $getProjectDB, callable $getDatabasesDB, Device $deviceForFiles, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, Document $document): void + protected function deleteProject(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFiles, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, Document $document): void { $projectInternalId = $document->getSequence(); $projectId = $document->getId(); @@ -663,44 +617,23 @@ class Deletes extends Action $sharedTablesV1 = \in_array($dsn->getHost(), $sharedTablesV1); $sharedTablesV2 = !$projectTables && !$sharedTablesV1; - $allDatabases = [ - new Document([ - 'database' => $document->getAttribute('database') - ]), - ...$dbForProject->find('databases', [ - Query::equal('type', [DATABASE_TYPE_DOCUMENTSDB, DATABASE_TYPE_VECTORSDB]), - Query::limit(5000), - ]), - ]; - $databasesToClean = []; - - foreach ($allDatabases as $db) { - $key = $db->getAttribute('database'); - - if ($key) { - $databasesToClean[$key] ??= $db; + $dbForProject->foreach(Database::METADATA, function (Document $collection) use ($dbForProject, $projectTables, $projectCollectionIds) { + try { + if ($projectTables || !\in_array($collection->getId(), $projectCollectionIds)) { + $dbForProject->deleteCollection($collection->getId()); + } else { + $this->deleteByGroup( + $collection->getId(), + [ + Query::orderAsc() + ], + database: $dbForProject + ); + } + } catch (Throwable $e) { + Console::error('Error deleting ' . $collection->getId() . ' ' . $e->getMessage()); } - } - - $databasesToClean = array_values($databasesToClean); - - $executionActionPerDatabase = function (Document $databaseDoc, $callback) use ($getDatabasesDB, $document) { - /** - * @var Database $dbForDatabases - */ - $dbForDatabases = $getDatabasesDB($databaseDoc, $document); - $callback($dbForDatabases); - }; - - batch(array_map( - fn ($databaseDoc) => fn () => $this->cleanDatabase( - $databaseDoc, - $executionActionPerDatabase, - $projectTables, - $projectCollectionIds - ), - $databasesToClean - )); + }); // Delete Platforms $this->deleteByGroup('platforms', [ @@ -755,15 +688,7 @@ class Deletes extends Action // Delete metadata table if ($projectTables) { - batch(array_map( - fn ($databaseDoc) => fn () => - $executionActionPerDatabase( - $databaseDoc, - fn (Database $dbForDatabases) => - $dbForDatabases->deleteCollection(Database::METADATA) - ), - $databasesToClean - )); + $dbForProject->deleteCollection(Database::METADATA); } elseif ($sharedTablesV1) { $this->deleteByGroup( Database::METADATA, diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index 75e1341d0a..25d5bfa027 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -52,18 +52,6 @@ class Migrations extends Action protected ?Device $deviceForMigrations; protected ?Device $deviceForFiles; protected ?Document $project; - - protected Document $sourceProject; - - /** - * @var callable - */ - protected mixed $getDatabasesDB; - - /** - * @var callable(Document $databaseDSN): Database - */ - protected mixed $getProjectDB; protected array $plan = []; /** @@ -93,8 +81,6 @@ class Migrations extends Action ->inject('project') ->inject('dbForProject') ->inject('dbForPlatform') - ->inject('getDatabasesDB') - ->inject('getProjectDB') ->inject('logError') ->inject('queueForRealtime') ->inject('deviceForMigrations') @@ -115,8 +101,6 @@ class Migrations extends Action Document $project, Database $dbForProject, Database $dbForPlatform, - callable $getDatabasesDB, - callable $getProjectDB, callable $logError, Realtime $queueForRealtime, Device $deviceForMigrations, @@ -128,9 +112,6 @@ class Migrations extends Action Authorization $authorization, ): void { $payload = $message->getPayload() ?? []; - $this->getDatabasesDB = $getDatabasesDB; - $this->getProjectDB = $getProjectDB; - $this->deviceForMigrations = $deviceForMigrations; $this->deviceForFiles = $deviceForFiles; $this->plan = $plan; @@ -199,16 +180,13 @@ class Migrations extends Action $resourceId = $migration->getAttribute('resourceId'); $credentials = $migration->getAttribute('credentials'); $migrationOptions = $migration->getAttribute('options'); - /** @var Database|null $projectDB */ - $projectDB = null; - if ($credentials['projectId']) { - $this->sourceProject = $this->dbForPlatform->getDocument('projects', $credentials['projectId']); - $projectDB = call_user_func($this->getProjectDB, $this->sourceProject); - } - $getDatabasesDB = fn (Document $database): Database => - $this->getDatabasesDBForProject($database); + $dataSource = SourceAppwrite::SOURCE_API; + $database = null; $queries = []; + if ($source === SourceAppwrite::getName() && $destination === DestinationCSV::getName()) { + $dataSource = SourceAppwrite::SOURCE_DATABASE; + $database = $this->dbForProject; $queries = Query::parseQueries($migrationOptions['queries']); } @@ -238,17 +216,15 @@ class Migrations extends Action $credentials['projectId'], $credentials['endpoint'], $credentials['apiKey'], - $getDatabasesDB, - SourceAppwrite::SOURCE_DATABASE, - $projectDB, - $queries + $dataSource, + $database, + $queries, ), CSV::getName() => new CSV( $resourceId, $migrationOptions['path'], $this->deviceForMigrations, - $this->dbForProject, - $getDatabasesDB + $this->dbForProject ), default => throw new \Exception('Invalid source type'), }; @@ -274,7 +250,6 @@ class Migrations extends Action $credentials['destinationEndpoint'], $credentials['destinationApiKey'], $this->dbForProject, - $this->getDatabasesDB, Config::getParam('collections', [])['databases']['collections'], ), DestinationCSV::getName() => new DestinationCSV( @@ -328,10 +303,6 @@ class Migrations extends Action 'disabledMetrics' => [ METRIC_DATABASES_OPERATIONS_READS, METRIC_DATABASES_OPERATIONS_WRITES, - METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB, - METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB, - METRIC_DATABASES_OPERATIONS_READS_VECTORSDB, - METRIC_DATABASES_OPERATIONS_WRITES_VECTORSDB, METRIC_NETWORK_REQUESTS, METRIC_NETWORK_INBOUND, METRIC_NETWORK_OUTBOUND, @@ -547,9 +518,11 @@ class Migrations extends Action } $destination?->success(); $source?->success(); - } - if ($migration->getAttribute('destination') === DestinationCSV::getName()) { - $this->handleCSVExportComplete($project, $migration, $queueForMails, $queueForRealtime, $platform, $authorization); + + // TODO: Move to CSV hook + if ($migration->getAttribute('destination') === DestinationCSV::getName()) { + $this->handleCSVExportComplete($project, $migration, $queueForMails, $queueForRealtime, $platform, $authorization); + } } } finally { $source?->cleanup(); @@ -562,14 +535,6 @@ class Migrations extends Action } } - protected function getDatabasesDBForProject(Document $database) - { - if ($this->sourceProject) { - return ($this->getDatabasesDB)($database, $this->sourceProject); - } - return ($this->getDatabasesDB)($database); - } - /** * Handle actions to be performed when a CSV export migration is successfully completed * diff --git a/src/Appwrite/Platform/Workers/StatsResources.php b/src/Appwrite/Platform/Workers/StatsResources.php index 0e7a9bb0a7..e464455470 100644 --- a/src/Appwrite/Platform/Workers/StatsResources.php +++ b/src/Appwrite/Platform/Workers/StatsResources.php @@ -47,7 +47,6 @@ class StatsResources extends Action ->inject('project') ->inject('getProjectDB') ->inject('getLogsDB') - ->inject('getDatabasesDB') ->inject('dbForPlatform') ->inject('logError') ->callback($this->action(...)); @@ -57,13 +56,11 @@ class StatsResources extends Action * @param Message $message * @param Document $project * @param callable $getProjectDB - * @param callable $getLogsDB - * @param callable $getDatabasesDB * @return void * @throws \Utopia\Database\Exception * @throws Exception */ - public function action(Message $message, Document $project, callable $getProjectDB, callable $getLogsDB, callable $getDatabasesDB, Database $dbForPlatform, callable $logError): void + public function action(Message $message, Document $project, callable $getProjectDB, callable $getLogsDB, Database $dbForPlatform, callable $logError): void { $this->logError = $logError; @@ -79,10 +76,10 @@ class StatsResources extends Action // Reset documents for each job $this->documents = []; - $this->countForProject($dbForPlatform, $getLogsDB, $getProjectDB, $getDatabasesDB, $project); + $this->countForProject($dbForPlatform, $getLogsDB, $getProjectDB, $project); } - protected function countForProject(Database $dbForPlatform, callable $getLogsDB, callable $getProjectDB, callable $getDatabasesDB, Document $project): void + protected function countForProject(Database $dbForPlatform, callable $getLogsDB, callable $getProjectDB, Document $project): void { /** @var \Utopia\Database\Database $dbForLogs */ $dbForLogs = call_user_func($getLogsDB, $project); @@ -110,9 +107,7 @@ class StatsResources extends Action ]); - $databases = $dbForProject->count('databases', [Query::equal('type', [DATABASE_TYPE_LEGACY, DATABASE_TYPE_TABLESDB])]); - $documentsdb = $dbForProject->count('databases', [Query::equal('type', [DATABASE_TYPE_DOCUMENTSDB])]); - $vectorsdb = $dbForProject->count('databases', [Query::equal('type', [DATABASE_TYPE_VECTORSDB])]); + $databases = $dbForProject->count('databases'); $buckets = $dbForProject->count('buckets'); $users = $dbForProject->count('users'); @@ -147,8 +142,6 @@ class StatsResources extends Action $metrics = [ METRIC_DATABASES => $databases, - METRIC_DATABASES_DOCUMENTSDB => $documentsdb, - METRIC_DATABASES_VECTORSDB => $vectorsdb, METRIC_BUCKETS => $buckets, METRIC_USERS => $users, METRIC_FUNCTIONS => $functions, @@ -186,7 +179,7 @@ class StatsResources extends Action } try { - $dbForProject->skipFilters(fn () => $this->countForDatabase($dbForProject, $getDatabasesDB, $region), ['subQueryAttributes', 'subQueryIndexes']); + $dbForProject->skipFilters(fn () => $this->countForDatabase($dbForProject, $region), ['subQueryAttributes', 'subQueryIndexes']); } catch (Throwable $th) { call_user_func_array($this->logError, [$th, "StatsResources", "count_for_database_{$project->getId()}"]); } @@ -262,101 +255,51 @@ class StatsResources extends Action $this->createStatsDocuments($region, METRIC_FILES_IMAGES_TRANSFORMED, $totalImageTransformations); } - protected function countForDatabase(Database $dbForProject, callable $getDatabasesDB, string $region) + protected function countForDatabase(Database $dbForProject, string $region) { $totalCollections = 0; $totalDocuments = 0; + $totalDatabaseStorage = 0; - // documentsdb - $totalCollectionsDocumentsdb = 0; - $totalDocumentsDocumentsdb = 0; - $totalDatabaseStorageDocumentsdb = 0; - - // vectorsdb - $totalCollectionsVectordb = 0; - $totalDocumentsVectordb = 0; - $totalDatabaseStorageVectordb = 0; - - - $this->foreachDocument($dbForProject, 'databases', [], function ($database) use ($dbForProject, $getDatabasesDB, $region, &$totalCollections, &$totalDocuments, &$totalDatabaseStorage, &$totalCollectionsDocumentsdb, &$totalDocumentsDocumentsdb, &$totalDatabaseStorageDocumentsdb, &$totalCollectionsVectordb, &$totalDocumentsVectordb, &$totalDatabaseStorageVectordb) { - $dbForDatabases = $getDatabasesDB($database); + $this->foreachDocument($dbForProject, 'databases', [], function ($database) use ($dbForProject, $region, &$totalCollections, &$totalDocuments, &$totalDatabaseStorage) { $collections = $dbForProject->count('database_' . $database->getSequence()); - $databaseType = $database->getAttribute('type'); - $collectionsMetric = METRIC_DATABASE_ID_COLLECTIONS; - if (!empty($databaseType) && $databaseType !== DATABASE_TYPE_LEGACY && $databaseType !== DATABASE_TYPE_TABLESDB) { - $collectionsMetric = $databaseType . '.' . $collectionsMetric; - } - $metric = str_replace('{databaseInternalId}', $database->getSequence(), $collectionsMetric); + $metric = str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_COLLECTIONS); $this->createStatsDocuments($region, $metric, $collections); - [$documents, $storage] = $this->countForCollections($dbForProject, $dbForDatabases, $database, $region); + [$documents, $storage] = $this->countForCollections($dbForProject, $database, $region); - switch ($database->getAttribute('type')) { - case DATABASE_TYPE_DOCUMENTSDB: - $totalDatabaseStorageDocumentsdb += $storage; - $totalDocumentsDocumentsdb += $documents; - $totalCollectionsDocumentsdb += $collections; - break; - case DATABASE_TYPE_VECTORSDB: - $totalDatabaseStorageVectordb += $storage; - $totalDocumentsVectordb += $documents; - $totalCollectionsVectordb += $collections; - break; - default: - $totalDatabaseStorage += $storage; - $totalDocuments += $documents; - $totalCollections += $collections; - } + $totalDatabaseStorage += $storage; + $totalDocuments += $documents; + $totalCollections += $collections; }); $this->createStatsDocuments($region, METRIC_COLLECTIONS, $totalCollections); $this->createStatsDocuments($region, METRIC_DOCUMENTS, $totalDocuments); $this->createStatsDocuments($region, METRIC_DATABASES_STORAGE, $totalDatabaseStorage); - - $this->createStatsDocuments($region, METRIC_COLLECTIONS_DOCUMENTSDB, $totalCollectionsDocumentsdb); - $this->createStatsDocuments($region, METRIC_DOCUMENTS_DOCUMENTSDB, $totalDocumentsDocumentsdb); - $this->createStatsDocuments($region, METRIC_DATABASES_STORAGE_DOCUMENTSDB, $totalDatabaseStorageDocumentsdb); - - $this->createStatsDocuments($region, METRIC_COLLECTIONS_VECTORSDB, $totalCollectionsVectordb); - $this->createStatsDocuments($region, METRIC_DOCUMENTS_VECTORSDB, $totalDocumentsVectordb); - $this->createStatsDocuments($region, METRIC_DATABASES_STORAGE_VECTORSDB, $totalDatabaseStorageVectordb); } - protected function countForCollections(Database $dbForProject, Database $dbForDatabases, Document $database, string $region): array + protected function countForCollections(Database $dbForProject, Document $database, string $region): array { $databaseDocuments = 0; $databaseStorage = 0; - $databaseType = $database->getAttribute('type'); - $databaseIdCollectionIdDocumentsMetric = METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS; - $databaseIdCollectionIdStorageMetric = METRIC_DATABASE_ID_COLLECTION_ID_STORAGE; - $databaseIdDocumentsMetric = METRIC_DATABASE_ID_DOCUMENTS; - $databaseIdStorageMetric = METRIC_DATABASE_ID_STORAGE; - - if ($databaseType !== DATABASE_TYPE_LEGACY && $databaseType !== DATABASE_TYPE_TABLESDB) { - $databaseIdCollectionIdDocumentsMetric = $databaseType . '.' . $databaseIdCollectionIdDocumentsMetric; - $databaseIdCollectionIdStorageMetric = $databaseType . '.' . $databaseIdCollectionIdStorageMetric; - $databaseIdDocumentsMetric = $databaseType . '.' . $databaseIdDocumentsMetric; - $databaseIdStorageMetric = $databaseType . '.' . $databaseIdStorageMetric; - } - - $this->foreachDocument($dbForProject, 'database_' . $database->getSequence(), [], function ($collection) use ($dbForDatabases, $database, $region, &$databaseStorage, &$databaseDocuments, $databaseIdCollectionIdDocumentsMetric, $databaseIdCollectionIdStorageMetric) { - $documents = $dbForDatabases->count('database_' . $database->getSequence() . '_collection_' . $collection->getSequence()); - $metric = str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getSequence(), $collection->getSequence()], $databaseIdCollectionIdDocumentsMetric); + $this->foreachDocument($dbForProject, 'database_' . $database->getSequence(), [], function ($collection) use ($dbForProject, $database, $region, &$databaseStorage, &$databaseDocuments) { + $documents = $dbForProject->count('database_' . $database->getSequence() . '_collection_' . $collection->getSequence()); + $metric = str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getSequence(), $collection->getSequence()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS); $this->createStatsDocuments($region, $metric, $documents); $databaseDocuments += $documents; - $collectionStorage = $dbForDatabases->getSizeOfCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence()); - $metric = str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getSequence(), $collection->getSequence()], $databaseIdCollectionIdStorageMetric); + $collectionStorage = $dbForProject->getSizeOfCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence()); + $metric = str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getSequence(), $collection->getSequence()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE); $this->createStatsDocuments($region, $metric, $collectionStorage); $databaseStorage += $collectionStorage; }); - $metric = str_replace(['{databaseInternalId}'], [$database->getSequence()], $databaseIdDocumentsMetric); + $metric = str_replace(['{databaseInternalId}'], [$database->getSequence()], METRIC_DATABASE_ID_DOCUMENTS); $this->createStatsDocuments($region, $metric, $databaseDocuments); - $metric = str_replace(['{databaseInternalId}'], [$database->getSequence()], $databaseIdStorageMetric); + $metric = str_replace(['{databaseInternalId}'], [$database->getSequence()], METRIC_DATABASE_ID_STORAGE); $this->createStatsDocuments($region, $metric, $databaseStorage); return [$databaseDocuments, $databaseStorage]; diff --git a/src/Appwrite/Platform/Workers/StatsUsage.php b/src/Appwrite/Platform/Workers/StatsUsage.php index 1e0a2eabba..76be33d06b 100644 --- a/src/Appwrite/Platform/Workers/StatsUsage.php +++ b/src/Appwrite/Platform/Workers/StatsUsage.php @@ -47,8 +47,6 @@ class StatsUsage extends Action */ protected array $skipBaseMetrics = [ METRIC_DATABASES => true, - METRIC_DATABASES_DOCUMENTSDB => true, - METRIC_DATABASES_VECTORSDB => true, METRIC_BUCKETS => true, METRIC_USERS => true, METRIC_FUNCTIONS => true, @@ -68,13 +66,7 @@ class StatsUsage extends Action METRIC_BUILDS => true, METRIC_COLLECTIONS => true, METRIC_DOCUMENTS => true, - METRIC_COLLECTIONS_DOCUMENTSDB => true, - METRIC_DOCUMENTS_DOCUMENTSDB => true, - METRIC_COLLECTIONS_VECTORSDB => true, - METRIC_DOCUMENTS_VECTORSDB => true, METRIC_DATABASES_STORAGE => true, - METRIC_DATABASES_STORAGE_DOCUMENTSDB => true, - METRIC_DATABASES_STORAGE_VECTORSDB => true, ]; /** @@ -93,12 +85,6 @@ class StatsUsage extends Action '.databases.storage' ]; - public const DATABASE_PREFIXES = [ - DATABASE_TYPE_LEGACY, - DATABASE_TYPE_TABLESDB, - DATABASE_TYPE_DOCUMENTSDB, - ]; - /** * @var callable(): Database */ @@ -160,11 +146,6 @@ class StatsUsage extends Action $aggregationInterval = (int) System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', '20'); $project = new Document($payload['project'] ?? []); $projectId = $project->getSequence(); - - // Get database type from context - $databaseContext = $payload['context']['database'] ?? null; - $databaseType = $databaseContext ? (new Document($databaseContext))->getAttribute('type', '') : ''; - foreach ($payload['reduce'] ?? [] as $document) { if (empty($document)) { continue; @@ -174,8 +155,7 @@ class StatsUsage extends Action project: $project, document: new Document($document), metrics: $payload['metrics'], - getProjectDB: $getProjectDB, - databaseType: $databaseType + getProjectDB: $getProjectDB ); } @@ -213,10 +193,9 @@ class StatsUsage extends Action * @param Document $document * @param array $metrics * @param callable(): Database $getProjectDB - * @param string $databaseType Database type from context * @return void */ - protected function reduce(Document $project, Document $document, array &$metrics, callable $getProjectDB, string $databaseType = ''): void + protected function reduce(Document $project, Document $document, array &$metrics, callable $getProjectDB): void { $dbForProject = $getProjectDB($project); @@ -232,48 +211,38 @@ class StatsUsage extends Action } break; case $document->getCollection() === 'databases': // databases - $databaseCollectionsMetric = implode('.', array_filter([$databaseType,METRIC_COLLECTIONS])); - $databaseDocumentsMetric = implode('.', array_filter([$databaseType,METRIC_DOCUMENTS])); - - $databaseIdCollectionsMetric = implode('.', array_filter([$databaseType,METRIC_DATABASE_ID_COLLECTIONS])); - $databaseIdDocumentsMetric = implode('.', array_filter([$databaseType,METRIC_DATABASE_ID_DOCUMENTS])); - - $collections = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{databaseInternalId}', $document->getSequence(), $databaseIdCollectionsMetric))); - $documents = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{databaseInternalId}', $document->getSequence(), $databaseIdDocumentsMetric))); + $collections = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{databaseInternalId}', $document->getSequence(), METRIC_DATABASE_ID_COLLECTIONS))); + $documents = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{databaseInternalId}', $document->getSequence(), METRIC_DATABASE_ID_DOCUMENTS))); if (!empty($collections['value'])) { $metrics[] = [ - 'key' => $databaseCollectionsMetric, + 'key' => METRIC_COLLECTIONS, 'value' => ($collections['value'] * -1), ]; } if (!empty($documents['value'])) { $metrics[] = [ - 'key' => $databaseDocumentsMetric, + 'key' => METRIC_DOCUMENTS, 'value' => ($documents['value'] * -1), ]; } break; case str_starts_with($document->getCollection(), 'database_') && !str_contains($document->getCollection(), 'collection'): //collections - $databaseDocumentsMetric = implode('.', array_filter([$databaseType,METRIC_DOCUMENTS])); - $databaseIdCollectionIdDocumentsMetric = implode('.', array_filter([$databaseType,METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS])); - $databaseIdDocumentsMetric = implode('.', array_filter([$databaseType,METRIC_DATABASE_ID_DOCUMENTS])); - $parts = explode('_', $document->getCollection()); $databaseInternalId = $parts[1] ?? 0; $documents = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace( ['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $document->getSequence()], - $databaseIdCollectionIdDocumentsMetric + METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS ))); if (!empty($documents['value'])) { $metrics[] = [ - 'key' => $databaseDocumentsMetric, + 'key' => METRIC_DOCUMENTS, 'value' => ($documents['value'] * -1), ]; $metrics[] = [ - 'key' => str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), + 'key' => str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS), 'value' => ($documents['value'] * -1), ]; } @@ -504,11 +473,8 @@ class StatsUsage extends Action if (array_key_exists($stat->getAttribute('metric'), $this->skipBaseMetrics)) { return; } - foreach ($this->skipParentIdMetrics as $skipMetric) { - $metricParts = explode('.', $stat->getAttribute('metric')); - $metric = implode('.', in_array($metricParts[0], self::DATABASE_PREFIXES) ? array_slice($metricParts, 1) : $metricParts); - if (str_ends_with($metric, $skipMetric)) { + if (str_ends_with($stat->getAttribute('metric'), $skipMetric)) { return; } } diff --git a/src/Appwrite/SDK/Specification/Format.php b/src/Appwrite/SDK/Specification/Format.php index 04ecafa8fc..bd2f063073 100644 --- a/src/Appwrite/SDK/Specification/Format.php +++ b/src/Appwrite/SDK/Specification/Format.php @@ -309,7 +309,7 @@ abstract class Format case 'createIndex': switch ($param) { case 'type': - return 'DatabasesIndexType'; + return 'IndexType'; case 'orders': return 'OrderBy'; } @@ -342,45 +342,7 @@ abstract class Format case 'createIndex': switch ($param) { case 'type': - return 'TablesDBIndexType'; - case 'orders': - return 'OrderBy'; - } - } - break; - case 'documentsDB': - switch ($method) { - case 'getUsage': - case 'listUsage': - case 'getCollectionUsage': - switch ($param) { - case 'range': - return 'UsageRange'; - } - break; - case 'createIndex': - switch ($param) { - case 'type': - return 'DocumentsDBIndexType'; - case 'orders': - return 'OrderBy'; - } - } - break; - case 'vectorsDB': - switch ($method) { - case 'getUsage': - case 'listUsage': - case 'getCollectionUsage': - switch ($param) { - case 'range': - return 'UsageRange'; - } - break; - case 'createIndex': - switch ($param) { - case 'type': - return 'VectorsDBIndexType'; + return 'IndexType'; case 'orders': return 'OrderBy'; } @@ -668,8 +630,6 @@ abstract class Format } break; case 'databases': - case 'documentsDB': - case 'vectorsDB': switch ($method) { case 'getUsage': case 'listUsage': diff --git a/src/Appwrite/Utopia/Database/Validator/Attributes.php b/src/Appwrite/Utopia/Database/Validator/Attributes.php index acb04bd97e..e9bd009217 100644 --- a/src/Appwrite/Utopia/Database/Validator/Attributes.php +++ b/src/Appwrite/Utopia/Database/Validator/Attributes.php @@ -45,12 +45,10 @@ class Attributes extends Validator /** * @param int $maxAttributes Maximum number of attributes allowed * @param bool $supportForSpatialAttributes Whether DB supports spatial attributes - * @param bool $supportForAttributes Whether DB supports attributes or not */ public function __construct( int $maxAttributes = APP_LIMIT_ARRAY_PARAMS_SIZE, protected bool $supportForSpatialAttributes = true, - protected bool $supportForAttributes = true ) { $this->maxAttributes = $maxAttributes; } @@ -80,11 +78,6 @@ class Attributes extends Validator return false; } - if (\count($value) && !$this->supportForAttributes) { - $this->message = 'Attributes are not supported by the current database'; - return false; - } - if (\count($value) > $this->maxAttributes) { $this->message = 'Maximum of ' . $this->maxAttributes . ' attributes allowed'; return false; diff --git a/src/Appwrite/Utopia/Database/Validator/Operation.php b/src/Appwrite/Utopia/Database/Validator/Operation.php index 6d50611708..e6884ac677 100644 --- a/src/Appwrite/Utopia/Database/Validator/Operation.php +++ b/src/Appwrite/Utopia/Database/Validator/Operation.php @@ -59,7 +59,6 @@ class Operation extends Validator { switch ($this->type) { case 'legacy': - case 'documentsdb': $this->collectionIdName = 'collectionId'; $this->documentIdName = 'documentId'; break; diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index 9d9bbde00b..02f2a57c5b 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -87,6 +87,8 @@ class Base extends Queries $allAttributes[] = $attribute; } + + $validators = [ new Limit(), new Offset(), diff --git a/src/Appwrite/Utopia/Request/Filters/V21.php b/src/Appwrite/Utopia/Request/Filters/V21.php index 4feb8e1926..d51ec28a1e 100644 --- a/src/Appwrite/Utopia/Request/Filters/V21.php +++ b/src/Appwrite/Utopia/Request/Filters/V21.php @@ -6,10 +6,13 @@ use Appwrite\Utopia\Request\Filter; class V21 extends Filter { - // Convert 1.8.0 params to 1.9.0 + // Convert 1.8.0 params to 1.8.2 public function parse(array $content, string $model): array { switch ($model) { + case 'webhooks.create': + $content = $this->fillWebhookid($content); + break; case 'functions.createTemplateDeployment': case 'sites.createTemplateDeployment': $content = $this->convertVersionToTypeAndReference($content); @@ -48,4 +51,10 @@ class V21 extends Filter return $content; } + + protected function fillWebhookid(array $content): array + { + $content['webhookId'] = $content['webhookId'] ?? 'unique()'; + return $content; + } } diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index c2fc520da3..682c645047 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -32,10 +32,6 @@ class Response extends SwooleResponse public const MODEL_BASE_LIST = 'baseList'; public const MODEL_USAGE_DATABASES = 'usageDatabases'; public const MODEL_USAGE_DATABASE = 'usageDatabase'; - public const MODEL_USAGE_DOCUMENTSDBS = 'usageDocumentsDBs'; - public const MODEL_USAGE_DOCUMENTSDB = 'usageDocumentsDB'; - public const MODEL_USAGE_VECTORSDBS = 'usageVectorsDBs'; - public const MODEL_USAGE_VECTORSDB = 'usageVectorsDB'; public const MODEL_USAGE_TABLE = 'usageTable'; public const MODEL_USAGE_COLLECTION = 'usageCollection'; public const MODEL_USAGE_USERS = 'usageUsers'; @@ -52,10 +48,6 @@ class Response extends SwooleResponse public const MODEL_DATABASE_LIST = 'databaseList'; public const MODEL_COLLECTION = 'collection'; public const MODEL_COLLECTION_LIST = 'collectionList'; - public const MODEL_VECTORSDB_COLLECTION = 'vectorsdbCollection'; - public const MODEL_VECTORSDB_COLLECTION_LIST = 'vectorsdbCollectionList'; - public const MODEL_EMBEDDING = 'embedding'; - public const MODEL_EMBEDDING_LIST = 'embeddingList'; public const MODEL_TABLE = 'table'; public const MODEL_TABLE_LIST = 'tableList'; public const MODEL_INDEX = 'index'; @@ -87,8 +79,6 @@ class Response extends SwooleResponse public const MODEL_ATTRIBUTE_TEXT = 'attributeText'; public const MODEL_ATTRIBUTE_MEDIUMTEXT = 'attributeMediumtext'; public const MODEL_ATTRIBUTE_LONGTEXT = 'attributeLongtext'; - public const MODEL_ATTRIBUTE_OBJECT = 'attributeObject'; - public const MODEL_ATTRIBUTE_VECTOR = 'attributeVector'; // Database Columns public const MODEL_COLUMN = 'column'; diff --git a/src/Appwrite/Utopia/Response/Model/AttributeObject.php b/src/Appwrite/Utopia/Response/Model/AttributeObject.php deleted file mode 100644 index 542f7f744c..0000000000 --- a/src/Appwrite/Utopia/Response/Model/AttributeObject.php +++ /dev/null @@ -1,27 +0,0 @@ - 'object', - ]; - - public function getName(): string - { - return 'AttributeObject'; - } - - public function getType(): string - { - return Response::MODEL_ATTRIBUTE_OBJECT; - } -} diff --git a/src/Appwrite/Utopia/Response/Model/AttributeVector.php b/src/Appwrite/Utopia/Response/Model/AttributeVector.php deleted file mode 100644 index 4b58b979ee..0000000000 --- a/src/Appwrite/Utopia/Response/Model/AttributeVector.php +++ /dev/null @@ -1,35 +0,0 @@ -addRule('size', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Vector dimensions.', - 'default' => 0, - 'example' => 1536, - ]); - } - - public array $conditions = [ - 'type' => 'vector', - ]; - - public function getName(): string - { - return 'AttributeVector'; - } - - public function getType(): string - { - return Response::MODEL_ATTRIBUTE_VECTOR; - } -} diff --git a/src/Appwrite/Utopia/Response/Model/Database.php b/src/Appwrite/Utopia/Response/Model/Database.php index df9ad2e1f5..59f32b3162 100644 --- a/src/Appwrite/Utopia/Response/Model/Database.php +++ b/src/Appwrite/Utopia/Response/Model/Database.php @@ -45,8 +45,9 @@ class Database extends Model 'description' => 'Database type.', 'default' => 'legacy', 'example' => 'legacy', - 'enum' => ['legacy', 'tablesdb', 'documentsdb', 'vectorsdb'], - ]); + 'enum' => ['legacy', 'tablesdb'], + ]) + ; } /** diff --git a/src/Appwrite/Utopia/Response/Model/Embedding.php b/src/Appwrite/Utopia/Response/Model/Embedding.php deleted file mode 100644 index 9fce913723..0000000000 --- a/src/Appwrite/Utopia/Response/Model/Embedding.php +++ /dev/null @@ -1,47 +0,0 @@ -addRule('model', [ - 'type' => self::TYPE_STRING, - 'description' => 'Embedding model used to generate embeddings.', - 'example' => 'embeddinggemma' - ]) - ->addRule('dimension', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Number of dimensions for each embedding vector.', - 'example' => 768 - ]) - ->addRule('embedding', [ - 'type' => self::TYPE_FLOAT, - 'array' => true, - 'default' => [], - 'description' => 'Embedding vector values. If an error occurs, this will be an empty array.', - 'example' => [0.01, 0.02, 0.03] - ]) - ->addRule('error', [ - 'type' => self::TYPE_STRING, - 'array' => false, - 'default' => '', - 'description' => 'Error message if embedding generation fails. Empty string if no error.', - 'example' => 'Error message' - ]); - } -} diff --git a/src/Appwrite/Utopia/Response/Model/UsageDocumentsDB.php b/src/Appwrite/Utopia/Response/Model/UsageDocumentsDB.php deleted file mode 100644 index 099a9887b8..0000000000 --- a/src/Appwrite/Utopia/Response/Model/UsageDocumentsDB.php +++ /dev/null @@ -1,96 +0,0 @@ -addRule('range', [ - 'type' => self::TYPE_STRING, - 'description' => 'Time range of the usage stats.', - 'default' => '', - 'example' => '30d', - ]) - ->addRule('collectionsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of collections.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('documentsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of documents.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('storageTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated storage used in bytes.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('databaseReadsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total number of database reads.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('databaseWritesTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total number of database writes.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('collections', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of collections per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('documents', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of documents per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('storage', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated storage used in bytes per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('databaseReads', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'An array of aggregated number of database reads.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('databaseWrites', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'An array of aggregated number of database writes.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ; - } - - public function getName(): string - { - return 'UsageDocumentsDB'; - } - - public function getType(): string - { - return Response::MODEL_USAGE_DOCUMENTSDB; - } -} diff --git a/src/Appwrite/Utopia/Response/Model/UsageDocumentsDBs.php b/src/Appwrite/Utopia/Response/Model/UsageDocumentsDBs.php deleted file mode 100644 index 5ce229ce4a..0000000000 --- a/src/Appwrite/Utopia/Response/Model/UsageDocumentsDBs.php +++ /dev/null @@ -1,109 +0,0 @@ -addRule('range', [ - 'type' => self::TYPE_STRING, - 'description' => 'Time range of the usage stats.', - 'default' => '', - 'example' => '30d', - ]) - ->addRule('databasesTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of DocumentsDB databases.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('collectionsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of collections.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('documentsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of documents.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('storageTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of total databases storage in bytes.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('databasesReadsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total number of databases reads.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('databasesWritesTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total number of databases writes.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('databases', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of databases per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('collections', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of collections per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('documents', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of documents per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('storage', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'An array of the aggregated number of databases storage in bytes per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('databasesReads', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'An array of aggregated number of database reads.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('databasesWrites', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'An array of aggregated number of database writes.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ; - } - - public function getName(): string - { - return 'UsageDocumentsDBs'; - } - - public function getType(): string - { - return Response::MODEL_USAGE_DOCUMENTSDBS; - } -} diff --git a/src/Appwrite/Utopia/Response/Model/UsageProject.php b/src/Appwrite/Utopia/Response/Model/UsageProject.php index e00c4bc1dc..ee644aa845 100644 --- a/src/Appwrite/Utopia/Response/Model/UsageProject.php +++ b/src/Appwrite/Utopia/Response/Model/UsageProject.php @@ -18,13 +18,7 @@ class UsageProject extends Model ]) ->addRule('documentsTotal', [ 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of documents in legacy/tablesdb.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('documentsdbDocumentsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of documents in documentsdb.', + 'description' => 'Total aggregated number of documents.', 'default' => 0, 'example' => 0, ]) @@ -40,24 +34,12 @@ class UsageProject extends Model 'default' => 0, 'example' => 0, ]) - ->addRule('documentsdbTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of documentsdb.', - 'default' => 0, - 'example' => 0, - ]) ->addRule('databasesStorageTotal', [ 'type' => self::TYPE_INTEGER, 'description' => 'Total aggregated sum of databases storage size (in bytes).', 'default' => 0, 'example' => 0, ]) - ->addRule('documentsdbDatabasesStorageTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated sum of documentsdb databases storage size (in bytes).', - 'default' => 0, - 'example' => 0, - ]) ->addRule('usersTotal', [ 'type' => self::TYPE_INTEGER, 'description' => 'Total aggregated number of users.', @@ -118,18 +100,6 @@ class UsageProject extends Model 'default' => 0, 'example' => 0, ]) - ->addRule('documentsdbDatabasesReadsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total number of documentsdb databases reads.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('documentsdbDatabasesWritesTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total number of documentsdb databases writes.', - 'default' => 0, - 'example' => 0, - ]) ->addRule('requests', [ 'type' => Response::MODEL_METRIC, 'description' => 'Aggregated number of requests per period.', @@ -233,27 +203,6 @@ class UsageProject extends Model 'example' => [], 'array' => true ]) - ->addRule('documentsdbDatabasesReads', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'An array of aggregated number of documentsdb database reads.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('documentsdbDatabasesWrites', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'An array of aggregated number of documentsdb database writes.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('documentsdbDatabasesStorage', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'An array of aggregated sum of documentsdb databases storage size (in bytes) per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) ->addRule('imageTransformations', [ 'type' => Response::MODEL_METRIC, 'description' => 'An array of aggregated number of image transformations.', @@ -267,133 +216,6 @@ class UsageProject extends Model 'default' => 0, 'example' => 0, ]) - // VectorsDB aggregates - ->addRule('vectorsdbDatabasesTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of VectorsDB databases.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('vectorsdbCollectionsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of VectorsDB collections.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('vectorsdbDocumentsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of VectorsDB documents.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('vectorsdbDatabasesStorageTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated VectorsDB storage (bytes).', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('vectorsdbDatabasesReadsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of VectorsDB reads.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('vectorsdbDatabasesWritesTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of VectorsDB writes.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('vectorsdbDatabases', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated VectorsDB databases per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('vectorsdbCollections', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated VectorsDB collections per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('vectorsdbDocuments', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated VectorsDB documents per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('vectorsdbDatabasesStorage', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated VectorsDB storage per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('vectorsdbDatabasesReads', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated VectorsDB reads per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('vectorsdbDatabasesWrites', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated VectorsDB writes per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('embeddingsText', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of text embedding calls per period.', - 'default' => [], - 'example' => [] - ]) - ->addRule('embeddingsTextTokens', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of tokens processed by text embeddings per period.', - 'default' => [], - 'example' => [] - ]) - ->addRule('embeddingsTextDuration', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated duration spent generating text embeddings per period.', - 'default' => [], - 'example' => [] - ]) - ->addRule('embeddingsTextErrors', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of errors while generating text embeddings per period.', - 'default' => [], - 'example' => [] - ]) - ->addRule('embeddingsTextTotal', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Total aggregated number of text embedding calls.', - 'default' => 0, - 'example' => 0 - ]) - ->addRule('embeddingsTextTokensTotal', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Total aggregated number of tokens processed by text.', - 'default' => 0, - 'example' => 0 - ]) - ->addRule('embeddingsTextDurationTotal', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Total aggregated duration spent generating text embeddings.', - 'default' => 0, - 'example' => 0 - ]) - ->addRule('embeddingsTextErrorsTotal', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Total aggregated number of errors while generating text embeddings.', - 'default' => 0, - 'example' => 0 - ]) ; } diff --git a/src/Appwrite/Utopia/Response/Model/UsageVectorsDB.php b/src/Appwrite/Utopia/Response/Model/UsageVectorsDB.php deleted file mode 100644 index c652a3d62e..0000000000 --- a/src/Appwrite/Utopia/Response/Model/UsageVectorsDB.php +++ /dev/null @@ -1,96 +0,0 @@ -addRule('range', [ - 'type' => self::TYPE_STRING, - 'description' => 'Time range of the usage stats.', - 'default' => '', - 'example' => '30d', - ]) - ->addRule('collectionsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of collections.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('documentsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of documents.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('storageTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated storage used in bytes.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('databaseReadsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total number of database reads.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('databaseWritesTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total number of database writes.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('collections', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of collections per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('documents', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of documents per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('storage', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated storage used in bytes per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('databaseReads', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'An array of aggregated number of database reads.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('databaseWrites', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'An array of aggregated number of database writes.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ; - } - - public function getName(): string - { - return 'UsageVectorsDB'; - } - - public function getType(): string - { - return Response::MODEL_USAGE_VECTORSDB; - } -} diff --git a/src/Appwrite/Utopia/Response/Model/UsageVectorsDBs.php b/src/Appwrite/Utopia/Response/Model/UsageVectorsDBs.php deleted file mode 100644 index 1f5fe7853d..0000000000 --- a/src/Appwrite/Utopia/Response/Model/UsageVectorsDBs.php +++ /dev/null @@ -1,109 +0,0 @@ -addRule('range', [ - 'type' => self::TYPE_STRING, - 'description' => 'Time range of the usage stats.', - 'default' => '', - 'example' => '30d', - ]) - ->addRule('databasesTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of VectorsDB databases.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('collectionsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of collections.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('documentsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated number of documents.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('storageTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total aggregated storage in bytes.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('databasesReadsTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total number of database reads.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('databasesWritesTotal', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Total number of database writes.', - 'default' => 0, - 'example' => 0, - ]) - ->addRule('databases', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of databases per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('collections', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of collections per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('documents', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated number of documents per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('storage', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'Aggregated storage in bytes per period.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('databasesReads', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'An array of aggregated number of database reads.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ->addRule('databasesWrites', [ - 'type' => Response::MODEL_METRIC, - 'description' => 'An array of aggregated number of database writes.', - 'default' => [], - 'example' => [], - 'array' => true - ]) - ; - } - - public function getName(): string - { - return 'UsageVectorsDBs'; - } - - public function getType(): string - { - return Response::MODEL_USAGE_VECTORSDBS; - } -} diff --git a/src/Appwrite/Utopia/Response/Model/VectorsDBCollection.php b/src/Appwrite/Utopia/Response/Model/VectorsDBCollection.php deleted file mode 100644 index 5053628e74..0000000000 --- a/src/Appwrite/Utopia/Response/Model/VectorsDBCollection.php +++ /dev/null @@ -1,41 +0,0 @@ -addRule('dimension', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Embedding dimension.', - 'default' => 0, - 'example' => 1536, - ]) - ->addRule('attributes', [ - 'type' => [ - Response::MODEL_ATTRIBUTE_OBJECT, - Response::MODEL_ATTRIBUTE_VECTOR, - ], - 'description' => 'Collection attributes.', - 'default' => [], - 'example' => new \stdClass(), - 'array' => true, - ]) - ; - } - - public function getName(): string - { - return 'VectorsDB Collection'; - } - - public function getType(): string - { - return Response::MODEL_VECTORSDB_COLLECTION; - } -} diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php index eea53d9ea8..0e484d4dcf 100644 --- a/tests/e2e/General/UsageTest.php +++ b/tests/e2e/General/UsageTest.php @@ -3,7 +3,6 @@ namespace Tests\E2E\General; use Appwrite\Platform\Modules\Compute\Specification; -use Appwrite\Tests\Retry; use CURLFile; use DateTime; use PHPUnit\Framework\Attributes\Depends; @@ -600,8 +599,6 @@ class UsageTest extends Scope $collectionsTotal = $data['collectionsTotal']; $documentsTotal = $data['documentsTotal']; - sleep(self::WAIT); - $this->assertEventually(function () use ($requestsTotal, $databasesTotal, $documentsTotal) { $response = $this->client->call( Client::METHOD_GET, @@ -926,446 +923,6 @@ class UsageTest extends Scope } #[Depends('testDatabaseStatsTablesAPI')] - public function testPrepareDocumentsDBStats(array $data): array - { - $documentsTotal = 0; - $collectionsTotal = 0; - $documentsDbTotal = 0; - $databasesTotal = $data['databasesTotal']; - $requestsTotal = $data['requestsTotal']; - - for ($i = 0; $i < self::CREATE; $i++) { - $name = uniqid() . ' documentsdb'; - - $response = $this->client->call( - Client::METHOD_POST, - '/documentsdb', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), - [ - 'databaseId' => 'unique()', - 'name' => $name, - ] - ); - - $this->assertEquals($name, $response['body']['name']); - $this->assertNotEmpty($response['body']['$id']); - - $requestsTotal += 1; - $documentsDbTotal += 1; - - $documentsDbId = $response['body']['$id']; - - if ($i < (self::CREATE / 2)) { - $response = $this->client->call( - Client::METHOD_DELETE, - '/documentsdb/' . $documentsDbId, - array_merge([ - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), - ); - - $this->assertEmpty($response['body']); - - $documentsDbTotal -= 1; - $requestsTotal += 1; - } - } - - for ($i = 0; $i < self::CREATE; $i++) { - $name = uniqid() . ' collection'; - - $response = $this->client->call( - Client::METHOD_POST, - '/documentsdb/' . $documentsDbId . '/collections', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), - [ - 'collectionId' => 'unique()', - 'name' => $name, - 'documentSecurity' => false, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ] - ); - - $this->assertEquals($name, $response['body']['name']); - $this->assertNotEmpty($response['body']['$id']); - - $requestsTotal += 1; - $collectionsTotal += 1; - - $collectionId = $response['body']['$id']; - - if ($i < (self::CREATE / 2)) { - $response = $this->client->call( - Client::METHOD_DELETE, - '/documentsdb/' . $documentsDbId . '/collections/' . $collectionId, - array_merge([ - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), - ); - - $this->assertEmpty($response['body']); - - $collectionsTotal -= 1; - $requestsTotal += 1; - } - } - - for ($i = 0; $i < self::CREATE; $i++) { - $response = $this->client->call( - Client::METHOD_POST, - '/documentsdb/' . $documentsDbId . '/collections/' . $collectionId . '/documents', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), - [ - 'documentId' => 'unique()', - 'data' => [ - 'name' => uniqid() . ' document', - 'value' => $i - ] - ] - ); - - $this->assertNotEmpty($response['body']['$id']); - - $requestsTotal += 1; - $documentsTotal += 1; - - $documentId = $response['body']['$id']; - - if ($i < (self::CREATE / 2)) { - $response = $this->client->call( - Client::METHOD_DELETE, - '/documentsdb/' . $documentsDbId . '/collections/' . $collectionId . '/documents/' . $documentId, - array_merge([ - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), - ); - - $this->assertEmpty($response['body']); - - $documentsTotal -= 1; - $requestsTotal += 1; - } - } - - return array_merge($data, [ - 'documentsDbId' => $documentsDbId, - 'documentsDbCollectionId' => $collectionId, - 'requestsTotal' => $requestsTotal, - 'databasesTotal' => $databasesTotal, - 'documentsDbTotal' => $documentsDbTotal, - 'documentsDbCollectionsTotal' => $collectionsTotal, - 'documentsDbDocumentsTotal' => $documentsTotal, - ]); - } - - #[Depends('testPrepareDocumentsDBStats')] - #[Retry(count: 1)] - public function testDocumentsDBStats(array $data): array - { - $documentsDbId = $data['documentsDbId']; - $collectionId = $data['documentsDbCollectionId']; - $requestsTotal = $data['requestsTotal']; - $databasesTotal = $data['databasesTotal']; - $documentsDbTotal = $data['documentsDbTotal']; - $collectionsTotal = $data['documentsDbCollectionsTotal']; - $documentsTotal = $data['documentsDbDocumentsTotal']; - - sleep(self::WAIT); - - $response = $this->client->call( - Client::METHOD_GET, - '/project/usage', - $this->getConsoleHeaders(), - [ - 'period' => '1d', - 'startDate' => self::getToday(), - 'endDate' => self::getTomorrow(), - ] - ); - - $this->assertGreaterThanOrEqual(31, count($response['body'])); - $this->assertCount(1, $response['body']['requests']); - $this->assertCount(1, $response['body']['network']); - $this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']); - $this->validateDates($response['body']['requests']); - // documentsdbTotal should reflect only documents DB instances, not relational databases. - $this->assertEquals($documentsDbTotal, $response['body']['documentsdbTotal']); - $this->assertEquals($documentsTotal, $response['body']['documentsdbDocumentsTotal']); - - $response = $this->client->call( - Client::METHOD_GET, - '/databases/usage?range=30d', - $this->getConsoleHeaders() - ); - - $this->assertEquals($databasesTotal, $response['body']['databases'][array_key_last($response['body']['databases'])]['value']); - $this->validateDates($response['body']['databases']); - - $this->assertEventually(function () use ($documentsDbId, $collectionsTotal, $documentsTotal) { - $response = $this->client->call( - Client::METHOD_GET, - '/documentsdb/' . $documentsDbId . '/usage?range=30d', - $this->getConsoleHeaders() - ); - - $this->assertEquals($collectionsTotal, $response['body']['collections'][array_key_last($response['body']['collections'])]['value']); - $this->validateDates($response['body']['collections']); - - $this->assertEquals($documentsTotal, $response['body']['documents'][array_key_last($response['body']['documents'])]['value']); - $this->validateDates($response['body']['documents']); - }); - - $this->assertEventually(function () use ($documentsDbId, $collectionId, $documentsTotal) { - $response = $this->client->call( - Client::METHOD_GET, - '/documentsdb/' . $documentsDbId . '/collections/' . $collectionId . '/usage?range=30d', - $this->getConsoleHeaders() - ); - - $this->assertEquals($documentsTotal, $response['body']['documents'][array_key_last($response['body']['documents'])]['value']); - $this->validateDates($response['body']['documents']); - }); - - return $data; - } - - #[Depends('testDocumentsDBStats')] - public function testPrepareVectorsDBStats(array $data): array - { - $documentsTotal = 0; - $collectionsTotal = 0; - $vectordbTotal = 0; - $databasesTotal = $data['databasesTotal']; - $requestsTotal = $data['requestsTotal']; - - for ($i = 0; $i < self::CREATE; $i++) { - $name = uniqid() . ' vectorsdb'; - - $response = $this->client->call( - Client::METHOD_POST, - '/vectorsdb', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), - [ - 'databaseId' => 'unique()', - 'name' => $name, - ] - ); - - $this->assertEquals($name, $response['body']['name']); - $this->assertNotEmpty($response['body']['$id']); - - $requestsTotal += 1; - $vectordbTotal += 1; - - $vectordbId = $response['body']['$id']; - - if ($i < (self::CREATE / 2)) { - $response = $this->client->call( - Client::METHOD_DELETE, - '/vectorsdb/' . $vectordbId, - array_merge([ - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), - ); - - $this->assertEmpty($response['body']); - - $vectordbTotal -= 1; - $requestsTotal += 1; - } - } - - for ($i = 0; $i < self::CREATE; $i++) { - $name = uniqid() . ' collection'; - - $response = $this->client->call( - Client::METHOD_POST, - '/vectorsdb/' . $vectordbId . '/collections', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), - [ - 'collectionId' => 'unique()', - 'name' => $name, - 'dimension' => 1536, - 'documentSecurity' => false, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ] - ); - - $this->assertEquals($name, $response['body']['name']); - $this->assertNotEmpty($response['body']['$id']); - - $requestsTotal += 1; - $collectionsTotal += 1; - - $collectionId = $response['body']['$id']; - - if ($i < (self::CREATE / 2)) { - $response = $this->client->call( - Client::METHOD_DELETE, - '/vectorsdb/' . $vectordbId . '/collections/' . $collectionId, - array_merge([ - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), - ); - - $this->assertEmpty($response['body']); - - $collectionsTotal -= 1; - $requestsTotal += 1; - } - } - - for ($i = 0; $i < self::CREATE; $i++) { - $response = $this->client->call( - Client::METHOD_POST, - '/vectorsdb/' . $vectordbId . '/collections/' . $collectionId . '/documents', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), - [ - 'documentId' => 'unique()', - 'data' => [ - 'embeddings' => array_fill(0, 1536, 0.1), - 'metadata' => [ - 'name' => uniqid() . ' document', - 'value' => $i - ] - ] - ] - ); - - $this->assertNotEmpty($response['body']['$id']); - - $requestsTotal += 1; - $documentsTotal += 1; - - $documentId = $response['body']['$id']; - - if ($i < (self::CREATE / 2)) { - $response = $this->client->call( - Client::METHOD_DELETE, - '/vectorsdb/' . $vectordbId . '/collections/' . $collectionId . '/documents/' . $documentId, - array_merge([ - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), - ); - - $this->assertEmpty($response['body']); - - $documentsTotal -= 1; - $requestsTotal += 1; - } - } - - return array_merge($data, [ - 'vectordbId' => $vectordbId, - 'vectordbCollectionId' => $collectionId, - 'requestsTotal' => $requestsTotal, - 'databasesTotal' => $databasesTotal, - 'vectordbTotal' => $vectordbTotal, - 'vectordbCollectionsTotal' => $collectionsTotal, - 'vectordbDocumentsTotal' => $documentsTotal, - ]); - } - - #[Depends('testPrepareVectorsDBStats')] - #[Retry(count: 1)] - public function testVectorsDBStats(array $data): array - { - $vectordbId = $data['vectordbId']; - $collectionId = $data['vectordbCollectionId']; - $requestsTotal = $data['requestsTotal']; - $databasesTotal = $data['databasesTotal']; - $vectordbTotal = $data['vectordbTotal']; - $collectionsTotal = $data['vectordbCollectionsTotal']; - $documentsTotal = $data['vectordbDocumentsTotal']; - - $this->assertEventually(function () use ($requestsTotal, $vectordbTotal, $documentsTotal) { - $response = $this->client->call( - Client::METHOD_GET, - '/project/usage', - $this->getConsoleHeaders(), - [ - 'period' => '1d', - 'startDate' => self::getToday(), - 'endDate' => self::getTomorrow(), - ] - ); - - $this->assertGreaterThanOrEqual(31, count($response['body'])); - $this->assertCount(1, $response['body']['requests']); - $this->assertCount(1, $response['body']['network']); - $this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']); - $this->validateDates($response['body']['requests']); - // vectordbTotal should reflect only VectorsDB instances, not relational databases. - $this->assertEquals($vectordbTotal, $response['body']['vectordbDatabasesTotal']); - $this->assertEquals($documentsTotal, $response['body']['vectordbDocumentsTotal']); - }); - - $response = $this->client->call( - Client::METHOD_GET, - '/databases/usage?range=30d', - $this->getConsoleHeaders() - ); - - $this->assertEquals($databasesTotal, $response['body']['databases'][array_key_last($response['body']['databases'])]['value']); - $this->validateDates($response['body']['databases']); - - $this->assertEventually(function () use ($vectordbId, $collectionsTotal, $documentsTotal) { - $response = $this->client->call( - Client::METHOD_GET, - '/vectorsdb/' . $vectordbId . '/usage?range=30d', - $this->getConsoleHeaders() - ); - - $this->assertEquals($collectionsTotal, $response['body']['collections'][array_key_last($response['body']['collections'])]['value']); - $this->validateDates($response['body']['collections']); - - $this->assertEquals($documentsTotal, $response['body']['documents'][array_key_last($response['body']['documents'])]['value']); - $this->validateDates($response['body']['documents']); - }); - - $this->assertEventually(function () use ($vectordbId, $collectionId, $documentsTotal) { - $response = $this->client->call( - Client::METHOD_GET, - '/vectorsdb/' . $vectordbId . '/collections/' . $collectionId . '/usage?range=30d', - $this->getConsoleHeaders() - ); - - $this->assertEquals($documentsTotal, $response['body']['documents'][array_key_last($response['body']['documents'])]['value']); - $this->validateDates($response['body']['documents']); - }); - - return $data; - } - - #[Depends('testVectorsDBStats')] public function testPrepareFunctionsStats(array $data): array { $executionTime = 0; @@ -1844,75 +1401,6 @@ class UsageTest extends Scope }); } - public function testEmbeddingsTextUsageDoesNotBreakProjectUsage(): void - { - // Trigger embeddings endpoint a few times so stats usage worker has data to aggregate - for ($i = 0; $i < 3; $i++) { - $response = $this->client->call( - Client::METHOD_POST, - '/vectorsdb/embeddings/text', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], $this->getHeaders()), - [ - 'model' => 'embeddinggemma', - 'texts' => [ - 'usage test text ' . $i, - ], - ] - ); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertIsArray($response['body']['embeddings']); - $this->assertGreaterThan(0, $response['body']['total']); - } - - // Ensure project usage endpoint still responds correctly after embeddings calls - $this->assertEventually(function () { - $response = $this->client->call( - Client::METHOD_GET, - '/project/usage', - $this->getConsoleHeaders(), - [ - 'period' => '1h', - 'startDate' => self::getToday(), - 'endDate' => self::getTomorrow(), - ] - ); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertArrayHasKey('requests', $response['body']); - $this->assertArrayHasKey('network', $response['body']); - $this->assertArrayHasKey('executionsTotal', $response['body']); - - // New embeddings metrics should be present after calls above - $this->assertArrayHasKey('embeddingsText', $response['body']); - $this->assertArrayHasKey('embeddingsTextErrors', $response['body']); - $this->assertArrayHasKey('embeddingsTextTokens', $response['body']); - $this->assertArrayHasKey('embeddingsTextDuration', $response['body']); - $this->assertArrayHasKey('embeddingsTextTotal', $response['body']); - $this->assertArrayHasKey('embeddingsTextErrorsTotal', $response['body']); - $this->assertArrayHasKey('embeddingsTextTokensTotal', $response['body']); - $this->assertArrayHasKey('embeddingsTextDurationTotal', $response['body']); - - // Time-series arrays should be non-empty - $this->assertNotEmpty($response['body']['embeddingsText']); - $this->assertNotEmpty($response['body']['embeddingsTextTokens']); - $this->assertNotEmpty($response['body']['embeddingsTextDuration']); - $this->validateDates($response['body']['embeddingsText']); - $this->validateDates($response['body']['embeddingsTextTokens']); - $this->validateDates($response['body']['embeddingsTextDuration']); - - // Total scalars should be greater than 0 (or >= 0 for errors) - $this->assertGreaterThan(0, $response['body']['embeddingsTextTotal']); - $this->assertGreaterThanOrEqual(0, $response['body']['embeddingsTextErrorsTotal']); - $this->assertGreaterThan(0, $response['body']['embeddingsTextTokensTotal']); - $this->assertGreaterThan(0, $response['body']['embeddingsTextDurationTotal']); - }); - } - public function tearDown(): void { $this->projectId = ''; diff --git a/tests/e2e/Scopes/ApiDocumentsDB.php b/tests/e2e/Scopes/ApiDocumentsDB.php deleted file mode 100644 index 9948b03971..0000000000 --- a/tests/e2e/Scopes/ApiDocumentsDB.php +++ /dev/null @@ -1,109 +0,0 @@ -assertNotEmpty($devKey['body']); $this->assertNotEmpty($devKey['body']['secret']); - $webhook = $this->client->call(Client::METHOD_POST, '/projects/' . $project['body']['$id'] . '/webhooks', [ + $webhook = $this->client->call(Client::METHOD_POST, '/webhooks', [ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'cookie' => 'a_session_console=' . $this->getRoot()['session'], - 'x-appwrite-project' => 'console', + 'x-appwrite-project' => $project['body']['$id'], + 'x-appwrite-mode' => 'admin' ], [ + 'webhookId' => 'unique()', 'name' => 'Webhook Test', 'events' => [ 'databases.*', diff --git a/tests/e2e/Scopes/SchemaPolling.php b/tests/e2e/Scopes/SchemaPolling.php index 867ff3213b..8296147c12 100644 --- a/tests/e2e/Scopes/SchemaPolling.php +++ b/tests/e2e/Scopes/SchemaPolling.php @@ -22,9 +22,6 @@ trait SchemaPolling */ protected function waitForAttribute(string $databaseId, string $containerId, string $attributeKey, int $timeoutMs = 240000, int $waitMs = 500): void { - if (!$this->getSupportForAttributes()) { - return; - } $this->assertEventually(function () use ($databaseId, $containerId, $attributeKey) { $attribute = $this->client->call( Client::METHOD_GET, diff --git a/tests/e2e/Scopes/Scope.php b/tests/e2e/Scopes/Scope.php index 8c62c0c14a..a8152ef77e 100644 --- a/tests/e2e/Scopes/Scope.php +++ b/tests/e2e/Scopes/Scope.php @@ -144,14 +144,6 @@ abstract class Scope extends TestCase return $this->getConsoleVariables()['supportForSchemas'] ?? true; } - /** - * Check if the database adapter supports attributes - */ - protected function getSupportForAttributes(): bool - { - return $this->getConsoleVariables()['supportForAttributes'] ?? true; - } - /** * Get the maximum index length supported by the database adapter */ diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 7b967d7673..7f23f2966c 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -68,31 +68,6 @@ trait DatabasesBase return self::$databaseCache[$cacheKey]; } - /** - * Helper to create an attribute on a collection. - * - * @param string $databaseId - * @param string $collectionId - * @param string $type - * @param array $payload - * - * @return array - */ - protected function createAttribute(string $databaseId, string $collectionId, string $type, array $payload): array - { - return $this->client->call( - Client::METHOD_POST, - $this->getSchemaUrl($databaseId, $collectionId) . '/' . $type, - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], - $payload - ); - } - - /** * Setup: Create database and collections * Uses static caching to avoid recreating resources @@ -175,51 +150,75 @@ trait DatabasesBase $data = $this->setupCollection(); $databaseId = $data['databaseId']; - if (!$this->getSupportForAttributes()) { - self::$attributesCache[$cacheKey] = $data; - return self::$attributesCache[$cacheKey]; - } - $title = $this->createAttribute($databaseId, $data['moviesId'], 'string', [ + $title = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $data['moviesId']) . '/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, ]); - $description = $this->createAttribute($databaseId, $data['moviesId'], 'string', [ + $description = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $data['moviesId']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'key' => 'description', 'size' => 512, 'required' => false, 'default' => '', ]); - $tagline = $this->createAttribute($databaseId, $data['moviesId'], 'string', [ + $tagline = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $data['moviesId']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'key' => 'tagline', 'size' => 512, 'required' => false, 'default' => '', ]); - $releaseYear = $this->createAttribute($databaseId, $data['moviesId'], 'integer', [ + $releaseYear = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $data['moviesId']) . '/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'key' => 'releaseYear', 'required' => true, 'min' => 1900, 'max' => 2200, ]); - $duration = $this->createAttribute($databaseId, $data['moviesId'], 'integer', [ + $duration = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $data['moviesId']) . '/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'key' => 'duration', 'required' => false, 'min' => 60, ]); - $actors = $this->createAttribute($databaseId, $data['moviesId'], 'string', [ + $actors = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $data['moviesId']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'key' => 'actors', 'size' => 256, 'required' => false, 'array' => true, ]); - $datetime = $this->createAttribute($databaseId, $data['moviesId'], 'datetime', [ + $datetime = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $data['moviesId']) . '/datetime', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ 'key' => 'birthDay', 'required' => false, ]); @@ -934,10 +933,6 @@ trait DatabasesBase public function testCreateAttributes(): void { - if (!$this->getSupportForAttributes()) { - $this->markTestSkipped('Attributes are not supported by this database adapter'); - return; - } // Use dedicated collections for this test to avoid conflicts with setupAttributes() $data = $this->setupDatabase(); $databaseId = $data['databaseId']; @@ -1187,10 +1182,6 @@ trait DatabasesBase public function testListAttributes(): void { - if (!$this->getSupportForAttributes()) { - $this->markTestSkipped('Attributes are not supported by this database adapter'); - return; - } $data = $this->setupAttributes(); $databaseId = $data['databaseId']; $response = $this->client->call(Client::METHOD_GET, $this->getSchemaUrl($databaseId, $data['moviesId']), array_merge([ @@ -1219,10 +1210,6 @@ trait DatabasesBase public function testPatchAttribute(): void { - if (!$this->getSupportForAttributes()) { - $this->markTestSkipped('Attributes are not supported by this database adapter'); - return; - } $data = $this->setupDatabase(); $databaseId = $data['databaseId']; @@ -1288,10 +1275,6 @@ trait DatabasesBase public function testUpdateAttributeEnum(): void { - if (!$this->getSupportForAttributes()) { - $this->markTestSkipped('Attributes are not supported by this database adapter'); - return; - } $database = $this->client->call(Client::METHOD_POST, $this->getApiBasePath(), [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1349,10 +1332,6 @@ trait DatabasesBase public function testAttributeResponseModels(): void { - if (!$this->getSupportForAttributes()) { - $this->markTestSkipped('Attributes are not supported by this database adapter'); - return; - } $data = $this->setupAttributes(); $databaseId = $data['databaseId']; $collection = $this->client->call(Client::METHOD_POST, $this->getContainerUrl($databaseId), array_merge([ @@ -2064,75 +2043,65 @@ trait DatabasesBase $this->assertEquals(201, $collection['headers']['status-code']); $collectionId = $collection['body']['$id']; - // Create attributes needed for index testing (only when supported). - // DocumentsDB can still create indexes without a predefined schema. - if ($this->getSupportForAttributes()) { - $title = $this->createAttribute($databaseId, $collectionId, 'string', [ - 'key' => 'title', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $title['headers']['status-code']); + // Create attributes needed for index testing + $title = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/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']); - $description = $this->createAttribute($databaseId, $collectionId, 'string', [ - 'key' => 'description', - 'size' => 512, - 'required' => false, - 'default' => '', - ]); - $this->assertEquals(202, $description['headers']['status-code']); + $description = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), ['key' => 'description', 'size' => 512, 'required' => false, 'default' => '']); + $this->assertEquals(202, $description['headers']['status-code']); - $tagline = $this->createAttribute($databaseId, $collectionId, 'string', [ - 'key' => 'tagline', - 'size' => 512, - 'required' => false, - 'default' => '', - ]); - $this->assertEquals(202, $tagline['headers']['status-code']); + $tagline = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), ['key' => 'tagline', 'size' => 512, 'required' => false, 'default' => '']); + $this->assertEquals(202, $tagline['headers']['status-code']); - $releaseYear = $this->createAttribute($databaseId, $collectionId, 'integer', [ - 'key' => 'releaseYear', - 'required' => true, - 'min' => 1900, - 'max' => 2200, - ]); - $this->assertEquals(202, $releaseYear['headers']['status-code']); + $releaseYear = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), ['key' => 'releaseYear', 'required' => true, 'min' => 1900, 'max' => 2200]); + $this->assertEquals(202, $releaseYear['headers']['status-code']); - $actors = $this->createAttribute($databaseId, $collectionId, 'string', [ - 'key' => 'actors', - 'size' => 256, - 'required' => false, - 'array' => true, - ]); - $this->assertEquals(202, $actors['headers']['status-code']); + $actors = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), ['key' => 'actors', 'size' => 256, 'required' => false, 'array' => true]); + $this->assertEquals(202, $actors['headers']['status-code']); - $birthDay = $this->createAttribute($databaseId, $collectionId, 'datetime', [ - 'key' => 'birthDay', - 'required' => false, - ]); - $this->assertEquals(202, $birthDay['headers']['status-code']); + $birthDay = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/datetime', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), ['key' => 'birthDay', 'required' => false]); + $this->assertEquals(202, $birthDay['headers']['status-code']); - $integers = $this->createAttribute($databaseId, $collectionId, 'integer', [ - 'key' => 'integers', - 'required' => false, - 'array' => true, - 'min' => 10, - 'max' => 99, - ]); - $this->assertEquals(202, $integers['headers']['status-code']); + $integers = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), ['key' => 'integers', 'required' => false, 'array' => true, 'min' => 10, 'max' => 99]); + $this->assertEquals(202, $integers['headers']['status-code']); - $integers2 = $this->createAttribute($databaseId, $collectionId, 'integer', [ - 'key' => 'integers2', - 'required' => false, - 'array' => true, - 'min' => 10, - 'max' => 99, - ]); - $this->assertEquals(202, $integers2['headers']['status-code']); + $integers2 = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), ['key' => 'integers2', 'required' => false, 'array' => true, 'min' => 10, 'max' => 99]); + $this->assertEquals(202, $integers2['headers']['status-code']); - // Wait for attributes to be ready - $this->waitForAllAttributes($databaseId, $collectionId); - } + // Wait for attributes to be ready + $this->waitForAllAttributes($databaseId, $collectionId); $titleIndex = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ 'content-type' => 'application/json', @@ -2249,16 +2218,13 @@ trait DatabasesBase $this->getIndexAttributesParam() => ['description', 'tagline'], ]); - // documentsdb isn't aware of the size so it will create - if ($this->getSupportForAttributes()) { - if ($this->getMaxIndexLength() < 1024) { - // Only SQL-based adapters (MariaDB, PostgreSQL) enforce byte-level index length limits - $this->assertEquals(400, $tooLong['headers']['status-code']); - $this->assertStringContainsString('Index length is longer than the maximum', $tooLong['body']['message']); - } else { - // MongoDB (maxIndexLength=1024) doesn't exceed the limit with 512+512 - $this->assertEquals(202, $tooLong['headers']['status-code']); - } + if ($this->getMaxIndexLength() < 1024) { + // Only SQL-based adapters (MariaDB, PostgreSQL) enforce byte-level index length limits + $this->assertEquals(400, $tooLong['headers']['status-code']); + $this->assertStringContainsString('Index length is longer than the maximum', $tooLong['body']['message']); + } else { + // MongoDB (maxIndexLength=1024) doesn't exceed the limit with 512+512 + $this->assertEquals(202, $tooLong['headers']['status-code']); } $fulltextArray = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ @@ -2272,126 +2238,119 @@ trait DatabasesBase ]); $this->assertEquals(400, $fulltextArray['headers']['status-code']); - $errorMessage = $this->getSupportForAttributes() ? "Creating indexes on array attributes is not currently supported." : "There is already a fulltext index in the collection"; - $this->assertEquals($errorMessage, $fulltextArray['body']['message']); + $this->assertEquals('Creating indexes on array attributes is not currently supported.', $fulltextArray['body']['message']); - if ($this->getSupportForAttributes()) { + $actorsArray = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'key' => 'index-actors', + 'type' => 'key', + $this->getIndexAttributesParam() => ['actors'], + ]); - $actorsArray = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), [ - 'key' => 'index-actors', - 'type' => 'key', - $this->getIndexAttributesParam() => ['actors'], - ]); + $this->assertEquals(400, $actorsArray['headers']['status-code']); + $this->assertEquals('Creating indexes on array attributes is not currently supported.', $actorsArray['body']['message']); - $this->assertEquals(400, $actorsArray['headers']['status-code']); - $this->assertEquals('Creating indexes on array attributes is not currently supported.', $actorsArray['body']['message']); + $twoLevelsArray = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'key' => 'index-ip-actors', + 'type' => 'key', + $this->getIndexAttributesParam() => ['releaseYear', 'actors'], // 2 levels + 'orders' => ['DESC', 'DESC'], + ]); - $twoLevelsArray = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), [ - 'key' => 'index-ip-actors', - 'type' => 'key', - $this->getIndexAttributesParam() => ['releaseYear', 'actors'], // 2 levels - 'orders' => ['DESC', 'DESC'], - ]); + $this->assertEquals(400, $twoLevelsArray['headers']['status-code']); + $this->assertEquals('Creating indexes on array attributes is not currently supported.', $twoLevelsArray['body']['message']); - $this->assertEquals(400, $twoLevelsArray['headers']['status-code']); - $this->assertEquals('Creating indexes on array attributes is not currently supported.', $twoLevelsArray['body']['message']); + $unknown = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'key' => 'index-unknown', + 'type' => 'key', + $this->getIndexAttributesParam() => ['Unknown'], + ]); - $unknown = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), [ - 'key' => 'index-unknown', - 'type' => 'key', - $this->getIndexAttributesParam() => ['Unknown'], - ]); + $this->assertEquals(400, $unknown['headers']['status-code']); + $this->assertStringContainsString('\'Unknown\' required for the index could not be found', $unknown['body']['message']); - $this->assertEquals(400, $unknown['headers']['status-code']); - $this->assertStringContainsString('\'Unknown\' required for the index could not be found', $unknown['body']['message']); - $index1 = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), [ - 'key' => 'integers-order', - 'type' => 'key', - $this->getIndexAttributesParam() => ['integers'], // array attribute - 'orders' => ['DESC'], // Check order is removed in API - ]); + $index1 = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'key' => 'integers-order', + 'type' => 'key', + $this->getIndexAttributesParam() => ['integers'], // array attribute + 'orders' => ['DESC'], // Check order is removed in API + ]); - $this->assertEquals(400, $index1['headers']['status-code']); - $this->assertEquals('Creating indexes on array attributes is not currently supported.', $index1['body']['message']); + $this->assertEquals(400, $index1['headers']['status-code']); + $this->assertEquals('Creating indexes on array attributes is not currently supported.', $index1['body']['message']); - $index2 = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), [ - 'key' => 'integers-size', - 'type' => 'key', - $this->getIndexAttributesParam() => ['integers2'], // array attribute - ]); + $index2 = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'key' => 'integers-size', + 'type' => 'key', + $this->getIndexAttributesParam() => ['integers2'], // array attribute + ]); - $this->assertEquals(400, $index2['headers']['status-code']); - $this->assertEquals('Creating indexes on array attributes is not currently supported.', $index2['body']['message']); + $this->assertEquals(400, $index2['headers']['status-code']); + $this->assertEquals('Creating indexes on array attributes is not currently supported.', $index2['body']['message']); - if (!$this->getSupportForMultipleFulltextIndexes()) { - // Some databases only allow one fulltext index per collection - $this->assertEquals('There is already a fulltext index in the collection', $fulltextReleaseYear['body']['message']); - } else { - $this->assertEquals('Attribute "releaseYear" cannot be part of a fulltext index, must be of type string', $fulltextReleaseYear['body']['message']); - } + if (!$this->getSupportForMultipleFulltextIndexes()) { + // Some databases only allow one fulltext index per collection + $this->assertEquals('There is already a fulltext index in the collection', $fulltextReleaseYear['body']['message']); + } else { + $this->assertEquals('Attribute "releaseYear" cannot be part of a fulltext index, must be of type string', $fulltextReleaseYear['body']['message']); + } - /** - * Create Indexes by worker - */ - $this->waitForAllIndexes($databaseId, $collectionId); + /** + * Create Indexes by worker + */ + $this->waitForAllIndexes($databaseId, $collectionId); - $collectionResponse = $this->client->call(Client::METHOD_GET, $this->getContainerUrl($databaseId, $collectionId), array_merge([ + $collectionResponse = $this->client->call(Client::METHOD_GET, $this->getContainerUrl($databaseId, $collectionId), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), []); + + $this->assertIsArray($collectionResponse['body']['indexes']); + $expectedIndexCount = $this->getMaxIndexLength() < 1024 ? 4 : 5; // MongoDB accepts tooLong index + $this->assertCount($expectedIndexCount, $collectionResponse['body']['indexes']); + $indexKeys = array_column($collectionResponse['body']['indexes'], 'key'); + $this->assertContains($titleIndex['body']['key'], $indexKeys); + $this->assertContains($releaseYearIndex['body']['key'], $indexKeys); + $this->assertContains($releaseWithDate1['body']['key'], $indexKeys); + $this->assertContains($releaseWithDate2['body']['key'], $indexKeys); + + $this->assertEventually(function () use ($databaseId, $collectionId) { + $collResp = $this->client->call(Client::METHOD_GET, $this->getContainerUrl($databaseId, $collectionId), array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), []); + ])); - $this->assertIsArray($collectionResponse['body']['indexes']); - $expectedIndexCount = $this->getMaxIndexLength() < 1024 ? 4 : 5; // MongoDB accepts tooLong index - $this->assertCount($expectedIndexCount, $collectionResponse['body']['indexes']); - $indexKeys = array_column($collectionResponse['body']['indexes'], 'key'); - $this->assertContains($titleIndex['body']['key'], $indexKeys); - $this->assertContains($releaseYearIndex['body']['key'], $indexKeys); - $this->assertContains($releaseWithDate1['body']['key'], $indexKeys); - $this->assertContains($releaseWithDate2['body']['key'], $indexKeys); + foreach ($collResp['body']['indexes'] as $index) { + $this->assertEquals('available', $index['status']); + } - $this->assertEventually(function () use ($databaseId, $collectionId) { - $collResp = $this->client->call(Client::METHOD_GET, $this->getContainerUrl($databaseId, $collectionId), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - foreach ($collResp['body']['indexes'] as $index) { - $this->assertEquals('available', $index['status']); - } - - return true; - }, 60000, 500); - } + return true; + }, 60000, 500); } public function testGetIndexByKeyWithLengths(): void { - if (!$this->getSupportForAttributes()) { - $this->expectNotToPerformAssertions(); - return; - } $data = $this->setupAttributes(); $databaseId = $data['databaseId']; $collectionId = $data['moviesId']; @@ -2585,7 +2544,6 @@ trait DatabasesBase $this->getRecordIdParam() => ID::unique(), 'data' => [ 'releaseYear' => 2020, // Missing title, expect an 400 error - 'birthDay' => null // adding null here as documentsdb will require it as for documentsdb this document will be created ], 'permissions' => [ Permission::read(Role::user($this->getUser()['$id'])), @@ -2605,11 +2563,7 @@ trait DatabasesBase $this->assertCount(2, $document1['body']['actors']); $this->assertEquals($document1['body']['actors'][0], 'Chris Evans'); $this->assertEquals($document1['body']['actors'][1], 'Samuel Jackson'); - if ($this->getSupportForAttributes()) { - $this->assertEquals($document1['body']['birthDay'], '1975-06-12T12:12:55.000+00:00'); - } else { - $this->assertEquals($document1['body']['birthDay'], '1975-06-12 14:12:55+02:00'); - } + $this->assertEquals($document1['body']['birthDay'], '1975-06-12T12:12:55.000+00:00'); $this->assertTrue(array_key_exists('$sequence', $document1['body'])); $this->getSupportForIntegerIds() @@ -2646,18 +2600,10 @@ trait DatabasesBase $this->assertCount(2, $document3['body']['actors']); $this->assertEquals($document3['body']['actors'][0], 'Tom Holland'); $this->assertEquals($document3['body']['actors'][1], 'Zendaya Maree Stoermer'); - if ($this->getSupportForAttributes()) { - $this->assertEquals($document3['body']['birthDay'], '1975-06-12T18:12:55.000+00:00'); // UTC for NY - } else { - $this->assertEquals($document1['body']['birthDay'], '1975-06-12 14:12:55+02:00'); - } + $this->assertEquals($document3['body']['birthDay'], '1975-06-12T18:12:55.000+00:00'); // UTC for NY $this->assertTrue(array_key_exists('$sequence', $document3['body'])); - if ($this->getSupportForAttributes()) { - $this->assertEquals(400, $document4['headers']['status-code']); - } else { - $this->assertEquals(201, $document4['headers']['status-code']); - } + $this->assertEquals(400, $document4['headers']['status-code']); } public function testUpsertDocument(): void @@ -2810,10 +2756,6 @@ trait DatabasesBase $this->assertEquals(204, $document['headers']['status-code']); // relationship behaviour - only test on databases that support relationships - /** @var array|null $person */ - $person = null; - /** @var array|null $library */ - $library = null; if ($this->getSupportForRelationships()) { $person = $this->client->call(Client::METHOD_POST, $this->getContainerUrl($databaseId), array_merge([ 'content-type' => 'application/json', @@ -3135,7 +3077,7 @@ trait DatabasesBase $this->assertEquals(204, $deleteResponse['headers']['status-code']); // upsertion for the related document without passing permissions - only for databases that support relationships - if ($this->getSupportForRelationships() && $person !== null && $library !== null) { + if ($this->getSupportForRelationships()) { // data should get added $newPersonId = ID::unique(); $personNoPerm = $this->client->call(Client::METHOD_PUT, $this->getRecordUrl($databaseId, $person['body']['$id'], $newPersonId), array_merge([ @@ -3326,10 +3268,6 @@ trait DatabasesBase public function testListDocumentsWithCache(): void { - if (!$this->getSupportForAttributes()) { - $this->markTestSkipped('Attributes are not supported by this database adapter'); - return; - } $data = $this->setupDocuments(); $databaseId = $data['databaseId']; $docIds = $data['documentIds']; @@ -3460,10 +3398,6 @@ trait DatabasesBase public function testListDocumentsCacheBustedByAttributeChange(): void { - if (!$this->getSupportForAttributes()) { - $this->markTestSkipped('Attributes are not supported by this database adapter'); - return; - } $data = $this->setupDocuments(); $databaseId = $data['databaseId']; $docIds = $data['documentIds']; @@ -4044,43 +3978,34 @@ trait DatabasesBase ]); $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertEquals(0, $documents['body']['total']); - // for tablesdb/legacy it is full match , for docsdb inner pattern is matched - if ($this->getSupportForAttributes()) { - $this->assertEquals(0, $documents['body']['total']); - } else { - $this->assertGreaterThan(0, $documents['body']['total']); - } + $documents = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::greaterThan('birthDay', '16/01/2024 12:00:00AM')->toString(), + ], + ]); - if ($this->getSupportForAttributes()) { - $documents = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::greaterThan('birthDay', '16/01/2024 12:00:00AM')->toString(), - ], - ]); + $this->assertEquals(400, $documents['headers']['status-code']); + $this->assertEquals('Invalid query: Query value is invalid for attribute "birthDay"', $documents['body']['message']); - $this->assertEquals(400, $documents['headers']['status-code']); - $this->assertEquals('Invalid query: Query value is invalid for attribute "birthDay"', $documents['body']['message']); - - $documents = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::greaterThan('birthDay', '1960-01-01 10:10:10+02:30')->toString(), - ], - ]); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertGreaterThanOrEqual(2, count($documents['body'][$this->getRecordResource()])); - $birthDays = array_column($documents['body'][$this->getRecordResource()], 'birthDay'); - $this->assertContains('1975-06-12T12:12:55.000+00:00', $birthDays); - $this->assertContains('1975-06-12T18:12:55.000+00:00', $birthDays); - } + $documents = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::greaterThan('birthDay', '1960-01-01 10:10:10+02:30')->toString(), + ], + ]); + $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertGreaterThanOrEqual(2, count($documents['body'][$this->getRecordResource()])); + $birthDays = array_column($documents['body'][$this->getRecordResource()], 'birthDay'); + $this->assertContains('1975-06-12T12:12:55.000+00:00', $birthDays); + $this->assertContains('1975-06-12T18:12:55.000+00:00', $birthDays); $documents = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([ 'content-type' => 'application/json', @@ -4724,10 +4649,6 @@ trait DatabasesBase public function testInvalidDocumentStructure(): void { - if (!$this->getSupportForAttributes()) { - $this->markTestSkipped('Attributes are not supported by this database adapter'); - return; - } $database = $this->client->call(Client::METHOD_POST, $this->getApiBasePath(), array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -5428,18 +5349,22 @@ trait DatabasesBase $this->assertEquals($collection['body'][$this->getSecurityResponseKey()], true); $collectionId = $collection['body']['$id']; - if ($this->getSupportForAttributes()) { - $attribute = $this->createAttribute($databaseId, $collectionId, 'string', [ - 'key' => 'attribute', - 'size' => 64, - 'required' => true, - ]); - $this->assertEquals(202, $attribute['headers']['status-code'], 202); - $this->assertEquals('attribute', $attribute['body']['key']); - // wait for db to add attribute - $this->waitForAttribute($databaseId, $collectionId, 'attribute'); - } + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'attribute', + 'size' => 64, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code'], 202); + $this->assertEquals('attribute', $attribute['body']['key']); + + // wait for db to add attribute + $this->waitForAttribute($databaseId, $collectionId, 'attribute'); $index = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ 'content-type' => 'application/json', @@ -5448,7 +5373,7 @@ trait DatabasesBase ]), [ 'key' => 'key_attribute', 'type' => 'key', - $this->getIndexAttributesParam() => ['attribute'], + $this->getIndexAttributesParam() => [$attribute['body']['key']], ]); $this->assertEquals(202, $index['headers']['status-code']); @@ -5614,17 +5539,21 @@ trait DatabasesBase $this->assertEquals($collection['body'][$this->getSecurityResponseKey()], false); $collectionId = $collection['body']['$id']; - if ($this->getSupportForAttributes()) { - $attribute = $this->createAttribute($databaseId, $collectionId, 'string', [ - 'key' => 'attribute', - 'size' => 64, - 'required' => true, - ]); - $this->assertEquals(202, $attribute['headers']['status-code'], 202); - $this->assertEquals('attribute', $attribute['body']['key']); - $this->waitForAttribute($databaseId, $collectionId, 'attribute'); - } + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'attribute', + 'size' => 64, + 'required' => true, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code'], 202); + $this->assertEquals('attribute', $attribute['body']['key']); + + $this->waitForAttribute($databaseId, $collectionId, 'attribute'); $index = $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ 'content-type' => 'application/json', @@ -5633,7 +5562,7 @@ trait DatabasesBase ]), [ 'key' => 'key_attribute', 'type' => 'key', - $this->getIndexAttributesParam() => ['attribute'], + $this->getIndexAttributesParam() => [$attribute['body']['key']], ]); $this->assertEquals(202, $index['headers']['status-code'], 'Index creation failed: ' . json_encode($index['body'] ?? [])); @@ -6000,18 +5929,21 @@ trait DatabasesBase $moviesId = $movies['body']['$id']; // create attribute - if ($this->getSupportForAttributes()) { - $title = $this->createAttribute($databaseId, $moviesId, 'string', [ - 'key' => 'title', - 'size' => 256, - 'required' => true, - ]); + $title = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $moviesId) . '/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']); + $this->assertEquals(202, $title['headers']['status-code']); + + // wait for database worker to create attributes + $this->waitForAttribute($databaseId, $moviesId, 'title'); - // wait for database worker to create attributes - $this->waitForAttribute($databaseId, $moviesId, 'title'); - } // add document $document = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $moviesId), array_merge([ 'content-type' => 'application/json', @@ -6076,11 +6008,6 @@ trait DatabasesBase public function testAttributeBooleanDefault(): void { - if (!$this->getSupportForAttributes()) { - $this->expectNotToPerformAssertions(); - return; - } - $data = $this->setupDatabase(); $databaseId = $data['databaseId']; @@ -6967,25 +6894,32 @@ trait DatabasesBase $this->assertEquals(201, $presidents['headers']['status-code']); $this->assertEquals($presidents['body']['name'], 'USA Presidents'); - // Create Attributes (only for adapters that support attributes) - if ($this->getSupportForAttributes()) { - $firstName = $this->createAttribute($databaseId, $presidents['body']['$id'], 'string', [ - 'key' => 'first_name', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $firstName['headers']['status-code']); + // Create Attributes + $firstName = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $presidents['body']['$id']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'first_name', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $firstName['headers']['status-code']); - $lastName = $this->createAttribute($databaseId, $presidents['body']['$id'], 'string', [ - 'key' => 'last_name', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $lastName['headers']['status-code']); + $lastName = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $presidents['body']['$id']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'last_name', + 'size' => 256, + 'required' => true, + ]); - // Wait for worker - $this->waitForAllAttributes($databaseId, $presidents['body']['$id']); - } + $this->assertEquals(202, $lastName['headers']['status-code']); + + // Wait for worker + $this->waitForAllAttributes($databaseId, $presidents['body']['$id']); $document1 = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $presidents['body']['$id']), array_merge([ 'content-type' => 'application/json', @@ -7190,19 +7124,20 @@ trait DatabasesBase 'databaseId' => $databaseId, ]; - // Create attribute only on adapters that support attributes; DocumentsDB can still store the field schemalessly - if ($this->getSupportForAttributes()) { - $longtext = $this->createAttribute($data['databaseId'], $data['$id'], 'string', [ - 'key' => 'longtext', - 'size' => 100000000, - 'required' => false, - 'default' => null, - ]); + $longtext = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($data['databaseId'], $data['$id']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'longtext', + 'size' => 100000000, + 'required' => false, + 'default' => null, + ]); - $this->assertEquals(202, $longtext['headers']['status-code']); + $this->assertEquals($longtext['headers']['status-code'], 202); - $this->waitForAttribute($data['databaseId'], $data['$id'], 'longtext'); - } + $this->waitForAttribute($data['databaseId'], $data['$id'], 'longtext'); for ($i = 0; $i < 10; $i++) { $this->client->call(Client::METHOD_POST, $this->getRecordUrl($data['databaseId'], $data['$id']), array_merge([ @@ -7270,20 +7205,17 @@ trait DatabasesBase ]); $collectionId = $collection['body']['$id']; - // Add integer attribute only when supported; schemaless adapters (e.g. documentsdb) - // can still store the field without a predefined attribute. - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/integer', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'count', - 'required' => true, - ]); + // Add integer attribute + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'count', + 'required' => true, + ]); - $this->waitForAttribute($databaseId, $collectionId, 'count'); - } + $this->waitForAttribute($databaseId, $collectionId, 'count'); // Create document with initial count = 5 $doc = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $collectionId), array_merge([ @@ -7348,10 +7280,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ])); - $this->assertEquals( - $this->getSupportForAttributes() ? 404 : 200, - $notFound['headers']['status-code'] - ); + $this->assertEquals(404, $notFound['headers']['status-code']); // Test increment with value 0 $inc3 = $this->client->call(Client::METHOD_PATCH, $this->getRecordUrl($databaseId, $collectionId, $docId) . "/count/increment", array_merge([ @@ -7392,20 +7321,17 @@ trait DatabasesBase $collectionId = $collection['body']['$id']; - // Add integer attribute only when supported; schemaless adapters can still - // store the field without a predefined attribute. - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/integer', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'count', - 'required' => true, - ]); + // Add integer attribute + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId) . '/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'count', + 'required' => true, + ]); - $this->waitForAttribute($databaseId, $collectionId, 'count'); - } + $this->waitForAttribute($databaseId, $collectionId, 'count'); // Create document with initial count = 10 $doc = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $collectionId), array_merge([ @@ -9827,25 +9753,32 @@ trait DatabasesBase $this->assertEquals(201, $movies['headers']['status-code']); $this->assertEquals($movies['body']['name'], 'Movies'); - // Create Attributes (only when supported; DocumentsDB can still store fields schemalessly) - if ($this->getSupportForAttributes()) { - $title = $this->createAttribute($databaseId, $movies['body']['$id'], 'string', [ - 'key' => 'title', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $title['headers']['status-code']); + // Create Attributes + $title = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $movies['body']['$id']) . '/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']); - $genre = $this->createAttribute($databaseId, $movies['body']['$id'], 'string', [ - 'key' => 'genre', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $genre['headers']['status-code']); + $genre = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $movies['body']['$id']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'genre', + 'size' => 256, + 'required' => true, + ]); - // Wait for worker - $this->waitForAllAttributes($databaseId, $movies['body']['$id']); - } + $this->assertEquals(202, $genre['headers']['status-code']); + + // Wait for worker + $this->waitForAllAttributes($databaseId, $movies['body']['$id']); $row1 = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $movies['body']['$id']), array_merge([ 'content-type' => 'application/json', @@ -9988,24 +9921,31 @@ trait DatabasesBase $this->assertEquals(201, $products['headers']['status-code']); $this->assertEquals($products['body']['name'], 'Products'); - // Create Attributes (only when supported) - if ($this->getSupportForAttributes()) { - $name = $this->createAttribute($databaseId, $products['body']['$id'], 'string', [ - 'key' => 'name', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $name['headers']['status-code']); + // Create Attributes + $name = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $products['body']['$id']) . '/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->createAttribute($databaseId, $products['body']['$id'], 'float', [ - 'key' => 'price', - 'required' => true, - ]); - $this->assertEquals(202, $price['headers']['status-code']); + $price = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $products['body']['$id']) . '/float', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'price', + 'required' => true, + ]); - // Wait for worker - $this->waitForAllAttributes($databaseId, $products['body']['$id']); - } + $this->assertEquals(202, $price['headers']['status-code']); + + // Wait for worker + $this->waitForAllAttributes($databaseId, $products['body']['$id']); $row1 = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $products['body']['$id']), array_merge([ 'content-type' => 'application/json', @@ -10113,25 +10053,32 @@ trait DatabasesBase $this->assertEquals(201, $employees['headers']['status-code']); $this->assertEquals($employees['body']['name'], 'Employees'); - // Create Attributes (only when supported) - if ($this->getSupportForAttributes()) { - $name = $this->createAttribute($databaseId, $employees['body']['$id'], 'string', [ - 'key' => 'name', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $name['headers']['status-code']); + // Create Attributes + $name = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $employees['body']['$id']) . '/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']); - $department = $this->createAttribute($databaseId, $employees['body']['$id'], 'string', [ - 'key' => 'department', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $department['headers']['status-code']); + $department = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $employees['body']['$id']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'department', + 'size' => 256, + 'required' => true, + ]); - // Wait for worker - $this->waitForAllAttributes($databaseId, $employees['body']['$id']); - } + $this->assertEquals(202, $department['headers']['status-code']); + + // Wait for worker + $this->waitForAllAttributes($databaseId, $employees['body']['$id']); $row1 = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $employees['body']['$id']), array_merge([ 'content-type' => 'application/json', @@ -10239,25 +10186,32 @@ trait DatabasesBase $this->assertEquals(201, $files['headers']['status-code']); $this->assertEquals($files['body']['name'], 'Files'); - // Create Attributes (only when supported) - if ($this->getSupportForAttributes()) { - $filename = $this->createAttribute($databaseId, $files['body']['$id'], 'string', [ - 'key' => 'filename', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $filename['headers']['status-code']); + // Create Attributes + $filename = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $files['body']['$id']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'filename', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $filename['headers']['status-code']); - $type = $this->createAttribute($databaseId, $files['body']['$id'], 'string', [ - 'key' => 'type', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $type['headers']['status-code']); + $type = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $files['body']['$id']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'type', + 'size' => 256, + 'required' => true, + ]); - // Wait for worker - $this->waitForAllAttributes($databaseId, $files['body']['$id']); - } + $this->assertEquals(202, $type['headers']['status-code']); + + // Wait for worker + $this->waitForAllAttributes($databaseId, $files['body']['$id']); $row1 = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $files['body']['$id']), array_merge([ 'content-type' => 'application/json', @@ -10365,25 +10319,32 @@ trait DatabasesBase $this->assertEquals(201, $posts['headers']['status-code']); $this->assertEquals($posts['body']['name'], 'Posts'); - // Create Attributes (only when supported) - if ($this->getSupportForAttributes()) { - $title = $this->createAttribute($databaseId, $posts['body']['$id'], 'string', [ - 'key' => 'title', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $title['headers']['status-code']); + // Create Attributes + $title = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $posts['body']['$id']) . '/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->createAttribute($databaseId, $posts['body']['$id'], 'string', [ - 'key' => 'content', - 'size' => 512, - 'required' => true, - ]); - $this->assertEquals(202, $content['headers']['status-code']); + $content = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $posts['body']['$id']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'content', + 'size' => 512, + 'required' => true, + ]); - // Wait for worker - $this->waitForAllAttributes($databaseId, $posts['body']['$id']); - } + $this->assertEquals(202, $content['headers']['status-code']); + + // Wait for worker + $this->waitForAllAttributes($databaseId, $posts['body']['$id']); $row1 = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $posts['body']['$id']), array_merge([ 'content-type' => 'application/json', @@ -10498,25 +10459,32 @@ trait DatabasesBase $this->assertEquals(201, $events['headers']['status-code']); $this->assertEquals($events['body']['name'], 'Events'); - // Create Attributes (only when supported) - if ($this->getSupportForAttributes()) { - $name = $this->createAttribute($databaseId, $events['body']['$id'], 'string', [ - 'key' => 'name', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $name['headers']['status-code']); + // Create Attributes + $name = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $events['body']['$id']) . '/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']); - $description = $this->createAttribute($databaseId, $events['body']['$id'], 'string', [ - 'key' => 'description', - 'size' => 512, - 'required' => true, - ]); - $this->assertEquals(202, $description['headers']['status-code']); + $description = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $events['body']['$id']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'description', + 'size' => 512, + 'required' => true, + ]); - // Wait for worker - $this->waitForAllAttributes($databaseId, $events['body']['$id']); - } + $this->assertEquals(202, $description['headers']['status-code']); + + // Wait for worker + $this->waitForAllAttributes($databaseId, $events['body']['$id']); $row1 = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $events['body']['$id']), array_merge([ 'content-type' => 'application/json', @@ -10631,25 +10599,31 @@ trait DatabasesBase $this->assertEquals(201, $articles['headers']['status-code']); $this->assertEquals($articles['body']['name'], 'Articles'); - // Create Attributes (only when supported) - if ($this->getSupportForAttributes()) { - $title = $this->createAttribute($databaseId, $articles['body']['$id'], 'string', [ - 'key' => 'title', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $title['headers']['status-code']); + // Create Attributes + $title = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $articles['body']['$id']) . '/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->createAttribute($databaseId, $articles['body']['$id'], 'string', [ - 'key' => 'content', - 'size' => 5000, - 'required' => true, - ]); - $this->assertEquals(202, $content['headers']['status-code']); + $content = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $articles['body']['$id']) . '/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 - $this->waitForAllAttributes($databaseId, $articles['body']['$id']); - } + // Wait for attributes to be available + $this->waitForAllAttributes($databaseId, $articles['body']['$id']); // Create first article $row1 = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $articles['body']['$id']), array_merge([ @@ -10810,25 +10784,32 @@ trait DatabasesBase $this->assertEquals(201, $tasks['headers']['status-code']); $this->assertEquals($tasks['body']['name'], 'Tasks'); - // Create Attributes (only when supported) - if ($this->getSupportForAttributes()) { - $title = $this->createAttribute($databaseId, $tasks['body']['$id'], 'string', [ - 'key' => 'title', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $title['headers']['status-code']); + // Create Attributes + $title = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tasks['body']['$id']) . '/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']); - $status = $this->createAttribute($databaseId, $tasks['body']['$id'], 'string', [ - 'key' => 'status', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $status['headers']['status-code']); + $status = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tasks['body']['$id']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => true, + ]); - // Wait for worker - $this->waitForAllAttributes($databaseId, $tasks['body']['$id']); - } + $this->assertEquals(202, $status['headers']['status-code']); + + // Wait for worker + $this->waitForAllAttributes($databaseId, $tasks['body']['$id']); $row1 = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $tasks['body']['$id']), array_merge([ 'content-type' => 'application/json', @@ -10976,25 +10957,32 @@ trait DatabasesBase $this->assertEquals(201, $orders['headers']['status-code']); $this->assertEquals($orders['body']['name'], 'Orders'); - // Create Attributes (only when supported) - if ($this->getSupportForAttributes()) { - $orderNumber = $this->createAttribute($databaseId, $orders['body']['$id'], 'string', [ - 'key' => 'orderNumber', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $orderNumber['headers']['status-code']); + // Create Attributes + $orderNumber = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $orders['body']['$id']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'orderNumber', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $orderNumber['headers']['status-code']); - $status = $this->createAttribute($databaseId, $orders['body']['$id'], 'string', [ - 'key' => 'status', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $status['headers']['status-code']); + $status = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $orders['body']['$id']) . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => true, + ]); - // Wait for worker - $this->waitForAllAttributes($databaseId, $orders['body']['$id']); - } + $this->assertEquals(202, $status['headers']['status-code']); + + // Wait for worker + $this->waitForAllAttributes($databaseId, $orders['body']['$id']); $row1 = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $orders['body']['$id']), array_merge([ 'content-type' => 'application/json', @@ -11141,24 +11129,30 @@ trait DatabasesBase $this->assertEquals(201, $products['headers']['status-code']); $this->assertEquals($products['body']['name'], 'Products'); - // Create Attributes (only when supported) - if ($this->getSupportForAttributes()) { - $name = $this->createAttribute($databaseId, $products['body']['$id'], 'string', [ - 'key' => 'name', - 'size' => 256, - 'required' => true, - ]); - $this->assertEquals(202, $name['headers']['status-code']); + // Create Attributes + $name = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $products['body']['$id']) . '/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->createAttribute($databaseId, $products['body']['$id'], 'float', [ - 'key' => 'price', - 'required' => true, - ]); - $this->assertEquals(202, $price['headers']['status-code']); + $price = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $products['body']['$id']) . '/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 - $this->waitForAllAttributes($databaseId, $products['body']['$id']); - } + // Wait for attributes to be available + $this->waitForAllAttributes($databaseId, $products['body']['$id']); // Create first product $row1 = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $products['body']['$id']), array_merge([ diff --git a/tests/e2e/Services/Databases/DocumentsDB/DocumentsDBIndexTest.php b/tests/e2e/Services/Databases/DocumentsDB/DocumentsDBIndexTest.php deleted file mode 100644 index 1fdcc84d0c..0000000000 --- a/tests/e2e/Services/Databases/DocumentsDB/DocumentsDBIndexTest.php +++ /dev/null @@ -1,362 +0,0 @@ -client->call( - 'POST', - '/documentsdb', - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], - [ - 'databaseId' => ID::unique(), - 'name' => 'DocumentsDB Indexes', - ] - ); - - $this->assertNotEmpty($database['body']['$id']); - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - $movies = $this->client->call( - 'POST', - '/documentsdb/' . $databaseId . '/collections', - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], - [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'documentSecurity' => true, - ] - ); - - $this->assertEquals(201, $movies['headers']['status-code']); - $moviesId = $movies['body']['$id']; - - $titleIndex = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'titleIndex', - 'type' => 'fulltext', - 'attributes' => ['title'], - ]); - - $this->assertEquals(202, $titleIndex['headers']['status-code']); - $this->assertEquals('titleIndex', $titleIndex['body']['key']); - $this->assertEquals('fulltext', $titleIndex['body']['type']); - $this->assertCount(1, $titleIndex['body']['attributes']); - $this->assertEquals('title', $titleIndex['body']['attributes'][0]); - - $releaseYearIndex = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'releaseYear', - 'type' => 'key', - 'attributes' => ['releaseYear'], - ]); - - $this->assertEquals(202, $releaseYearIndex['headers']['status-code']); - $this->assertEquals('releaseYear', $releaseYearIndex['body']['key']); - $this->assertEquals('key', $releaseYearIndex['body']['type']); - $this->assertCount(1, $releaseYearIndex['body']['attributes']); - $this->assertEquals('releaseYear', $releaseYearIndex['body']['attributes'][0]); - - $releaseWithDate1 = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'releaseYearDated', - 'type' => 'key', - 'attributes' => ['releaseYear', '$createdAt', '$updatedAt'], - ]); - - $this->assertEquals(202, $releaseWithDate1['headers']['status-code']); - $this->assertEquals('releaseYearDated', $releaseWithDate1['body']['key']); - $this->assertEquals('key', $releaseWithDate1['body']['type']); - $this->assertCount(3, $releaseWithDate1['body']['attributes']); - $this->assertEquals('releaseYear', $releaseWithDate1['body']['attributes'][0]); - $this->assertEquals('$createdAt', $releaseWithDate1['body']['attributes'][1]); - $this->assertEquals('$updatedAt', $releaseWithDate1['body']['attributes'][2]); - - $releaseWithDate2 = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'birthDay', - 'type' => 'key', - 'attributes' => ['birthDay'], - ]); - - $this->assertEquals(202, $releaseWithDate2['headers']['status-code']); - $this->assertEquals('birthDay', $releaseWithDate2['body']['key']); - $this->assertEquals('key', $releaseWithDate2['body']['type']); - $this->assertCount(1, $releaseWithDate2['body']['attributes']); - $this->assertEquals('birthDay', $releaseWithDate2['body']['attributes'][0]); - - // Failure cases - $fulltextReleaseYear = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'releaseYearDated', - 'type' => 'fulltext', - 'attributes' => ['releaseYear'], - ]); - $this->assertEquals(400, $fulltextReleaseYear['headers']['status-code']); - - $noAttributes = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'none', - 'type' => 'key', - 'attributes' => [], - ]); - $this->assertEquals(400, $noAttributes['headers']['status-code']); - - $duplicates = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'duplicate', - 'type' => 'fulltext', - 'attributes' => ['releaseYear', 'releaseYear'], - ]); - $this->assertEquals(400, $duplicates['headers']['status-code']); - - $tooLong = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'tooLong', - 'type' => 'key', - 'attributes' => ['description', 'tagline'], - ]); - $this->assertEquals(202, $tooLong['headers']['status-code']); - - $fulltextArray = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'ft', - 'type' => 'fulltext', - 'attributes' => ['actors'], - ]); - $this->assertEquals(400, $fulltextArray['headers']['status-code']); - - $actorsArray = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'index-actors', - 'type' => 'key', - 'attributes' => ['actors'], - ]); - $this->assertEquals(202, $actorsArray['headers']['status-code']); - - $twoLevelsArray = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'index-ip-actors', - 'type' => 'key', - 'attributes' => ['releaseYear', 'actors'], - 'orders' => ['DESC', 'DESC'], - ]); - $this->assertEquals(202, $twoLevelsArray['headers']['status-code']); - - $unknown = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'index-unknown', - 'type' => 'key', - 'attributes' => ['Unknown'], - ]); - $this->assertEquals(202, $unknown['headers']['status-code']); - - $index1 = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'integers-order', - 'type' => 'key', - 'attributes' => ['integers'], - 'orders' => ['DESC'], - ]); - $this->assertEquals(202, $index1['headers']['status-code']); - - $index2 = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$moviesId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'integers-size', - 'type' => 'key', - 'attributes' => ['integers'], - ]); - $this->assertEquals(202, $index2['headers']['status-code']); - - // Let worker create indexes - sleep(2); - - $moviesWithIndexes = $this->client->call('GET', "/documentsdb/{$databaseId}/collections/{$moviesId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - $this->assertIsArray($moviesWithIndexes['body']['indexes']); - $this->assertCount(10, $moviesWithIndexes['body']['indexes']); - - $this->assertEventually(function () use ($databaseId, $moviesId) { - $movies = $this->client->call('GET', "/documentsdb/{$databaseId}/collections/{$moviesId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - foreach ($movies['body']['indexes'] as $index) { - $this->assertEquals('available', $index['status']); - } - - return true; - }, 60000, 500); - } - - public function testGetIndexByKeyWithLengths(): void - { - $database = $this->client->call( - 'POST', - '/documentsdb', - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], - [ - 'databaseId' => ID::unique(), - 'name' => 'DocumentsDB Index Lengths', - ] - ); - - $this->assertNotEmpty($database['body']['$id']); - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - $collection = $this->client->call( - 'POST', - "/documentsdb/{$databaseId}/collections", - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], - [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'documentSecurity' => true, - ] - ); - - $this->assertEquals(201, $collection['headers']['status-code']); - $collectionId = $collection['body']['$id']; - - $create = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'lengthTestIndex', - 'type' => 'key', - 'attributes' => ['title', 'description'], - 'lengths' => [128, 200], - ]); - $this->assertEquals(202, $create['headers']['status-code']); - - $index = $this->client->call('GET', "/documentsdb/{$databaseId}/collections/{$collectionId}/indexes/lengthTestIndex", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - $this->assertEquals(200, $index['headers']['status-code']); - $this->assertEquals('lengthTestIndex', $index['body']['key']); - $this->assertEquals([128, 200], $index['body']['lengths']); - - $create = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'lengthOverrideTestIndex', - 'type' => 'key', - 'attributes' => ['actors-new'], - 'lengths' => [Database::MAX_ARRAY_INDEX_LENGTH], - ]); - $this->assertEquals(202, $create['headers']['status-code']); - - $index = $this->client->call('GET', "/documentsdb/{$databaseId}/collections/{$collectionId}/indexes/lengthOverrideTestIndex", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - $this->assertEquals([Database::MAX_ARRAY_INDEX_LENGTH], $index['body']['lengths']); - - $create = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'lengthCountExceededIndex', - 'type' => 'key', - 'attributes' => ['title-not-throw-error'], - 'lengths' => [128, 128], - ]); - $this->assertEquals(202, $create['headers']['status-code']); - - $create = $this->client->call('POST', "/documentsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'lengthTooLargeIndex', - 'type' => 'key', - 'attributes' => ['title', 'description', 'tagline', 'actors'], - 'lengths' => [256, 256, 256, 20], - ]); - $this->assertEquals(202, $create['headers']['status-code']); - } -} diff --git a/tests/e2e/Services/Databases/DocumentsDBConsoleClientTest.php b/tests/e2e/Services/Databases/DocumentsDBConsoleClientTest.php deleted file mode 100644 index 895cf67490..0000000000 --- a/tests/e2e/Services/Databases/DocumentsDBConsoleClientTest.php +++ /dev/null @@ -1,16 +0,0 @@ -authorization)) { - return $this->authorization; - } - - $this->authorization = new Authorization(); - - return $this->authorization; - } - - public function createCollection(): array - { - $database = $this->client->call( - Client::METHOD_POST, - $this->getDatabaseUrl(), - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), - [ - 'databaseId' => ID::unique(), - 'name' => 'InvalidDocumentDatabase', - ] - ); - $this->assertEquals(201, $database['headers']['status-code']); - $this->assertEquals('InvalidDocumentDatabase', $database['body']['name']); - - $databaseId = $database['body']['$id']; - $publicMovies = $this->client->call( - Client::METHOD_POST, - $this->getContainerUrl($databaseId), - $this->getServerHeader(), - [ - $this->getContainerIdParam() => ID::unique(), - 'name' => 'Movies', - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ] - ); - $this->assertEquals(201, $publicMovies['headers']['status-code']); - - $privateMovies = $this->client->call( - Client::METHOD_POST, - $this->getContainerUrl($databaseId), - $this->getServerHeader(), - [ - $this->getContainerIdParam() => ID::unique(), - 'name' => 'Movies', - 'permissions' => [], - $this->getSecurityParam() => true, - ] - ); - $this->assertEquals(201, $privateMovies['headers']['status-code']); - - $publicCollection = ['id' => $publicMovies['body']['$id']]; - $privateCollection = ['id' => $privateMovies['body']['$id']]; - - return [ - 'databaseId' => $databaseId, - 'publicCollectionId' => $publicCollection['id'], - 'privateCollectionId' => $privateCollection['id'], - ]; - } - - public static function permissionsProvider(): array - { - return [ - [[Permission::read(Role::any())]], - [[Permission::read(Role::users())]], - [[Permission::update(Role::any()), Permission::delete(Role::any())]], - [[Permission::read(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any())]], - [[Permission::read(Role::users()), Permission::update(Role::users()), Permission::delete(Role::users())]], - [[Permission::read(Role::any()), Permission::update(Role::users()), Permission::delete(Role::users())]], - ]; - } - - #[DataProvider('permissionsProvider')] - public function testReadDocuments($permissions) - { - $data = $this->createCollection(); - $publicCollectionId = $data['publicCollectionId']; - $privateCollectionId = $data['privateCollectionId']; - $databaseId = $data['databaseId']; - - $publicResponse = $this->client->call( - Client::METHOD_POST, - $this->getRecordUrl($databaseId, $publicCollectionId), - $this->getServerHeader(), - [ - $this->getRecordIdParam() => ID::unique(), - 'data' => [ - 'title' => 'Lorem', - ], - 'permissions' => $permissions, - ] - ); - $privateResponse = $this->client->call( - Client::METHOD_POST, - $this->getRecordUrl($databaseId, $privateCollectionId), - $this->getServerHeader(), - [ - $this->getRecordIdParam() => ID::unique(), - 'data' => [ - 'title' => 'Lorem', - ], - 'permissions' => $permissions, - ] - ); - - $this->assertEquals(201, $publicResponse['headers']['status-code']); - $this->assertEquals(201, $privateResponse['headers']['status-code']); - - $roles = $this->getAuthorization()->getRoles(); - $this->getAuthorization()->cleanRoles(); - - $publicDocuments = $this->client->call( - Client::METHOD_GET, - $this->getRecordUrl($databaseId, $publicCollectionId), - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ] - ); - $privateDocuments = $this->client->call( - Client::METHOD_GET, - $this->getRecordUrl($databaseId, $privateCollectionId), - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ] - ); - - $recordKey = $this->getRecordResource(); - $this->assertEquals(1, $publicDocuments['body']['total']); - $this->assertEquals($permissions, $publicDocuments['body'][$recordKey][0]['$permissions']); - - if (\in_array(Permission::read(Role::any()), $permissions)) { - $this->assertEquals(1, $privateDocuments['body']['total']); - $this->assertEquals($permissions, $privateDocuments['body'][$recordKey][0]['$permissions']); - } else { - $this->assertEquals(0, $privateDocuments['body']['total']); - } - - foreach ($roles as $role) { - $this->getAuthorization()->addRole($role); - } - } - - public function testWriteDocument() - { - $data = $this->createCollection(); - $publicCollectionId = $data['publicCollectionId']; - $privateCollectionId = $data['privateCollectionId']; - $databaseId = $data['databaseId']; - - $roles = $this->getAuthorization()->getRoles(); - $this->getAuthorization()->cleanRoles(); - - $publicResponse = $this->client->call( - Client::METHOD_POST, - $this->getRecordUrl($databaseId, $publicCollectionId), - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], - [ - $this->getRecordIdParam() => ID::unique(), - 'data' => [ - 'title' => 'Lorem', - ], - ] - ); - - $publicDocumentId = $publicResponse['body']['$id']; - $this->assertEquals(201, $publicResponse['headers']['status-code']); - - $privateResponse = $this->client->call( - Client::METHOD_POST, - $this->getRecordUrl($databaseId, $privateCollectionId), - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], - [ - $this->getRecordIdParam() => ID::unique(), - 'data' => [ - 'title' => 'Lorem', - ], - ] - ); - - $this->assertEquals(401, $privateResponse['headers']['status-code']); - - // Create a document in private collection with API key so we can test that update and delete are also not allowed - $privateResponse = $this->client->call( - Client::METHOD_POST, - $this->getRecordUrl($databaseId, $privateCollectionId), - $this->getServerHeader(), - [ - $this->getRecordIdParam() => ID::unique(), - 'data' => [ - 'title' => 'Lorem', - ], - ] - ); - - $this->assertEquals(201, $privateResponse['headers']['status-code']); - $privateDocumentId = $privateResponse['body']['$id']; - - $publicDocument = $this->client->call( - Client::METHOD_PATCH, - $this->getRecordUrl($databaseId, $publicCollectionId, $publicDocumentId), - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], - [ - 'data' => [ - 'title' => 'Thor: Ragnarok', - ], - ] - ); - - $this->assertEquals(200, $publicDocument['headers']['status-code']); - $this->assertEquals('Thor: Ragnarok', $publicDocument['body']['title']); - - $privateDocument = $this->client->call( - Client::METHOD_PATCH, - $this->getRecordUrl($databaseId, $privateCollectionId, $privateDocumentId), - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], - [ - 'data' => [ - 'title' => 'Thor: Ragnarok', - ], - ] - ); - - $this->assertEquals(401, $privateDocument['headers']['status-code']); - - $publicDocument = $this->client->call( - Client::METHOD_DELETE, - $this->getRecordUrl($databaseId, $publicCollectionId, $publicDocumentId), - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ] - ); - - $this->assertEquals(204, $publicDocument['headers']['status-code']); - - $privateDocument = $this->client->call( - Client::METHOD_DELETE, - $this->getRecordUrl($databaseId, $privateCollectionId, $privateDocumentId), - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ] - ); - - $this->assertEquals(401, $privateDocument['headers']['status-code']); - - foreach ($roles as $role) { - $this->getAuthorization()->addRole($role); - } - } - - public function testWriteDocumentWithPermissions() - { - $database = $this->client->call( - Client::METHOD_POST, - $this->getDatabaseUrl(), - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), - [ - 'databaseId' => ID::unique(), - 'name' => 'GuestPermissionsWrite', - ] - ); - $this->assertEquals(201, $database['headers']['status-code']); - $this->assertEquals('GuestPermissionsWrite', $database['body']['name']); - - $databaseId = $database['body']['$id']; - $movies = $this->client->call( - Client::METHOD_POST, - $this->getContainerUrl($databaseId), - $this->getServerHeader(), - [ - $this->getContainerIdParam() => ID::unique(), - 'name' => 'Movies', - 'permissions' => [ - Permission::create(Role::any()), - ], - $this->getSecurityParam() => true, - ] - ); - - $moviesId = $movies['body']['$id']; - - $document = $this->client->call( - Client::METHOD_POST, - $this->getRecordUrl($databaseId, $moviesId), - [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], - [ - $this->getRecordIdParam() => ID::unique(), - 'data' => [ - 'title' => 'Thor: Ragnarok', - ], - 'permissions' => [ - Permission::read(Role::any()), - ], - ] - ); - - $this->assertEquals(201, $document['headers']['status-code']); - $this->assertEquals('Thor: Ragnarok', $document['body']['title']); - } -} diff --git a/tests/e2e/Services/Databases/Permissions/DocumentsDBPermissionsMemberTest.php b/tests/e2e/Services/Databases/Permissions/DocumentsDBPermissionsMemberTest.php deleted file mode 100644 index 88e84b017d..0000000000 --- a/tests/e2e/Services/Databases/Permissions/DocumentsDBPermissionsMemberTest.php +++ /dev/null @@ -1,238 +0,0 @@ - $this->createUser('user1', 'lorem@ipsum.com'), - 'user2' => $this->createUser('user2', 'dolor@ipsum.com'), - ]; - } - - public static function permissionsProvider(): array - { - return [ - [[Permission::read(Role::any())], 1, 1, 1], - [[Permission::read(Role::users())], 1, 1, 1], - [[Permission::read(Role::user(ID::custom('random')))], 1, 1, 0], - [[Permission::read(Role::user(ID::custom('lorem'))), Permission::update(Role::user('lorem')), Permission::delete(Role::user('lorem'))], 1, 1, 0], - [[Permission::read(Role::user(ID::custom('dolor'))), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))], 1, 1, 0], - [[Permission::read(Role::user(ID::custom('dolor'))), Permission::read(Role::user('lorem')), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))], 1, 1, 0], - [[Permission::update(Role::any()), Permission::delete(Role::any())], 1, 1, 0], - [[Permission::read(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any())], 1, 1, 1], - [[Permission::read(Role::any()), Permission::update(Role::users()), Permission::delete(Role::users())], 1, 1, 1], - [[Permission::read(Role::user(ID::custom('user1')))], 1, 1, 1], - [[Permission::read(Role::user(ID::custom('user1'))), Permission::read(Role::user(ID::custom('user1')))], 1, 1, 1], - [[Permission::read(Role::users()), Permission::update(Role::users()), Permission::delete(Role::users())], 1, 1, 1], - ]; - } - - /** - * Setup database helper - */ - protected function setupDatabase(): array - { - $cacheKey = $this->getProject()['$id'] . '_' . static::class; - - if (!empty(self::$setupDatabaseCache[$cacheKey])) { - return self::$setupDatabaseCache[$cacheKey]; - } - - $this->createUsers(); - - $db = $this->client->call( - Client::METHOD_POST, - $this->getDatabaseUrl(), - $this->getServerHeader(), - [ - 'databaseId' => ID::unique(), - 'name' => 'Test Database', - ] - ); - $this->assertEquals(201, $db['headers']['status-code']); - - $databaseId = $db['body']['$id']; - - $public = $this->client->call( - Client::METHOD_POST, - $this->getContainerUrl($databaseId), - $this->getServerHeader(), - [ - $this->getContainerIdParam() => ID::unique(), - 'name' => 'Movies', - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - $this->getSecurityParam() => true, - ] - ); - $this->assertEquals(201, $public['headers']['status-code']); - $this->collections = ['public' => $public['body']['$id']]; - - $private = $this->client->call( - Client::METHOD_POST, - $this->getContainerUrl($databaseId), - $this->getServerHeader(), - [ - $this->getContainerIdParam() => ID::unique(), - 'name' => 'Private Movies', - 'permissions' => [ - Permission::read(Role::users()), - Permission::create(Role::users()), - Permission::update(Role::users()), - Permission::delete(Role::users()), - ], - $this->getSecurityParam() => true, - ] - ); - $this->assertEquals(201, $private['headers']['status-code']); - $this->collections['private'] = $private['body']['$id']; - - $doconly = $this->client->call( - Client::METHOD_POST, - $this->getContainerUrl($databaseId), - $this->getServerHeader(), - [ - $this->getContainerIdParam() => ID::unique(), - 'name' => 'Document Only Movies', - 'permissions' => [], - $this->getSecurityParam() => true, - ] - ); - $this->assertEquals(201, $doconly['headers']['status-code']); - $this->collections['doconly'] = $doconly['body']['$id']; - - self::$setupDatabaseCache[$cacheKey] = [ - 'users' => $this->users, - 'collections' => $this->collections, - 'databaseId' => $databaseId, - ]; - - return self::$setupDatabaseCache[$cacheKey]; - } - - #[DataProvider('permissionsProvider')] - public function testReadDocuments($permissions, $anyCount, $usersCount, $docOnlyCount) - { - $data = $this->setupDatabase(); - $users = $data['users']; - $collections = $data['collections']; - $databaseId = $data['databaseId']; - - $response = $this->client->call( - Client::METHOD_POST, - $this->getRecordUrl($databaseId, $collections['public']), - $this->getServerHeader(), - [ - $this->getRecordIdParam() => ID::unique(), - 'data' => [ - 'title' => 'Lorem', - ], - 'permissions' => $permissions, - ] - ); - $this->assertEquals(201, $response['headers']['status-code']); - - $response = $this->client->call( - Client::METHOD_POST, - $this->getRecordUrl($databaseId, $collections['private']), - $this->getServerHeader(), - [ - $this->getRecordIdParam() => ID::unique(), - 'data' => [ - 'title' => 'Lorem', - ], - 'permissions' => $permissions, - ] - ); - $this->assertEquals(201, $response['headers']['status-code']); - - $response = $this->client->call( - Client::METHOD_POST, - $this->getRecordUrl($databaseId, $collections['doconly']), - $this->getServerHeader(), - [ - $this->getRecordIdParam() => ID::unique(), - 'data' => [ - 'title' => 'Lorem', - ], - 'permissions' => $permissions, - ] - ); - $this->assertEquals(201, $response['headers']['status-code']); - - /** - * Check "any" permission collection - */ - $documents = $this->client->call( - Client::METHOD_GET, - $this->getRecordUrl($databaseId, $collections['public']), - [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users['user1']['session'], - ] - ); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertGreaterThanOrEqual($anyCount, $documents['body']['total']); - - /** - * Check "users" permission collection - */ - $documents = $this->client->call( - Client::METHOD_GET, - $this->getRecordUrl($databaseId, $collections['private']), - [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users['user1']['session'], - ] - ); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertGreaterThanOrEqual($usersCount, $documents['body']['total']); - - /** - * Check "user:user1" document only permission collection - */ - $documents = $this->client->call( - Client::METHOD_GET, - $this->getRecordUrl($databaseId, $collections['doconly']), - [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users['user1']['session'], - ] - ); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertGreaterThanOrEqual($docOnlyCount, $documents['body']['total']); - } -} diff --git a/tests/e2e/Services/Databases/Permissions/DocumentsDBPermissionsTeamTest.php b/tests/e2e/Services/Databases/Permissions/DocumentsDBPermissionsTeamTest.php deleted file mode 100644 index db9adf9bb1..0000000000 --- a/tests/e2e/Services/Databases/Permissions/DocumentsDBPermissionsTeamTest.php +++ /dev/null @@ -1,234 +0,0 @@ - $this->createTeam('team1', 'Team 1'), - 'team2' => $this->createTeam('team2', 'Team 2'), - ]; - } - - public function createUsers(): array - { - return [ - 'user1' => $this->createUser('user1', 'lorem@ipsum.com'), - 'user2' => $this->createUser('user2', 'dolor@ipsum.com'), - 'user3' => $this->createUser('user3', 'sit@ipsum.com'), - ]; - } - - public function createCollections($teams) - { - $db = $this->client->call( - Client::METHOD_POST, - $this->getDatabaseUrl(), - $this->getServerHeader(), - [ - 'databaseId' => $this->databaseId, - 'name' => 'Test Database', - ] - ); - $this->assertEquals(201, $db['headers']['status-code']); - - $collection1 = $this->client->call( - Client::METHOD_POST, - $this->getContainerUrl($this->databaseId), - $this->getServerHeader(), - [ - $this->getContainerIdParam() => ID::custom('collection1'), - 'name' => 'Collection 1', - 'permissions' => [ - Permission::read(Role::team($teams['team1']['$id'])), - Permission::create(Role::team($teams['team1']['$id'], 'admin')), - Permission::update(Role::team($teams['team1']['$id'], 'admin')), - Permission::delete(Role::team($teams['team1']['$id'], 'admin')), - ], - ] - ); - $this->assertEquals(201, $collection1['headers']['status-code']); - - $this->collections['collection1'] = $collection1['body']['$id']; - - $collection2 = $this->client->call( - Client::METHOD_POST, - $this->getContainerUrl($this->databaseId), - $this->getServerHeader(), - [ - $this->getContainerIdParam() => ID::custom('collection2'), - 'name' => 'Collection 2', - 'permissions' => [ - Permission::read(Role::team($teams['team2']['$id'])), - Permission::create(Role::team($teams['team2']['$id'], 'owner')), - Permission::update(Role::team($teams['team2']['$id'], 'owner')), - Permission::delete(Role::team($teams['team2']['$id'], 'owner')), - ], - ] - ); - $this->assertEquals(201, $collection2['headers']['status-code']); - - $this->collections['collection2'] = $collection2['body']['$id']; - - return $this->collections; - } - - /* - * $success = can $user read from $collection - * [$user, $collection, $success] - */ - public static function readDocumentsProvider(): array - { - return [ - ['user1', 'collection1', true], - ['user2', 'collection1', false], - ['user3', 'collection1', true], - ['user1', 'collection2', false], - ['user2', 'collection2', true], - ['user3', 'collection2', true], - ]; - } - - /* - * $success = can $user write to $collection - * [$user, $collection, $success] - */ - public static function writeDocumentsProvider(): array - { - return [ - ['user1', 'collection1', true], - ['user2', 'collection1', false], - ['user3', 'collection1', false], - ['user1', 'collection2', false], - ['user2', 'collection2', true], - ['user3', 'collection2', false], - ]; - } - - /** - * Setup database helper - */ - protected function setupDatabase(): array - { - $cacheKey = $this->getProject()['$id'] . '_' . static::class; - - if (!empty(self::$setupDatabaseCache[$cacheKey])) { - return self::$setupDatabaseCache[$cacheKey]; - } - - $this->createUsers(); - $this->createTeams(); - - $this->addToTeam('user1', 'team1', ['admin']); - $this->addToTeam('user2', 'team2', ['owner']); - - // user3 in both teams but with no roles - $this->addToTeam('user3', 'team1'); - $this->addToTeam('user3', 'team2'); - - $this->createCollections($this->teams); - - $response = $this->client->call( - Client::METHOD_POST, - $this->getRecordUrl($this->databaseId, $this->collections['collection1']), - $this->getServerHeader(), - [ - $this->getRecordIdParam() => ID::unique(), - 'data' => [ - 'title' => 'Lorem', - ], - ] - ); - $this->assertEquals(201, $response['headers']['status-code']); - - $response = $this->client->call( - Client::METHOD_POST, - $this->getRecordUrl($this->databaseId, $this->collections['collection2']), - $this->getServerHeader(), - [ - $this->getRecordIdParam() => ID::unique(), - 'data' => [ - 'title' => 'Ipsum', - ], - ] - ); - $this->assertEquals(201, $response['headers']['status-code']); - - self::$setupDatabaseCache[$cacheKey] = $this->users; - - return self::$setupDatabaseCache[$cacheKey]; - } - - #[DataProvider('readDocumentsProvider')] - public function testReadDocuments($user, $collection, $success) - { - $users = $this->setupDatabase(); - - $documents = $this->client->call( - Client::METHOD_GET, - $this->getRecordUrl($this->databaseId, $collection), - [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users[$user]['session'], - ] - ); - - if ($success) { - $this->assertCount(1, $documents['body'][$this->getRecordResource()]); - } else { - $this->assertEquals(401, $documents['headers']['status-code']); - } - } - - #[DataProvider('writeDocumentsProvider')] - public function testWriteDocuments($user, $collection, $success) - { - $users = $this->setupDatabase(); - - $documents = $this->client->call( - Client::METHOD_POST, - $this->getRecordUrl($this->databaseId, $collection), - [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users[$user]['session'], - ], - [ - $this->getRecordIdParam() => ID::unique(), - 'data' => [ - 'title' => 'Ipsum', - ], - ] - ); - - if ($success) { - $this->assertEquals(201, $documents['headers']['status-code']); - } else { - // 401 if user is a part of team, 404 otherwise - $this->assertContains($documents['headers']['status-code'], [401, 404]); - } - } -} diff --git a/tests/e2e/Services/Databases/Permissions/VectorsDBPermissionsGuestTest.php b/tests/e2e/Services/Databases/Permissions/VectorsDBPermissionsGuestTest.php deleted file mode 100644 index 52ddcc8586..0000000000 --- a/tests/e2e/Services/Databases/Permissions/VectorsDBPermissionsGuestTest.php +++ /dev/null @@ -1,281 +0,0 @@ -authorization)) { - return $this->authorization; - } - - $this->authorization = new Authorization(); - - return $this->authorization; - } - - public function createCollection(): array - { - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'VectorGuestDB', - ]); - $this->assertEquals(201, $database['headers']['status-code']); - $this->assertEquals('VectorGuestDB', $database['body']['name']); - - $databaseId = $database['body']['$id']; - $publicMovies = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - $privateMovies = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'dimension' => 3, - 'permissions' => [], - 'documentSecurity' => true, - ]); - - $publicCollection = ['id' => $publicMovies['body']['$id']]; - $privateCollection = ['id' => $privateMovies['body']['$id']]; - - return [ - 'databaseId' => $databaseId, - 'publicCollectionId' => $publicCollection['id'], - 'privateCollectionId' => $privateCollection['id'], - ]; - } - - public static function permissionsProvider(): array - { - return [ - [[Permission::read(Role::any())]], - [[Permission::read(Role::users())]], - [[Permission::update(Role::any()), Permission::delete(Role::any())]], - [[Permission::read(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any())]], - [[Permission::read(Role::users()), Permission::update(Role::users()), Permission::delete(Role::users())]], - [[Permission::read(Role::any()), Permission::update(Role::users()), Permission::delete(Role::users())]], - ]; - } - - #[DataProvider('permissionsProvider')] - public function testReadDocuments($permissions) - { - $data = $this->createCollection(); - $publicCollectionId = $data['publicCollectionId']; - $privateCollectionId = $data['privateCollectionId']; - $databaseId = $data['databaseId']; - - $publicResponse = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ], - 'permissions' => $permissions, - ]); - $privateResponse = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ], - 'permissions' => $permissions, - ]); - - $this->assertEquals(201, $publicResponse['headers']['status-code']); - $this->assertEquals(201, $privateResponse['headers']['status-code']); - - $roles = $this->getAuthorization()->getRoles(); - $this->getAuthorization()->cleanRoles(); - - $publicDocuments = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]); - $privateDocuments = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]); - - $this->assertEquals(1, $publicDocuments['body']['total']); - $this->assertEquals($permissions, $publicDocuments['body']['documents'][0]['$permissions']); - - if (\in_array(Permission::read(Role::any()), $permissions)) { - $this->assertEquals(1, $privateDocuments['body']['total']); - $this->assertEquals($permissions, $privateDocuments['body']['documents'][0]['$permissions']); - } else { - $this->assertEquals(0, $privateDocuments['body']['total']); - } - - foreach ($roles as $role) { - $this->getAuthorization()->addRole($role); - } - } - - public function testWriteDocument() - { - $data = $this->createCollection(); - $publicCollectionId = $data['publicCollectionId']; - $privateCollectionId = $data['privateCollectionId']; - $databaseId = $data['databaseId']; - - $roles = $this->getAuthorization()->getRoles(); - $this->getAuthorization()->cleanRoles(); - - $publicResponse = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ] - ]); - - $publicDocumentId = $publicResponse['body']['$id']; - $this->assertEquals(201, $publicResponse['headers']['status-code']); - - $privateResponse = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ], - ]); - - $this->assertEquals(401, $privateResponse['headers']['status-code']); - - // Create a document in private collection with API key so we can test that update and delete are also not allowed - $privateResponse = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 0.0, 1.0], - 'metadata' => ['title' => 'Lorem'], - ], - ]); - - $this->assertEquals(201, $privateResponse['headers']['status-code']); - $privateDocumentId = $privateResponse['body']['$id']; - - $publicDocument = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents/' . $publicDocumentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'data' => [ - 'embeddings' => [0.5, 0.5, 0.0], - 'metadata' => ['title' => 'Thor: Ragnarok'], - ], - ]); - - $this->assertEquals(200, $publicDocument['headers']['status-code']); - $this->assertEquals('Thor: Ragnarok', $publicDocument['body']['metadata']['title']); - - $privateDocument = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents/' . $privateDocumentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'data' => [ - 'embeddings' => [0.2, 0.3, 0.5], - 'metadata' => ['title' => 'Thor: Ragnarok'], - ], - ]); - - $this->assertEquals(401, $privateDocument['headers']['status-code']); - - $publicDocument = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents/' . $publicDocumentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]); - - $this->assertEquals(204, $publicDocument['headers']['status-code']); - - $privateDocument = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents/' . $privateDocumentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]); - - $this->assertEquals(401, $privateDocument['headers']['status-code']); - - foreach ($roles as $role) { - $this->getAuthorization()->addRole($role); - } - } - - public function testWriteDocumentWithPermissions() - { - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'VectorGuestPermsWrite', - ]); - $this->assertEquals(201, $database['headers']['status-code']); - $this->assertEquals('VectorGuestPermsWrite', $database['body']['name']); - - $databaseId = $database['body']['$id']; - $movies = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'dimension' => 3, - 'permissions' => [ - Permission::create(Role::any()), - ], - 'documentSecurity' => true - ]); - - $moviesId = $movies['body']['$id']; - - $document = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $moviesId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['title' => 'Thor: Ragnarok'], - ], - 'permissions' => [ - Permission::read(Role::any()), - ] - ]); - - $this->assertEquals(201, $document['headers']['status-code']); - $this->assertEquals('Thor: Ragnarok', $document['body']['metadata']['title']); - } -} diff --git a/tests/e2e/Services/Databases/Permissions/VectorsDBPermissionsMemberTest.php b/tests/e2e/Services/Databases/Permissions/VectorsDBPermissionsMemberTest.php deleted file mode 100644 index 3043a42dd5..0000000000 --- a/tests/e2e/Services/Databases/Permissions/VectorsDBPermissionsMemberTest.php +++ /dev/null @@ -1,197 +0,0 @@ - $this->createUser('user1', 'lorem@ipsum.com'), - 'user2' => $this->createUser('user2', 'dolor@ipsum.com'), - ]; - } - - public static function permissionsProvider(): array - { - return [ - [[Permission::read(Role::any())], 1, 1, 1], - [[Permission::read(Role::users())], 2, 2, 2], - [[Permission::read(Role::user(ID::custom('random')))], 3, 3, 2], - [[Permission::read(Role::user(ID::custom('lorem'))), Permission::update(Role::user('lorem')), Permission::delete(Role::user('lorem'))], 4, 4, 2], - [[Permission::read(Role::user(ID::custom('dolor'))), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))], 5, 5, 2], - [[Permission::read(Role::user(ID::custom('dolor'))), Permission::read(Role::user('lorem')), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))], 6, 6, 2], - [[Permission::update(Role::any()), Permission::delete(Role::any())], 7, 7, 2], - [[Permission::read(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any())], 8, 8, 3], - [[Permission::read(Role::any()), Permission::update(Role::users()), Permission::delete(Role::users())], 9, 9, 4], - [[Permission::read(Role::user(ID::custom('user1')))], 10, 10, 5], - [[Permission::read(Role::user(ID::custom('user1'))), Permission::read(Role::user(ID::custom('user1')))], 11, 11, 6], - [[Permission::read(Role::users()), Permission::update(Role::users()), Permission::delete(Role::users())], 12, 12, 7], - ]; - } - - /** - * Setup database - * - * Data providers lose object state so explicitly pass [$users, $collections] to each iteration - * - * @return array - * @throws \Exception - */ - public function testSetupDatabase(): array - { - $this->createUsers(); - - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', $this->getServerHeader(), [ - 'databaseId' => ID::unique(), - 'name' => 'Test Database', - ]); - $this->assertEquals(201, $db['headers']['status-code']); - - $databaseId = $db['body']['$id']; - - $public = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'documentSecurity' => true, - ]); - $this->assertEquals(201, $public['headers']['status-code']); - $this->collections = ['public' => $public['body']['$id']]; - - $private = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::unique(), - 'name' => 'Private Movies', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::users()), - Permission::create(Role::users()), - Permission::update(Role::users()), - Permission::delete(Role::users()), - ], - 'documentSecurity' => true, - ]); - $this->assertEquals(201, $private['headers']['status-code']); - $this->collections['private'] = $private['body']['$id']; - - $doconly = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::unique(), - 'name' => 'Document Only Movies', - 'dimension' => 3, - 'permissions' => [], - 'documentSecurity' => true, - ]); - $this->assertEquals(201, $private['headers']['status-code']); - $this->collections['doconly'] = $doconly['body']['$id']; - - return [ - 'users' => $this->users, - 'collections' => $this->collections, - 'databaseId' => $databaseId - ]; - } - - /** - * Data provider params are passed before test dependencies. - */ - #[DataProvider('permissionsProvider')] - #[Depends('testSetupDatabase')] - public function testReadDocuments($permissions, $anyCount, $usersCount, $docOnlyCount, $data) - { - $users = $data['users']; - $collections = $data['collections']; - $databaseId = $data['databaseId']; - - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $collections['public'] . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ], - 'permissions' => $permissions - ]); - $this->assertEquals(201, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $collections['private'] . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ], - 'permissions' => $permissions - ]); - $this->assertEquals(201, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $collections['doconly'] . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 0.0, 1.0], - 'metadata' => ['title' => 'Lorem'], - ], - 'permissions' => $permissions - ]); - $this->assertEquals(201, $response['headers']['status-code']); - - /** - * Check "any" permission collection - */ - $documents = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collections['public'] . '/documents', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users['user1']['session'], - ]); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals($anyCount, $documents['body']['total']); - - /** - * Check "users" permission collection - */ - $documents = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collections['private'] . '/documents', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users['user1']['session'], - ]); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals($usersCount, $documents['body']['total']); - - /** - * Check "user:user1" document only permission collection - */ - $documents = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collections['doconly'] . '/documents', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users['user1']['session'], - ]); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals($docOnlyCount, $documents['body']['total']); - } -} diff --git a/tests/e2e/Services/Databases/Permissions/VectorsDBPermissionsTeamTest.php b/tests/e2e/Services/Databases/Permissions/VectorsDBPermissionsTeamTest.php deleted file mode 100644 index 11709ed729..0000000000 --- a/tests/e2e/Services/Databases/Permissions/VectorsDBPermissionsTeamTest.php +++ /dev/null @@ -1,200 +0,0 @@ - $this->createTeam('team1', 'Team 1'), - 'team2' => $this->createTeam('team2', 'Team 2'), - ]; - } - - public function createUsers(): array - { - return [ - 'user1' => $this->createUser('user1', 'lorem@ipsum.com'), - 'user2' => $this->createUser('user2', 'dolor@ipsum.com'), - 'user3' => $this->createUser('user3', 'sit@ipsum.com'), - ]; - } - - public function createCollections($teams) - { - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', $this->getServerHeader(), [ - 'databaseId' => $this->databaseId, - 'name' => 'Test Database', - ]); - $this->assertEquals(201, $db['headers']['status-code']); - - $collection1 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $this->databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::custom('collection1'), - 'name' => 'Collection 1', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::team($teams['team1']['$id'])), - Permission::create(Role::team($teams['team1']['$id'], 'admin')), - Permission::update(Role::team($teams['team1']['$id'], 'admin')), - Permission::delete(Role::team($teams['team1']['$id'], 'admin')), - ], - ]); - - $this->collections['collection1'] = $collection1['body']['$id']; - - $collection2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $this->databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::custom('collection2'), - 'name' => 'Collection 2', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::team($teams['team2']['$id'])), - Permission::create(Role::team($teams['team2']['$id'], 'owner')), - Permission::update(Role::team($teams['team2']['$id'], 'owner')), - Permission::delete(Role::team($teams['team2']['$id'], 'owner')), - ] - ]); - - $this->collections['collection2'] = $collection2['body']['$id']; - - return $this->collections; - } - - /* - * $success = can $user read from $collection - * [$user, $collection, $success] - */ - public static function readDocumentsProvider(): array - { - return [ - ['user1', 'collection1', true], - ['user2', 'collection1', false], - ['user3', 'collection1', true], - ['user1', 'collection2', false], - ['user2', 'collection2', true], - ['user3', 'collection2', true], - ]; - } - - /* - * $success = can $user write to $collection - * [$user, $collection, $success] - */ - public static function writeDocumentsProvider(): array - { - return [ - ['user1', 'collection1', true], - ['user2', 'collection1', false], - ['user3', 'collection1', false], - ['user1', 'collection2', false], - ['user2', 'collection2', true], - ['user3', 'collection2', false], - ]; - } - - /** - * Setup database - * - * Data providers lose object state - * so explicitly pass $users to each iteration - * @return array $users - */ - public function testSetupDatabase(): array - { - $this->createUsers(); - $this->createTeams(); - - $this->addToTeam('user1', 'team1', ['admin']); - $this->addToTeam('user2', 'team2', ['owner']); - - // user3 in both teams but with no roles - $this->addToTeam('user3', 'team1'); - $this->addToTeam('user3', 'team2'); - - $this->createCollections($this->teams); - - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $this->databaseId . '/collections/' . $this->collections['collection1'] . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ], - ]); - $this->assertEquals(201, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $this->databaseId . '/collections/' . $this->collections['collection2'] . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['title' => 'Ipsum'], - ], - ]); - $this->assertEquals(201, $response['headers']['status-code']); - - return $this->users; - } - - /** - * Data provider params are passed before test dependencies. - */ - #[Depends('testSetupDatabase')] - #[DataProvider('readDocumentsProvider')] - public function testReadDocuments($user, $collection, $success, $users) - { - $documents = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $this->databaseId . '/collections/' . $collection . '/documents', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users[$user]['session'], - ]); - - if ($success) { - $this->assertCount(1, $documents['body']['documents']); - } else { - $this->assertEquals(401, $documents['headers']['status-code']); - } - } - - #[Depends('testSetupDatabase')] - #[DataProvider('writeDocumentsProvider')] - public function testWriteDocuments($user, $collection, $success, $users) - { - $documents = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $this->databaseId . '/collections/' . $collection . '/documents', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users[$user]['session'], - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.2, 0.3, 0.5], - 'metadata' => ['title' => 'Ipsum'], - ], - ]); - - if ($success) { - $this->assertEquals(201, $documents['headers']['status-code']); - } else { - // 401 if user is a part of team, 404 otherwise - $this->assertContains($documents['headers']['status-code'], [401, 404]); - } - } -} diff --git a/tests/e2e/Services/Databases/Transactions/ACIDBase.php b/tests/e2e/Services/Databases/Transactions/ACIDBase.php index 1a6ee83b33..070b83734f 100644 --- a/tests/e2e/Services/Databases/Transactions/ACIDBase.php +++ b/tests/e2e/Services/Databases/Transactions/ACIDBase.php @@ -47,20 +47,18 @@ trait ACIDBase $collectionId = $collection['body']['$id']; - if ($this->getSupportForAttributes()) { - // Add unique attribute - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'email', - 'size' => 256, - 'required' => true, - ]); + // Add unique attribute + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'email', + 'size' => 256, + 'required' => true, + ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->waitForAllAttributes($databaseId, $collectionId); // Add unique index $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId), array_merge([ @@ -176,11 +174,6 @@ trait ACIDBase */ public function testConsistency(): void { - if (!$this->getSupportForAttributes()) { - $this->markTestSkipped('This adapter does not support attributes; schema constraint consistency cannot be tested.'); - return; - } - // Create database $database = $this->client->call(Client::METHOD_POST, $this->getDatabaseUrl(), array_merge([ 'content-type' => 'application/json', @@ -343,21 +336,19 @@ trait ACIDBase $collectionId = $collection['body']['$id']; - if ($this->getSupportForAttributes()) { - // Add counter attribute - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'integer'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'counter', - 'required' => true, - 'min' => 0, - 'max' => 1000000 - ]); + // Add counter attribute + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'integer'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => true, + 'min' => 0, + 'max' => 1000000 + ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->waitForAllAttributes($databaseId, $collectionId); // Create initial document with counter $doc = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $collectionId), array_merge([ @@ -503,20 +494,18 @@ trait ACIDBase $collectionId = $collection['body']['$id']; - if ($this->getSupportForAttributes()) { - // Add attribute - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'data', - 'size' => 256, - 'required' => true, - ]); + // Add attribute + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => true, + ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->waitForAllAttributes($databaseId, $collectionId); // Create and commit transaction with multiple operations $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ diff --git a/tests/e2e/Services/Databases/Transactions/DocumentsDBACIDTest.php b/tests/e2e/Services/Databases/Transactions/DocumentsDBACIDTest.php deleted file mode 100644 index eb597e3488..0000000000 --- a/tests/e2e/Services/Databases/Transactions/DocumentsDBACIDTest.php +++ /dev/null @@ -1,18 +0,0 @@ -assertEquals(201, $collection['headers']['status-code']); - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 255, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); - $this->assertEquals(202, $attribute['headers']['status-code']); - $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); - } + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -152,20 +150,18 @@ trait TransactionPermissionsBase $this->assertEquals(201, $collection['headers']['status-code']); - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 255, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); - $this->assertEquals(202, $attribute['headers']['status-code']); - $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); - } + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); // Create a document first with API key $doc = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($this->getPermissionsDatabase(), $collection['body']['$id']), array_merge([ @@ -228,20 +224,18 @@ trait TransactionPermissionsBase $this->assertEquals(201, $collection['headers']['status-code']); - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 255, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); - $this->assertEquals(202, $attribute['headers']['status-code']); - $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); - } + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); $doc = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($this->getPermissionsDatabase(), $collection['body']['$id']), array_merge([ 'content-type' => 'application/json', @@ -303,20 +297,18 @@ trait TransactionPermissionsBase $this->assertEquals(201, $collection['headers']['status-code']); - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 255, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); - $this->assertEquals(202, $attribute['headers']['status-code']); - $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); - } + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); // Create a document with update permission at document level $doc = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($this->getPermissionsDatabase(), $collection['body']['$id']), array_merge([ @@ -384,20 +376,18 @@ trait TransactionPermissionsBase $this->assertEquals(201, $collection['headers']['status-code']); - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 255, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); - $this->assertEquals(202, $attribute['headers']['status-code']); - $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); - } + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); // Create a document with delete permission at document level $doc = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($this->getPermissionsDatabase(), $collection['body']['$id']), array_merge([ @@ -467,20 +457,18 @@ trait TransactionPermissionsBase $this->assertEquals(201, $collection['headers']['status-code']); // Add attribute - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 255, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); - $this->assertEquals(202, $attribute['headers']['status-code']); - $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); - } + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -539,20 +527,18 @@ trait TransactionPermissionsBase $this->assertEquals(201, $collection['headers']['status-code']); - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 255, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); - $this->assertEquals(202, $attribute['headers']['status-code']); - $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); - } + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -611,20 +597,18 @@ trait TransactionPermissionsBase $this->assertEquals(201, $collection['headers']['status-code']); - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 255, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); - $this->assertEquals(202, $attribute['headers']['status-code']); - $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); - } + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -676,20 +660,18 @@ trait TransactionPermissionsBase $this->assertEquals(201, $collection['headers']['status-code']); - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 255, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); - $this->assertEquals(202, $attribute['headers']['status-code']); - $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); - } + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -741,20 +723,18 @@ trait TransactionPermissionsBase $this->assertEquals(201, $collection['headers']['status-code']); - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 255, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); - $this->assertEquals(202, $attribute['headers']['status-code']); - $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); - } + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -1080,20 +1060,18 @@ trait TransactionPermissionsBase $this->assertEquals(201, $collection['headers']['status-code']); - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 255, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($this->getPermissionsDatabase(), $collection['body']['$id'], 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'title', + 'size' => 255, + 'required' => true, + ]); - $this->assertEquals(202, $attribute['headers']['status-code']); - $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); - } + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->waitForAllAttributes($this->getPermissionsDatabase(), $collection['body']['$id']); // Create user 1 (fresh) and their transaction $user1 = $this->getUser(true); diff --git a/tests/e2e/Services/Databases/Transactions/TransactionsBase.php b/tests/e2e/Services/Databases/Transactions/TransactionsBase.php index c69c35f663..479e4d5c68 100644 --- a/tests/e2e/Services/Databases/Transactions/TransactionsBase.php +++ b/tests/e2e/Services/Databases/Transactions/TransactionsBase.php @@ -70,21 +70,19 @@ trait TransactionsBase $this->assertEquals(201, $collection['headers']['status-code']); self::$sharedCollectionId = $collection['body']['$id']; - // Create a standard 'name' attribute only if attributes are supported - if ($this->getSupportForAttributes()) { - $nameAttr = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, self::$sharedCollectionId, "string", null), 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, $nameAttr['headers']['status-code']); + // Create a standard 'name' attribute + $nameAttr = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, self::$sharedCollectionId, "string", null), 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, $nameAttr['headers']['status-code']); - $this->waitForAllAttributes($databaseId, self::$sharedCollectionId); - } + $this->waitForAllAttributes($databaseId, self::$sharedCollectionId); return self::$sharedCollectionId; } @@ -221,22 +219,20 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Add attributes - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'string', null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'name', - 'size' => 256, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'string', null), 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, $attribute['headers']['status-code']); + $this->assertEquals(202, $attribute['headers']['status-code']); - // Wait for attribute to be created - $this->waitForAllAttributes($databaseId, $collectionId); - } + // Wait for attribute to be created + $this->waitForAllAttributes($databaseId, $collectionId); // Add valid operations $response = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl($transactionId) . "/operations", array_merge([ @@ -369,20 +365,18 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Add attributes - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'string', null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'name', - 'size' => 256, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'string', null), 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, $attribute['headers']['status-code']); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->waitForAllAttributes($databaseId, $collectionId); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -523,19 +517,17 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Add attribute - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'string', null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'value', - 'size' => 256, - 'required' => true, - ]); + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'string', null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'value', + 'size' => 256, + 'required' => true, + ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->waitForAllAttributes($databaseId, $collectionId); // Add operations $response = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl($transactionId) . "/operations", array_merge([ @@ -615,18 +607,17 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attribute - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'data', - 'size' => 256, - 'required' => false, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => false, + ]); + + $this->waitForAllAttributes($databaseId, $collectionId); // Create transaction with minimum TTL (60 seconds) $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -706,19 +697,17 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attribute - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'value', - 'size' => 256, - 'required' => false, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'value', + 'size' => 256, + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -830,21 +819,19 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attribute - if ($this->getSupportForAttributes()) { - $counterAttr = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'counter', - 'required' => true, - 'min' => 0, - 'max' => 1000000, - ]); - $this->assertEquals(202, $counterAttr['headers']['status-code']); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $counterAttr = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => true, + 'min' => 0, + 'max' => 1000000, + ]); + $this->assertEquals(202, $counterAttr['headers']['status-code']); + $this->waitForAllAttributes($databaseId, $collectionId); // Create initial document $doc = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $collectionId, null), array_merge([ @@ -972,19 +959,17 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attribute - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'data', - 'size' => 256, - 'required' => false, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create document $doc = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $collectionId, null), array_merge([ @@ -1079,29 +1064,27 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'category', - 'size' => 256, - 'required' => true, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => true, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create some initial documents for ($i = 1; $i <= 5; $i++) { @@ -1245,19 +1228,17 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes with constraints - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'email', - 'size' => 256, - 'required' => true, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'email', + 'size' => 256, + 'required' => true, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create unique index on email $this->client->call(Client::METHOD_POST, $this->getIndexUrl($databaseId, $collectionId, null), array_merge([ @@ -1380,20 +1361,18 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; - if ($this->getSupportForAttributes()) { - // Create attribute - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'data', - 'size' => 256, - 'required' => false, - ]); + // Create attribute + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => false, + ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->waitForAllAttributes($databaseId, $collectionId); // Test double commit $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -1506,21 +1485,18 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; - if ($this->getSupportForAttributes()) { - // Create attribute - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'data', - 'size' => 256, - 'required' => false, - ]); - - $this->waitForAllAttributes($databaseId, $collectionId); - } + // Create attribute + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'data', + 'size' => 256, + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -1619,22 +1595,20 @@ trait TransactionsBase ['key' => 'data', 'type' => 'string', 'size' => 256, 'required' => false], ]; - if ($this->getSupportForAttributes()) { - foreach ($attributes as $attr) { - $type = $attr['type']; - unset($attr['type']); + foreach ($attributes as $attr) { + $type = $attr['type']; + unset($attr['type']); - $response = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, $type, null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), $attr); + $response = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, $type, null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), $attr); - $this->assertEquals(202, $response['headers']['status-code']); - } - $this->waitForAllAttributes($databaseId, $collectionId); + $this->assertEquals(202, $response['headers']['status-code']); } + $this->waitForAllAttributes($databaseId, $collectionId); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -1725,40 +1699,38 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'counter', - 'required' => false, - 'min' => 0, - 'max' => 10000, - ]); + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'min' => 0, + 'max' => 10000, + ]); - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'category', - 'size' => 256, - 'required' => false, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create document outside transaction $doc = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $collectionId, null), array_merge([ @@ -1864,30 +1836,28 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'counter', - 'required' => false, - 'min' => 0, - 'max' => 10000, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'min' => 0, + 'max' => 10000, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -2061,29 +2031,27 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'category', - 'size' => 256, - 'required' => false, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -2207,29 +2175,27 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'category', - 'size' => 256, - 'required' => false, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create documents for bulk testing for ($i = 1; $i <= 3; $i++) { @@ -2333,30 +2299,28 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'counter', - 'required' => false, - 'min' => 0, - 'max' => 10000, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'min' => 0, + 'max' => 10000, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create one document outside transaction $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $collectionId, null), array_merge([ @@ -2481,29 +2445,27 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'category', - 'size' => 256, - 'required' => false, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create documents for bulk testing for ($i = 1; $i <= 3; $i++) { @@ -2607,40 +2569,38 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'status', - 'size' => 256, - 'required' => false, - ]); + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => false, + ]); - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'priority', - 'required' => false, - 'min' => 1, - 'max' => 10, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'priority', + 'required' => false, + 'min' => 1, + 'max' => 10, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create an existing document outside transaction for testing $existingDoc = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $collectionId, null), array_merge([ @@ -2888,21 +2848,18 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; - if ($this->getSupportForAttributes()) { - // Create attribute - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->waitForAllAttributes($databaseId, $collectionId); - } + // Create attribute + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->waitForAllAttributes($databaseId, $collectionId); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -3033,38 +2990,36 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'age', - 'required' => true, - ]); + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'age', + 'required' => true, + ]); - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'status', - 'size' => 256, - 'required' => true, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => true, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create some existing documents for ($i = 1; $i <= 3; $i++) { @@ -3215,39 +3170,37 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'category', - 'size' => 256, - 'required' => true, - ]); + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 256, + 'required' => true, + ]); - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'priority', - 'size' => 256, - 'required' => true, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'priority', + 'size' => 256, + 'required' => true, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create existing documents for ($i = 1; $i <= 4; $i++) { @@ -3392,29 +3345,27 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'type', - 'size' => 256, - 'required' => true, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'type', + 'size' => 256, + 'required' => true, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create existing documents for ($i = 1; $i <= 3; $i++) { @@ -3556,29 +3507,27 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Create attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), 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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'status', - 'size' => 256, - 'required' => true, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 256, + 'required' => true, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create existing documents for ($i = 1; $i <= 5; $i++) { @@ -3714,31 +3663,28 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; - if ($this->getSupportForAttributes()) { - // Add integer attributes - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'counter', - 'required' => false, - 'default' => 0, - ]); + // Add integer attributes + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'default' => 0, + ]); - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'score', - 'required' => false, - 'default' => 100, - ]); - - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'score', + 'required' => false, + 'default' => 100, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create initial document $doc = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $collectionId, null), array_merge([ @@ -3876,21 +3822,18 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; - if ($this->getSupportForAttributes()) { - // Add balance attribute - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'balance', - 'required' => false, - 'default' => 0, - ]); - - $this->waitForAllAttributes($databaseId, $collectionId); - } + // Add balance attribute + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'balance', + 'required' => false, + 'default' => 0, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create initial documents $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $collectionId, null), array_merge([ @@ -4022,29 +3965,27 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Add attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'status', - 'size' => 50, - 'required' => false, - ]); + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 50, + 'required' => false, + ]); - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'category', - 'size' => 50, - 'required' => false, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'category', + 'size' => 50, + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create initial documents for ($i = 1; $i <= 5; $i++) { @@ -4166,28 +4107,26 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Add attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'name', - 'size' => 100, - 'required' => false, - ]); + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 100, + 'required' => false, + ]); - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'value', - 'required' => false, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'value', + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create some initial documents $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $collectionId, null), array_merge([ @@ -4327,28 +4266,26 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Add attributes - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'type', - 'size' => 50, - 'required' => false, - ]); + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "string", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'type', + 'size' => 50, + 'required' => false, + ]); - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'priority', - 'required' => false, - ]); - $this->waitForAllAttributes($databaseId, $collectionId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, "integer", null), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'priority', + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $collectionId); // Create initial documents for ($i = 1; $i <= 10; $i++) { @@ -4468,22 +4405,20 @@ trait TransactionsBase $collectionId = $collection['body']['$id']; // Add required attribute - if ($this->getSupportForAttributes()) { - $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'string', null), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'name', - 'size' => 256, - 'required' => true, - ]); + $attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $collectionId, 'string', null), 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, $attribute['headers']['status-code']); + $this->assertEquals(202, $attribute['headers']['status-code']); - // Wait for attribute to be ready - $this->waitForAllAttributes($databaseId, $collectionId); - } + // Wait for attribute to be ready + $this->waitForAllAttributes($databaseId, $collectionId); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -4917,29 +4852,27 @@ trait TransactionsBase $tableId = $table['body']['$id']; // Add columns - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'integer'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'counter', - 'required' => false, - 'default' => 0, - ]); + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'integer'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'default' => 0, + ]); - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'status', - 'size' => 50, - 'required' => false, - ]); + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 50, + 'required' => false, + ]); - $this->waitForAllAttributes($databaseId, $tableId); - } + $this->waitForAllAttributes($databaseId, $tableId); // Create initial row $row = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $tableId), array_merge([ @@ -5054,21 +4987,18 @@ trait TransactionsBase $tableId = $table['body']['$id']; - if ($this->getSupportForAttributes()) { - // Add balance column - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'integer'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'balance', - 'required' => false, - 'default' => 0, - ]); - - $this->waitForAllAttributes($databaseId, $tableId); - } + // Add balance column + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'integer'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'balance', + 'required' => false, + 'default' => 0, + ]); + $this->waitForAllAttributes($databaseId, $tableId); // Create initial row $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $tableId), array_merge([ @@ -5165,19 +5095,17 @@ trait TransactionsBase $tableId = $table['body']['$id']; // Add columns - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'status', - 'size' => 50, - 'required' => false, - ]); + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 50, + 'required' => false, + ]); - $this->waitForAllAttributes($databaseId, $tableId); - } + $this->waitForAllAttributes($databaseId, $tableId); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -5277,20 +5205,17 @@ trait TransactionsBase $tableId = $table['body']['$id']; - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'name', - 'size' => 50, - 'required' => false, - ]); - - $this->waitForAllAttributes($databaseId, $tableId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 50, + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $tableId); $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ 'content-type' => 'application/json', @@ -5384,20 +5309,17 @@ trait TransactionsBase $tableId = $table['body']['$id']; - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'status', - 'size' => 50, - 'required' => false, - ]); - - $this->waitForAllAttributes($databaseId, $tableId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'status', + 'size' => 50, + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $tableId); $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ 'content-type' => 'application/json', @@ -5505,20 +5427,17 @@ trait TransactionsBase 'required' => true, ]); - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'flag', - 'size' => 256, - 'required' => false, - ]); - - $this->waitForAllAttributes($databaseId, $tableId); - } + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'flag', + 'size' => 256, + 'required' => false, + ]); + $this->waitForAllAttributes($databaseId, $tableId); $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ 'content-type' => 'application/json', @@ -5624,30 +5543,28 @@ trait TransactionsBase $tableId = $table['body']['$id']; // Create columns - if ($this->getSupportForAttributes()) { - $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, '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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, '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->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'integer'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'counter', - 'required' => false, - 'min' => 0, - 'max' => 10000, - ]); + $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'integer'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'counter', + 'required' => false, + 'min' => 0, + 'max' => 10000, + ]); - $this->waitForAllAttributes($databaseId, $tableId); - } + $this->waitForAllAttributes($databaseId, $tableId); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, $this->getTransactionUrl(), array_merge([ @@ -5770,21 +5687,19 @@ trait TransactionsBase $tableId = $table['body']['$id']; // Create array column - if ($this->getSupportForAttributes()) { - $column = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'string'), array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'items', - 'size' => 255, - 'required' => false, - 'array' => true, - ]); + $column = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $tableId, 'string'), array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'items', + 'size' => 255, + 'required' => false, + 'array' => true, + ]); - $this->assertEquals(202, $column['headers']['status-code']); - $this->waitForAllAttributes($databaseId, $tableId); - } + $this->assertEquals(202, $column['headers']['status-code']); + $this->waitForAllAttributes($databaseId, $tableId); // Create initial row with some items $row = $this->client->call(Client::METHOD_POST, $this->getRecordUrl($databaseId, $tableId), array_merge([ diff --git a/tests/e2e/Services/Databases/Transactions/VectorsDBACIDTest.php b/tests/e2e/Services/Databases/Transactions/VectorsDBACIDTest.php deleted file mode 100644 index 914c8d0c5b..0000000000 --- a/tests/e2e/Services/Databases/Transactions/VectorsDBACIDTest.php +++ /dev/null @@ -1,528 +0,0 @@ -client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'AtomicityTestDB' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create collection for the test - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'AtomicityTest', - 'dimension' => 3, - 'documentSecurity' => false, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - ], - ]); - - $this->assertEquals(201, $collection['headers']['status-code']); - $collectionId = $collection['body']['$id']; - - // Create a document outside the transaction - $existingDocumentId = 'existing_doc'; - $doc1 = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => $existingDocumentId, - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['email' => 'existing@example.com'], - ], - ]); - - $this->assertEquals(201, $doc1['headers']['status-code']); - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(201, $transaction['headers']['status-code'], 'Transaction creation should succeed. Response: ' . json_encode($transaction)); - $this->assertArrayHasKey('$id', $transaction['body'], 'Transaction response should have $id. Response body: ' . json_encode($transaction['body'])); - $transactionId = $transaction['body']['$id']; - - // Add operations - second create reuses an existing documentId and should cause the commit to fail - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documents' => [ - [ - '$id' => 'txn_doc_1', - 'embeddings' => $this->generateEmbeddings(3, 0.2), - 'metadata' => ['email' => 'newuser@example.com'], - ], - [ - '$id' => $existingDocumentId, - 'embeddings' => $this->generateEmbeddings(3, 0.3), - 'metadata' => ['email' => 'duplicate@example.com'], - ], - [ - '$id' => 'txn_doc_2', - 'embeddings' => $this->generateEmbeddings(3, 0.4), - 'metadata' => ['email' => 'should-not-exist@example.com'], - ], - ], - 'transactionId' => $transactionId, - ]); - - $this->assertEquals(200, $response['headers']['status-code'], 'Adding documents via normal route should succeed. Response: ' . json_encode($response['body'])); - - // Attempt to commit - should fail due to duplicate document ID - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(409, $response['headers']['status-code']); - - // Verify NO new documents were created (atomicity) - $documents = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(1, $documents['body']['total']); - $this->assertEquals('existing@example.com', $documents['body']['documents'][0]['metadata']['email']); - } - - /** - * Test consistency - schema validation and constraints - */ - public function testConsistency(): void - { - // Create database - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'ConsistencyTestDB' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create collection - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'ConsistencyTest', - 'dimension' => 3, - 'documentSecurity' => false, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - ], - ]); - - $this->assertEquals(201, $collection['headers']['status-code']); - $collectionId = $collection['body']['$id']; - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $transactionId = $transaction['body']['$id']; - - // Stage operations with valid and invalid data (embedding length mismatch) - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.2), - 'metadata' => ['name' => 'Valid User'], - ], - ], - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(2, 0.5), // Invalid dimensions - 'metadata' => ['name' => 'Invalid User'], - ], - ], - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.6), - 'metadata' => ['name' => 'Should Not Persist'], - ], - ], - ], - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - - // Attempt to commit - should fail due to invalid embeddings - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertContains($response['headers']['status-code'], [400, 409, 500], 'Transaction commit should fail due to validation. Response: ' . json_encode($response['body'])); - - // Verify no documents were created - $documents = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(0, $documents['body']['total']); - } - - /** - * Test isolation - concurrent transactions on same data - */ - public function testIsolation(): void - { - // Create database - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'IsolationTestDB' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create collection - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'IsolationTest', - 'dimension' => 3, - 'documentSecurity' => false, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::update(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create initial document with status metadata - $doc = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => 'shared_doc', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['status' => 'pending'], - ], - ]); - - $this->assertEquals(201, $doc['headers']['status-code']); - - // Create first transaction - $transaction1 = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(201, $transaction1['headers']['status-code'], 'Transaction 1 creation should succeed'); - $this->assertArrayHasKey('$id', $transaction1['body'], 'Transaction 1 response should have $id'); - $transactionId1 = $transaction1['body']['$id']; - - // Transaction 1: update status to approved - $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId1}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'update', - 'documentId' => 'shared_doc', - 'data' => [ - 'metadata' => ['status' => 'approved'], - ], - ], - ], - ]); - - // Commit first transaction - $response1 = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId1}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - $this->assertEquals(200, $response1['headers']['status-code']); - - // Document should reflect the first transaction's update - $document = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/shared_doc", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals('approved', $document['body']['metadata']['status']); - - // Create second transaction after first commit - $transaction2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(201, $transaction2['headers']['status-code'], 'Transaction 2 creation should succeed'); - $this->assertArrayHasKey('$id', $transaction2['body'], 'Transaction 2 response should have $id'); - $transactionId2 = $transaction2['body']['$id']; - - // Transaction 2: update status to declined - $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId2}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'update', - 'documentId' => 'shared_doc', - 'data' => [ - 'metadata' => ['status' => 'declined'], - ], - ], - ], - ]); - - // Commit second transaction and ensure isolation guarantees - $response2 = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId2}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response2['headers']['status-code']); - - // Final document should reflect the second transaction's update - $document = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/shared_doc", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals('declined', $document['body']['metadata']['status']); - } - - /** - * Test durability - committed data persists - */ - public function testDurability(): void - { - // Create database - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'DurabilityTestDB' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create collection - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'DurabilityTest', - 'dimension' => 3, - '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']; - - // Create transaction with multiple operations - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(201, $transaction['headers']['status-code'], 'Transaction creation should succeed'); - $this->assertArrayHasKey('$id', $transaction['body'], 'Transaction response should have $id'); - $transactionId = $transaction['body']['$id']; - - // Create two documents via normal route inside transaction - $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documents' => [ - [ - '$id' => 'durable_doc_1', - 'embeddings' => $this->generateEmbeddings(3, 0.3), - 'metadata' => ['data' => 'Important data 1'], - ], - [ - '$id' => 'durable_doc_2', - 'embeddings' => $this->generateEmbeddings(3, 0.5), - 'metadata' => ['data' => 'Important data 2'], - ], - ], - 'transactionId' => $transactionId, - ]); - - // Update first document inside the same transaction - $this->client->call(Client::METHOD_PATCH, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'data' => [ - 'metadata' => ['data' => 'Updated important data 1'], - ], - 'transactionId' => $transactionId, - ]); - - // Commit transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code'], 'Commit should succeed. Response: ' . json_encode($response['body'])); - $this->assertEquals('committed', $response['body']['status']); - - // Verify documents exist and have correct data - $document1 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals(200, $document1['headers']['status-code']); - $this->assertEquals('Updated important data 1', $document1['body']['metadata']['data']); - - $document2 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_2", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals(200, $document2['headers']['status-code']); - $this->assertEquals('Important data 2', $document2['body']['metadata']['data']); - - // Further update outside transaction to ensure persistence - $update = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'metadata' => ['data' => 'Modified outside transaction'], - ], - ]); - $this->assertEquals(200, $update['headers']['status-code']); - - // Verify the update persisted - $document1 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals('Modified outside transaction', $document1['body']['metadata']['data']); - - // List all documents to verify total count - $documents = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals(2, $documents['body']['total']); - } -} diff --git a/tests/e2e/Services/Databases/Transactions/VectorsDBTransactionsConsoleClientTest.php b/tests/e2e/Services/Databases/Transactions/VectorsDBTransactionsConsoleClientTest.php deleted file mode 100644 index f6f217ab69..0000000000 --- a/tests/e2e/Services/Databases/Transactions/VectorsDBTransactionsConsoleClientTest.php +++ /dev/null @@ -1,15 +0,0 @@ -client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Test Database' - ]); - - $this->assertNotEmpty($database['body']['$id']); - $this->assertEquals(201, $database['headers']['status-code']); - $this->assertEquals('Test Database', $database['body']['name']); - $this->assertEquals('vectorsdb', $database['body']['type']); - - return ['databaseId' => $database['body']['$id']]; - } - - #[Depends('testCreateCollectionSample')] - public function testCreateDocument(array $data): array - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - // Build embedding vector matching collection dimensions (1536) - $vector = array_fill(0, 1536, 0.1); - $vector[0] = 1.0; - - $res = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $vector, - 'metadata' => ['type' => 'sample', 'rank' => 1] - ], - 'permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ] - ]); - - $this->assertEquals(201, $res['headers']['status-code']); - $this->assertNotEmpty($res['body']['$id']); - $documentId = $res['body']['$id']; - - // createdAt/updatedAt should be present and equal on initial create - $this->assertArrayHasKey('$createdAt', $res['body']); - $this->assertArrayHasKey('$updatedAt', $res['body']); - $this->assertNotEmpty($res['body']['$createdAt']); - $this->assertNotEmpty($res['body']['$updatedAt']); - $this->assertEquals($res['body']['$createdAt'], $res['body']['$updatedAt']); - - // Edge: invalid dimensions (vector too short) → expect 4xx - $badVec = [1.0, 0.0]; - $bad = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $badVec, - 'metadata' => ['type' => 'bad'] - ], - ]); - $this->assertGreaterThanOrEqual(400, $bad['headers']['status-code']); - $this->assertLessThan(500, $bad['headers']['status-code']); - - // Edge: invalid type values (strings) → expect 4xx - $strVec = ['1.0', '0.0', '0.0']; - $bad2 = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $strVec, - 'metadata' => ['type' => 'bad-strings'] - ], - ]); - $this->assertGreaterThanOrEqual(400, $bad2['headers']['status-code']); - $this->assertLessThan(500, $bad2['headers']['status-code']); - - // Create another valid doc to verify list totals later - $res2 = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $vector, - 'metadata' => ['type' => 'sample', 'rank' => 99] - ], - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $res2['headers']['status-code']); - $documentId2 = $res2['body']['$id']; - - return [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'documentId' => $documentId, - 'documentId2' => $documentId2, - 'createdAt' => $res['body']['$createdAt'], - 'updatedAt' => $res['body']['$updatedAt'], - ]; - } - - #[Depends('testCreateDocument')] - public function testGetDocument(array $data): array - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - $documentId = $data['documentId']; - - $res = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertEquals($documentId, $res['body']['$id']); - - // Edge: missing document should return 404 - $missing = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/" . ID::unique(), [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(404, $missing['headers']['status-code']); - - return $data; - } - - #[Depends('testCreateDocument')] - public function testListDocuments(array $data): array - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - $list = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [Query::limit(5)->toString()] - ]); - - $this->assertEquals(200, $list['headers']['status-code']); - $this->assertIsInt($list['body']['total']); - $this->assertGreaterThanOrEqual(1, $list['body']['total']); - - // Pagination: limit 1, then offset 1 - $page1 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [ - Query::limit(1)->toString(), - Query::orderAsc('$id')->toString() - ] - ]); - $this->assertEquals(200, $page1['headers']['status-code']); - $this->assertEquals(1, \count($page1['body']['documents'] ?? [])); - - $page2 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [ - Query::limit(1)->toString(), - Query::offset(1)->toString(), - Query::orderAsc('$id')->toString() - ] - ]); - $this->assertEquals(200, $page2['headers']['status-code']); - $this->assertEquals(1, \count($page2['body']['documents'] ?? [])); - - return $data; - } - - #[Depends('testCreateDocument')] - public function testUpsertDocument(array $data): array - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - $documentId = $data['documentId']; - - $vector = array_fill(0, 1536, 0.0); - // $vector[1] = 1.0; - - $upd = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'data' => [ - 'embeddings' => $vector, - 'metadata' => ['type' => 'sample', 'rank' => 2] - ] - ]); - - $this->assertEquals(200, $upd['headers']['status-code']); - - // Verify update took effect - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertEquals(2, $get['body']['metadata']['rank']); - // updatedAt should be greater or changed from earlier - $this->assertArrayHasKey('$updatedAt', $get['body']); - - return $data; - } - - #[Depends('testUpsertDocument')] - public function testUpdateDocument(array $data): array - { - // Upsert is used for update semantics - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - $documentId = $data['documentId']; - - $vector = array_fill(0, 1536, 0.0); - $vector[2] = 1.0; - - $upd = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'data' => [ - 'embeddings' => $vector, - 'metadata' => ['type' => 'sample', 'rank' => 3] - ] - ]); - - $this->assertEquals(200, $upd['headers']['status-code']); - - // Re-update to check idempotence and metadata replacement - $vector2 = array_fill(0, 1536, 0.0); - $vector2[3] = 1.0; - $upd2 = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'data' => [ - 'embeddings' => $vector2, - 'metadata' => ['type' => 'sample', 'rank' => 4] - ] - ]); - $this->assertEquals(200, $upd2['headers']['status-code']); - - // Verify updatedAt changed again - $get2 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get2['headers']['status-code']); - $this->assertArrayHasKey('$updatedAt', $get2['body']); - - return $data; - } - - #[Depends('testUpdateDocument')] - public function testDocumentsVectorQueries(array $data): array - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - // Create two more documents with distinct embeddings - $mk = function (array $vec, string $name) use ($databaseId, $collectionId) { - $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $vec, - 'metadata' => ['name' => $name] - ], - 'permissions' => [Permission::read(Role::any())] - ]); - }; - - $vA = array_fill(0, 1536, 0.0); - $vA[0] = 1.0; // close to [1,0,0,...] - $vB = array_fill(0, 1536, 0.0); - $vB[1] = 1.0; // close to [0,1,0,...] - - $mk($vA, 'A'); - $mk($vB, 'B'); - - // Dot product - $dot = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [ - Query::vectorDot('embeddings', $vA)->toString(), - Query::limit(2)->toString() - ] - ]); - $this->assertEquals(200, $dot['headers']['status-code']); - $this->assertGreaterThanOrEqual(1, $dot['body']['total']); - - // Cosine - $cos = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [ - Query::vectorCosine('embeddings', $vB)->toString(), - Query::limit(2)->toString() - ] - ]); - $this->assertEquals(200, $cos['headers']['status-code']); - $this->assertGreaterThanOrEqual(1, $cos['body']['total']); - - // Euclidean - $eu = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [ - Query::vectorEuclidean('embeddings', $vA)->toString(), - Query::limit(2)->toString() - ] - ]); - $this->assertEquals(200, $eu['headers']['status-code']); - $this->assertGreaterThanOrEqual(1, $eu['body']['total']); - - // Combined vector + metadata filters - $combo = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [ - Query::vectorCosine('embeddings', $vA)->toString(), - Query::notEqual('metadata', [['name' => 'B']])->toString(), - Query::limit(2)->toString() - ] - ]); - $this->assertEquals(200, $combo['headers']['status-code']); - $this->assertGreaterThanOrEqual(1, $combo['body']['total']); - - // Ordering with $id ascending combined with vector - $ordered = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [ - Query::vectorDot('embeddings', $vA)->toString(), - Query::orderAsc('$id')->toString(), - Query::limit(3)->toString() - ] - ]); - $this->assertEquals(200, $ordered['headers']['status-code']); - - return $data; - } - - #[Depends('testDocumentsVectorQueries')] - public function testDeleteDocument(array $data): void - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - $documentId = $data['documentId']; - - $del = $this->client->call(Client::METHOD_DELETE, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(204, $del['headers']['status-code']); - - // GET after delete should be 404 - $getMissing = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(404, $getMissing['headers']['status-code']); - - // List should still work and reflect at least one less document compared to earlier pages (best-effort) - $list = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [Query::limit(5)->toString()] - ]); - $this->assertEquals(200, $list['headers']['status-code']); - } - - #[Depends('testCreateCollectionSample')] - public function testDocumentPermissions(array $data): void - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - // Create doc readable only by a specific user - $docId = ID::unique(); - $vector = array_fill(0, 1536, 0.0); - $vector[0] = 1.0; - $create = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documentId' => $docId, - 'data' => [ - 'embeddings' => $vector, - 'metadata' => ['scope' => 'private'] - ], - 'permissions' => [ - Permission::read(Role::user($this->getUser()['$id'])) - ] - ]); - $this->assertEquals(201, $create['headers']['status-code']); - - $guest = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$docId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ]); - $this->assertEquals(404, $guest['headers']['status-code']); - - // GET with key should succeed regardless of document user-level permission - $withKey = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$docId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $withKey['headers']['status-code']); - } - - #[Depends('testCreateDatabase')] - public function testCreateCollection(array $data): array - { - $databaseId = $data['databaseId']; - /** - * Test for SUCCESS - */ - $movies = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'documentSecurity' => true, - 'dimension' => 1536, - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - ], - ]); - - $this->assertEquals(201, $movies['headers']['status-code']); - $this->assertEquals($movies['body']['name'], 'Movies'); - - $actors = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Actors', - 'documentSecurity' => true, - 'dimension' => 1536, - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - ], - ]); - - $this->assertEquals(201, $actors['headers']['status-code']); - $this->assertEquals($actors['body']['name'], 'Actors'); - - return [ - 'databaseId' => $databaseId, - 'moviesId' => $movies['body']['$id'], - 'actorsId' => $actors['body']['$id'], - ]; - } - - public function testCreateDatabaseSample(): array - { - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Sample VectorsDB' - ]); - - $this->assertNotEmpty($database['body']['$id']); - $this->assertEquals(201, $database['headers']['status-code']); - $this->assertEquals('Sample VectorsDB', $database['body']['name']); - $this->assertEquals('vectorsdb', $database['body']['type']); - - return ['databaseId' => $database['body']['$id']]; - } - - #[Depends('testCreateDatabaseSample')] - public function testCreateCollectionSample(array $data): array - { - $databaseId = $data['databaseId']; - - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Sample Collection', - 'dimension' => 1536, - 'documentSecurity' => true, - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - ], - ]); - - $this->assertEquals(201, $collection['headers']['status-code']); - $this->assertEquals('Sample Collection', $collection['body']['name']); - $this->assertEquals(1536, $collection['body']['dimension']); - - return [ - 'databaseId' => $databaseId, - 'collectionId' => $collection['body']['$id'], - ]; - } - - public function testCreateMultipleDatabasesWithCollections(): array - { - $projectId = $this->getProject()['$id']; - $apiKey = $this->getProject()['apiKey']; - $userId = $this->getUser()['$id']; - - /** - * Helper to create a database - */ - $createDatabase = function (string $name) use ($projectId, $apiKey) { - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apiKey - ], [ - 'databaseId' => ID::unique(), - 'name' => $name - ]); - - $this->assertEquals(201, $db['headers']['status-code']); - $this->assertEquals('vectorsdb', $db['body']['type']); - $this->assertEquals($name, $db['body']['name']); - $this->assertNotEmpty($db['body']['$id']); - - return $db['body']['$id']; - }; - - /** - * Helper to create a collection - */ - $createCollection = function (string $databaseId, string $name, int $dimensions = 1536) use ($projectId, $apiKey, $userId) { - $res = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apiKey - ], [ - 'collectionId' => ID::unique(), - 'name' => $name, - 'documentSecurity' => true, - 'dimension' => $dimensions, - 'permissions' => [ - Permission::create(Role::user($userId)), - ], - ]); - - $this->assertEquals(201, $res['headers']['status-code']); - $this->assertEquals($name, $res['body']['name']); - - return $res['body']['$id']; - }; - - /** - * === Database 1: MediaDB === - */ - $mediaDbId = $createDatabase('MediaDB'); - - $mediaCollections = ['Movies', 'Actors', 'Directors']; - $mediaCollectionIds = []; - - foreach ($mediaCollections as $col) { - $mediaCollectionIds[$col] = $createCollection($mediaDbId, $col); - } - - /** - * === Database 2: ContentDB === - */ - $contentDbId = $createDatabase('ContentDB'); - - $contentCollections = ['Articles', 'Authors']; - $contentCollectionIds = []; - - foreach ($contentCollections as $col) { - $contentCollectionIds[$col] = $createCollection($contentDbId, $col); - } - - // Create a tiny-dimension collection and insert a document to validate vector and object attributes - $tinyCollectionName = 'VectorsTiny'; - $tinyDimensions = 8; - $tinyCollectionId = $createCollection($mediaDbId, $tinyCollectionName, $tinyDimensions); - - return [ - 'databases' => [ - 'MediaDB' => [ - 'id' => $mediaDbId, - 'collections' => $mediaCollectionIds + ['VectorsTiny' => $tinyCollectionId], - ], - 'ContentDB' => [ - 'id' => $contentDbId, - 'collections' => $contentCollectionIds, - ], - ] - ]; - } - - public function testInvalidCollectionDimensions(): void - { - // dimensions = 0 -> expect 4xx - $bad0 = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'BadDims0' - ]); - $this->assertEquals(201, $bad0['headers']['status-code']); - $dbId = $bad0['body']['$id']; - $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $dbId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'ZeroDims', - 'documentSecurity' => true, - 'dimension' => 0, - 'permissions' => [Permission::create(Role::user($this->getUser()['$id']))], - ]); - $this->assertGreaterThanOrEqual(400, $col['headers']['status-code']); - $this->assertLessThan(500, $col['headers']['status-code']); - - // dimensions too large -> expect 4xx - $col2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $dbId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'HugeDims', - 'documentSecurity' => true, - 'dimension' => 16001, - 'permissions' => [Permission::create(Role::user($this->getUser()['$id']))], - ]); - $this->assertGreaterThanOrEqual(400, $col2['headers']['status-code']); - $this->assertLessThan(500, $col2['headers']['status-code']); - } - - public function testSingleDimensionVectorCollection(): void - { - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'SingleDim' - ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'OneDim', - 'documentSecurity' => true, - 'dimension' => 1, - 'permissions' => [Permission::create(Role::user($this->getUser()['$id']))], - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - // Create two docs with 1D embeddings - $id1 = ID::unique(); - $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id1}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'data' => ['embeddings' => [1.0]] - ]); - $id2 = ID::unique(); - $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id2}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'data' => ['embeddings' => [0.5]] - ]); - - // Query with vectorCosine - $res = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [Query::vectorCosine('embeddings', [1.0])->toString(), Query::limit(2)->toString()] - ]); - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertGreaterThanOrEqual(1, $res['body']['total']); - } - - public function testVectorInvalidValues(): void - { - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'InvalidVals' - ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'Docs', - 'documentSecurity' => true, - 'dimension' => 3, - 'permissions' => [Permission::create(Role::user($this->getUser()['$id']))], - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - $badPayloads = [ - ['embeddings' => [INF, 0.0, 0.0]], - ['embeddings' => [-INF, 0.0, 0.0]], - ['embeddings' => [NAN, 0.0, 0.0]], - ['embeddings' => ['x' => 1.0, 'y' => 0.0, 'z' => 0.0]], - ['embeddings' => [1.0, null, 0.0]], - ['embeddings' => [[1.0], [0.0], [0.0]]], - ['embeddings' => [true, false, true]], - ['embeddings' => [1.0, '2.0', 3.0]], - (function () { - $v = []; - $v[0] = 1.0; - $v[2] = 1.0; - return ['embeddings' => $v]; - })(), - ]; - - foreach ($badPayloads as $payload) { - $resp = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/" . ID::unique(), [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'data' => $payload - ]); - $this->assertGreaterThanOrEqual(400, $resp['headers']['status-code']); - $this->assertLessThan(500, $resp['headers']['status-code']); - } - } - - public function testVectorAllZerosAndQuery(): void - { - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'ZerosDB' - ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'Zeros', - 'documentSecurity' => true, - 'dimension' => 3, - 'permissions' => [Permission::create(Role::user($this->getUser()['$id']))], - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/" . ID::unique(), [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'data' => ['embeddings' => [0.0, 0.0, 0.0]] ]); - - $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/" . ID::unique(), [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'data' => ['embeddings' => [1.0, 0.0, 0.0]] ]); - - $results = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'queries' => [Query::vectorCosine('embeddings', [1.0, 0.0, 0.0])->toString()] ]); - $this->assertEquals(200, $results['headers']['status-code']); - $this->assertGreaterThan(0, $results['body']['total']); - } - - public function testVectorMultipleQueriesRejection(): void - { - // Create a simple DB and collection - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'databaseId' => ID::unique(), 'name' => 'MultiQueryDB' ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'collectionId' => ID::unique(), 'name' => 'Docs', 'documentSecurity' => true, 'dimension' => 3, 'permissions' => [Permission::create(Role::user($this->getUser()['$id']))] ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - // Two vector queries simultaneously should fail - $fail = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [ - Query::vectorCosine('embeddings', [1.0, 0.0, 0.0])->toString(), - Query::vectorEuclidean('embeddings', [1.0, 0.0, 0.0])->toString() - ] - ]); - $this->assertGreaterThanOrEqual(400, $fail['headers']['status-code']); - $this->assertLessThan(500, $fail['headers']['status-code']); - } - - public function testVectorQueryOnNonVectorAttribute(): void - { - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'databaseId' => ID::unique(), 'name' => 'NonVec' ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'collectionId' => ID::unique(), 'name' => 'Docs', 'documentSecurity' => true, 'dimension' => 3, 'permissions' => [Permission::create(Role::user($this->getUser()['$id']))] ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - // Query on non-vector attribute 'metadata' should fail - $fail = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'queries' => [Query::vectorCosine('metadata', [1.0, 0.0, 0.0])->toString()] ]); - $this->assertGreaterThanOrEqual(400, $fail['headers']['status-code']); - $this->assertLessThan(500, $fail['headers']['status-code']); - } - - public function testVectorEmptyQueryCollection(): void - { - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'databaseId' => ID::unique(), 'name' => 'EmptyQ' ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - $col = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'collectionId' => ID::unique(), 'name' => 'Docs', 'documentSecurity' => true, 'dimension' => 3, 'permissions' => [Permission::create(Role::user($this->getUser()['$id']))] ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - $res = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'queries' => [Query::vectorCosine('embeddings', [1.0, 0.0, 0.0])->toString()] ]); - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertEquals(0, $res['body']['total']); - } - - #[Depends('testCreateCollection')] - public function testCreateIndexes(array $data): array - { - $databaseId = $data['databaseId']; - $collectionId = $data['moviesId']; - - // HNSW Euclidean - $idxEuclidean = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'key' => 'embedding_euclidean', - 'type' => Database::INDEX_HNSW_EUCLIDEAN, - 'attributes' => ['embeddings'] - ]); - $this->assertEquals(202, $idxEuclidean['headers']['status-code']); - - // HNSW Dot (Inner Product) - $idxDot = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'key' => 'embedding_dot', - 'type' => Database::INDEX_HNSW_DOT, - 'attributes' => ['embeddings'] - ]); - $this->assertEquals(202, $idxDot['headers']['status-code']); - - // HNSW Cosine - $idxCosine = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'key' => 'embedding_cosine', - 'type' => Database::INDEX_HNSW_COSINE, - 'attributes' => ['embeddings'] - ]); - $this->assertEquals(202, $idxCosine['headers']['status-code']); - - return [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'indexes' => ['embedding_euclidean', 'embedding_dot', 'embedding_cosine'] - ]; - } - - #[Depends('testCreateIndexes')] - public function testListIndexes(array $data): void - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - $list = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - - $this->assertEquals(200, $list['headers']['status-code']); - $keys = array_map(fn ($i) => $i['key'], $list['body']['indexes'] ?? []); - foreach ($data['indexes'] as $expectedKey) { - $this->assertContains($expectedKey, $keys); - } - } - - #[Depends('testCreateIndexes')] - public function testGetIndexByKey(array $data): void - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - $keysToTypes = [ - 'embedding_euclidean' => Database::INDEX_HNSW_EUCLIDEAN, - 'embedding_dot' => Database::INDEX_HNSW_DOT, - 'embedding_cosine' => Database::INDEX_HNSW_COSINE, - ]; - - foreach ($keysToTypes as $key => $type) { - $res = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes/{$key}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertEquals($key, $res['body']['key']); - $this->assertEquals($type, $res['body']['type']); - } - } - -} diff --git a/tests/e2e/Services/Databases/VectorsDB/DatabasesConsoleClientTest.php b/tests/e2e/Services/Databases/VectorsDB/DatabasesConsoleClientTest.php deleted file mode 100644 index abe4d4968b..0000000000 --- a/tests/e2e/Services/Databases/VectorsDB/DatabasesConsoleClientTest.php +++ /dev/null @@ -1,312 +0,0 @@ -client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'databaseId' => ID::unique(), - 'name' => 'Vector Console DB', - ]); - $this->assertEquals(201, $database['headers']['status-code']); - $this->assertEquals('Vector Console DB', $database['body']['name']); - $this->assertTrue($database['body']['enabled']); - - $databaseId = $database['body']['$id']; - - /** - * Test for SUCCESS - */ - $movies = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'documentSecurity' => true, - ]); - - $this->assertEquals(201, $movies['headers']['status-code']); - $this->assertEquals($movies['body']['name'], 'Movies'); - - /** - * Test when database is disabled but can still create collections - */ - $database = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Vector Console DB Updated', - 'enabled' => false, - ]); - - $this->assertFalse($database['body']['enabled']); - - $tvShows = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'collectionId' => ID::unique(), - 'name' => 'TvShows', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'documentSecurity' => true, - ]); - - /** - * Test when collection is disabled but can still modify collections - */ - $database = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $movies['body']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Movies', - 'enabled' => false, - ]); - - $this->assertEquals(201, $tvShows['headers']['status-code']); - $this->assertEquals($tvShows['body']['name'], 'TvShows'); - - return ['moviesId' => $movies['body']['$id'], 'databaseId' => $databaseId, 'tvShowsId' => $tvShows['body']['$id']]; - } - - #[Depends('testCreateCollection')] - public function testListCollection(array $data) - { - /** - * Test when database is disabled but can still call list collections - */ - $databaseId = $data['databaseId']; - - $collections = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders())); - - $this->assertEquals(200, $collections['headers']['status-code']); - $this->assertEquals(2, $collections['body']['total']); - } - - #[Depends('testCreateCollection')] - public function testGetCollection(array $data) - { - $databaseId = $data['databaseId']; - $moviesCollectionId = $data['moviesId']; - - /** - * Test when database and collection are disabled but can still call get collection - */ - $collection = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $moviesCollectionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $collection['headers']['status-code']); - $this->assertEquals('Movies', $collection['body']['name']); - $this->assertEquals($moviesCollectionId, $collection['body']['$id']); - $this->assertFalse($collection['body']['enabled']); - } - - #[Depends('testCreateCollection')] - public function testUpdateCollection(array $data) - { - $databaseId = $data['databaseId']; - $moviesCollectionId = $data['moviesId']; - - /** - * Test When database and collection are disabled but can still call update collection - */ - $collection = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $moviesCollectionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Movies Updated', - 'enabled' => false - ]); - - $this->assertEquals(200, $collection['headers']['status-code']); - $this->assertEquals('Movies Updated', $collection['body']['name']); - $this->assertEquals($moviesCollectionId, $collection['body']['$id']); - $this->assertFalse($collection['body']['enabled']); - } - - #[Depends('testCreateCollection')] - public function testDeleteCollection(array $data) - { - $databaseId = $data['databaseId']; - $tvShowsId = $data['tvShowsId']; - - /** - * Test when database and collection are disabled but can still call delete collection - */ - $response = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId . '/collections/' . $tvShowsId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(204, $response['headers']['status-code']); - $this->assertEquals($response['body'], ""); - } - - #[Depends('testCreateCollection')] - public function testGetDatabaseUsage(array $data) - { - $databaseId = $data['databaseId']; - /** - * Test for FAILURE - */ - - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '32h' - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - /** - * Test for SUCCESS - */ - - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '24h' - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(11, count($response['body'])); - $this->assertEquals('24h', $response['body']['range']); - $this->assertIsNumeric($response['body']['documentsTotal']); - $this->assertIsNumeric($response['body']['collectionsTotal']); - $this->assertIsArray($response['body']['collections']); - $this->assertIsArray($response['body']['documents']); - } - - - #[Depends('testCreateCollection')] - public function testGetCollectionUsage(array $data) - { - $databaseId = $data['databaseId']; - /** - * Test for FAILURE - */ - - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '32h' - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/randomCollectionId/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '24h' - ]); - - $this->assertEquals(404, $response['headers']['status-code']); - - /** - * Test for SUCCESS - */ - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '24h' - ]); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(3, count($response['body'])); - $this->assertEquals('24h', $response['body']['range']); - $this->assertIsNumeric($response['body']['documentsTotal']); - $this->assertIsArray($response['body']['documents']); - } - - #[Depends('testCreateCollection')] - public function testGetCollectionLogs(array $data) - { - $databaseId = $data['databaseId']; - /** - * Test for SUCCESS - */ - $logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $logs['headers']['status-code']); - $this->assertIsArray($logs['body']['logs']); - $this->assertIsNumeric($logs['body']['total']); - - $logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [Query::limit(1)->toString()] - ]); - - $this->assertEquals(200, $logs['headers']['status-code']); - $this->assertIsArray($logs['body']['logs']); - $this->assertLessThanOrEqual(1, count($logs['body']['logs'])); - $this->assertIsNumeric($logs['body']['total']); - - $logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [Query::offset(1)->toString()] - ]); - - $this->assertEquals(200, $logs['headers']['status-code']); - $this->assertIsArray($logs['body']['logs']); - $this->assertIsNumeric($logs['body']['total']); - - $logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [Query::offset(1)->toString(), Query::limit(1)->toString()] - ]); - - $this->assertEquals(200, $logs['headers']['status-code']); - $this->assertIsArray($logs['body']['logs']); - $this->assertLessThanOrEqual(1, count($logs['body']['logs'])); - $this->assertIsNumeric($logs['body']['total']); - } -} diff --git a/tests/e2e/Services/Databases/VectorsDB/DatabasesCustomClientTest.php b/tests/e2e/Services/Databases/VectorsDB/DatabasesCustomClientTest.php deleted file mode 100644 index b27cb420b4..0000000000 --- a/tests/e2e/Services/Databases/VectorsDB/DatabasesCustomClientTest.php +++ /dev/null @@ -1,205 +0,0 @@ -client->call(Client::METHOD_POST, '/vectorsdb', [ - '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']; - - // Collection aliases write to create, update, delete - $movies = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'dimension' => 3, - 'documentSecurity' => true, - 'permissions' => [ - Permission::write(Role::user($this->getUser()['$id'])), - ], - ]); - - $moviesId = $movies['body']['$id']; - - $this->assertContains(Permission::create(Role::user($this->getUser()['$id'])), $movies['body']['$permissions']); - $this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $movies['body']['$permissions']); - $this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $movies['body']['$permissions']); - - // VectorsDB uses fixed schema (embeddings, metadata). No attribute creation needed. - - // Document aliases write to update, delete - $document1 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['k' => 'v'], - ], - 'permissions' => [ - Permission::write(Role::user($this->getUser()['$id'])), - ] - ]); - - $this->assertNotContains(Permission::create(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']); - $this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']); - $this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']); - - /** - * Test for FAILURE - */ - - // Document does not allow create permission - $document2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['k' => 'v'], - ], - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - ] - ]); - - $this->assertEquals(400, $document2['headers']['status-code']); - } - - public function testUpdateWithoutPermission(): array - { - // As a part of preparation, we get ID of currently logged-in user - $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals(200, $response['headers']['status-code']); - - $userId = $response['body']['$id']; - - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::custom('permissionCheckDatabase'), - 'name' => 'Test Database', - ]); - $this->assertEquals(201, $database['headers']['status-code']); - $this->assertEquals('Test Database', $database['body']['name']); - - $databaseId = $database['body']['$id']; - // Create collection - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::custom('permissionCheck'), - 'name' => 'permissionCheck', - 'dimension' => 3, - 'permissions' => [], - 'documentSecurity' => true, - ]); - $this->assertEquals(201, $response['headers']['status-code']); - - // Creating document by server, give read permission to our user + some other user - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/permissionCheck/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documentId' => ID::custom('permissionCheckDocument'), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['name' => 'AppwriteBeginner'], - ], - 'permissions' => [ - Permission::read(Role::user(ID::custom('user2'))), - Permission::read(Role::user($userId)), - Permission::update(Role::user($userId)), - Permission::delete(Role::user($userId)), - ], - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - - // Update document - // This is the point of this test. We should be allowed to do this action, and it should not fail on permission check - $response = $this->client->call(Client::METHOD_PATCH, '/vectorsdb/' . $databaseId . '/collections/permissionCheck/documents/permissionCheckDocument', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['name' => 'AppwriteExpert'], - ] - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Get name of the document, should be the new one - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/permissionCheck/documents/permissionCheckDocument', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals("AppwriteExpert", $response['body']['metadata']['name']); - - // Cleanup to prevent collision with other tests - // Delete collection - $response = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId . '/collections/permissionCheck', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(204, $response['headers']['status-code']); - - - // Wait for database worker to finish deleting collection - sleep(2); - - // Make sure collection has been deleted - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/permissionCheck', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - $this->assertEquals(404, $response['headers']['status-code']); - - return []; - } -} diff --git a/tests/e2e/Services/Databases/VectorsDB/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/VectorsDB/DatabasesCustomServerTest.php deleted file mode 100644 index 9564b76079..0000000000 --- a/tests/e2e/Services/Databases/VectorsDB/DatabasesCustomServerTest.php +++ /dev/null @@ -1,964 +0,0 @@ -client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::custom('first'), - 'name' => 'Test 1', - ]); - $this->assertEquals(201, $db1['headers']['status-code']); - $this->assertEquals('Test 1', $db1['body']['name']); - $this->assertEquals('vectorsdb', $db1['body']['type']); - // Validate database response model fields on create - $this->assertArrayHasKey('$id', $db1['body']); - $this->assertArrayHasKey('$createdAt', $db1['body']); - $this->assertArrayHasKey('$updatedAt', $db1['body']); - $this->assertArrayHasKey('enabled', $db1['body']); - - $db2 = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::custom('second'), - 'name' => 'Test 2', - ]); - $this->assertEquals(201, $db2['headers']['status-code']); - $this->assertEquals('Test 2', $db2['body']['name']); - $this->assertEquals('vectorsdb', $db2['body']['type']); - - $list = $this->client->call(Client::METHOD_GET, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $list['headers']['status-code']); - $this->assertIsInt($list['body']['total']); - $this->assertGreaterThanOrEqual(2, $list['body']['total']); - $this->assertIsArray($list['body']['databases']); - $this->assertArrayHasKey('$id', $list['body']['databases'][0]); - $this->assertArrayHasKey('name', $list['body']['databases'][0]); - $this->assertArrayHasKey('type', $list['body']['databases'][0]); - - return ['databaseId' => $db1['body']['$id']]; - } - - #[Depends('testListDatabases')] - public function testGetDatabase(array $data): array - { - $databaseId = $data['databaseId']; - $res = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertEquals($databaseId, $res['body']['$id']); - $this->assertEquals('Test 1', $res['body']['name']); - $this->assertEquals('vectorsdb', $res['body']['type']); - return ['databaseId' => $databaseId]; - } - - #[Depends('testListDatabases')] - public function testUpdateDatabase(array $data): array - { - $databaseId = $data['databaseId']; - $res = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test 1 Updated', - ]); - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertEquals('Test 1 Updated', $res['body']['name']); - $this->assertEquals('vectorsdb', $res['body']['type']); - return ['databaseId' => $databaseId]; - } - - #[Depends('testListDatabases')] - public function testDeleteDatabase(array $data): void - { - $databaseId = $data['databaseId']; - $del = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(204, $del['headers']['status-code']); - $this->assertEquals("", $del['body']); - - $get = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(404, $get['headers']['status-code']); - } - - public function testCollectionsCRUD(): array - { - // Create database for collections tests - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Collections DB', - ]); - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create two collections - $col1 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test 1', - 'collectionId' => ID::custom('first'), - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'documentSecurity' => true, - 'dimension' => 3, - ]); - $this->assertEquals(201, $col1['headers']['status-code']); - // Validate collection response model on create - $this->assertArrayHasKey('$id', $col1['body']); - $this->assertArrayHasKey('$createdAt', $col1['body']); - $this->assertArrayHasKey('$updatedAt', $col1['body']); - $this->assertArrayHasKey('enabled', $col1['body']); - $this->assertArrayHasKey('documentSecurity', $col1['body']); - $this->assertArrayHasKey('dimension', $col1['body']); - - $col2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test 2', - 'collectionId' => ID::custom('second'), - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'documentSecurity' => true, - 'dimension' => 3, - ]); - $this->assertEquals(201, $col2['headers']['status-code']); - $this->assertArrayHasKey('$id', $col2['body']); - $this->assertArrayHasKey('$createdAt', $col2['body']); - $this->assertArrayHasKey('$updatedAt', $col2['body']); - - // List collections - $list = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $list['headers']['status-code']); - $this->assertIsInt($list['body']['total']); - $this->assertGreaterThanOrEqual(2, $list['body']['total']); - $this->assertIsArray($list['body']['collections']); - $this->assertArrayHasKey('$id', $list['body']['collections'][0]); - $this->assertArrayHasKey('name', $list['body']['collections'][0]); - $this->assertArrayHasKey('dimension', $list['body']['collections'][0]); - - // Get collection - $get = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $col1['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertEquals($col1['body']['$id'], $get['body']['$id']); - $this->assertEquals('Test 1', $get['body']['name']); - $this->assertEquals(3, $get['body']['dimension']); - - // Update collection (name only) - $upd = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $col1['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test 1 Updated', - ]); - $this->assertEquals(200, $upd['headers']['status-code']); - $this->assertEquals('Test 1 Updated', $upd['body']['name']); - $this->assertArrayHasKey('$updatedAt', $upd['body']); - - // Delete collection - $del = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId . '/collections/' . $col2['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(204, $del['headers']['status-code']); - $this->assertEquals("", $del['body']); - - return [ - 'databaseId' => $databaseId, - 'collectionId' => $col1['body']['$id'], - ]; - } - - #[Depends('testCollectionsCRUD')] - public function testUpdateCollectionMore(array $data): array - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - // Update collection name and dimensions - $upd = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test 1 Renamed', - 'dimension' => 4, - ]); - $this->assertEquals(200, $upd['headers']['status-code']); - $this->assertEquals('Test 1 Renamed', $upd['body']['name']); - $this->assertEquals(4, $upd['body']['dimension']); - - // Read back to confirm - $get = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertEquals('Test 1 Renamed', $get['body']['name']); - $this->assertEquals(4, $get['body']['dimension']); - - return $data; - } - - #[Depends('testCollectionsCRUD')] - public function testUpdateCollectionEnabledFlag(array $data): array - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - // Disable collection - $disable = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Updated', - 'enabled' => false, - ]); - $this->assertEquals(200, $disable['headers']['status-code']); - $this->assertFalse($disable['body']['enabled']); - - // Re-enable collection - $enable = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Updated', - 'enabled' => true, - ]); - $this->assertEquals(200, $enable['headers']['status-code']); - $this->assertTrue($enable['body']['enabled']); - - return $data; - } - - public function testUpdateDatabaseNameAndEnabled(): void - { - // Create isolated database for this test to avoid ordering conflicts - $create = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Update DB', - ]); - $this->assertEquals(201, $create['headers']['status-code']); - $databaseId = $create['body']['$id']; - - // Update name - $rename = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test DB Renamed', - ]); - $this->assertEquals(200, $rename['headers']['status-code']); - $this->assertEquals('Test DB Renamed', $rename['body']['name']); - - // Toggle enabled off then on - $disable = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test DB Renamed', - 'enabled' => false, - ]); - $this->assertEquals(200, $disable['headers']['status-code']); - $this->assertFalse($disable['body']['enabled']); - - $enable = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test DB Renamed', - 'enabled' => true, - ]); - $this->assertEquals(200, $enable['headers']['status-code']); - $this->assertTrue($enable['body']['enabled']); - - // Cleanup - $del = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(204, $del['headers']['status-code']); - } - - #[Depends('testCollectionsCRUD')] - public function testRecreateIndex(array $data): void - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - // Create a new index variant - $create = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'key' => 'embedding_euclidean_v2', - 'type' => Database::INDEX_HNSW_EUCLIDEAN, - 'attributes' => ['embeddings'] - ]); - $this->assertEquals(202, $create['headers']['status-code']); - - // Ensure it exists - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes/embedding_euclidean_v2", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertEquals('embedding_euclidean_v2', $get['body']['key']); - - // Delete it - $del = $this->client->call(Client::METHOD_DELETE, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes/embedding_euclidean_v2", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(204, $del['headers']['status-code']); - } - - #[Depends('testCollectionsCRUD')] - public function testIndexesCRUD(array $data): void - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - // Create indexes - $eu = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'key' => 'embedding_euclidean', - 'type' => Database::INDEX_HNSW_EUCLIDEAN, - 'attributes' => ['embeddings'] - ]); - $this->assertEquals(202, $eu['headers']['status-code']); - - $dot = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'key' => 'embedding_dot', - 'type' => Database::INDEX_HNSW_DOT, - 'attributes' => ['embeddings'] - ]); - $this->assertEquals(202, $dot['headers']['status-code']); - - $cos = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'key' => 'embedding_cosine', - 'type' => Database::INDEX_HNSW_COSINE, - 'attributes' => ['embeddings'] - ]); - $this->assertEquals(202, $cos['headers']['status-code']); - - // List indexes - $list = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $list['headers']['status-code']); - $this->assertIsArray($list['body']['indexes']); - $keys = array_map(fn ($i) => $i['key'], $list['body']['indexes']); - $this->assertContains('embedding_euclidean', $keys); - $this->assertContains('embedding_dot', $keys); - $this->assertContains('embedding_cosine', $keys); - - // Get index by key - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes/embedding_euclidean", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertEquals('embedding_euclidean', $get['body']['key']); - $this->assertEquals(Database::INDEX_HNSW_EUCLIDEAN, $get['body']['type']); - - // Delete index - $del = $this->client->call(Client::METHOD_DELETE, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes/embedding_dot", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(204, $del['headers']['status-code']); - sleep(4); - // Ensure it's gone - $getMissing = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes/embedding_dot", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(404, $getMissing['headers']['status-code']); - } - - public function testBulkCreate(): array - { - // Setup: create isolated database and collection - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'BulkDBCreate' - ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'BulkColCreate', - 'documentSecurity' => true, - 'dimension' => 3, - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - $docs = [ - [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['group' => 'bulkA'], - '$permissions' => [Permission::read(Role::any())] - ], - [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['group' => 'bulkB'], - '$permissions' => [Permission::read(Role::any())] - ], - ]; - - $res = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documents' => $docs - ]); - - $this->assertEquals(201, $res['headers']['status-code']); - $this->assertIsInt($res['body']['total'] ?? 0); - $this->assertGreaterThanOrEqual(2, $res['body']['total']); - $this->assertIsArray($res['body']['documents']); - $this->assertCount(2, $res['body']['documents']); - - $ids = array_map(fn ($d) => $d['$id'], $res['body']['documents']); - $this->assertNotEmpty($ids[0]); - $this->assertNotEmpty($ids[1]); - - // Fetch and validate persisted data via GET - foreach ($ids as $i => $id) { - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertEquals($id, $get['body']['$id']); - $this->assertIsArray($get['body']['embeddings']); - $this->assertCount(3, $get['body']['embeddings']); - $this->assertArrayHasKey('group', $get['body']['metadata']); - } - - return [ 'databaseId' => $databaseId, 'collectionId' => $collectionId, 'bulkIds' => $ids ]; - } - - public function testCreateTextEmbeddingsSuccessAndErrors(): void - { - // Setup new database and collection - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'EmbedDB', - ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'EmbedCol', - 'documentSecurity' => true, - 'dimension' => 3, - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - // Success: two embeddings - $this->assertEventually(function () { - $ok = $this->client->call(Client::METHOD_POST, "/vectorsdb/embeddings/text", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'model' => 'embeddinggemma', - 'texts' => [ - 'hello world', - 'second sentence', - ], - ]); - $this->assertEquals(200, $ok['headers']['status-code']); - $this->assertIsInt($ok['body']['total'] ?? 0); - $this->assertEquals(2, $ok['body']['total']); - $this->assertIsArray($ok['body']['embeddings']); - $this->assertCount(2, $ok['body']['embeddings']); - foreach ($ok['body']['embeddings'] as $embed) { - $this->assertIsString($embed['model']); - $this->assertIsInt($embed['dimension']); - $this->assertIsArray($embed['embedding']); - $this->assertGreaterThan(0, count($embed['embedding'])); - $this->assertArrayHasKey('error', $embed); - } - }, 3000, 100); - - // Error: missing texts payload - $missingTexts = $this->client->call(Client::METHOD_POST, "/vectorsdb/embeddings/text", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], []); - $this->assertEquals(400, $missingTexts['headers']['status-code']); - - // Error: invalid texts item type (must be strings) - $invalidItem = $this->client->call(Client::METHOD_POST, "/vectorsdb/embeddings/text", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'model' => 'embeddinggemma', - 'texts' => [ - 'valid text', - 123, // invalid, not a string - ], - ]); - $this->assertEquals(400, $invalidItem['headers']['status-code']); - - // Error: unknown embedding model - $unknownModel = $this->client->call(Client::METHOD_POST, "/vectorsdb/embeddings/text", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'model' => 'nonexistent-model', - 'texts' => ['hello'], - ]); - $this->assertEquals(400, $unknownModel['headers']['status-code']); - } - - public function testBulkUpsert(): void - { - // Setup fresh db/collection - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'databaseId' => ID::unique(), 'name' => 'BulkDBUpsert' ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'BulkColUpsert', - 'documentSecurity' => true, - 'dimension' => 3, - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - $docs = [ - [ - 'embeddings' => [0.5, 0.5, 0.0], - 'metadata' => ['group' => 'bulkA', 'updated' => true], - '$permissions' => [Permission::read(Role::any())] - ], - [ - 'embeddings' => [0.2, 0.8, 0.0], - 'metadata' => ['group' => 'bulkB', 'updated' => true], - '$permissions' => [Permission::read(Role::any())] - ], - ]; - - $res = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documents' => $docs - ]); - - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertIsArray($res['body']['documents']); - $this->assertCount(2, $res['body']['documents']); - $this->assertTrue($res['body']['documents'][0]['metadata']['updated']); - $this->assertTrue($res['body']['documents'][1]['metadata']['updated']); - - // Fetch and validate updated content - $ids = array_map(fn ($d) => $d['$id'], $res['body']['documents']); - foreach ($ids as $id) { - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertTrue($get['body']['metadata']['updated']); - } - - // Perform another bulk upsert to mutate the same documents - $docs2 = [ - [ 'embeddings' => [0.6, 0.4, 0.0], 'metadata' => ['updatedAgain' => true] ], - [ 'embeddings' => [0.3, 0.7, 0.0], 'metadata' => ['updatedAgain' => true] ], - ]; - $res2 = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documents' => $docs2 - ]); - $this->assertEquals(200, $res2['headers']['status-code']); - $this->assertIsArray($res2['body']['documents']); - $this->assertCount(2, $res2['body']['documents']); - - // Fetch again and assert second update persisted - $ids2 = array_map(fn ($d) => $d['$id'], $res2['body']['documents']); - foreach ($ids2 as $id) { - $get2 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get2['headers']['status-code']); - $this->assertTrue($get2['body']['metadata']['updatedAgain']); - } - } - - public function testBulkUpdate(): void - { - // Setup: create db/collection and two docs - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'databaseId' => ID::unique(), 'name' => 'BulkDBUpdate' ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'BulkColUpdate', - 'documentSecurity' => true, - 'dimension' => 3, - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - $seed = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documents' => [ - ['embeddings' => [1.0,0.0,0.0], 'metadata' => ['seed' => 1], '$permissions' => [Permission::read(Role::any())]], - ['embeddings' => [0.0,1.0,0.0], 'metadata' => ['seed' => 2], '$permissions' => [Permission::read(Role::any())]] - ] - ]); - $this->assertEquals(200, $seed['headers']['status-code']); - $ids = array_map(fn ($d) => $d['$id'], $seed['body']['documents']); - - $res = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'data' => [ 'metadata' => ['bulkUpdated' => true] ], - 'queries' => [ - \Utopia\Database\Query::equal('$id', $ids)->toString() - ] - ]); - - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertIsArray($res['body']['documents']); - $this->assertCount(2, $res['body']['documents']); - foreach ($res['body']['documents'] as $doc) { - $this->assertTrue($doc['metadata']['bulkUpdated']); - } - - // Fetch by IDs and assert update persisted - foreach ($ids as $id) { - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertTrue($get['body']['metadata']['bulkUpdated']); - } - } - - public function testBulkDelete(): void - { - // Setup: create db/collection and two docs - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'databaseId' => ID::unique(), 'name' => 'BulkDBDelete' ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'BulkColDelete', - 'documentSecurity' => true, - 'dimension' => 3, - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - $seed = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documents' => [ - ['embeddings' => [1.0,0.0,0.0], 'metadata' => ['seed' => 1], '$permissions' => [Permission::read(Role::any())]], - ['embeddings' => [0.0,1.0,0.0], 'metadata' => ['seed' => 2], '$permissions' => [Permission::read(Role::any())]] - ] - ]); - $this->assertEquals(200, $seed['headers']['status-code']); - $ids = array_map(fn ($d) => $d['$id'], $seed['body']['documents']); - - $res = $this->client->call(Client::METHOD_DELETE, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [ - \Utopia\Database\Query::equal('$id', $ids)->toString() - ] - ]); - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertIsInt($res['body']['total'] ?? 0); - $this->assertGreaterThanOrEqual(2, $res['body']['total']); - - // Ensure they are deleted - foreach ($ids as $id) { - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(404, $get['headers']['status-code']); - } - } - - public function testCustomTimestamps(): void - { - // Setup: create database and collection - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'TimestampTestDB' - ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'TimestampTestCollection', - 'documentSecurity' => true, - 'dimension' => 1536, - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - // Test: Create document with custom timestamps using PUT (upsert) - $customCreatedAt = '1970-01-01T00:00:00.000+00:00'; - $customUpdatedAt = '1970-01-01T00:00:00.000+00:00'; - $vector = array_fill(0, 1536, 0.0); - $vector[0] = 1.0; - $documentId = ID::unique(); - - $doc = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documentId' => $documentId, - 'data' => [ - '$createdAt' => $customCreatedAt, - '$updatedAt' => $customUpdatedAt, - 'embeddings' => $vector, - 'metadata' => ['test' => 'custom_timestamps'] - ] - ]); - - $this->assertEquals(201, $doc['headers']['status-code']); - $documentId = $doc['body']['$id']; - $this->assertNotEmpty($documentId); - - // Verify timestamps were set correctly - $this->assertEquals($customCreatedAt, $doc['body']['$createdAt'], 'CreatedAt should match custom timestamp'); - $this->assertEquals($customUpdatedAt, $doc['body']['$updatedAt'], 'UpdatedAt should match custom timestamp'); - - // Fetch document and verify timestamps persist - $fetched = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - - $this->assertEquals(200, $fetched['headers']['status-code']); - $this->assertEquals($customCreatedAt, $fetched['body']['$createdAt'], 'CreatedAt should persist after fetch'); - $this->assertEquals($customUpdatedAt, $fetched['body']['$updatedAt'], 'UpdatedAt should persist after fetch'); - - // Test: Update document with new custom timestamps - $newCustomUpdatedAt = '2000-01-01T12:00:00.000+00:00'; - $vector2 = array_fill(0, 1536, 0.0); - $vector2[1] = 1.0; - - $updated = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'data' => [ - '$createdAt' => $customCreatedAt, // Keep original createdAt - '$updatedAt' => $newCustomUpdatedAt, // Update updatedAt - 'embeddings' => $vector2, - 'metadata' => ['test' => 'updated_timestamps'] - ] - ]); - - $this->assertEquals(200, $updated['headers']['status-code']); - $this->assertEquals($customCreatedAt, $updated['body']['$createdAt'], 'CreatedAt should remain unchanged'); - $this->assertEquals($newCustomUpdatedAt, $updated['body']['$updatedAt'], 'UpdatedAt should be updated to new custom timestamp'); - - // Final verification - $final = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - - $this->assertEquals(200, $final['headers']['status-code']); - $this->assertEquals($customCreatedAt, $final['body']['$createdAt'], 'CreatedAt should persist through updates'); - $this->assertEquals($newCustomUpdatedAt, $final['body']['$updatedAt'], 'UpdatedAt should reflect the latest custom timestamp'); - } - -} diff --git a/tests/e2e/Services/Databases/VectorsDB/Permissions/DatabasesPermissionsGuestTest.php b/tests/e2e/Services/Databases/VectorsDB/Permissions/DatabasesPermissionsGuestTest.php deleted file mode 100644 index 9335b7f55b..0000000000 --- a/tests/e2e/Services/Databases/VectorsDB/Permissions/DatabasesPermissionsGuestTest.php +++ /dev/null @@ -1,280 +0,0 @@ -authorization)) { - return $this->authorization; - } - - $this->authorization = new Authorization(); - - return $this->authorization; - } - - public function createCollection(): array - { - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'VectorGuestDB', - ]); - $this->assertEquals(201, $database['headers']['status-code']); - $this->assertEquals('VectorGuestDB', $database['body']['name']); - - $databaseId = $database['body']['$id']; - $publicMovies = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - $privateMovies = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'dimension' => 3, - 'permissions' => [], - 'documentSecurity' => true, - ]); - - $publicCollection = ['id' => $publicMovies['body']['$id']]; - $privateCollection = ['id' => $privateMovies['body']['$id']]; - - return [ - 'databaseId' => $databaseId, - 'publicCollectionId' => $publicCollection['id'], - 'privateCollectionId' => $privateCollection['id'], - ]; - } - - public static function permissionsProvider(): array - { - return [ - [[Permission::read(Role::any())]], - [[Permission::read(Role::users())]], - [[Permission::update(Role::any()), Permission::delete(Role::any())]], - [[Permission::read(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any())]], - [[Permission::read(Role::users()), Permission::update(Role::users()), Permission::delete(Role::users())]], - [[Permission::read(Role::any()), Permission::update(Role::users()), Permission::delete(Role::users())]], - ]; - } - - #[DataProvider('permissionsProvider')] - public function testReadDocuments($permissions) - { - $data = $this->createCollection(); - $publicCollectionId = $data['publicCollectionId']; - $privateCollectionId = $data['privateCollectionId']; - $databaseId = $data['databaseId']; - - $publicResponse = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ], - 'permissions' => $permissions, - ]); - $privateResponse = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ], - 'permissions' => $permissions, - ]); - - $this->assertEquals(201, $publicResponse['headers']['status-code']); - $this->assertEquals(201, $privateResponse['headers']['status-code']); - - $roles = $this->getAuthorization()->getRoles(); - $this->getAuthorization()->cleanRoles(); - - $publicDocuments = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]); - $privateDocuments = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]); - - $this->assertEquals(1, $publicDocuments['body']['total']); - $this->assertEquals($permissions, $publicDocuments['body']['documents'][0]['$permissions']); - - if (\in_array(Permission::read(Role::any()), $permissions)) { - $this->assertEquals(1, $privateDocuments['body']['total']); - $this->assertEquals($permissions, $privateDocuments['body']['documents'][0]['$permissions']); - } else { - $this->assertEquals(0, $privateDocuments['body']['total']); - } - - foreach ($roles as $role) { - $this->getAuthorization()->addRole($role); - } - } - - public function testWriteDocument() - { - $data = $this->createCollection(); - $publicCollectionId = $data['publicCollectionId']; - $privateCollectionId = $data['privateCollectionId']; - $databaseId = $data['databaseId']; - - $roles = $this->getAuthorization()->getRoles(); - $this->getAuthorization()->cleanRoles(); - - $publicResponse = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ] - ]); - - $publicDocumentId = $publicResponse['body']['$id']; - $this->assertEquals(201, $publicResponse['headers']['status-code']); - - $privateResponse = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ], - ]); - - $this->assertEquals(401, $privateResponse['headers']['status-code']); - - // Create a document in private collection with API key so we can test that update and delete are also not allowed - $privateResponse = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 0.0, 1.0], - 'metadata' => ['title' => 'Lorem'], - ], - ]); - - $this->assertEquals(201, $privateResponse['headers']['status-code']); - $privateDocumentId = $privateResponse['body']['$id']; - - $publicDocument = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents/' . $publicDocumentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'data' => [ - 'embeddings' => [0.5, 0.5, 0.0], - 'metadata' => ['title' => 'Thor: Ragnarok'], - ], - ]); - - $this->assertEquals(200, $publicDocument['headers']['status-code']); - $this->assertEquals('Thor: Ragnarok', $publicDocument['body']['metadata']['title']); - - $privateDocument = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents/' . $privateDocumentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'data' => [ - 'embeddings' => [0.2, 0.3, 0.5], - 'metadata' => ['title' => 'Thor: Ragnarok'], - ], - ]); - - $this->assertEquals(401, $privateDocument['headers']['status-code']); - - $publicDocument = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId . '/collections/' . $publicCollectionId . '/documents/' . $publicDocumentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]); - - $this->assertEquals(204, $publicDocument['headers']['status-code']); - - $privateDocument = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId . '/collections/' . $privateCollectionId . '/documents/' . $privateDocumentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]); - - $this->assertEquals(401, $privateDocument['headers']['status-code']); - - foreach ($roles as $role) { - $this->getAuthorization()->addRole($role); - } - } - - public function testWriteDocumentWithPermissions() - { - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'VectorGuestPermsWrite', - ]); - $this->assertEquals(201, $database['headers']['status-code']); - $this->assertEquals('VectorGuestPermsWrite', $database['body']['name']); - - $databaseId = $database['body']['$id']; - $movies = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'dimension' => 3, - 'permissions' => [ - Permission::create(Role::any()), - ], - 'documentSecurity' => true - ]); - - $moviesId = $movies['body']['$id']; - - $document = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $moviesId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['title' => 'Thor: Ragnarok'], - ], - 'permissions' => [ - Permission::read(Role::any()), - ] - ]); - - $this->assertEquals(201, $document['headers']['status-code']); - $this->assertEquals('Thor: Ragnarok', $document['body']['metadata']['title']); - } -} diff --git a/tests/e2e/Services/Databases/VectorsDB/Permissions/DatabasesPermissionsMemberTest.php b/tests/e2e/Services/Databases/VectorsDB/Permissions/DatabasesPermissionsMemberTest.php deleted file mode 100644 index cbc2add857..0000000000 --- a/tests/e2e/Services/Databases/VectorsDB/Permissions/DatabasesPermissionsMemberTest.php +++ /dev/null @@ -1,196 +0,0 @@ - $this->createUser('user1', 'lorem@ipsum.com'), - 'user2' => $this->createUser('user2', 'dolor@ipsum.com'), - ]; - } - - public static function permissionsProvider(): array - { - return [ - [[Permission::read(Role::any())], 1, 1, 1], - [[Permission::read(Role::users())], 2, 2, 2], - [[Permission::read(Role::user(ID::custom('random')))], 3, 3, 2], - [[Permission::read(Role::user(ID::custom('lorem'))), Permission::update(Role::user('lorem')), Permission::delete(Role::user('lorem'))], 4, 4, 2], - [[Permission::read(Role::user(ID::custom('dolor'))), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))], 5, 5, 2], - [[Permission::read(Role::user(ID::custom('dolor'))), Permission::read(Role::user('lorem')), Permission::update(Role::user('dolor')), Permission::delete(Role::user('dolor'))], 6, 6, 2], - [[Permission::update(Role::any()), Permission::delete(Role::any())], 7, 7, 2], - [[Permission::read(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any())], 8, 8, 3], - [[Permission::read(Role::any()), Permission::update(Role::users()), Permission::delete(Role::users())], 9, 9, 4], - [[Permission::read(Role::user(ID::custom('user1')))], 10, 10, 5], - [[Permission::read(Role::user(ID::custom('user1'))), Permission::read(Role::user(ID::custom('user1')))], 11, 11, 6], - [[Permission::read(Role::users()), Permission::update(Role::users()), Permission::delete(Role::users())], 12, 12, 7], - ]; - } - - /** - * Setup database - * - * Data providers lose object state so explicitly pass [$users, $collections] to each iteration - * - * @return array - * @throws \Exception - */ - public function testSetupDatabase(): array - { - $this->createUsers(); - - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', $this->getServerHeader(), [ - 'databaseId' => ID::unique(), - 'name' => 'Test Database', - ]); - $this->assertEquals(201, $db['headers']['status-code']); - - $databaseId = $db['body']['$id']; - - $public = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'documentSecurity' => true, - ]); - $this->assertEquals(201, $public['headers']['status-code']); - $this->collections = ['public' => $public['body']['$id']]; - - $private = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::unique(), - 'name' => 'Private Movies', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::users()), - Permission::create(Role::users()), - Permission::update(Role::users()), - Permission::delete(Role::users()), - ], - 'documentSecurity' => true, - ]); - $this->assertEquals(201, $private['headers']['status-code']); - $this->collections['private'] = $private['body']['$id']; - - $doconly = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::unique(), - 'name' => 'Document Only Movies', - 'dimension' => 3, - 'permissions' => [], - 'documentSecurity' => true, - ]); - $this->assertEquals(201, $private['headers']['status-code']); - $this->collections['doconly'] = $doconly['body']['$id']; - - return [ - 'users' => $this->users, - 'collections' => $this->collections, - 'databaseId' => $databaseId - ]; - } - - /** - * Data provider params are passed before test dependencies. - */ - #[DataProvider('permissionsProvider')] - #[Depends('testSetupDatabase')] - public function testReadDocuments($permissions, $anyCount, $usersCount, $docOnlyCount, $data) - { - $users = $data['users']; - $collections = $data['collections']; - $databaseId = $data['databaseId']; - - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $collections['public'] . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ], - 'permissions' => $permissions - ]); - $this->assertEquals(201, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $collections['private'] . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ], - 'permissions' => $permissions - ]); - $this->assertEquals(201, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $collections['doconly'] . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 0.0, 1.0], - 'metadata' => ['title' => 'Lorem'], - ], - 'permissions' => $permissions - ]); - $this->assertEquals(201, $response['headers']['status-code']); - - /** - * Check "any" permission collection - */ - $documents = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collections['public'] . '/documents', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users['user1']['session'], - ]); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals($anyCount, $documents['body']['total']); - - /** - * Check "users" permission collection - */ - $documents = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collections['private'] . '/documents', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users['user1']['session'], - ]); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals($usersCount, $documents['body']['total']); - - /** - * Check "user:user1" document only permission collection - */ - $documents = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collections['doconly'] . '/documents', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users['user1']['session'], - ]); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals($docOnlyCount, $documents['body']['total']); - } -} diff --git a/tests/e2e/Services/Databases/VectorsDB/Permissions/DatabasesPermissionsScope.php b/tests/e2e/Services/Databases/VectorsDB/Permissions/DatabasesPermissionsScope.php deleted file mode 100644 index be1800b654..0000000000 --- a/tests/e2e/Services/Databases/VectorsDB/Permissions/DatabasesPermissionsScope.php +++ /dev/null @@ -1,87 +0,0 @@ -client->call(Client::METHOD_POST, '/account', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? '', - ], [ - 'userId' => $id, - 'email' => $email, - 'password' => $password - ]); - - $this->assertEquals(201, $user['headers']['status-code']); - - $session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'email' => $email, - 'password' => $password, - ]); - - $session = $session['cookies']['a_session_' . $this->getProject()['$id']]; - - $user = [ - '$id' => $user['body']['$id'], - 'email' => $user['body']['email'], - 'session' => $session, - ]; - $this->users[$id] = $user; - - return $user; - } - - public function getCreatedUser(string $id): array - { - return $this->users[$id] ?? []; - } - - public function createTeam(string $id, string $name): array - { - $team = $this->client->call(Client::METHOD_POST, '/teams', $this->getServerHeader(), [ - 'teamId' => $id, - 'name' => $name - ]); - $this->teams[$id] = $team['body']; - - return $team['body']; - } - - public function addToTeam(string $user, string $team, array $roles = []): array - { - $membership = $this->client->call(Client::METHOD_POST, '/teams/' . $team . '/memberships', $this->getServerHeader(), [ - 'teamId' => $team, - 'email' => $this->getCreatedUser($user)['email'], - 'roles' => $roles, - 'url' => 'http://localhost:5000/join-us#title' - ]); - - return [ - 'user' => $membership['body']['userId'], - 'membership' => $membership['body']['$id'] - ]; - } - - public function getServerHeader(): array - { - return [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]; - } -} diff --git a/tests/e2e/Services/Databases/VectorsDB/Permissions/DatabasesPermissionsTeamTest.php b/tests/e2e/Services/Databases/VectorsDB/Permissions/DatabasesPermissionsTeamTest.php deleted file mode 100644 index 4091ea7140..0000000000 --- a/tests/e2e/Services/Databases/VectorsDB/Permissions/DatabasesPermissionsTeamTest.php +++ /dev/null @@ -1,199 +0,0 @@ - $this->createTeam('team1', 'Team 1'), - 'team2' => $this->createTeam('team2', 'Team 2'), - ]; - } - - public function createUsers(): array - { - return [ - 'user1' => $this->createUser('user1', 'lorem@ipsum.com'), - 'user2' => $this->createUser('user2', 'dolor@ipsum.com'), - 'user3' => $this->createUser('user3', 'sit@ipsum.com'), - ]; - } - - public function createCollections($teams) - { - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', $this->getServerHeader(), [ - 'databaseId' => $this->databaseId, - 'name' => 'Test Database', - ]); - $this->assertEquals(201, $db['headers']['status-code']); - - $collection1 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $this->databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::custom('collection1'), - 'name' => 'Collection 1', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::team($teams['team1']['$id'])), - Permission::create(Role::team($teams['team1']['$id'], 'admin')), - Permission::update(Role::team($teams['team1']['$id'], 'admin')), - Permission::delete(Role::team($teams['team1']['$id'], 'admin')), - ], - ]); - - $this->collections['collection1'] = $collection1['body']['$id']; - - $collection2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $this->databaseId . '/collections', $this->getServerHeader(), [ - 'collectionId' => ID::custom('collection2'), - 'name' => 'Collection 2', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::team($teams['team2']['$id'])), - Permission::create(Role::team($teams['team2']['$id'], 'owner')), - Permission::update(Role::team($teams['team2']['$id'], 'owner')), - Permission::delete(Role::team($teams['team2']['$id'], 'owner')), - ] - ]); - - $this->collections['collection2'] = $collection2['body']['$id']; - - return $this->collections; - } - - /* - * $success = can $user read from $collection - * [$user, $collection, $success] - */ - public static function readDocumentsProvider(): array - { - return [ - ['user1', 'collection1', true], - ['user2', 'collection1', false], - ['user3', 'collection1', true], - ['user1', 'collection2', false], - ['user2', 'collection2', true], - ['user3', 'collection2', true], - ]; - } - - /* - * $success = can $user write to $collection - * [$user, $collection, $success] - */ - public static function writeDocumentsProvider(): array - { - return [ - ['user1', 'collection1', true], - ['user2', 'collection1', false], - ['user3', 'collection1', false], - ['user1', 'collection2', false], - ['user2', 'collection2', true], - ['user3', 'collection2', false], - ]; - } - - /** - * Setup database - * - * Data providers lose object state - * so explicitly pass $users to each iteration - * @return array $users - */ - public function testSetupDatabase(): array - { - $this->createUsers(); - $this->createTeams(); - - $this->addToTeam('user1', 'team1', ['admin']); - $this->addToTeam('user2', 'team2', ['owner']); - - // user3 in both teams but with no roles - $this->addToTeam('user3', 'team1'); - $this->addToTeam('user3', 'team2'); - - $this->createCollections($this->teams); - - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $this->databaseId . '/collections/' . $this->collections['collection1'] . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['title' => 'Lorem'], - ], - ]); - $this->assertEquals(201, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $this->databaseId . '/collections/' . $this->collections['collection2'] . '/documents', $this->getServerHeader(), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['title' => 'Ipsum'], - ], - ]); - $this->assertEquals(201, $response['headers']['status-code']); - - return $this->users; - } - - /** - * Data provider params are passed before test dependencies. - */ - #[Depends('testSetupDatabase')] - #[DataProvider('readDocumentsProvider')] - public function testReadDocuments($user, $collection, $success, $users) - { - $documents = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $this->databaseId . '/collections/' . $collection . '/documents', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users[$user]['session'], - ]); - - if ($success) { - $this->assertCount(1, $documents['body']['documents']); - } else { - $this->assertEquals(401, $documents['headers']['status-code']); - } - } - - #[Depends('testSetupDatabase')] - #[DataProvider('writeDocumentsProvider')] - public function testWriteDocuments($user, $collection, $success, $users) - { - $documents = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $this->databaseId . '/collections/' . $collection . '/documents', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $users[$user]['session'], - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.2, 0.3, 0.5], - 'metadata' => ['title' => 'Ipsum'], - ], - ]); - - if ($success) { - $this->assertEquals(201, $documents['headers']['status-code']); - } else { - // 401 if user is a part of team, 404 otherwise - $this->assertContains($documents['headers']['status-code'], [401, 404]); - } - } -} diff --git a/tests/e2e/Services/Databases/VectorsDB/Transactions/ACIDTest.php b/tests/e2e/Services/Databases/VectorsDB/Transactions/ACIDTest.php deleted file mode 100644 index aa8d87eb8e..0000000000 --- a/tests/e2e/Services/Databases/VectorsDB/Transactions/ACIDTest.php +++ /dev/null @@ -1,528 +0,0 @@ -client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'AtomicityTestDB' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create collection for the test - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'AtomicityTest', - 'dimension' => 3, - 'documentSecurity' => false, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - ], - ]); - - $this->assertEquals(201, $collection['headers']['status-code']); - $collectionId = $collection['body']['$id']; - - // Create a document outside the transaction - $existingDocumentId = 'existing_doc'; - $doc1 = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => $existingDocumentId, - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['email' => 'existing@example.com'], - ], - ]); - - $this->assertEquals(201, $doc1['headers']['status-code']); - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(201, $transaction['headers']['status-code'], 'Transaction creation should succeed. Response: ' . json_encode($transaction)); - $this->assertArrayHasKey('$id', $transaction['body'], 'Transaction response should have $id. Response body: ' . json_encode($transaction['body'])); - $transactionId = $transaction['body']['$id']; - - // Add operations - second create reuses an existing documentId and should cause the commit to fail - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documents' => [ - [ - '$id' => 'txn_doc_1', - 'embeddings' => $this->generateEmbeddings(3, 0.2), - 'metadata' => ['email' => 'newuser@example.com'], - ], - [ - '$id' => $existingDocumentId, - 'embeddings' => $this->generateEmbeddings(3, 0.3), - 'metadata' => ['email' => 'duplicate@example.com'], - ], - [ - '$id' => 'txn_doc_2', - 'embeddings' => $this->generateEmbeddings(3, 0.4), - 'metadata' => ['email' => 'should-not-exist@example.com'], - ], - ], - 'transactionId' => $transactionId, - ]); - - $this->assertEquals(200, $response['headers']['status-code'], 'Adding documents via normal route should succeed. Response: ' . json_encode($response['body'])); - - // Attempt to commit - should fail due to duplicate document ID - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(409, $response['headers']['status-code']); - - // Verify NO new documents were created (atomicity) - $documents = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(1, $documents['body']['total']); - $this->assertEquals('existing@example.com', $documents['body']['documents'][0]['metadata']['email']); - } - - /** - * Test consistency - schema validation and constraints - */ - public function testConsistency(): void - { - // Create database - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'ConsistencyTestDB' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create collection - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'ConsistencyTest', - 'dimension' => 3, - 'documentSecurity' => false, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - ], - ]); - - $this->assertEquals(201, $collection['headers']['status-code']); - $collectionId = $collection['body']['$id']; - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $transactionId = $transaction['body']['$id']; - - // Stage operations with valid and invalid data (embedding length mismatch) - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.2), - 'metadata' => ['name' => 'Valid User'], - ], - ], - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(2, 0.5), // Invalid dimensions - 'metadata' => ['name' => 'Invalid User'], - ], - ], - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.6), - 'metadata' => ['name' => 'Should Not Persist'], - ], - ], - ], - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - - // Attempt to commit - should fail due to invalid embeddings - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertContains($response['headers']['status-code'], [400, 409, 500], 'Transaction commit should fail due to validation. Response: ' . json_encode($response['body'])); - - // Verify no documents were created - $documents = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(0, $documents['body']['total']); - } - - /** - * Test isolation - concurrent transactions on same data - */ - public function testIsolation(): void - { - // Create database - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'IsolationTestDB' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create collection - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'IsolationTest', - 'dimension' => 3, - 'documentSecurity' => false, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::update(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create initial document with status metadata - $doc = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => 'shared_doc', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['status' => 'pending'], - ], - ]); - - $this->assertEquals(201, $doc['headers']['status-code']); - - // Create first transaction - $transaction1 = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(201, $transaction1['headers']['status-code'], 'Transaction 1 creation should succeed'); - $this->assertArrayHasKey('$id', $transaction1['body'], 'Transaction 1 response should have $id'); - $transactionId1 = $transaction1['body']['$id']; - - // Transaction 1: update status to approved - $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId1}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'update', - 'documentId' => 'shared_doc', - 'data' => [ - 'metadata' => ['status' => 'approved'], - ], - ], - ], - ]); - - // Commit first transaction - $response1 = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId1}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - $this->assertEquals(200, $response1['headers']['status-code']); - - // Document should reflect the first transaction's update - $document = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/shared_doc", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals('approved', $document['body']['metadata']['status']); - - // Create second transaction after first commit - $transaction2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(201, $transaction2['headers']['status-code'], 'Transaction 2 creation should succeed'); - $this->assertArrayHasKey('$id', $transaction2['body'], 'Transaction 2 response should have $id'); - $transactionId2 = $transaction2['body']['$id']; - - // Transaction 2: update status to declined - $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId2}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'update', - 'documentId' => 'shared_doc', - 'data' => [ - 'metadata' => ['status' => 'declined'], - ], - ], - ], - ]); - - // Commit second transaction and ensure isolation guarantees - $response2 = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId2}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response2['headers']['status-code']); - - // Final document should reflect the second transaction's update - $document = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/shared_doc", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals('declined', $document['body']['metadata']['status']); - } - - /** - * Test durability - committed data persists - */ - public function testDurability(): void - { - // Create database - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'DurabilityTestDB' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create collection - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'DurabilityTest', - 'dimension' => 3, - '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']; - - // Create transaction with multiple operations - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(201, $transaction['headers']['status-code'], 'Transaction creation should succeed'); - $this->assertArrayHasKey('$id', $transaction['body'], 'Transaction response should have $id'); - $transactionId = $transaction['body']['$id']; - - // Create two documents via normal route inside transaction - $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documents' => [ - [ - '$id' => 'durable_doc_1', - 'embeddings' => $this->generateEmbeddings(3, 0.3), - 'metadata' => ['data' => 'Important data 1'], - ], - [ - '$id' => 'durable_doc_2', - 'embeddings' => $this->generateEmbeddings(3, 0.5), - 'metadata' => ['data' => 'Important data 2'], - ], - ], - 'transactionId' => $transactionId, - ]); - - // Update first document inside the same transaction - $this->client->call(Client::METHOD_PATCH, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'data' => [ - 'metadata' => ['data' => 'Updated important data 1'], - ], - 'transactionId' => $transactionId, - ]); - - // Commit transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code'], 'Commit should succeed. Response: ' . json_encode($response['body'])); - $this->assertEquals('committed', $response['body']['status']); - - // Verify documents exist and have correct data - $document1 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals(200, $document1['headers']['status-code']); - $this->assertEquals('Updated important data 1', $document1['body']['metadata']['data']); - - $document2 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_2", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals(200, $document2['headers']['status-code']); - $this->assertEquals('Important data 2', $document2['body']['metadata']['data']); - - // Further update outside transaction to ensure persistence - $update = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'metadata' => ['data' => 'Modified outside transaction'], - ], - ]); - $this->assertEquals(200, $update['headers']['status-code']); - - // Verify the update persisted - $document1 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/durable_doc_1", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals('Modified outside transaction', $document1['body']['metadata']['data']); - - // List all documents to verify total count - $documents = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals(2, $documents['body']['total']); - } -} diff --git a/tests/e2e/Services/Databases/VectorsDB/Transactions/TransactionsBase.php b/tests/e2e/Services/Databases/VectorsDB/Transactions/TransactionsBase.php deleted file mode 100644 index 70150a3bc8..0000000000 --- a/tests/e2e/Services/Databases/VectorsDB/Transactions/TransactionsBase.php +++ /dev/null @@ -1,2371 +0,0 @@ -client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'TransactionTestDatabase' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Test creating a transaction with default TTL - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertArrayHasKey('$id', $response['body']); - $this->assertArrayHasKey('status', $response['body']); - $this->assertArrayHasKey('operations', $response['body']); - $this->assertArrayHasKey('expiresAt', $response['body']); - $this->assertEquals('pending', $response['body']['status']); - $this->assertEquals(0, $response['body']['operations']); - - $transactionId1 = $response['body']['$id']; - - // Test creating a transaction with custom TTL - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'ttl' => 900 - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals('pending', $response['body']['status']); - - $expiresAt = new \DateTime($response['body']['expiresAt']); - $now = new \DateTime(); - $diff = $expiresAt->getTimestamp() - $now->getTimestamp(); - $this->assertGreaterThan(800, $diff); - $this->assertLessThan(1000, $diff); - - $transactionId2 = $response['body']['$id']; - - // Test invalid TTL values - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'ttl' => 30 // Below minimum - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'ttl' => 4000 // Above maximum - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - } - - /** - * Test adding operations to a transaction - */ - public function testCreateOperations(): void - { - // Create database first - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'TransactionOperationsTestDB' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(201, $transaction['headers']['status-code']); - $transactionId = $transaction['body']['$id']; - - // Create a collection for testing - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TransactionOperationsTest', - 'dimension' => 3, - '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 valid operations - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => 'doc1', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['name' => 'Test Document 1'] - ] - ], - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => 'doc2', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.2), - 'metadata' => ['name' => 'Test Document 2'] - ] - ] - ] - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals(2, $response['body']['operations']); - - // Test adding more operations - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'update', - 'documentId' => 'doc1', - 'data' => [ - 'metadata' => ['name' => 'Updated Document 1'] - ] - ] - ] - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals(3, $response['body']['operations']); - - // Test invalid database ID - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => 'invalid_database', - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['name' => 'Test'] - ] - ] - ] - ]); - - $this->assertEquals(404, $response['headers']['status-code'], 'Invalid database should return 404. Got: ' . json_encode($response['body'])); - - // Test invalid collection ID - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => 'invalid_collection', - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['name' => 'Test'] - ] - ] - ] - ]); - - $this->assertEquals(404, $response['headers']['status-code']); - } - - /** - * Test committing a transaction - */ - public function testCommit(): void - { - // Create database first - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'TransactionCommitTestDB' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create collection - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TransactionCommitTest', - 'dimension' => 3, - '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']; - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(201, $transaction['headers']['status-code']); - $transactionId = $transaction['body']['$id']; - - // Add operations - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => 'doc1', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['name' => 'Test Document 1'] - ] - ], - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => 'doc2', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.2), - 'metadata' => ['name' => 'Test Document 2'] - ] - ], - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'update', - 'documentId' => 'doc1', - 'data' => [ - 'metadata' => ['name' => 'Updated Document 1'] - ] - ] - ] - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals(3, $response['body']['operations']); - - // Commit the transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('committed', $response['body']['status']); - - // Verify documents were created - $documents = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals(2, $documents['body']['total']); - - // Verify the update was applied - $doc1Found = false; - foreach ($documents['body']['documents'] as $doc) { - if ($doc['$id'] === 'doc1') { - $this->assertEquals('Updated Document 1', $doc['metadata']['name']); - $doc1Found = true; - } - } - $this->assertTrue($doc1Found, 'Document doc1 should exist with updated name'); - - // Test committing already committed transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - } - - /** - * Test rolling back a transaction - */ - public function testRollback(): void - { - // Create database first - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'TransactionRollbackTestDB' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(201, $transaction['headers']['status-code']); - $transactionId = $transaction['body']['$id']; - - // Create a collection for rollback test - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TransactionRollbackTest', - 'dimension' => 3, - 'documentSecurity' => false, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Add operations - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => 'rollback_doc', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['value' => 'Should not exist'] - ] - ] - ] - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - - // Rollback the transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'rollback' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('failed', $response['body']['status']); - - // Verify no documents were created - $documents = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals(0, $documents['body']['total']); - } - - /** - * Test transaction expiration - */ - public function testTransactionExpiration(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'ExpirationTestDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create transaction with minimum TTL (60 seconds) - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'ttl' => 60 - ]); - - $this->assertEquals(201, $transaction['headers']['status-code']); - $transactionId = $transaction['body']['$id']; - - // Add operation - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['data' => 'Should expire'] - ] - ] - ] - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - - // Verify transaction was created with correct expiration - $txnDetails = $this->client->call(Client::METHOD_GET, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(200, $txnDetails['headers']['status-code']); - $this->assertEquals('pending', $txnDetails['body']['status']); - - // Verify expiration time is approximately 60 seconds from now - $expiresAt = new \DateTime($txnDetails['body']['expiresAt']); - $now = new \DateTime(); - $diff = $expiresAt->getTimestamp() - $now->getTimestamp(); - $this->assertGreaterThan(55, $diff); - $this->assertLessThan(65, $diff); - } - - /** - * Test maximum operations per transaction - */ - public function testTransactionSizeLimit(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'SizeLimitTestDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [Permission::create(Role::any())], - ]); - - $collectionId = $collection['body']['$id']; - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId = $transaction['body']['$id']; - - // Try to add operations exceeding the limit (assuming limit is 100) - // We'll add 50 operations twice to test incremental limit - $operations = []; - for ($i = 0; $i < 50; $i++) { - $operations[] = [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => 'doc_' . $i, - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.1 + ($i * 0.001)), - 'metadata' => ['value' => 'Test ' . $i] - ] - ]; - } - - // First batch should succeed - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => $operations - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals(50, $response['body']['operations']); - - // Second batch of 50 more operations - $operations = []; - for ($i = 50; $i < 100; $i++) { - $operations[] = [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'documentId' => 'doc_' . $i, - 'action' => 'create', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.1 + ($i * 0.001)), - 'metadata' => ['value' => 'Test ' . $i] - ] - ]; - } - - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => $operations - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals(100, $response['body']['operations']); - - // Try to add one more operation - should fail - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => 'doc_overflow', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['value' => 'This should fail'] - ] - ] - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - } - - /** - * Test concurrent transactions with conflicting operations - */ - public function testConcurrentTransactionConflicts(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'ConflictTestDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create initial document - $doc = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documentId' => 'shared_doc', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['counter' => 100] - ] - ]); - - $this->assertEquals(201, $doc['headers']['status-code']); - - // Create two transactions - $txn1 = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $txn2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId1 = $txn1['body']['$id']; - $transactionId2 = $txn2['body']['$id']; - - // Both transactions try to update the same document - $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId1}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'update', - 'documentId' => 'shared_doc', - 'data' => [ - 'metadata' => ['counter' => 200] - ] - ] - ] - ]); - - $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId2}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'update', - 'documentId' => 'shared_doc', - 'data' => [ - 'metadata' => ['counter' => 300] - ] - ] - ] - ]); - - // Commit first transaction - $response1 = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId1}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response1['headers']['status-code']); - - // Commit second transaction - should fail with conflict - $response2 = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId2}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(409, $response2['headers']['status-code']); // Conflict - - // Verify the document has the value from first transaction - $doc = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/shared_doc", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $doc['body']['metadata']['counter']); - } - - /** - * Test deleting a document that's being updated in a transaction - */ - public function testDeleteDocumentDuringTransaction(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'DeleteConflictDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create document - $doc = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documentId' => 'target_doc', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['data' => 'Original'] - ] - ]); - - $this->assertEquals(201, $doc['headers']['status-code']); - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId = $transaction['body']['$id']; - - // Add update operation to transaction - $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'update', - 'documentId' => 'target_doc', - 'data' => [ - 'metadata' => ['data' => 'Updated in transaction'] - ] - ] - ] - ]); - - // Delete the document outside of transaction - $response = $this->client->call(Client::METHOD_DELETE, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/target_doc", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(204, $response['headers']['status-code']); - - // Try to commit transaction - should fail because document no longer exists - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(404, $response['headers']['status-code']); // Conflict - } - - /** - * Test bulk operations in transactions - */ - public function testBulkOperations(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'BulkOpsDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create some initial documents - for ($i = 1; $i <= 5; $i++) { - $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documentId' => 'existing_' . $i, - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.1 + ($i * 0.01)), - 'metadata' => [ - 'name' => 'Existing ' . $i, - 'category' => 'old' - ] - ] - ]); - } - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId = $transaction['body']['$id']; - - // Add bulk operations - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - // Bulk create - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'bulkCreate', - 'data' => [ - [ - '$id' => 'bulk_1', - 'embeddings' => $this->generateEmbeddings(3, 0.2), - 'metadata' => ['name' => 'Bulk 1', 'category' => 'new'] - ], - [ - '$id' => 'bulk_2', - 'embeddings' => $this->generateEmbeddings(3, 0.3), - 'metadata' => ['name' => 'Bulk 2', 'category' => 'new'] - ], - [ - '$id' => 'bulk_3', - 'embeddings' => $this->generateEmbeddings(3, 0.4), - 'metadata' => ['name' => 'Bulk 3', 'category' => 'new'] - ], - ] - ], - // Bulk update - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'bulkUpdate', - 'data' => [ - 'queries' => [Query::equal('metadata', [['category' => 'old']])->toString()], - 'data' => ['metadata' => ['category' => 'updated']] - ] - ], - // Bulk delete - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'bulkDelete', - 'data' => [ - 'queries' => [Query::equal('$id', ['existing_5'])->toString()] - ] - ] - ] - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - - // Commit transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Verify results - $documents = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - // Should have 7 documents (5 existing - 1 deleted + 3 new) - $this->assertEquals(7, $documents['body']['total']); - - // Check categories were updated - $oldCategoryCount = 0; - $updatedCategoryCount = 0; - $newCategoryCount = 0; - - foreach ($documents['body']['documents'] as $doc) { - $category = $doc['metadata']['category'] ?? null; - switch ($category) { - case 'old': - $oldCategoryCount++; - break; - case 'updated': - $updatedCategoryCount++; - break; - case 'new': - $newCategoryCount++; - break; - } - } - - $this->assertEquals(0, $oldCategoryCount); - $this->assertEquals(4, $updatedCategoryCount); // 4 existing docs updated - $this->assertEquals(3, $newCategoryCount); // 3 new docs - } - - /** - * Test transaction with mixed success and failure operations - */ - public function testPartialFailureRollback(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'PartialFailureDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create HNSW index on embeddings - $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'embeddings_index', - 'type' => Database::INDEX_HNSW_EUCLIDEAN, - 'attributes' => ['embeddings'], - ]); - - sleep(2); - - // Create an existing document - $duplicateId = ID::unique(); - $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documentId' => $duplicateId, - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['email' => 'existing@example.com'] - ] - ]); - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId = $transaction['body']['$id']; - - // Add operations - mix of valid and invalid (duplicate id) - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.2), - 'metadata' => ['email' => 'valid1@example.com'] - ] - ], - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.3), - 'metadata' => ['email' => 'valid2@example.com'] - ] - ], - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => $duplicateId, - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.4), - 'metadata' => ['email' => 'existing@example.com'] - ] - ], - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.5), - 'metadata' => ['email' => 'valid3@example.com'] - ] - ], - ] - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - - // Try to commit - should fail and rollback all operations - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(409, $response['headers']['status-code']); // Conflict due to duplicate - - // Verify NO new documents were created (atomicity) - $documents = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(1, $documents['body']['total']); // Only the original document - $this->assertEquals('existing@example.com', $documents['body']['documents'][0]['metadata']['email']); - } - - /** - * Test double commit/rollback attempts - */ - public function testDoubleCommitRollback(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'DoubleCommitDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [Permission::create(Role::any())], - ]); - - $collectionId = $collection['body']['$id']; - - // Test double commit - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId = $transaction['body']['$id']; - - // Add operation - $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'create', - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['data' => 'Test'] - ] - ] - ] - ]); - - // First commit - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Second commit attempt - should fail - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(400, $response['headers']['status-code']); // Bad request - already committed - - // Test double rollback - $transaction2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId2 = $transaction2['body']['$id']; - - // First rollback - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId2}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'rollback' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Second rollback attempt - should fail - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId2}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'rollback' => true - ]); - - $this->assertEquals(400, $response['headers']['status-code']); // Bad request - already rolled back - } - - /** - * Test operations on non-existent documents - */ - public function testOperationsOnNonExistentDocuments(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'NonExistentDocDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [ - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId = $transaction['body']['$id']; - - // Try to update non-existent document - should fail at staging time with early validation - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'update', - 'documentId' => 'non_existent_doc', - 'data' => [ - 'metadata' => ['data' => 'Should fail'] - ] - ] - ] - ]); - - $this->assertEquals(404, $response['headers']['status-code']); // Document not found at staging time - - // Test delete non-existent document - should also fail at staging time with early validation - $transaction2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId2 = $transaction2['body']['$id']; - - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId2}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'action' => 'delete', - 'documentId' => 'non_existent_doc', - 'data' => [] - ] - ] - ]); - - $this->assertEquals(404, $response['headers']['status-code']); // Document not found at staging time - } - - /** - * Test createDocument with transactionId via normal route - */ - public function testCreateDocument(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'WriteRoutesTestDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'documentSecurity' => false, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(201, $transaction['headers']['status-code']); - $transactionId = $transaction['body']['$id']; - - // Create document via normal route with transactionId - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documentId' => 'doc_from_route', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => [ - 'name' => 'Created via normal route', - 'counter' => 100, - 'category' => 'test' - ] - ], - 'transactionId' => $transactionId - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - - // Document should not exist outside transaction yet - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/doc_from_route", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(404, $response['headers']['status-code']); - - // Commit transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Document should now exist - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/doc_from_route", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('Created via normal route', $response['body']['metadata']['name']); - } - - /** - * Test updateDocument with transactionId via normal route - */ - public function testUpdateDocument(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'UpdateRouteTestDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::update(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create document outside transaction - $doc = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documentId' => 'doc_to_update', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => [ - 'name' => 'Original name', - 'counter' => 50, - 'category' => 'original' - ] - ] - ]); - - $this->assertEquals(201, $doc['headers']['status-code']); - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId = $transaction['body']['$id']; - - // Update document via normal route with transactionId - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/doc_to_update", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'data' => [ - 'metadata' => [ - 'name' => 'Updated via normal route', - 'counter' => 150, - 'category' => 'updated' - ] - ], - 'transactionId' => $transactionId - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Document should still have original values outside transaction - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/doc_to_update", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals('Original name', $response['body']['metadata']['name']); - $this->assertEquals(50, $response['body']['metadata']['counter']); - - // Commit transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Document should now have updated values - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/doc_to_update", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals('Updated via normal route', $response['body']['metadata']['name']); - $this->assertEquals(150, $response['body']['metadata']['counter']); - } - - /** - * Test upsertDocument with transactionId via normal route - */ - public function testUpsertDocument(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'UpsertRouteTestDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::update(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId = $transaction['body']['$id']; - - // Upsert document (create) via normal route with transactionId - $response = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/doc_upsert", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documentId' => 'doc_upsert', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => [ - 'name' => 'Created by upsert', - 'counter' => 25 - ] - ], - 'transactionId' => $transactionId - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - - // Document should not exist outside transaction yet - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/doc_upsert", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(404, $response['headers']['status-code']); - - // Upsert same document (update) in same transaction - $response = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/doc_upsert", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documentId' => 'doc_upsert', - 'data' => [ - 'metadata' => [ - 'name' => 'Updated by upsert', - 'counter' => 75 - ] - ], - 'transactionId' => $transactionId - ]); - - $this->assertEquals(201, $response['headers']['status-code']); // Upsert in transaction returns 201 - - // Commit transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Document should now exist with updated values - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/doc_upsert", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('Updated by upsert', $response['body']['metadata']['name']); - $this->assertEquals(75, $response['body']['metadata']['counter']); - } - - /** - * Test deleteDocument with transactionId via normal route - */ - public function testDeleteDocument(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'DeleteRouteTestDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create document outside transaction - $doc = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documentId' => 'doc_to_delete', - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => ['name' => 'Will be deleted'] - ] - ]); - - $this->assertEquals(201, $doc['headers']['status-code']); - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId = $transaction['body']['$id']; - - // Delete document via normal route with transactionId - $response = $this->client->call(Client::METHOD_DELETE, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/doc_to_delete", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'transactionId' => $transactionId - ]); - - $this->assertEquals(204, $response['headers']['status-code']); - - // Document should still exist outside transaction - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/doc_to_delete", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Commit transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Document should no longer exist - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/doc_to_delete", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(404, $response['headers']['status-code']); - } - - /** - * Test bulkCreate with transactionId via normal route - */ - public function testBulkCreate(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'BulkCreateTestDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId = $transaction['body']['$id']; - - // Bulk create via normal route with transactionId - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documents' => [ - [ - '$id' => 'bulk_create_1', - 'embeddings' => $this->generateEmbeddings(3), - 'metadata' => [ - 'name' => 'Bulk created 1', - 'category' => 'bulk_created' - ] - ], - [ - '$id' => 'bulk_create_2', - 'embeddings' => $this->generateEmbeddings(3, 0.2), - 'metadata' => [ - 'name' => 'Bulk created 2', - 'category' => 'bulk_created' - ] - ], - [ - '$id' => 'bulk_create_3', - 'embeddings' => $this->generateEmbeddings(3, 0.3), - 'metadata' => [ - 'name' => 'Bulk created 3', - 'category' => 'bulk_created' - ] - ] - ], - 'transactionId' => $transactionId - ]); - - $this->assertEquals(200, $response['headers']['status-code']); // Bulk operations return 200 - - // Documents should not exist outside transaction yet - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [Query::equal('metadata', [['metadata' => ['category' => 'bulk_created']]])->toString()] - ]); - - $this->assertEquals(0, $response['body']['total']); - - // Individual document check - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/bulk_create_1", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(404, $response['headers']['status-code']); - - // Commit transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Documents should now exist - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [Query::equal('metadata', ['metadata' => ['category' => 'bulk_created']])->toString()] - ]); - - $this->assertEquals(3, $response['body']['total']); - - // Verify individual documents - for ($i = 1; $i <= 3; $i++) { - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/bulk_create_{$i}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals("Bulk created {$i}", $response['body']['metadata']['name']); - $this->assertEquals('bulk_created', $response['body']['metadata']['category']); - } - } - - /** - * Test bulkUpdate with transactionId via normal route - */ - public function testBulkUpdate(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'BulkUpdateTestDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::update(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create documents for bulk testing - for ($i = 1; $i <= 3; $i++) { - $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documentId' => 'bulk_update_' . $i, - 'data' => [ - 'embeddings' => $this->generateEmbeddings(3, 0.1 * $i), - 'metadata' => [ - 'name' => 'Bulk doc ' . $i, - 'category' => 'bulk_test' - ] - ] - ]); - } - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $transactionId = $transaction['body']['$id']; - - // Bulk update via normal route with transactionId - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'queries' => [Query::equal('metadata', ['metadata' => ['category' => 'bulk_test']])->toString()], - 'data' => ['metadata' => ['category' => 'bulk_updated']], - 'transactionId' => $transactionId - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Documents should still have original category outside transaction - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [Query::equal('metadata', ['metadata' => ['category' => 'bulk_test']])->toString()] - ]); - - $this->assertEquals(3, $response['body']['total']); - - // Commit transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Documents should now have updated category - $response = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [Query::equal('metadata', ['metadata' => ['category' => 'bulk_updated']])->toString()] - ]); - - $this->assertEquals(3, $response['body']['total']); - } - - /** - * Test bulkUpsert with transactionId via normal route - */ - public function testBulkUpsert(): void - { - // Create database and collection - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'BulkUpsertTestDB' - ]); - - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'TestCollection', - 'dimension' => 3, - 'permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::update(Role::any()), - ], - ]); - - $collectionId = $collection['body']['$id']; - - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(201, $transaction['headers']['status-code']); - $transactionId = $transaction['body']['$id']; - - // Test 1: Invalid action type - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'action' => 'invalidAction', - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'documentId' => ID::unique(), - 'data' => ['name' => 'Test'] - ] - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 2: Missing required action field - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'documentId' => ID::unique(), - 'data' => ['name' => 'Test'] - ] - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 3: Missing required databaseId field - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'action' => 'create', - 'collectionId' => $collectionId, - 'documentId' => ID::unique(), - 'data' => ['name' => 'Test'] - ] - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 4: Missing documentId for create operation - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'action' => 'create', - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'data' => ['name' => 'Test'] - ] - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 5: Missing data for create operation - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'action' => 'create', - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'documentId' => ID::unique() - ] - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 6: BulkCreate with non-array data - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'action' => 'bulkCreate', - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'data' => 'not an array' - ] - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 7: BulkUpdate with missing queries - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'action' => 'bulkUpdate', - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'data' => [ - 'data' => ['name' => 'Updated'] - ] - ] - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 8: Empty operations array - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 9: Operations not an array - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => 'not an array' - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - } - - /** - * Test validation for committing/rolling back transactions - */ - public function testCommitRollbackValidation(): void - { - // Create transaction - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(201, $transaction['headers']['status-code']); - $transactionId = $transaction['body']['$id']; - - // Test 1: Missing both commit and rollback - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), []); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 2: Both commit and rollback set to true - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true, - 'rollback' => true - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 3: Invalid transaction ID - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/invalid_id", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(404, $response['headers']['status-code']); - - // Commit the transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Test 4: Attempt to commit already committed transaction - $response = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/transactions/{$transactionId}", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'commit' => true - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - } - - /** - * Test validation for non-existent resources - */ - public function testNonExistentResources(): void - { - // Create database and transaction - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'ResourceTestDatabase' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - $transaction = $this->client->call(Client::METHOD_POST, '/vectorsdb/transactions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(201, $transaction['headers']['status-code']); - $transactionId = $transaction['body']['$id']; - - // Test 1: Non-existent database - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'action' => 'create', - 'databaseId' => 'nonExistentDatabase', - 'collectionId' => 'someCollection', - 'documentId' => ID::unique(), - 'data' => ['name' => 'Test'] - ] - ] - ]); - - $this->assertEquals(404, $response['headers']['status-code']); - - // Test 2: Non-existent collection - $response = $this->client->call(Client::METHOD_POST, "/vectorsdb/transactions/{$transactionId}/operations", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'operations' => [ - [ - 'action' => 'create', - 'databaseId' => $databaseId, - 'collectionId' => 'nonExistentCollection', - 'documentId' => ID::unique(), - 'data' => ['name' => 'Test'] - ] - ] - ]); - - $this->assertEquals(404, $response['headers']['status-code']); - } -} diff --git a/tests/e2e/Services/Databases/VectorsDB/Transactions/TransactionsConsoleClientTest.php b/tests/e2e/Services/Databases/VectorsDB/Transactions/TransactionsConsoleClientTest.php deleted file mode 100644 index 40ff27c572..0000000000 --- a/tests/e2e/Services/Databases/VectorsDB/Transactions/TransactionsConsoleClientTest.php +++ /dev/null @@ -1,14 +0,0 @@ -client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'databaseId' => ID::unique(), - 'name' => 'Vector Console DB', - ]); - $this->assertEquals(201, $database['headers']['status-code']); - $this->assertEquals('Vector Console DB', $database['body']['name']); - $this->assertTrue($database['body']['enabled']); - - $databaseId = $database['body']['$id']; - - /** - * Test for SUCCESS - */ - $movies = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'documentSecurity' => true, - ]); - - $this->assertEquals(201, $movies['headers']['status-code']); - $this->assertEquals($movies['body']['name'], 'Movies'); - - /** - * Test when database is disabled but can still create collections - */ - $database = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Vector Console DB Updated', - 'enabled' => false, - ]); - - $this->assertFalse($database['body']['enabled']); - - $tvShows = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'collectionId' => ID::unique(), - 'name' => 'TvShows', - 'dimension' => 3, - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'documentSecurity' => true, - ]); - - /** - * Test when collection is disabled but can still modify collections - */ - $database = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $movies['body']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Movies', - 'enabled' => false, - ]); - - $this->assertEquals(201, $tvShows['headers']['status-code']); - $this->assertEquals($tvShows['body']['name'], 'TvShows'); - - return ['moviesId' => $movies['body']['$id'], 'databaseId' => $databaseId, 'tvShowsId' => $tvShows['body']['$id']]; - } - - #[Depends('testCreateCollection')] - public function testListCollection(array $data) - { - /** - * Test when database is disabled but can still call list collections - */ - $databaseId = $data['databaseId']; - - $collections = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders())); - - $this->assertEquals(200, $collections['headers']['status-code']); - $this->assertEquals(2, $collections['body']['total']); - } - - #[Depends('testCreateCollection')] - public function testGetCollection(array $data) - { - $databaseId = $data['databaseId']; - $moviesCollectionId = $data['moviesId']; - - /** - * Test when database and collection are disabled but can still call get collection - */ - $collection = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $moviesCollectionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $collection['headers']['status-code']); - $this->assertEquals('Movies', $collection['body']['name']); - $this->assertEquals($moviesCollectionId, $collection['body']['$id']); - $this->assertFalse($collection['body']['enabled']); - } - - #[Depends('testCreateCollection')] - public function testUpdateCollection(array $data) - { - $databaseId = $data['databaseId']; - $moviesCollectionId = $data['moviesId']; - - /** - * Test When database and collection are disabled but can still call update collection - */ - $collection = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $moviesCollectionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Movies Updated', - 'enabled' => false - ]); - - $this->assertEquals(200, $collection['headers']['status-code']); - $this->assertEquals('Movies Updated', $collection['body']['name']); - $this->assertEquals($moviesCollectionId, $collection['body']['$id']); - $this->assertFalse($collection['body']['enabled']); - } - - #[Depends('testCreateCollection')] - public function testDeleteCollection(array $data) - { - $databaseId = $data['databaseId']; - $tvShowsId = $data['tvShowsId']; - - /** - * Test when database and collection are disabled but can still call delete collection - */ - $response = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId . '/collections/' . $tvShowsId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(204, $response['headers']['status-code']); - $this->assertEquals($response['body'], ""); - } - - #[Depends('testCreateCollection')] - public function testGetDatabaseUsage(array $data) - { - $databaseId = $data['databaseId']; - /** - * Test for FAILURE - */ - - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '32h' - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - /** - * Test for SUCCESS - */ - - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '24h' - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(11, count($response['body'])); - $this->assertEquals('24h', $response['body']['range']); - $this->assertIsNumeric($response['body']['documentsTotal']); - $this->assertIsNumeric($response['body']['collectionsTotal']); - $this->assertIsArray($response['body']['collections']); - $this->assertIsArray($response['body']['documents']); - } - - - #[Depends('testCreateCollection')] - public function testGetCollectionUsage(array $data) - { - $databaseId = $data['databaseId']; - /** - * Test for FAILURE - */ - - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '32h' - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/randomCollectionId/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '24h' - ]); - - $this->assertEquals(404, $response['headers']['status-code']); - - /** - * Test for SUCCESS - */ - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '24h' - ]); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(3, count($response['body'])); - $this->assertEquals('24h', $response['body']['range']); - $this->assertIsNumeric($response['body']['documentsTotal']); - $this->assertIsArray($response['body']['documents']); - } - - #[Depends('testCreateCollection')] - public function testGetCollectionLogs(array $data) - { - $databaseId = $data['databaseId']; - /** - * Test for SUCCESS - */ - $logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $logs['headers']['status-code']); - $this->assertIsArray($logs['body']['logs']); - $this->assertIsNumeric($logs['body']['total']); - - $logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [Query::limit(1)->toString()] - ]); - - $this->assertEquals(200, $logs['headers']['status-code']); - $this->assertIsArray($logs['body']['logs']); - $this->assertLessThanOrEqual(1, count($logs['body']['logs'])); - $this->assertIsNumeric($logs['body']['total']); - - $logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [Query::offset(1)->toString()] - ]); - - $this->assertEquals(200, $logs['headers']['status-code']); - $this->assertIsArray($logs['body']['logs']); - $this->assertIsNumeric($logs['body']['total']); - - $logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [Query::offset(1)->toString(), Query::limit(1)->toString()] - ]); - - $this->assertEquals(200, $logs['headers']['status-code']); - $this->assertIsArray($logs['body']['logs']); - $this->assertLessThanOrEqual(1, count($logs['body']['logs'])); - $this->assertIsNumeric($logs['body']['total']); - } -} diff --git a/tests/e2e/Services/Databases/VectorsDBCustomClientTest.php b/tests/e2e/Services/Databases/VectorsDBCustomClientTest.php deleted file mode 100644 index 7add5c7f71..0000000000 --- a/tests/e2e/Services/Databases/VectorsDBCustomClientTest.php +++ /dev/null @@ -1,205 +0,0 @@ -client->call(Client::METHOD_POST, '/vectorsdb', [ - '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']; - - // Collection aliases write to create, update, delete - $movies = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Movies', - 'dimension' => 3, - 'documentSecurity' => true, - 'permissions' => [ - Permission::write(Role::user($this->getUser()['$id'])), - ], - ]); - - $moviesId = $movies['body']['$id']; - - $this->assertContains(Permission::create(Role::user($this->getUser()['$id'])), $movies['body']['$permissions']); - $this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $movies['body']['$permissions']); - $this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $movies['body']['$permissions']); - - // VectorsDB uses fixed schema (embeddings, metadata). No attribute creation needed. - - // Document aliases write to update, delete - $document1 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['k' => 'v'], - ], - 'permissions' => [ - Permission::write(Role::user($this->getUser()['$id'])), - ] - ]); - - $this->assertNotContains(Permission::create(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']); - $this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']); - $this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']); - - /** - * Test for FAILURE - */ - - // Document does not allow create permission - $document2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['k' => 'v'], - ], - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - ] - ]); - - $this->assertEquals(400, $document2['headers']['status-code']); - } - - public function testUpdateWithoutPermission(): array - { - // As a part of preparation, we get ID of currently logged-in user - $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals(200, $response['headers']['status-code']); - - $userId = $response['body']['$id']; - - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::custom('permissionCheckDatabase'), - 'name' => 'Test Database', - ]); - $this->assertEquals(201, $database['headers']['status-code']); - $this->assertEquals('Test Database', $database['body']['name']); - - $databaseId = $database['body']['$id']; - // Create collection - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::custom('permissionCheck'), - 'name' => 'permissionCheck', - 'dimension' => 3, - 'permissions' => [], - 'documentSecurity' => true, - ]); - $this->assertEquals(201, $response['headers']['status-code']); - - // Creating document by server, give read permission to our user + some other user - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/permissionCheck/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documentId' => ID::custom('permissionCheckDocument'), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['name' => 'AppwriteBeginner'], - ], - 'permissions' => [ - Permission::read(Role::user(ID::custom('user2'))), - Permission::read(Role::user($userId)), - Permission::update(Role::user($userId)), - Permission::delete(Role::user($userId)), - ], - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - - // Update document - // This is the point of this test. We should be allowed to do this action, and it should not fail on permission check - $response = $this->client->call(Client::METHOD_PATCH, '/vectorsdb/' . $databaseId . '/collections/permissionCheck/documents/permissionCheckDocument', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['name' => 'AppwriteExpert'], - ] - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Get name of the document, should be the new one - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/permissionCheck/documents/permissionCheckDocument', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals("AppwriteExpert", $response['body']['metadata']['name']); - - // Cleanup to prevent collision with other tests - // Delete collection - $response = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId . '/collections/permissionCheck', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(204, $response['headers']['status-code']); - - - // Wait for database worker to finish deleting collection - sleep(2); - - // Make sure collection has been deleted - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/permissionCheck', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - $this->assertEquals(404, $response['headers']['status-code']); - - return []; - } -} diff --git a/tests/e2e/Services/Databases/VectorsDBCustomServerTest.php b/tests/e2e/Services/Databases/VectorsDBCustomServerTest.php deleted file mode 100644 index ceb672443e..0000000000 --- a/tests/e2e/Services/Databases/VectorsDBCustomServerTest.php +++ /dev/null @@ -1,963 +0,0 @@ -client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::custom('first'), - 'name' => 'Test 1', - ]); - $this->assertEquals(201, $db1['headers']['status-code']); - $this->assertEquals('Test 1', $db1['body']['name']); - $this->assertEquals('vectorsdb', $db1['body']['type']); - // Validate database response model fields on create - $this->assertArrayHasKey('$id', $db1['body']); - $this->assertArrayHasKey('$createdAt', $db1['body']); - $this->assertArrayHasKey('$updatedAt', $db1['body']); - $this->assertArrayHasKey('enabled', $db1['body']); - - $db2 = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::custom('second'), - 'name' => 'Test 2', - ]); - $this->assertEquals(201, $db2['headers']['status-code']); - $this->assertEquals('Test 2', $db2['body']['name']); - $this->assertEquals('vectorsdb', $db2['body']['type']); - - $list = $this->client->call(Client::METHOD_GET, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $list['headers']['status-code']); - $this->assertIsInt($list['body']['total']); - $this->assertGreaterThanOrEqual(2, $list['body']['total']); - $this->assertIsArray($list['body']['databases']); - $this->assertArrayHasKey('$id', $list['body']['databases'][0]); - $this->assertArrayHasKey('name', $list['body']['databases'][0]); - $this->assertArrayHasKey('type', $list['body']['databases'][0]); - - return ['databaseId' => $db1['body']['$id']]; - } - - #[Depends('testListDatabases')] - public function testGetDatabase(array $data): array - { - $databaseId = $data['databaseId']; - $res = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertEquals($databaseId, $res['body']['$id']); - $this->assertEquals('Test 1', $res['body']['name']); - $this->assertEquals('vectorsdb', $res['body']['type']); - return ['databaseId' => $databaseId]; - } - - #[Depends('testListDatabases')] - public function testUpdateDatabase(array $data): array - { - $databaseId = $data['databaseId']; - $res = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test 1 Updated', - ]); - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertEquals('Test 1 Updated', $res['body']['name']); - $this->assertEquals('vectorsdb', $res['body']['type']); - return ['databaseId' => $databaseId]; - } - - #[Depends('testListDatabases')] - public function testDeleteDatabase(array $data): void - { - $databaseId = $data['databaseId']; - $del = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(204, $del['headers']['status-code']); - $this->assertEquals("", $del['body']); - - $get = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(404, $get['headers']['status-code']); - } - - public function testCollectionsCRUD(): array - { - // Create database for collections tests - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Collections DB', - ]); - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - // Create two collections - $col1 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test 1', - 'collectionId' => ID::custom('first'), - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'documentSecurity' => true, - 'dimension' => 3, - ]); - $this->assertEquals(201, $col1['headers']['status-code']); - // Validate collection response model on create - $this->assertArrayHasKey('$id', $col1['body']); - $this->assertArrayHasKey('$createdAt', $col1['body']); - $this->assertArrayHasKey('$updatedAt', $col1['body']); - $this->assertArrayHasKey('enabled', $col1['body']); - $this->assertArrayHasKey('documentSecurity', $col1['body']); - $this->assertArrayHasKey('dimension', $col1['body']); - - $col2 = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test 2', - 'collectionId' => ID::custom('second'), - 'permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'documentSecurity' => true, - 'dimension' => 3, - ]); - $this->assertEquals(201, $col2['headers']['status-code']); - $this->assertArrayHasKey('$id', $col2['body']); - $this->assertArrayHasKey('$createdAt', $col2['body']); - $this->assertArrayHasKey('$updatedAt', $col2['body']); - - // List collections - $list = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $list['headers']['status-code']); - $this->assertIsInt($list['body']['total']); - $this->assertGreaterThanOrEqual(2, $list['body']['total']); - $this->assertIsArray($list['body']['collections']); - $this->assertArrayHasKey('$id', $list['body']['collections'][0]); - $this->assertArrayHasKey('name', $list['body']['collections'][0]); - $this->assertArrayHasKey('dimension', $list['body']['collections'][0]); - - // Get collection - $get = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $col1['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertEquals($col1['body']['$id'], $get['body']['$id']); - $this->assertEquals('Test 1', $get['body']['name']); - $this->assertEquals(3, $get['body']['dimension']); - - // Update collection (name only) - $upd = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $col1['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test 1 Updated', - ]); - $this->assertEquals(200, $upd['headers']['status-code']); - $this->assertEquals('Test 1 Updated', $upd['body']['name']); - $this->assertArrayHasKey('$updatedAt', $upd['body']); - - // Delete collection - $del = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId . '/collections/' . $col2['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(204, $del['headers']['status-code']); - $this->assertEquals("", $del['body']); - - return [ - 'databaseId' => $databaseId, - 'collectionId' => $col1['body']['$id'], - ]; - } - - #[Depends('testCollectionsCRUD')] - public function testUpdateCollectionMore(array $data): array - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - // Update collection name and dimensions - $upd = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test 1 Renamed', - 'dimension' => 4, - ]); - $this->assertEquals(200, $upd['headers']['status-code']); - $this->assertEquals('Test 1 Renamed', $upd['body']['name']); - $this->assertEquals(4, $upd['body']['dimension']); - - // Read back to confirm - $get = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertEquals('Test 1 Renamed', $get['body']['name']); - $this->assertEquals(4, $get['body']['dimension']); - - return $data; - } - - #[Depends('testCollectionsCRUD')] - public function testUpdateCollectionEnabledFlag(array $data): array - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - // Disable collection - $disable = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Updated', - 'enabled' => false, - ]); - $this->assertEquals(200, $disable['headers']['status-code']); - $this->assertFalse($disable['body']['enabled']); - - // Re-enable collection - $enable = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Updated', - 'enabled' => true, - ]); - $this->assertEquals(200, $enable['headers']['status-code']); - $this->assertTrue($enable['body']['enabled']); - - return $data; - } - - public function testUpdateDatabaseNameAndEnabled(): void - { - // Create isolated database for this test to avoid ordering conflicts - $create = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Update DB', - ]); - $this->assertEquals(201, $create['headers']['status-code']); - $databaseId = $create['body']['$id']; - - // Update name - $rename = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test DB Renamed', - ]); - $this->assertEquals(200, $rename['headers']['status-code']); - $this->assertEquals('Test DB Renamed', $rename['body']['name']); - - // Toggle enabled off then on - $disable = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test DB Renamed', - 'enabled' => false, - ]); - $this->assertEquals(200, $disable['headers']['status-code']); - $this->assertFalse($disable['body']['enabled']); - - $enable = $this->client->call(Client::METHOD_PUT, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'name' => 'Test DB Renamed', - 'enabled' => true, - ]); - $this->assertEquals(200, $enable['headers']['status-code']); - $this->assertTrue($enable['body']['enabled']); - - // Cleanup - $del = $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(204, $del['headers']['status-code']); - } - - #[Depends('testCollectionsCRUD')] - public function testRecreateIndex(array $data): void - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - // Create a new index variant - $create = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'key' => 'embedding_euclidean_v2', - 'type' => Database::INDEX_HNSW_EUCLIDEAN, - 'attributes' => ['embeddings'] - ]); - $this->assertEquals(202, $create['headers']['status-code']); - - // Ensure it exists - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes/embedding_euclidean_v2", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertEquals('embedding_euclidean_v2', $get['body']['key']); - - // Delete it - $del = $this->client->call(Client::METHOD_DELETE, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes/embedding_euclidean_v2", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(204, $del['headers']['status-code']); - } - - #[Depends('testCollectionsCRUD')] - public function testIndexesCRUD(array $data): void - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - // Create indexes - $eu = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'key' => 'embedding_euclidean', - 'type' => Database::INDEX_HNSW_EUCLIDEAN, - 'attributes' => ['embeddings'] - ]); - $this->assertEquals(202, $eu['headers']['status-code']); - - $dot = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'key' => 'embedding_dot', - 'type' => Database::INDEX_HNSW_DOT, - 'attributes' => ['embeddings'] - ]); - $this->assertEquals(202, $dot['headers']['status-code']); - - $cos = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'key' => 'embedding_cosine', - 'type' => Database::INDEX_HNSW_COSINE, - 'attributes' => ['embeddings'] - ]); - $this->assertEquals(202, $cos['headers']['status-code']); - - // List indexes - $list = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $list['headers']['status-code']); - $this->assertIsArray($list['body']['indexes']); - $keys = array_map(fn ($i) => $i['key'], $list['body']['indexes']); - $this->assertContains('embedding_euclidean', $keys); - $this->assertContains('embedding_dot', $keys); - $this->assertContains('embedding_cosine', $keys); - - // Get index by key - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes/embedding_euclidean", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertEquals('embedding_euclidean', $get['body']['key']); - $this->assertEquals(Database::INDEX_HNSW_EUCLIDEAN, $get['body']['type']); - - // Delete index - $del = $this->client->call(Client::METHOD_DELETE, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes/embedding_dot", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(204, $del['headers']['status-code']); - sleep(4); - // Ensure it's gone - $getMissing = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/indexes/embedding_dot", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(404, $getMissing['headers']['status-code']); - } - - public function testBulkCreate(): array - { - // Setup: create isolated database and collection - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'BulkDBCreate' - ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'BulkColCreate', - 'documentSecurity' => true, - 'dimension' => 3, - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - $docs = [ - [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['group' => 'bulkA'], - '$permissions' => [Permission::read(Role::any())] - ], - [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['group' => 'bulkB'], - '$permissions' => [Permission::read(Role::any())] - ], - ]; - - $res = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documents' => $docs - ]); - - $this->assertEquals(201, $res['headers']['status-code']); - $this->assertIsInt($res['body']['total'] ?? 0); - $this->assertGreaterThanOrEqual(2, $res['body']['total']); - $this->assertIsArray($res['body']['documents']); - $this->assertCount(2, $res['body']['documents']); - - $ids = array_map(fn ($d) => $d['$id'], $res['body']['documents']); - $this->assertNotEmpty($ids[0]); - $this->assertNotEmpty($ids[1]); - - // Fetch and validate persisted data via GET - foreach ($ids as $i => $id) { - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertEquals($id, $get['body']['$id']); - $this->assertIsArray($get['body']['embeddings']); - $this->assertCount(3, $get['body']['embeddings']); - $this->assertArrayHasKey('group', $get['body']['metadata']); - } - - return [ 'databaseId' => $databaseId, 'collectionId' => $collectionId, 'bulkIds' => $ids ]; - } - - public function testCreateTextEmbeddingsSuccessAndErrors(): void - { - // Setup new database and collection - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'EmbedDB', - ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'EmbedCol', - 'documentSecurity' => true, - 'dimension' => 3, - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - // Success: two embeddings - $this->assertEventually(function () { - $ok = $this->client->call(Client::METHOD_POST, "/vectorsdb/embeddings/text", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'model' => 'embeddinggemma', - 'texts' => [ - 'hello world', - 'second sentence', - ], - ]); - $this->assertEquals(200, $ok['headers']['status-code']); - $this->assertIsInt($ok['body']['total'] ?? 0); - $this->assertEquals(2, $ok['body']['total']); - $this->assertIsArray($ok['body']['embeddings']); - $this->assertCount(2, $ok['body']['embeddings']); - foreach ($ok['body']['embeddings'] as $embed) { - $this->assertIsString($embed['model']); - $this->assertIsInt($embed['dimension']); - $this->assertIsArray($embed['embedding']); - $this->assertGreaterThan(0, count($embed['embedding'])); - $this->assertArrayHasKey('error', $embed); - } - }, 3000, 100); - - // Error: missing texts payload - $missingTexts = $this->client->call(Client::METHOD_POST, "/vectorsdb/embeddings/text", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], []); - $this->assertEquals(400, $missingTexts['headers']['status-code']); - - // Error: invalid texts item type (must be strings) - $invalidItem = $this->client->call(Client::METHOD_POST, "/vectorsdb/embeddings/text", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'model' => 'embeddinggemma', - 'texts' => [ - 'valid text', - 123, // invalid, not a string - ], - ]); - $this->assertEquals(400, $invalidItem['headers']['status-code']); - - // Error: unknown embedding model - $unknownModel = $this->client->call(Client::METHOD_POST, "/vectorsdb/embeddings/text", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'model' => 'nonexistent-model', - 'texts' => ['hello'], - ]); - $this->assertEquals(400, $unknownModel['headers']['status-code']); - } - - public function testBulkUpsert(): void - { - // Setup fresh db/collection - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'databaseId' => ID::unique(), 'name' => 'BulkDBUpsert' ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'BulkColUpsert', - 'documentSecurity' => true, - 'dimension' => 3, - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - $docs = [ - [ - 'embeddings' => [0.5, 0.5, 0.0], - 'metadata' => ['group' => 'bulkA', 'updated' => true], - '$permissions' => [Permission::read(Role::any())] - ], - [ - 'embeddings' => [0.2, 0.8, 0.0], - 'metadata' => ['group' => 'bulkB', 'updated' => true], - '$permissions' => [Permission::read(Role::any())] - ], - ]; - - $res = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documents' => $docs - ]); - - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertIsArray($res['body']['documents']); - $this->assertCount(2, $res['body']['documents']); - $this->assertTrue($res['body']['documents'][0]['metadata']['updated']); - $this->assertTrue($res['body']['documents'][1]['metadata']['updated']); - - // Fetch and validate updated content - $ids = array_map(fn ($d) => $d['$id'], $res['body']['documents']); - foreach ($ids as $id) { - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertTrue($get['body']['metadata']['updated']); - } - - // Perform another bulk upsert to mutate the same documents - $docs2 = [ - [ 'embeddings' => [0.6, 0.4, 0.0], 'metadata' => ['updatedAgain' => true] ], - [ 'embeddings' => [0.3, 0.7, 0.0], 'metadata' => ['updatedAgain' => true] ], - ]; - $res2 = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documents' => $docs2 - ]); - $this->assertEquals(200, $res2['headers']['status-code']); - $this->assertIsArray($res2['body']['documents']); - $this->assertCount(2, $res2['body']['documents']); - - // Fetch again and assert second update persisted - $ids2 = array_map(fn ($d) => $d['$id'], $res2['body']['documents']); - foreach ($ids2 as $id) { - $get2 = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get2['headers']['status-code']); - $this->assertTrue($get2['body']['metadata']['updatedAgain']); - } - } - - public function testBulkUpdate(): void - { - // Setup: create db/collection and two docs - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'databaseId' => ID::unique(), 'name' => 'BulkDBUpdate' ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'BulkColUpdate', - 'documentSecurity' => true, - 'dimension' => 3, - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - $seed = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documents' => [ - ['embeddings' => [1.0,0.0,0.0], 'metadata' => ['seed' => 1], '$permissions' => [Permission::read(Role::any())]], - ['embeddings' => [0.0,1.0,0.0], 'metadata' => ['seed' => 2], '$permissions' => [Permission::read(Role::any())]] - ] - ]); - $this->assertEquals(200, $seed['headers']['status-code']); - $ids = array_map(fn ($d) => $d['$id'], $seed['body']['documents']); - - $res = $this->client->call(Client::METHOD_PATCH, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'data' => [ 'metadata' => ['bulkUpdated' => true] ], - 'queries' => [ - \Utopia\Database\Query::equal('$id', $ids)->toString() - ] - ]); - - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertIsArray($res['body']['documents']); - $this->assertCount(2, $res['body']['documents']); - foreach ($res['body']['documents'] as $doc) { - $this->assertTrue($doc['metadata']['bulkUpdated']); - } - - // Fetch by IDs and assert update persisted - foreach ($ids as $id) { - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(200, $get['headers']['status-code']); - $this->assertTrue($get['body']['metadata']['bulkUpdated']); - } - } - - public function testBulkDelete(): void - { - // Setup: create db/collection and two docs - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ 'databaseId' => ID::unique(), 'name' => 'BulkDBDelete' ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'BulkColDelete', - 'documentSecurity' => true, - 'dimension' => 3, - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - $seed = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documents' => [ - ['embeddings' => [1.0,0.0,0.0], 'metadata' => ['seed' => 1], '$permissions' => [Permission::read(Role::any())]], - ['embeddings' => [0.0,1.0,0.0], 'metadata' => ['seed' => 2], '$permissions' => [Permission::read(Role::any())]] - ] - ]); - $this->assertEquals(200, $seed['headers']['status-code']); - $ids = array_map(fn ($d) => $d['$id'], $seed['body']['documents']); - - $res = $this->client->call(Client::METHOD_DELETE, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'queries' => [ - \Utopia\Database\Query::equal('$id', $ids)->toString() - ] - ]); - $this->assertEquals(200, $res['headers']['status-code']); - $this->assertIsInt($res['body']['total'] ?? 0); - $this->assertGreaterThanOrEqual(2, $res['body']['total']); - - // Ensure they are deleted - foreach ($ids as $id) { - $get = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$id}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - $this->assertEquals(404, $get['headers']['status-code']); - } - } - - public function testCustomTimestamps(): void - { - // Setup: create database and collection - $db = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'TimestampTestDB' - ]); - $this->assertEquals(201, $db['headers']['status-code']); - $databaseId = $db['body']['$id']; - - $col = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'TimestampTestCollection', - 'documentSecurity' => true, - 'dimension' => 1536, - 'permissions' => [Permission::read(Role::any())] - ]); - $this->assertEquals(201, $col['headers']['status-code']); - $collectionId = $col['body']['$id']; - - // Test: Create document with custom timestamps using PUT (upsert) - $customCreatedAt = '1970-01-01T00:00:00.000+00:00'; - $customUpdatedAt = '1970-01-01T00:00:00.000+00:00'; - $vector = array_fill(0, 1536, 0.0); - $vector[0] = 1.0; - $documentId = ID::unique(); - - $doc = $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'documentId' => $documentId, - 'data' => [ - '$createdAt' => $customCreatedAt, - '$updatedAt' => $customUpdatedAt, - 'embeddings' => $vector, - 'metadata' => ['test' => 'custom_timestamps'] - ] - ]); - - $this->assertEquals(201, $doc['headers']['status-code']); - $documentId = $doc['body']['$id']; - $this->assertNotEmpty($documentId); - - // Verify timestamps were set correctly - $this->assertEquals($customCreatedAt, $doc['body']['$createdAt'], 'CreatedAt should match custom timestamp'); - $this->assertEquals($customUpdatedAt, $doc['body']['$updatedAt'], 'UpdatedAt should match custom timestamp'); - - // Fetch document and verify timestamps persist - $fetched = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - - $this->assertEquals(200, $fetched['headers']['status-code']); - $this->assertEquals($customCreatedAt, $fetched['body']['$createdAt'], 'CreatedAt should persist after fetch'); - $this->assertEquals($customUpdatedAt, $fetched['body']['$updatedAt'], 'UpdatedAt should persist after fetch'); - - // Test: Update document with new custom timestamps - $newCustomUpdatedAt = '2000-01-01T12:00:00.000+00:00'; - $vector2 = array_fill(0, 1536, 0.0); - $vector2[1] = 1.0; - - $updated = $this->client->call(Client::METHOD_PUT, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'data' => [ - '$createdAt' => $customCreatedAt, // Keep original createdAt - '$updatedAt' => $newCustomUpdatedAt, // Update updatedAt - 'embeddings' => $vector2, - 'metadata' => ['test' => 'updated_timestamps'] - ] - ]); - - $this->assertEquals(200, $updated['headers']['status-code']); - $this->assertEquals($customCreatedAt, $updated['body']['$createdAt'], 'CreatedAt should remain unchanged'); - $this->assertEquals($newCustomUpdatedAt, $updated['body']['$updatedAt'], 'UpdatedAt should be updated to new custom timestamp'); - - // Final verification - $final = $this->client->call(Client::METHOD_GET, "/vectorsdb/{$databaseId}/collections/{$collectionId}/documents/{$documentId}", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]); - - $this->assertEquals(200, $final['headers']['status-code']); - $this->assertEquals($customCreatedAt, $final['body']['$createdAt'], 'CreatedAt should persist through updates'); - $this->assertEquals($newCustomUpdatedAt, $final['body']['$updatedAt'], 'UpdatedAt should reflect the latest custom timestamp'); - } - -} diff --git a/tests/e2e/Services/Migrations/MigrationsBase.php b/tests/e2e/Services/Migrations/MigrationsBase.php index acd8838374..0d992c472e 100644 --- a/tests/e2e/Services/Migrations/MigrationsBase.php +++ b/tests/e2e/Services/Migrations/MigrationsBase.php @@ -2,15 +2,12 @@ namespace Tests\E2E\Services\Migrations; -use Appwrite\Tests\Retry; use CURLFile; -use PHPUnit\Framework\Attributes\Depends; use Tests\E2E\Client; use Tests\E2E\General\UsageTest; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Services\Functions\FunctionsBase; use Utopia\Console; -use Utopia\Database\Database; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -189,26 +186,6 @@ trait MigrationsBase return $migrationResult; } - /** - * Get migration status by ID (without creating a new migration) - * - * @param string $migrationId - * @return array - */ - public function getMigrationStatus(string $migrationId): array - { - $response = $this->client->call(Client::METHOD_GET, '/migrations/' . $migrationId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']); - - return $response['body']; - } - /** * Appwrite E2E Migration Tests */ @@ -2555,1274 +2532,4 @@ trait MigrationsBase 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], ]); } - - /** - * Import VectorsDB documents from CSV - */ - public function testImportVectordbCSV(): void - { - $databaseId = null; - $collectionId = null; - $bucketId = null; - - try { - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Vector CSV Import DB' - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'collectionId' => ID::unique(), - 'name' => 'Vector CSV Import Collection', - 'dimension' => 3, - 'documentSecurity' => true, - ]); - - $this->assertEquals(201, $collection['headers']['status-code']); - $collectionId = $collection['body']['$id']; - - $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'bucketId' => ID::unique(), - 'name' => 'Vector CSV Bucket', - 'maximumFileSize' => 2000000, - 'allowedFileExtensions' => ['csv'], - ]); - - $this->assertEquals(201, $bucket['headers']['status-code']); - $bucketId = $bucket['body']['$id']; - - $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'fileId' => ID::unique(), - 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/csv/vectorsdb-documents.csv'), 'text/csv', 'vectorsdb-documents.csv'), - ]); - - $this->assertEquals(201, $file['headers']['status-code']); - $fileId = $file['body']['$id']; - - $migration = $this->performCsvMigration([ - 'fileId' => $fileId, - 'bucketId' => $bucketId, - 'resourceId' => $databaseId . ':' . $collectionId, - ]); - - $this->assertEquals(202, $migration['headers']['status-code']); - - $this->assertEventually(function () use ($migration) { - $migrationId = $migration['body']['$id']; - $status = $this->client->call(Client::METHOD_GET, '/migrations/' . $migrationId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - $this->assertEquals(200, $status['headers']['status-code']); - $this->assertEquals('finished', $status['body']['stage']); - $this->assertEquals('completed', $status['body']['status']); - $this->assertContains(Resource::TYPE_DOCUMENT, $status['body']['resources']); - $this->assertArrayHasKey(Resource::TYPE_DOCUMENT, $status['body']['statusCounters']); - $this->assertEquals(2, $status['body']['statusCounters'][Resource::TYPE_DOCUMENT]['success']); - - return true; - }, 60_000, 500); - - $documents = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'queries' => [ - Query::limit(10)->toString(), - ], - ]); - - $this->assertEquals(200, $documents['headers']['status-code']); - $this->assertEquals(2, $documents['body']['total']); - - $titles = array_map(fn ($doc) => $doc['metadata']['title'] ?? null, $documents['body']['documents']); - $this->assertContains('Vector Alpha', $titles); - $this->assertContains('Vector Beta', $titles); - } finally { - if ($bucketId) { - $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - } - - if ($databaseId) { - $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - } - } - } - - /** - * Export VectorsDB documents to CSV - */ - #[Retry(count: 1)] - public function testExportVectordbCSV(): void - { - $databaseId = null; - - try { - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Vector CSV Export DB', - ]); - - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - $collectionId = null; - $this->assertEventually(function () use ($databaseId, &$collectionId) { - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'collectionId' => ID::unique(), - 'name' => 'Vector CSV Export Collection', - 'dimension' => 3, - 'documentSecurity' => true, - ]); - - $this->assertEquals(201, $collection['headers']['status-code']); - $collectionId = $collection['body']['$id']; - }); - - $documentsPayload = [ - [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.11, 0.22, 0.33], - 'metadata' => ['title' => 'Vector Sample One', 'category' => 'alpha'], - ], - ], - [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.44, 0.55, 0.66], - 'metadata' => ['title' => 'Vector Sample Two', 'category' => 'beta'], - ], - ], - ]; - - foreach ($documentsPayload as $payload) { - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], $payload); - - $this->assertEquals(201, $response['headers']['status-code']); - } - - $filename = 'vectorsdb-export-' . ID::unique(); - $migration = $this->client->call(Client::METHOD_POST, '/migrations/csv/exports', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'resourceId' => $databaseId . ':' . $collectionId, - 'filename' => $filename, - 'columns' => [], - 'queries' => [], - 'delimiter' => ',', - 'enclosure' => '"', - 'escape' => '\\', - 'header' => true, - 'notify' => true, - ]); - - $this->assertEquals(202, $migration['headers']['status-code']); - - $migrationId = $migration['body']['$id']; - $this->assertEventually(function () use ($migrationId) { - $response = $this->client->call(Client::METHOD_GET, '/migrations/' . $migrationId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('finished', $response['body']['stage']); - $this->assertEquals('completed', $response['body']['status']); - - return true; - }, 30_000, 500); - - $this->assertEventually(function () { - $email = $this->getLastEmail(1, function (array $email) { - $this->assertEquals('Your CSV export is ready', $email['subject']); - }); - $this->assertNotEmpty($email); - $this->assertEquals('Your CSV export is ready', $email['subject']); - \preg_match('/href="([^"]*\/storage\/buckets\/[^"]*\/push[^"]*)"/', $email['html'], $matches); - $this->assertNotEmpty($matches[1], 'Download URL not found in email'); - $downloadUrl = html_entity_decode($matches[1]); - $components = \parse_url($downloadUrl); - $this->assertNotEmpty($components); - \parse_str($components['query'] ?? '', $queryParams); - $this->assertArrayHasKey('jwt', $queryParams); - $this->assertArrayHasKey('project', $queryParams); - - $path = \str_replace('/v1', '', $components['path']); - $downloadResponse = $this->client->call(Client::METHOD_GET, $path . '?project=' . $queryParams['project'] . '&jwt=' . $queryParams['jwt']); - $this->assertEquals(200, $downloadResponse['headers']['status-code']); - - $csvData = $downloadResponse['body']; - $this->assertStringContainsString('Vector Sample One', $csvData); - $this->assertStringContainsString('Vector Sample Two', $csvData); - $this->assertStringContainsString('[0.11,0.22,0.33]', $csvData); - }, 30_000, 500); - } finally { - if ($databaseId) { - $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - } - } - } - - /** - * DocumentsDB (schemaless) - */ - public function testAppwriteMigrationDocumentsDBDatabase(): array - { - $response = $this->client->call(Client::METHOD_POST, '/documentsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'databaseId' => ID::unique(), - 'name' => 'DocsDB - Migration DB' - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']); - $this->assertNotEmpty($response['body']['$id']); - - $databaseId = $response['body']['$id']; - - $result = $this->performMigrationSync([ - 'resources' => [ - Resource::TYPE_DATABASE_DOCUMENTSDB, - ], - 'endpoint' => $this->endpoint, - 'projectId' => $this->getProject()['$id'], - 'apiKey' => $this->getProject()['apiKey'], - ]); - - $this->assertEquals('completed', $result['status']); - $this->assertEquals([Resource::TYPE_DATABASE_DOCUMENTSDB], $result['resources']); - $this->assertArrayHasKey(Resource::TYPE_DATABASE_DOCUMENTSDB, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_DOCUMENTSDB]['error']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_DOCUMENTSDB]['pending']); - $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_DATABASE_DOCUMENTSDB]['success']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_DOCUMENTSDB]['processing']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_DOCUMENTSDB]['warning']); - - $response = $this->client->call(Client::METHOD_GET, '/documentsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']); - $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals($databaseId, $response['body']['$id']); - $this->assertEquals('DocsDB - Migration DB', $response['body']['name']); - - // Cleanup on destination - $this->client->call(Client::METHOD_DELETE, '/documentsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - return [ - 'databaseId' => $databaseId, - ]; - } - - /** - * VectorsDB (embeddings collections) - */ - public function testAppwriteMigrationVectorsDBDatabase(): array - { - $response = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'databaseId' => ID::unique(), - 'name' => 'VDB - Migration DB' - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']); - $this->assertNotEmpty($response['body']['$id']); - - $databaseId = $response['body']['$id']; - - $result = $this->performMigrationSync([ - 'resources' => [ - Resource::TYPE_DATABASE_VECTORSDB, - ], - 'endpoint' => $this->endpoint, - 'projectId' => $this->getProject()['$id'], - 'apiKey' => $this->getProject()['apiKey'], - ]); - - $this->assertEquals('completed', $result['status']); - $this->assertEquals([Resource::TYPE_DATABASE_VECTORSDB], $result['resources']); - $this->assertArrayHasKey(Resource::TYPE_DATABASE_VECTORSDB, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_VECTORSDB]['error'] ?? 0); - - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']); - $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals($databaseId, $response['body']['$id']); - $this->assertEquals('VDB - Migration DB', $response['body']['name']); - - // Cleanup on destination - $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - return [ - 'databaseId' => $databaseId, - ]; - } - - #[Depends('testAppwriteMigrationVectorsDBDatabase')] - public function testAppwriteMigrationVectorsDBCollection(array $data): array - { - $databaseId = $data['databaseId']; - - $collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'collectionId' => ID::unique(), - 'name' => 'VDB - Movies', - 'dimension' => 3, - ]); - - $this->assertEquals(201, $collection['headers']['status-code']); - - $collectionId = $collection['body']['$id']; - - $result = $this->performMigrationSync([ - 'resources' => [ - Resource::TYPE_DATABASE_VECTORSDB, - Resource::TYPE_COLLECTION, - Resource::TYPE_ATTRIBUTE, - ], - 'endpoint' => $this->endpoint, - 'projectId' => $this->getProject()['$id'], - 'apiKey' => $this->getProject()['apiKey'], - ]); - $this->assertEquals('completed', $result['status']); - - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']); - $this->assertEquals($collectionId, $response['body']['$id']); - $this->assertEquals('VDB - Movies', $response['body']['name']); - // Verify attributes are present (embeddings and metadata are default attributes) - $this->assertArrayHasKey('attributes', $response['body']); - $this->assertIsArray($response['body']['attributes']); - - // Cleanup - $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - return [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - ]; - } - - #[Depends('testAppwriteMigrationVectorsDBCollection')] - public function testAppwriteMigrationVectorsDBDocument(array $data): void - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - $document = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['title' => 'Migration Test Movie'], - ] - ]); - - $this->assertEquals(201, $document['headers']['status-code']); - $documentId = $document['body']['$id']; - - // Ensure attributes are exported before documents - $result = $this->performMigrationSync([ - 'resources' => [ - Resource::TYPE_DATABASE_VECTORSDB, - Resource::TYPE_COLLECTION, - Resource::TYPE_ATTRIBUTE, - Resource::TYPE_DOCUMENT, - ], - 'endpoint' => $this->endpoint, - 'projectId' => $this->getProject()['$id'], - 'apiKey' => $this->getProject()['apiKey'], - ]); - - $this->assertEquals('completed', $result['status']); - // Verify that TYPE_ATTRIBUTE appears in the resources array for VectorsDB - $this->assertContains(Resource::TYPE_ATTRIBUTE, $result['resources'], 'TYPE_ATTRIBUTE should be in resources array for VectorsDB'); - - // Verify attributes exist on destination before checking document - $collectionResponse = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $collectionResponse['headers']['status-code']); - $this->assertArrayHasKey('attributes', $collectionResponse['body']); - $this->assertIsArray($collectionResponse['body']['attributes']); - - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']); - $this->assertEquals($documentId, $response['body']['$id']); - $this->assertEquals('Migration Test Movie', $response['body']['metadata']['title']); - - // Cleanup - $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - } - - #[Depends('testAppwriteMigrationDocumentsDBDatabase')] - public function testAppwriteMigrationDocumentsDBCollection(array $data): array - { - $databaseId = $data['databaseId']; - - $collection = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $databaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'collectionId' => ID::unique(), - 'name' => 'DocsDB - Movies', - ]); - - $this->assertEquals(201, $collection['headers']['status-code']); - - $collectionId = $collection['body']['$id']; - - $result = $this->performMigrationSync([ - 'resources' => [ - Resource::TYPE_DATABASE_DOCUMENTSDB, - Resource::TYPE_COLLECTION, // collections in DocumentsDB map to tables in migration - ], - 'endpoint' => $this->endpoint, - 'projectId' => $this->getProject()['$id'], - 'apiKey' => $this->getProject()['apiKey'], - ]); - $this->assertEquals('completed', $result['status']); - foreach ([Resource::TYPE_DATABASE_DOCUMENTSDB, Resource::TYPE_COLLECTION] as $resource) { - $this->assertArrayHasKey($resource, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][$resource]['error']); - $this->assertEquals(0, $result['statusCounters'][$resource]['pending']); - $this->assertEquals(1, $result['statusCounters'][$resource]['success']); - $this->assertEquals(0, $result['statusCounters'][$resource]['processing']); - $this->assertEquals(0, $result['statusCounters'][$resource]['warning']); - } - - $response = $this->client->call(Client::METHOD_GET, '/documentsdb/' . $databaseId . '/collections/' . $collectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']); - $this->assertEquals($collectionId, $response['body']['$id']); - $this->assertEquals('DocsDB - Movies', $response['body']['name']); - - // Cleanup - $this->client->call(Client::METHOD_DELETE, '/documentsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - return [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - ]; - } - - #[Depends('testAppwriteMigrationDocumentsDBCollection')] - public function testAppwriteMigrationDocumentsDBDocument(array $data): void - { - $databaseId = $data['databaseId']; - $collectionId = $data['collectionId']; - - $document = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $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' => 'Migration Test Movie', - 'releaseYear' => 1999, - ] - ]); - - $this->assertEquals(201, $document['headers']['status-code']); - $documentId = $document['body']['$id']; - - $result = $this->performMigrationSync([ - 'resources' => [ - Resource::TYPE_DATABASE_DOCUMENTSDB, - Resource::TYPE_COLLECTION, - Resource::TYPE_DOCUMENT, - ], - 'endpoint' => $this->endpoint, - 'projectId' => $this->getProject()['$id'], - 'apiKey' => $this->getProject()['apiKey'], - ]); - - $this->assertEquals('completed', $result['status']); - - foreach ([Resource::TYPE_DATABASE_DOCUMENTSDB] as $resource) { - $this->assertArrayHasKey($resource, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][$resource]['error']); - $this->assertEquals(0, $result['statusCounters'][$resource]['pending']); - $this->assertEquals(1, $result['statusCounters'][$resource]['success']); - } - - $response = $this->client->call(Client::METHOD_GET, '/documentsdb/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']); - $this->assertEquals($documentId, $response['body']['$id']); - $this->assertEquals('Migration Test Movie', $response['body']['title']); - $this->assertEquals(1999, $response['body']['releaseYear']); - - // Cleanup - $this->client->call(Client::METHOD_DELETE, '/documentsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->client->call(Client::METHOD_DELETE, '/documentsdb/' . $databaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - } - - /** - * Migrate a project that contains both SQL Databases (/databases) and - * schemaless DocumentsDB (/documentsdb) in a single run and verify results. - * Uses a dedicated isolated source project to avoid interference from other tests. - */ - public function testAppwriteMigrationMixedDatabases(): void - { - // Create a fresh isolated source project for this test - $sourceProject = $this->getProject(true); - - // ====== Create SQL Database (/databases) with table, column, and row ====== - $sql = $this->client->call(Client::METHOD_POST, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Mixed SQL DB', - ]); - - $this->assertEquals(201, $sql['headers']['status-code']); - $this->assertNotEmpty($sql['body']['$id']); - $sqlDatabaseId = $sql['body']['$id']; - - // Create Table in SQL Database - $table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $sqlDatabaseId . '/tables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'tableId' => ID::unique(), - 'name' => 'Products', - ]); - - $this->assertEquals(201, $table['headers']['status-code']); - $tableId = $table['body']['$id']; - - // Create Column in Table - $column = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $sqlDatabaseId . '/tables/' . $tableId . '/columns/string', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'key' => 'productName', - 'size' => 255, - 'required' => true, - ]); - - $this->assertEquals(202, $column['headers']['status-code']); - - // Wait for column to be ready - $this->assertEventually(function () use ($sqlDatabaseId, $tableId, $sourceProject) { - $response = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $sqlDatabaseId . '/tables/' . $tableId . '/columns/productName', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('available', $response['body']['status']); - }, 5000, 500); - - $sqlIndexKey = 'product_unique'; - - $sqlIndex = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $sqlDatabaseId . '/tables/' . $tableId . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'key' => $sqlIndexKey, - 'type' => Database::INDEX_UNIQUE, - 'columns' => ['productName'], - ]); - - $this->assertEquals(202, $sqlIndex['headers']['status-code']); - - $this->assertEventually(function () use ($sqlDatabaseId, $tableId, $sqlIndexKey, $sourceProject) { - $index = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $sqlDatabaseId . '/tables/' . $tableId . '/indexes/' . $sqlIndexKey, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ]); - - $this->assertEquals(200, $index['headers']['status-code']); - $this->assertEquals('available', $index['body']['status']); - }, 30000, 500); - - // Create Row in Table - $row = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $sqlDatabaseId . '/tables/' . $tableId . '/rows', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'rowId' => ID::unique(), - 'data' => [ - 'productName' => 'Laptop', - ], - ]); - - $this->assertEquals(201, $row['headers']['status-code']); - $rowId = $row['body']['$id']; - - // ====== Create DocumentsDB (/documentsdb) with collection and document ====== - $docs = $this->client->call(Client::METHOD_POST, '/documentsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Mixed DocsDB', - ]); - - $this->assertEquals(201, $docs['headers']['status-code']); - $this->assertNotEmpty($docs['body']['$id']); - $docsDatabaseId = $docs['body']['$id']; - - // Create Collection in DocumentsDB - $collection = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $docsDatabaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'collectionId' => ID::unique(), - 'name' => 'Users', - ]); - - $this->assertEquals(201, $collection['headers']['status-code']); - $collectionId = $collection['body']['$id']; - - $documentsIndexKey = 'email_unique'; - - $documentsIndex = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $docsDatabaseId . '/collections/' . $collectionId . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'key' => $documentsIndexKey, - 'type' => Database::INDEX_UNIQUE, - 'attributes' => ['email'], - ]); - - $this->assertEquals(202, $documentsIndex['headers']['status-code']); - - $this->assertEventually(function () use ($docsDatabaseId, $collectionId, $documentsIndexKey, $sourceProject) { - $index = $this->client->call(Client::METHOD_GET, '/documentsdb/' . $docsDatabaseId . '/collections/' . $collectionId . '/indexes/' . $documentsIndexKey, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ]); - - $this->assertEquals(200, $index['headers']['status-code']); - $this->assertEquals('available', $index['body']['status']); - }, 30000, 500); - - // Create Document in Collection - $document = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $docsDatabaseId . '/collections/' . $collectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'name' => 'John Doe', - 'email' => 'john@example.com', - ], - ]); - - $this->assertEquals(201, $document['headers']['status-code']); - $documentId = $document['body']['$id']; - - // ====== Create VectorsDB (/vectorsdb) with collection and document ====== - $vector = $this->client->call(Client::METHOD_POST, '/vectorsdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Mixed VectorsDB', - ]); - - $this->assertEquals(201, $vector['headers']['status-code']); - $this->assertNotEmpty($vector['body']['$id']); - $vectorDatabaseId = $vector['body']['$id']; - - // Create Collection in VectorsDB - $vectorCollection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $vectorDatabaseId . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'collectionId' => ID::unique(), - 'name' => 'Products', - 'dimension' => 3, - ]); - - $this->assertEquals(201, $vectorCollection['headers']['status-code']); - $vectorCollectionId = $vectorCollection['body']['$id']; - - // Wait for VectorsDB collection attributes to be ready - $this->assertEventually(function () use ($vectorDatabaseId, $vectorCollectionId, $sourceProject) { - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $vectorDatabaseId . '/collections/' . $vectorCollectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertArrayHasKey('attributes', $response['body']); - $this->assertIsArray($response['body']['attributes']); - // Check that default attributes (embeddings and metadata) are present and ready - $attributeKeys = array_column($response['body']['attributes'], 'key'); - $this->assertContains('embeddings', $attributeKeys); - $this->assertContains('metadata', $attributeKeys); - // Check that attributes are available (if status field exists) - foreach ($response['body']['attributes'] as $attribute) { - if (isset($attribute['status']) && $attribute['status'] !== 'available') { - return false; - } - } - return true; - }, 10000, 500); - - $metadataIndexKey = '_key_metadata'; - $vectorIndexes = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $vectorDatabaseId . '/collections/' . $vectorCollectionId . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ]); - $this->assertEquals(200, $vectorIndexes['headers']['status-code']); - $metadataIndex = null; - foreach ($vectorIndexes['body']['indexes'] ?? [] as $index) { - if (($index['key'] ?? '') === $metadataIndexKey) { - $metadataIndex = $index; - break; - } - } - $this->assertNotNull($metadataIndex, 'Default metadata index should exist on source collection'); - $this->assertEquals(Database::INDEX_OBJECT, $metadataIndex['type']); - - $vectorEmbeddingIndexKey = 'embedding_euclidean'; - $vectorEmbeddingIndex = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $vectorDatabaseId . '/collections/' . $vectorCollectionId . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'key' => $vectorEmbeddingIndexKey, - 'type' => Database::INDEX_HNSW_EUCLIDEAN, - 'attributes' => ['embeddings'], - ]); - $this->assertEquals(202, $vectorEmbeddingIndex['headers']['status-code']); - - $this->assertEventually(function () use ($vectorDatabaseId, $vectorCollectionId, $vectorEmbeddingIndexKey, $sourceProject) { - $index = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $vectorDatabaseId . '/collections/' . $vectorCollectionId . '/indexes/' . $vectorEmbeddingIndexKey, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ]); - - $this->assertEquals(200, $index['headers']['status-code']); - $this->assertEquals(Database::INDEX_HNSW_EUCLIDEAN, $index['body']['type']); - if (isset($index['body']['status'])) { - $this->assertEquals('available', $index['body']['status']); - } - }, 30000, 500); - - // Create Document in VectorsDB Collection - $vectorDocument = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $vectorDatabaseId . '/collections/' . $vectorCollectionId . '/documents', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ], [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [0.5, 0.3, 0.2], - 'metadata' => ['name' => 'Product Vector'], - ], - ]); - - $this->assertEquals(201, $vectorDocument['headers']['status-code']); - $vectorDocumentId = $vectorDocument['body']['$id']; - - // ====== Perform migration including all three database kinds with all child resources ====== - $migrationConfig = [ - 'resources' => [ - Resource::TYPE_DATABASE, - Resource::TYPE_TABLE, - Resource::TYPE_COLUMN, - Resource::TYPE_ROW, - Resource::TYPE_DATABASE_DOCUMENTSDB, - Resource::TYPE_COLLECTION, - Resource::TYPE_DOCUMENT, - Resource::TYPE_DATABASE_VECTORSDB, - Resource::TYPE_ATTRIBUTE, - Resource::TYPE_INDEX, - ], - 'endpoint' => $this->endpoint, - 'projectId' => $sourceProject['$id'], - 'apiKey' => $sourceProject['apiKey'], - ]; - - // Perform migration sync once and get migration ID - $result = $this->performMigrationSync($migrationConfig); - $migrationId = $result['$id']; - $this->assertEquals('completed', $result['status']); - $this->assertEquals('Appwrite', $result['source']); - $this->assertEquals('Appwrite', $result['destination']); - $this->assertEquals([ - Resource::TYPE_DATABASE, - Resource::TYPE_TABLE, - Resource::TYPE_COLUMN, - Resource::TYPE_ROW, - Resource::TYPE_DATABASE_DOCUMENTSDB, - Resource::TYPE_COLLECTION, - Resource::TYPE_DOCUMENT, - Resource::TYPE_DATABASE_VECTORSDB, - Resource::TYPE_ATTRIBUTE, - Resource::TYPE_INDEX, - ], $result['resources']); - - // Get migration status before asserting SQL Database counters - $result = $this->getMigrationStatus($migrationId); - // Assert SQL Database counters - $this->assertArrayHasKey(Resource::TYPE_DATABASE, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE]['error']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE]['pending']); - $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_DATABASE]['success']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE]['processing']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE]['warning']); - - // Get migration status before asserting Table counters - $result = $this->getMigrationStatus($migrationId); - // Assert Table counters - $this->assertArrayHasKey(Resource::TYPE_TABLE, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TABLE]['error']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TABLE]['pending']); - $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_TABLE]['success']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TABLE]['processing']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TABLE]['warning']); - - // Get migration status before asserting Column counters - $result = $this->getMigrationStatus($migrationId); - // Assert Column counters - $this->assertArrayHasKey(Resource::TYPE_COLUMN, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_COLUMN]['error']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_COLUMN]['pending']); - $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_COLUMN]['success']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_COLUMN]['processing']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_COLUMN]['warning']); - - // Get migration status before asserting Row counters - $result = $this->getMigrationStatus($migrationId); - // Assert Row counters - $this->assertArrayHasKey(Resource::TYPE_ROW, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_ROW]['error']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_ROW]['pending']); - $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_ROW]['success']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_ROW]['processing']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_ROW]['warning']); - - // Get migration status before asserting DocumentsDB counters - $result = $this->getMigrationStatus($migrationId); - // Assert DocumentsDB counters - $this->assertArrayHasKey(Resource::TYPE_DATABASE_DOCUMENTSDB, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_DOCUMENTSDB]['error']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_DOCUMENTSDB]['pending']); - $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_DATABASE_DOCUMENTSDB]['success']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_DOCUMENTSDB]['processing']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_DOCUMENTSDB]['warning']); - - // Wait for all collections to be fully processed and status counters to be updated - // Note: Collections are being transferred but status counters may not be updated immediately - // This wait ensures the migration worker has finished processing all collections - $result = null; - $this->assertEventually(function () use ($migrationId, &$result) { - $result = $this->getMigrationStatus($migrationId); - - // Check if collections status counters exist - if (!isset($result['statusCounters'][Resource::TYPE_COLLECTION])) { - return false; - } - - $pendingCount = $result['statusCounters'][Resource::TYPE_COLLECTION]['pending'] ?? 0; - - // Return true only when pending count is 0 - return $pendingCount === 0; - }, 30000, 1000); // 30 second timeout, check every 1 second - - // Assert Collection counters (covers both DocumentsDB and VectorsDB collections) - $this->assertArrayHasKey(Resource::TYPE_COLLECTION, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_COLLECTION]['error']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_COLLECTION]['pending']); - $this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_COLLECTION]['success']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_COLLECTION]['processing']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_COLLECTION]['warning']); - - // Get migration status before asserting Document counters - $result = $this->getMigrationStatus($migrationId); - // Assert Document counters (covers both DocumentsDB and VectorsDB documents) - $this->assertArrayHasKey(Resource::TYPE_DOCUMENT, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DOCUMENT]['error']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DOCUMENT]['pending']); - $this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_DOCUMENT]['success']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DOCUMENT]['processing']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DOCUMENT]['warning']); - - // Get migration status before asserting VectorsDB counters - $result = $this->getMigrationStatus($migrationId); - // Assert VectorsDB counters - $this->assertArrayHasKey(Resource::TYPE_DATABASE_VECTORSDB, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_VECTORSDB]['error']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_VECTORSDB]['pending']); - $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_DATABASE_VECTORSDB]['success']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_VECTORSDB]['processing']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE_VECTORSDB]['warning']); - - // Get migration status before asserting Attribute counters - $result = $this->getMigrationStatus($migrationId); - // Assert Attribute counters (for VectorsDB) - $this->assertArrayHasKey(Resource::TYPE_ATTRIBUTE, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_ATTRIBUTE]['error']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_ATTRIBUTE]['pending']); - $this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_ATTRIBUTE]['success']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_ATTRIBUTE]['processing']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_ATTRIBUTE]['warning']); - - // Get migration status before asserting Index counters - $result = $this->getMigrationStatus($migrationId); - $this->assertArrayHasKey(Resource::TYPE_INDEX, $result['statusCounters']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_INDEX]['error']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_INDEX]['pending']); - $this->assertGreaterThanOrEqual(4, $result['statusCounters'][Resource::TYPE_INDEX]['success']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_INDEX]['processing']); - $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_INDEX]['warning']); - - // Get migration status before asserting counter count - $result = $this->getMigrationStatus($migrationId); - // Ensure only expected counters exist (10 total) - $this->assertCount(10, $result['statusCounters']); - - // ====== Validate on destination: SQL Database resources ====== - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $sqlDatabaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($sqlDatabaseId, $response['body']['$id']); - $this->assertEquals('Mixed SQL DB', $response['body']['name']); - - // Validate Table - $response = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $sqlDatabaseId . '/tables/' . $tableId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($tableId, $response['body']['$id']); - $this->assertEquals('Products', $response['body']['name']); - - // Validate Column - $response = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $sqlDatabaseId . '/tables/' . $tableId . '/columns/productName', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('productName', $response['body']['key']); - $this->assertEquals(255, $response['body']['size']); - $this->assertEquals(true, $response['body']['required']); - - // Validate Row - $response = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $sqlDatabaseId . '/tables/' . $tableId . '/rows/' . $rowId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($rowId, $response['body']['$id']); - $this->assertEquals('Laptop', $response['body']['productName']); - - $sqlIndexDestination = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $sqlDatabaseId . '/tables/' . $tableId . '/indexes/' . $sqlIndexKey, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - $this->assertEquals(200, $sqlIndexDestination['headers']['status-code']); - $this->assertEquals($sqlIndexKey, $sqlIndexDestination['body']['key']); - $this->assertEquals(Database::INDEX_UNIQUE, $sqlIndexDestination['body']['type']); - if (isset($sqlIndexDestination['body']['columns'])) { - $this->assertEquals(['productName'], $sqlIndexDestination['body']['columns']); - } - - // ====== Validate on destination: DocumentsDB resources ====== - $response = $this->client->call(Client::METHOD_GET, '/documentsdb/' . $docsDatabaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($docsDatabaseId, $response['body']['$id']); - $this->assertEquals('Mixed DocsDB', $response['body']['name']); - - // Validate Collection - $response = $this->client->call(Client::METHOD_GET, '/documentsdb/' . $docsDatabaseId . '/collections/' . $collectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($collectionId, $response['body']['$id']); - $this->assertEquals('Users', $response['body']['name']); - - // Validate Document - $response = $this->client->call(Client::METHOD_GET, '/documentsdb/' . $docsDatabaseId . '/collections/' . $collectionId . '/documents/' . $documentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($documentId, $response['body']['$id']); - $this->assertEquals('John Doe', $response['body']['name']); - $this->assertEquals('john@example.com', $response['body']['email']); - - $documentsIndexDestination = $this->client->call(Client::METHOD_GET, '/documentsdb/' . $docsDatabaseId . '/collections/' . $collectionId . '/indexes/' . $documentsIndexKey, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - $this->assertEquals(200, $documentsIndexDestination['headers']['status-code']); - $this->assertEquals($documentsIndexKey, $documentsIndexDestination['body']['key']); - $this->assertEquals(Database::INDEX_UNIQUE, $documentsIndexDestination['body']['type']); - if (isset($documentsIndexDestination['body']['attributes'])) { - $this->assertEquals(['email'], $documentsIndexDestination['body']['attributes']); - } - - // ====== Validate on destination: VectorsDB resources ====== - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $vectorDatabaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($vectorDatabaseId, $response['body']['$id']); - $this->assertEquals('Mixed VectorsDB', $response['body']['name']); - - // Validate VectorsDB Collection - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $vectorDatabaseId . '/collections/' . $vectorCollectionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($vectorCollectionId, $response['body']['$id']); - $this->assertEquals('Products', $response['body']['name']); - // Verify attributes are present (embeddings and metadata are default attributes) - $this->assertArrayHasKey('attributes', $response['body']); - $this->assertIsArray($response['body']['attributes']); - - $vectorIndexesDestination = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $vectorDatabaseId . '/collections/' . $vectorCollectionId . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - $this->assertEquals(200, $vectorIndexesDestination['headers']['status-code']); - $indexByKey = []; - foreach ($vectorIndexesDestination['body']['indexes'] ?? [] as $index) { - if (isset($index['key'])) { - $indexByKey[$index['key']] = $index; - } - } - $this->assertArrayHasKey($metadataIndexKey, $indexByKey, 'Metadata index should exist on destination'); - $this->assertEquals(Database::INDEX_OBJECT, $indexByKey[$metadataIndexKey]['type']); - $this->assertArrayHasKey($vectorEmbeddingIndexKey, $indexByKey, 'Embeddings HNSW index should exist on destination'); - $this->assertEquals(Database::INDEX_HNSW_EUCLIDEAN, $indexByKey[$vectorEmbeddingIndexKey]['type']); - - // Validate VectorsDB Document - $response = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $vectorDatabaseId . '/collections/' . $vectorCollectionId . '/documents/' . $vectorDocumentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($vectorDocumentId, $response['body']['$id']); - $this->assertEquals('Product Vector', $response['body']['metadata']['name']); - - // ====== Cleanup all destinations ====== - $this->client->call(Client::METHOD_DELETE, '/databases/' . $sqlDatabaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->client->call(Client::METHOD_DELETE, '/documentsdb/' . $docsDatabaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $vectorDatabaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getDestinationProject()['$id'], - 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], - ]); - - // ====== Cleanup sources ====== - $this->client->call(Client::METHOD_DELETE, '/databases/' . $sqlDatabaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ]); - - $this->client->call(Client::METHOD_DELETE, '/documentsdb/' . $docsDatabaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ]); - - $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $vectorDatabaseId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $sourceProject['$id'], - 'x-appwrite-key' => $sourceProject['apiKey'], - ]); - } } diff --git a/tests/e2e/Services/ProjectWebhooks/WebhooksBase.php b/tests/e2e/Services/ProjectWebhooks/WebhooksBase.php index 6a251e2c52..0f1ff7eab3 100644 --- a/tests/e2e/Services/ProjectWebhooks/WebhooksBase.php +++ b/tests/e2e/Services/ProjectWebhooks/WebhooksBase.php @@ -1711,6 +1711,7 @@ trait WebhooksBase 'content-type' => 'application/json', 'cookie' => 'a_session_console=' . $this->getRoot()['session'], 'x-appwrite-project' => 'console', + 'X-Appwrite-Response-Format' => '1.8.0' ], [ 'name' => 'Webhook Test', 'enabled' => true, @@ -1740,6 +1741,7 @@ trait WebhooksBase 'content-type' => 'application/json', 'cookie' => 'a_session_console=' . $this->getRoot()['session'], 'x-appwrite-project' => 'console', + 'X-Appwrite-Response-Format' => '1.8.0' ], [ 'name' => 'Webhook Test', 'enabled' => true, @@ -1779,6 +1781,7 @@ trait WebhooksBase 'content-type' => 'application/json', 'cookie' => 'a_session_console=' . $this->getRoot()['session'], 'x-appwrite-project' => 'console', + 'X-Appwrite-Response-Format' => '1.8.0' ], [ 'name' => 'Webhook Test', 'enabled' => true, @@ -1824,6 +1827,7 @@ trait WebhooksBase 'content-type' => 'application/json', 'cookie' => 'a_session_console=' . $this->getRoot()['session'], 'x-appwrite-project' => 'console', + 'X-Appwrite-Response-Format' => '1.8.0' ])); // assert that the webhook is now disabled after 10 consecutive failures diff --git a/tests/e2e/Services/Projects/ProjectsBase.php b/tests/e2e/Services/Projects/ProjectsBase.php index dc31b7aa85..231ec302de 100644 --- a/tests/e2e/Services/Projects/ProjectsBase.php +++ b/tests/e2e/Services/Projects/ProjectsBase.php @@ -81,10 +81,12 @@ trait ProjectsBase $projectData = $this->setupProjectData(); $id = $projectData['projectId']; - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin', ], $this->getHeaders()), [ + 'webhookId' => 'unique()', 'name' => 'Webhook Test', 'events' => ['users.*.create', 'users.*.update.email'], 'url' => 'https://appwrite.io', diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 28cb146508..d4945f8407 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -13,8 +13,6 @@ use Tests\E2E\Scopes\SideClient; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; -use Utopia\Database\Helpers\Permission; -use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\System\System; @@ -108,111 +106,6 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(401, $response['headers']['status-code']); } - public function testDeleteProjectWithMultiDB(): void - { - // Create a team and project - $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'teamId' => ID::unique(), - 'name' => 'MultiDB Team', - ]); - - $this->assertEquals(201, $team['headers']['status-code']); - $teamId = $team['body']['$id']; - - $project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'MultiDB Project', - 'teamId' => $teamId, - 'region' => System::getEnv('_APP_REGION', 'default') - ]); - - $this->assertEquals(201, $project['headers']['status-code']); - $projectId = $project['body']['$id']; - - $projectAdminHeaders = array_merge($this->getHeaders(), [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-mode' => 'admin', - ]); - - // Create legacy database and collection - $database = $this->client->call(Client::METHOD_POST, '/databases', $projectAdminHeaders, [ - 'databaseId' => ID::unique(), - 'name' => 'Legacy DB', - ]); - $this->assertEquals(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - - $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $projectAdminHeaders, [ - 'collectionId' => ID::unique(), - 'name' => 'Legacy Collection', - 'documentSecurity' => true, - 'permissions' => [ - Permission::create(Role::any()), - ], - ]); - $this->assertEquals(201, $collection['headers']['status-code']); - - // Create documentsdb database and collection - $documentsDb = $this->client->call(Client::METHOD_POST, '/documentsdb', $projectAdminHeaders, [ - 'databaseId' => ID::unique(), - 'name' => 'Documents DB', - ]); - $this->assertEquals(201, $documentsDb['headers']['status-code']); - $documentsDbId = $documentsDb['body']['$id']; - - $documentsCollection = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $documentsDbId . '/collections', $projectAdminHeaders, [ - 'collectionId' => ID::unique(), - 'name' => 'Documents Collection', - 'documentSecurity' => true, - 'permissions' => [ - Permission::create(Role::any()), - ], - ]); - $this->assertEquals(201, $documentsCollection['headers']['status-code']); - - // Create vectorsdb database and collection - $vectorDb = $this->client->call(Client::METHOD_POST, '/vectorsdb', $projectAdminHeaders, [ - 'databaseId' => ID::unique(), - 'name' => 'Vector DB', - ]); - $this->assertEquals(201, $vectorDb['headers']['status-code']); - $vectorDbId = $vectorDb['body']['$id']; - - $vectorCollection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $vectorDbId . '/collections', $projectAdminHeaders, [ - 'collectionId' => ID::unique(), - 'name' => 'Vector Collection', - 'dimension' => 3, - 'documentSecurity' => true, - 'permissions' => [ - Permission::create(Role::any()), - ], - ]); - $this->assertEquals(201, $vectorCollection['headers']['status-code']); - - // Delete project - $delete = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(204, $delete['headers']['status-code']); - - // Ensure project is gone - $getProject = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(404, $getProject['headers']['status-code']); - } - public function testCreateDuplicateProject(): void { // Create a team @@ -2850,10 +2743,12 @@ class ProjectsConsoleClientTest extends Scope $data = $this->setupProjectData(); $id = $data['projectId']; - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), [ + 'webhookId' => 'unique()', 'name' => 'Webhook Test', 'events' => ['users.*.create', 'users.*.update.email'], 'url' => 'https://appwrite.io', @@ -2875,10 +2770,12 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), [ + 'webhookId' => 'unique()', 'name' => 'Webhook Test', 'events' => ['account.unknown', 'users.*.update.email'], 'url' => 'https://appwrite.io', @@ -2889,10 +2786,12 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), [ + 'webhookId' => 'unique()', 'name' => 'Webhook Test', 'events' => ['users.*.create', 'users.*.update.email'], 'url' => 'invalid://appwrite.io', @@ -2906,9 +2805,10 @@ class ProjectsConsoleClientTest extends Scope $data = $this->setupProjectWithWebhook(); $id = $data['projectId']; - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/webhooks', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), []); $this->assertEquals(200, $response['headers']['status-code']); @@ -2926,9 +2826,10 @@ class ProjectsConsoleClientTest extends Scope $id = $data['projectId']; $webhookId = $data['webhookId']; - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), []); $this->assertEquals(200, $response['headers']['status-code']); @@ -2944,9 +2845,10 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks/error', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/webhooks/error', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), []); $this->assertEquals(404, $response['headers']['status-code']); @@ -2958,9 +2860,10 @@ class ProjectsConsoleClientTest extends Scope $id = $data['projectId']; $webhookId = $data['webhookId']; - $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), [ 'name' => 'Webhook Test Update', 'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'], @@ -2982,9 +2885,10 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals('', $response['body']['httpUser']); $this->assertEquals('', $response['body']['httpPass']); - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), []); $this->assertEquals(200, $response['headers']['status-code']); @@ -3004,9 +2908,10 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), [ 'name' => 'Webhook Test Update', 'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.unknown'], @@ -3016,9 +2921,10 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), [ 'name' => 'Webhook Test Update', 'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'], @@ -3028,9 +2934,10 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), [ 'name' => 'Webhook Test Update', 'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'], @@ -3047,9 +2954,10 @@ class ProjectsConsoleClientTest extends Scope $webhookId = $data['webhookId']; $signatureKey = $data['signatureKey']; - $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/webhooks/' . $webhookId . '/signature', array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/webhooks/' . $webhookId . '/signature', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); @@ -3064,10 +2972,12 @@ class ProjectsConsoleClientTest extends Scope $id = $projectData['projectId']; // Create a webhook to delete - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), [ + 'webhookId' => 'unique()', 'name' => 'Webhook To Delete', 'events' => ['users.*.create'], 'url' => 'https://appwrite.io', @@ -3079,17 +2989,19 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); $webhookId = $response['body']['$id']; - $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), []); $this->assertEquals(204, $response['headers']['status-code']); $this->assertEmpty($response['body']); - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), []); $this->assertEquals(404, $response['headers']['status-code']); @@ -3097,9 +3009,10 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/webhooks/error', array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/webhooks/error', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), []); $this->assertEquals(404, $response['headers']['status-code']); @@ -4457,9 +4370,10 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/webhooks/error', array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/webhooks/error', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), []); $this->assertEquals(404, $response['headers']['status-code']); diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index f6200ed209..d1d7d0d054 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -3884,1368 +3884,4 @@ class RealtimeCustomClientTest extends Scope } }); } - public function testChannelTablesDB() - { - $user = $this->getUser(); - $session = $user['session'] ?? ''; - $projectId = $this->getProject()['$id']; - - $client = $this->getWebsocket(['documents', 'collections'], [ - 'origin' => 'http://localhost', - 'cookie' => 'a_session_' . $projectId . '=' . $session - ]); - - $response = json_decode($client->receive(), true); - - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('connected', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertCount(2, $response['data']['channels']); - $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('collections', $response['data']['channels']); - $this->assertNotEmpty($response['data']['user']); - $this->assertEquals($user['$id'], $response['data']['user']['$id']); - - /** - * Test Database Create - */ - $database = $this->client->call(Client::METHOD_POST, '/tablesdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'Actors DB', - ]); - - $databaseId = $database['body']['$id']; - - /** - * Test Collection Create - */ - $actors = $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' => 'Actors', - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - ], - 'documentSecurity' => true, - ]); - - $actorsId = $actors['body']['$id']; - - $name = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/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']); - $this->assertEquals('name', $name['body']['key']); - $this->assertEquals('string', $name['body']['type']); - $this->assertEquals(256, $name['body']['size']); - $this->assertTrue($name['body']['required']); - - sleep(2); - - /** - * Test Document Create - */ - $document = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/rows', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'rowId' => ID::unique(), - 'data' => [ - 'name' => 'Chris Evans' - ], - 'permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $response = json_decode($client->receive(), true); - - $rowId = $document['body']['$id']; - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(8, $response['data']['channels']); - $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('databases.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $rowId, $response['data']['channels']); - $this->assertContains('databases.' . $databaseId . '.collections.' . $actorsId . '.documents', $response['data']['channels']); - $this->assertContains('databases.' . $databaseId . '.tables.' . $actorsId . '.rows.' . $rowId, $response['data']['channels']); - $this->assertContains('databases.' . $databaseId . '.tables.' . $actorsId . '.rows', $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$rowId}.create", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$rowId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*.create", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.{$rowId}.create", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.{$rowId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.create", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}", $response['data']['events']); - $this->assertContains("databases.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$rowId}.create", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$rowId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*.create", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.{$rowId}.create", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.{$rowId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.create", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}", $response['data']['events']); - $this->assertContains("tablesdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals('Chris Evans', $response['data']['payload']['name']); - - /** - * Test Document Update - */ - $document = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/rows/' . $rowId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'rowId' => ID::unique(), - 'data' => [ - 'name' => 'Chris Evans 2' - ], - 'permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(8, $response['data']['channels']); - $this->assertContains('documents', $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$rowId}", $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents", $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$rowId}.update", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.tables.{$actorsId}.rows", $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.tables.{$actorsId}.rows.{$rowId}.update", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$rowId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*.update", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.{$rowId}.update", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.{$rowId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}", $response['data']['events']); - $this->assertContains("databases.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$rowId}.update", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$rowId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.{$rowId}.update", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.{$rowId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}", $response['data']['events']); - $this->assertContains("tablesdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - - $this->assertEquals('Chris Evans 2', $response['data']['payload']['name']); - - /** - * Test Document Delete - */ - $document = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/rows', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'rowId' => ID::unique(), - 'data' => [ - 'name' => 'Bradley Cooper' - ], - 'permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $client->receive(); - - $rowId = $document['body']['$id']; - - $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/rows/' . $rowId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $response = json_decode($client->receive(), true); - - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(8, $response['data']['channels']); - $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('rows', $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$rowId}", $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents", $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.tables.{$actorsId}.rows.{$rowId}", $response['data']['channels']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$rowId}", $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.tables.{$actorsId}.rows", $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$rowId}.delete", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$rowId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.{$rowId}.delete", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.{$rowId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}", $response['data']['events']); - $this->assertContains("databases.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$rowId}.delete", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$rowId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*.delete", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.{$rowId}.delete", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.{$rowId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.delete", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}", $response['data']['events']); - $this->assertContains("tablesdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals('Bradley Cooper', $response['data']['payload']['name']); - - // test bulk create - $documents = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$actorsId}/rows", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'rows' => [ - [ - '$id' => ID::unique(), - 'name' => 'Robert Downey Jr.', - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ], - [ - '$id' => ID::unique(), - 'name' => 'Scarlett Johansson', - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ] - ], - ]); - - // Receive first document event - - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(8, $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.create", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*.create", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.create", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*.create", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.*.collections.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.create", $response['data']['events']); - $this->assertContains("databases.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$response['data']['payload']['$id']}.create", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*.create", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.create", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*.create", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.create", $response['data']['events']); - $this->assertContains("tablesdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertArrayHasKey('name', $response['data']['payload']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - $this->assertIsArray($response['data']['payload']['$permissions']); - $this->assertContains(Permission::read(Role::any()), $response['data']['payload']['$permissions']); - $this->assertContains(Permission::update(Role::any()), $response['data']['payload']['$permissions']); - $this->assertContains(Permission::delete(Role::any()), $response['data']['payload']['$permissions']); - - // Receive second document event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(8, $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.create", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*.create", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.create", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*.create", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.*.collections.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.create", $response['data']['events']); - $this->assertContains("databases.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$response['data']['payload']['$id']}.create", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*.create", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.create", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*.create", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.create", $response['data']['events']); - $this->assertContains("tablesdb.*", $response['data']['events']); - - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertArrayHasKey('name', $response['data']['payload']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - $this->assertIsArray($response['data']['payload']['$permissions']); - $this->assertContains(Permission::read(Role::any()), $response['data']['payload']['$permissions']); - $this->assertContains(Permission::update(Role::any()), $response['data']['payload']['$permissions']); - $this->assertContains(Permission::delete(Role::any()), $response['data']['payload']['$permissions']); - - // test bulk update - $response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/rows/', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'data' => [ - 'name' => 'Marvel Hero', - '$permissions' => [ - Permission::read(Role::user($this->getUser()['$id'])), - Permission::update(Role::user($this->getUser()['$id'])), - Permission::delete(Role::user($this->getUser()['$id'])), - ] - ], - ]); - $this->assertEquals(200, $response['headers']['status-code']); - - // Receive first document update event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(8, $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.update", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*.update", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.*.collections.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("databases.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$response['data']['payload']['$id']}.update", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertEquals('Marvel Hero', $response['data']['payload']['name']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - - // Receive second document update event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(8, $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.update", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*.update", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.*.collections.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("databases.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$response['data']['payload']['$id']}.update", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertEquals('Marvel Hero', $response['data']['payload']['name']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - - // Receive third document update event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(8, $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.update", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*.update", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.*.collections.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("databases.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$response['data']['payload']['$id']}.update", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.update", $response['data']['events']); - $this->assertContains("tablesdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertEquals('Marvel Hero', $response['data']['payload']['name']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - - // Test bulk delete - $response = $this->client->call(Client::METHOD_DELETE, "/tablesdb/{$databaseId}/tables/{$actorsId}/rows", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Receive first document delete event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(8, $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.delete", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.*.collections.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$response['data']['payload']['$id']}.delete", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*.delete", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.delete", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*.delete", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.delete", $response['data']['events']); - $this->assertContains("tablesdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertArrayHasKey('name', $response['data']['payload']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - $this->assertIsArray($response['data']['payload']['$permissions']); - - // Receive second document delete event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(8, $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.delete", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.*.collections.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertArrayHasKey('name', $response['data']['payload']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - $this->assertIsArray($response['data']['payload']['$permissions']); - - // Receive third document delete event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(8, $response['data']['channels']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.delete", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.*.collections.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("databases.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertArrayHasKey('name', $response['data']['payload']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - $this->assertIsArray($response['data']['payload']['$permissions']); - - // bulk upsert - $this->client->call(Client::METHOD_PUT, "/tablesdb/{$databaseId}/tables/{$actorsId}/rows", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'rows' => [ - [ - '$id' => ID::unique(), - 'name' => 'Robert Downey Jr.', - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ] - ], - ]); - - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(8, $response['data']['channels']); - - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.upsert", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*.upsert", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.upsert", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*.upsert", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.*.collections.*", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("databases.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("databases.{$databaseId}.collections.*.documents.*.upsert", $response['data']['events']); - $this->assertContains("databases.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.{$response['data']['payload']['$id']}.upsert", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*.upsert", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.upsert", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*.upsert", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}.rows.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*", $response['data']['events']); - $this->assertContains("tablesdb.*.tables.{$actorsId}", $response['data']['events']); - $this->assertContains("tablesdb.{$databaseId}.tables.*.rows.*.upsert", $response['data']['events']); - $this->assertContains("tablesdb.*", $response['data']['events']); - - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertArrayHasKey('name', $response['data']['payload']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - $this->assertIsArray($response['data']['payload']['$permissions']); - - $client->close(); - } - public function testChannelDocumentsdb() - { - $user = $this->getUser(); - $session = $user['session'] ?? ''; - $projectId = $this->getProject()['$id']; - - $client = $this->getWebsocket(['documents', 'collections'], [ - 'origin' => 'http://localhost', - 'cookie' => 'a_session_' . $projectId . '=' . $session - ]); - - $response = json_decode($client->receive(), true); - - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('connected', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertCount(2, $response['data']['channels']); - $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('collections', $response['data']['channels']); - $this->assertNotEmpty($response['data']['user']); - $this->assertEquals($user['$id'], $response['data']['user']['$id']); - - /** - * Test Database Create - */ - $database = $this->client->call(Client::METHOD_POST, '/documentsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'Actors DB', - ]); - - $databaseId = $database['body']['$id']; - - /** - * Test Collection Create - */ - $actors = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Actors', - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - ], - 'documentSecurity' => true, - ]); - - $actorsId = $actors['body']['$id']; - - /** - * Test Document Create - */ - $document = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $databaseId . '/collections/' . $actorsId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'name' => 'Chris Evans' - ], - 'permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $response = json_decode($client->receive(), true); - - $documentId = $document['body']['$id']; - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('documentsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $documentId, $response['data']['channels']); - $this->assertContains('documentsdb.' . $databaseId . '.collections.' . $actorsId . '.documents', $response['data']['channels']); - $this->assertContains('documentsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $documentId, $response['data']['channels']); - $this->assertContains('documentsdb.' . $databaseId . '.collections.' . $actorsId . '.documents', $response['data']['channels']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals('Chris Evans', $response['data']['payload']['name']); - - /** - * Test Document Update - */ - $document = $this->client->call(Client::METHOD_PATCH, '/documentsdb/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'name' => 'Chris Evans 2' - ], - 'permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains('documents', $response['data']['channels']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.{$documentId}", $response['data']['channels']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents", $response['data']['channels']); - $this->assertNotEmpty($response['data']['payload']); - - $this->assertEquals('Chris Evans 2', $response['data']['payload']['name']); - - /** - * Test Document Delete - */ - $document = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $databaseId . '/collections/' . $actorsId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'name' => 'Bradley Cooper' - ], - 'permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $client->receive(); - - $documentId = $document['body']['$id']; - - $this->client->call(Client::METHOD_DELETE, '/documentsdb/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $response = json_decode($client->receive(), true); - - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains('documents', $response['data']['channels']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.{$documentId}", $response['data']['channels']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents", $response['data']['channels']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.{$documentId}", $response['data']['channels']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents", $response['data']['channels']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals('Bradley Cooper', $response['data']['payload']['name']); - - // test bulk create - $documents = $this->client->call(Client::METHOD_POST, "/documentsdb/{$databaseId}/collections/{$actorsId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documents' => [ - [ - '$id' => ID::unique(), - 'name' => 'Robert Downey Jr.', - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ], - [ - '$id' => ID::unique(), - 'name' => 'Scarlett Johansson', - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ] - ], - ]); - - // Receive first document event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertArrayHasKey('name', $response['data']['payload']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - $this->assertIsArray($response['data']['payload']['$permissions']); - $this->assertContains(Permission::read(Role::any()), $response['data']['payload']['$permissions']); - $this->assertContains(Permission::update(Role::any()), $response['data']['payload']['$permissions']); - $this->assertContains(Permission::delete(Role::any()), $response['data']['payload']['$permissions']); - - // Receive second document event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertArrayHasKey('name', $response['data']['payload']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - $this->assertIsArray($response['data']['payload']['$permissions']); - $this->assertContains(Permission::read(Role::any()), $response['data']['payload']['$permissions']); - $this->assertContains(Permission::update(Role::any()), $response['data']['payload']['$permissions']); - $this->assertContains(Permission::delete(Role::any()), $response['data']['payload']['$permissions']); - - // test bulk update - $response = $this->client->call(Client::METHOD_PATCH, '/documentsdb/' . $databaseId . '/collections/' . $actorsId . '/documents/', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'data' => [ - 'name' => 'Marvel Hero', - '$permissions' => [ - Permission::read(Role::user($this->getUser()['$id'])), - Permission::update(Role::user($this->getUser()['$id'])), - Permission::delete(Role::user($this->getUser()['$id'])), - ] - ], - ]); - $this->assertEquals(200, $response['headers']['status-code']); - - // Receive first document update event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.update", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*.update", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("documentsdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertEquals('Marvel Hero', $response['data']['payload']['name']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - - // Receive second document update event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.update", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*.update", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("documentsdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertEquals('Marvel Hero', $response['data']['payload']['name']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - - // Receive third document update event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.update", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*.update", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.update", $response['data']['events']); - $this->assertContains("documentsdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertEquals('Marvel Hero', $response['data']['payload']['name']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - - // Test bulk delete - $response = $this->client->call(Client::METHOD_DELETE, "/documentsdb/{$databaseId}/collections/{$actorsId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(200, $response['headers']['status-code']); - - // Receive first document delete event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.delete", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*.delete", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("documentsdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertArrayHasKey('name', $response['data']['payload']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - $this->assertIsArray($response['data']['payload']['$permissions']); - - // Receive second document delete event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.delete", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*.delete", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("documentsdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertArrayHasKey('name', $response['data']['payload']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - $this->assertIsArray($response['data']['payload']['$permissions']); - - // Receive third document delete event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.delete", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*.delete", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.delete", $response['data']['events']); - $this->assertContains("documentsdb.*", $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertArrayHasKey('name', $response['data']['payload']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - $this->assertIsArray($response['data']['payload']['$permissions']); - - // bulk upsert - $this->client->call(Client::METHOD_PUT, "/documentsdb/{$databaseId}/collections/{$actorsId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documents' => [ - [ - '$id' => ID::unique(), - 'name' => 'Robert Downey Jr.', - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ] - ], - ]); - - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.upsert", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*.upsert", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.upsert", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*.upsert", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}.documents.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*", $response['data']['events']); - $this->assertContains("documentsdb.*.collections.{$actorsId}", $response['data']['events']); - $this->assertContains("documentsdb.{$databaseId}.collections.*.documents.*.upsert", $response['data']['events']); - $this->assertContains("documentsdb.*", $response['data']['events']); - - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - $this->assertArrayHasKey('$id', $response['data']['payload']); - $this->assertArrayHasKey('name', $response['data']['payload']); - $this->assertArrayHasKey('$permissions', $response['data']['payload']); - $this->assertIsArray($response['data']['payload']['$permissions']); - - $client->close(); - } - - public function testChannelVectorsDB() - { - $user = $this->getUser(); - $session = $user['session'] ?? ''; - $projectId = $this->getProject()['$id']; - - $client = $this->getWebsocket(['documents', 'collections'], [ - 'origin' => 'http://localhost', - 'cookie' => 'a_session_' . $projectId . '=' . $session - ]); - - $response = json_decode($client->receive(), true); - - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('connected', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertCount(2, $response['data']['channels']); - $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('collections', $response['data']['channels']); - $this->assertNotEmpty($response['data']['user']); - $this->assertEquals($user['$id'], $response['data']['user']['$id']); - - // Create VectorsDB database - $database = $this->client->call(Client::METHOD_POST, '/vectorsdb', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'databaseId' => ID::unique(), - 'name' => 'Actors VDB', - ]); - - $databaseId = $database['body']['$id']; - - // Create collection in VectorsDB - $actors = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Actors', - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - ], - 'documentSecurity' => true, - 'dimension' => 3, - ]); - - $actorsId = $actors['body']['$id']; - - // Create document in VectorsDB - $document = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $actorsId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['name' => 'Chris Evans'] - ], - 'permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $response = json_decode($client->receive(), true); - - $documentId = $document['body']['$id']; - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - // vectorsdb channels should include 3 items like documentsdb - $this->assertCount(3, $response['data']['channels']); - $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $documentId, $response['data']['channels']); - $this->assertContains('vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents', $response['data']['channels']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']['embeddings']); - $this->assertCount(3, $response['data']['payload']['embeddings']); - $this->assertEquals('Chris Evans', $response['data']['payload']['metadata']['name']); - - // Update document - $this->client->call(Client::METHOD_PATCH, '/vectorsdb/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['name' => 'Chris Evans 2'] - ], - 'permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ]); - - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains('vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $documentId, $response['data']['channels']); - $this->assertContains('vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents', $response['data']['channels']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']['embeddings']); - $this->assertEquals('Chris Evans 2', $response['data']['payload']['metadata']['name']); - - // Delete document - $this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains('vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $documentId, $response['data']['channels']); - $this->assertContains('vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents', $response['data']['channels']); - - // Bulk create two documents - $this->client->call(Client::METHOD_POST, "/vectorsdb/{$databaseId}/collections/{$actorsId}/documents", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'documents' => [ - [ - 'embeddings' => [1.0, 0.0, 0.0], - 'metadata' => ['name' => 'Robert Downey Jr.'], - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ], - [ - 'embeddings' => [0.0, 1.0, 0.0], - 'metadata' => ['name' => 'Scarlett Johansson'], - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ] - ], - ]); - - // Receive first bulk document event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains('vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $response['data']['payload']['$id'] . '.create', $response['data']['events']); - $this->assertContains('vectorsdb.*.collections.*.documents.*.create', $response['data']['events']); - $this->assertContains('vectorsdb.' . $databaseId . '.collections.*.documents.*.create', $response['data']['events']); - $this->assertContains('vectorsdb.*.collections.' . $actorsId . '.documents.*.create', $response['data']['events']); - $this->assertNotEmpty($response['data']['payload']); - $this->assertIsArray($response['data']['payload']); - - // Receive second bulk document event - $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(3, $response['data']['channels']); - $this->assertContains('vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $response['data']['payload']['$id'] . '.create', $response['data']['events']); - - $client->close(); - } } diff --git a/tests/resources/csv/vectorsdb-documents.csv b/tests/resources/csv/vectorsdb-documents.csv deleted file mode 100644 index b0b970703e..0000000000 --- a/tests/resources/csv/vectorsdb-documents.csv +++ /dev/null @@ -1,3 +0,0 @@ -$id,embeddings,metadata -vector-doc-1,"[0.15,0.25,0.35]","{""title"":""Vector Alpha"",""category"":""science""}" -vector-doc-2,"[0.55,0.65,0.75]","{""title"":""Vector Beta"",""category"":""history""}" \ No newline at end of file diff --git a/tests/resources/docker/docker-compose.yml b/tests/resources/docker/docker-compose.yml index 47a69f077b..02593f8123 100644 --- a/tests/resources/docker/docker-compose.yml +++ b/tests/resources/docker/docker-compose.yml @@ -76,7 +76,6 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS - - _APP_DB_ADAPTER - _APP_USAGE_STATS - _APP_STORAGE_ANTIVIRUS=disabled - _APP_STORAGE_LIMIT @@ -142,7 +141,6 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS - - _APP_DB_ADAPTER appwrite-worker-tasks: entrypoint: worker-tasks @@ -164,7 +162,6 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS - - _APP_DB_ADAPTER appwrite-worker-deletes: entrypoint: worker-deletes @@ -185,7 +182,6 @@ services: - _APP_REDIS_HOST - _APP_REDIS_PORT - _APP_DB_HOST - - _APP_DB_ADAPTER - _APP_DB_PORT - _APP_DB_SCHEMA - _APP_DB_USER diff --git a/tests/resources/postgresql/Dockerfile b/tests/resources/postgresql/Dockerfile new file mode 100644 index 0000000000..a731833b48 --- /dev/null +++ b/tests/resources/postgresql/Dockerfile @@ -0,0 +1,11 @@ +ARG POSTGRES_VERSION=17 +FROM postgres:${POSTGRES_VERSION} + +ARG POSTGRES_VERSION=17 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + postgresql-${POSTGRES_VERSION}-postgis-3 \ + postgresql-${POSTGRES_VERSION}-postgis-3-scripts \ + postgresql-${POSTGRES_VERSION}-pgvector \ + && rm -rf /var/lib/apt/lists/* diff --git a/tests/unit/Event/MockPublisher.php b/tests/unit/Event/MockPublisher.php index a7118d3c09..0b812e7032 100644 --- a/tests/unit/Event/MockPublisher.php +++ b/tests/unit/Event/MockPublisher.php @@ -23,7 +23,7 @@ class MockPublisher implements Publisher return $this->events[$queue] ?? null; } - public function retry(Queue $queue, ?int $limit = null): void + public function retry(Queue $queue, int $limit = null): void { // TODO: Implement retry() method. } diff --git a/tests/unit/Messaging/MessagingChannelsTest.php b/tests/unit/Messaging/MessagingChannelsTest.php index fc2d839ca6..598a47a901 100644 --- a/tests/unit/Messaging/MessagingChannelsTest.php +++ b/tests/unit/Messaging/MessagingChannelsTest.php @@ -16,7 +16,7 @@ class MessagingChannelsTest extends TestCase */ public $connectionsPerChannel = 10; - public ?Realtime $realtime = null; + public Realtime $realtime; public $connectionsCount = 0; public $connectionsAuthenticated = 0; public $connectionsGuest = 0; @@ -125,7 +125,7 @@ class MessagingChannelsTest extends TestCase public function tearDown(): void { - $this->realtime = null; + unset($this->realtime); $this->connectionsCount = 0; } diff --git a/tests/unit/Network/Validators/OriginTest.php b/tests/unit/Network/Validators/OriginTest.php index 7a19daecbf..64ce71951b 100644 --- a/tests/unit/Network/Validators/OriginTest.php +++ b/tests/unit/Network/Validators/OriginTest.php @@ -73,6 +73,10 @@ class OriginTest extends TestCase $this->assertEquals(false, $validator->isValid('ms-browser-extension://com.company.appname')); $this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Edge Extension) platform on your project console dashboard', $validator->getDescription()); + $this->assertEquals(true, $validator->isValid('tauri://localhost')); + $this->assertEquals(false, $validator->isValid('tauri://example.com')); + $this->assertEquals('Invalid Origin. Register your new client (example.com) as a new Web (Tauri) platform on your project console dashboard', $validator->getDescription()); + $this->assertEquals(false, $validator->isValid('random-scheme://localhost')); $this->assertEquals('Invalid Scheme. The scheme used (random-scheme) in the Origin (random-scheme://localhost) is not supported. If you are using a custom scheme, please change it to `appwrite-callback-`', $validator->getDescription()); }