From a824b46df0258f67cf81f37e2ac4cb3ff7c99acc Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 22 Mar 2023 17:49:08 +0200 Subject: [PATCH] Update relationship --- app/controllers/api/databases.php | 193 +++++++++++++++++- .../e2e/Services/Databases/DatabasesBase.php | 90 ++++++-- 2 files changed, 261 insertions(+), 22 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 005fba3b70..448267f5fd 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -169,7 +169,8 @@ function updateAttribute( bool $required = null, int|float $min = null, int|float $max = null, - array $elements = null + array $elements = null, + array $options = [] ): Document { $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); @@ -293,13 +294,32 @@ function updateAttribute( break; } - $dbForProject->updateAttribute( - collection: $collectionId, - id: $key, - required: $required, - default: $default, - formatOptions: $options ?? null - ); + if ($type === Database::VAR_RELATIONSHIP) { + $original = $attribute->getAttribute('options', []); + if (!empty($original['twoWayKey']) && $options['twoWayKey'] === $original['twoWayKey']) { + $options['twoWayKey'] = null; + } + + $options = array_merge($original, $options); + $attribute->setAttribute('options', $options); + var_dump($options); + + $dbForProject->updateRelationship( + collection: $collectionId, + key: $key, + newTwoWayKey: $options['twoWayKey'], + onUpdate: $options['onUpdate'], + onDelete: $options['onDelete'], + ); + } else { + $dbForProject->updateAttribute( + collection: $collectionId, + id: $key, + required: $required, + default: $default, + formatOptions: $options ?? null + ); + } $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute); $dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId); @@ -1541,6 +1561,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati EventDatabase $database, Event $events ) { + + if (empty($twoWayKey)) { + $twoWayKey = $collectionId; + } + $attribute = createAttribute( $databaseId, $collectionId, @@ -1579,6 +1604,158 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati ->dynamic($attribute, Response::MODEL_ATTRIBUTE_RELATIONSHIP); }); + +App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relationship') + ->alias('/v1/database/collections/:collectionId/attributes/relationship', ['databaseId' => 'default']) + ->desc('Create relationship Attribute') + ->groups(['api', 'database']) + ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') + ->label('scope', 'collections.write') + ->label('audits.event', 'attribute.create') + ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') + ->label('usage.metric', 'collections.{scope}.requests.update') + ->label('usage.params', ['databaseId:{request.databaseId}']) + ->label('sdk.namespace', 'databases') + ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) + ->label('sdk.method', 'createRelationshipAttribute') + ->label('sdk.description', '/docs/references/databases/create-relationship-attribute.md') + ->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_RELATIONSHIP) + ->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(), 'Attribute Key.') + ->param('relatedCollectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') + ->param('type', '', new WhiteList([Database::RELATION_ONE_TO_ONE, Database::RELATION_MANY_TO_ONE, Database::RELATION_MANY_TO_MANY, Database::RELATION_ONE_TO_MANY]), 'Relation type') + ->param('twoWay', false, new Boolean(), 'Is Two Way?', true) + ->param('twoWayKey', '', new Text(40), 'Two Way Key', true) + ->param('onUpdate', 'restrict', new WhiteList([Database::RELATION_MUTATE_CASCADE, Database::RELATION_MUTATE_RESTRICT, Database::RELATION_MUTATE_SET_NULL]), 'Constraints option', true) + ->param('onDelete', 'restrict', new WhiteList([Database::RELATION_MUTATE_CASCADE, Database::RELATION_MUTATE_RESTRICT, Database::RELATION_MUTATE_SET_NULL]), 'Constraints option', true) + ->inject('response') + ->inject('dbForProject') + ->inject('database') + ->inject('events') + ->action(function ( + string $databaseId, + string $collectionId, + string $key, + string $relatedCollectionId, + string $type, + bool $twoWay, + string $twoWayKey, + string $onUpdate, + string $onDelete, + Response $response, + Database $dbForProject, + EventDatabase $database, + Event $events + ) { + + if (empty($twoWayKey)) { + $twoWayKey = $collectionId; + } + + $attribute = createAttribute( + $databaseId, + $collectionId, + new Document([ + 'key' => $key, + 'type' => Database::VAR_RELATIONSHIP, + 'size' => 0, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + 'options' => [ + 'relatedCollection' => $relatedCollectionId, + 'relationType' => $type, + 'twoWay' => $twoWay, + 'twoWayKey' => $twoWayKey, + 'onUpdate' => $onUpdate, + 'onDelete' => $onDelete, + 'id' => $key + ] + ]), + $response, + $dbForProject, + $database, + $events + ); + + $options = $attribute->getAttribute('options', []); + foreach ($options as $key => $option) { + $attribute->setAttribute($key, $option); + } + + $response + ->setStatusCode(Response::STATUS_CODE_ACCEPTED) + ->dynamic($attribute, Response::MODEL_ATTRIBUTE_RELATIONSHIP); + }); + + +App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/relationship') + ->desc('Update Relationship Attribute') + ->groups(['api', 'database', 'schema']) + ->label('scope', 'collections.write') + ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') + ->label('audits.event', 'attribute.update') + ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') + ->label('usage.metric', 'collections.{scope}.requests.update') + ->label('usage.params', ['databaseId:{request.databaseId}']) + ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'databases') + ->label('sdk.method', 'updateRelationshipAttribute') + ->label('sdk.description', '/docs/references/databases/update-relationship-attribute.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_RELATIONSHIP) + ->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(), 'Attribute Key.') + ->param('twoWay', null, new Boolean(), 'Is Two Way?') + ->param('twoWayKey', null, new Text(40), 'Two Way Key') + ->param('onUpdate', null, new WhiteList([Database::RELATION_MUTATE_CASCADE, Database::RELATION_MUTATE_RESTRICT, Database::RELATION_MUTATE_SET_NULL]), 'Constraints option') + ->param('onDelete', null, new WhiteList([Database::RELATION_MUTATE_CASCADE, Database::RELATION_MUTATE_RESTRICT, Database::RELATION_MUTATE_SET_NULL]), 'Constraints option') + ->inject('response') + ->inject('dbForProject') + ->inject('events') + ->action(function ( + string $databaseId, + string $collectionId, + string $key, + bool $twoWay, + string $twoWayKey, + string $onUpdate, + string $onDelete, + Response $response, + Database $dbForProject, + Event $events +) { + $attribute = updateAttribute( + databaseId: $databaseId, + collectionId: $collectionId, + key: $key, + dbForProject: $dbForProject, + events: $events, + type: Database::VAR_RELATIONSHIP, + required: false, + options : [ + 'twoWay' => $twoWay, + 'twoWayKey' => $twoWayKey, + 'onUpdate' => $onUpdate, + 'onDelete' => $onDelete + ] + ); + + $options = $attribute->getAttribute('options', []); + foreach ($options as $key => $option) { + $attribute->setAttribute($key, $option); + } + + $response + ->setStatusCode(Response::STATUS_CODE_OK) + ->dynamic($attribute, Response::MODEL_ATTRIBUTE_RELATIONSHIP); + }); + App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') ->alias('/v1/database/collections/:collectionId/attributes', ['databaseId' => 'default']) ->desc('List Attributes') diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index bc0f83b226..efecf56de0 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -4,6 +4,7 @@ namespace Tests\E2E\Services\Databases; use Appwrite\Extend\Exception; use Tests\E2E\Client; +use Utopia\App; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -319,6 +320,7 @@ trait DatabasesBase $this->assertEquals(false, $attribute['body']['array']); $this->assertEquals('oneToOne', $attribute['body']['relationType']); $this->assertEquals(false, $attribute['body']['twoWay']); + $this->assertEquals('person', $attribute['body']['twoWayKey']); $this->assertEquals('cascade', $attribute['body']['onUpdate']); $this->assertEquals('restrict', $attribute['body']['onDelete']); @@ -351,7 +353,6 @@ trait DatabasesBase ], $this->getHeaders()), [ 'queries' => [ 'equal("libraryId", "library1")', - //'select("libraryId")' ], ]); @@ -385,43 +386,104 @@ trait DatabasesBase $this->assertEquals(404, $attribute['headers']['status-code']); // One person can own several libraries - $oneToMany = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/relationship', array_merge([ + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/relationship', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'key' => 'xxx', + 'key' => 'libraries', 'relatedCollectionId' => 'library', 'type' => Database::RELATION_ONE_TO_MANY, - 'twoWay' => true, 'twoWayKey' => 'personId', + 'twoWay' => true, ]); - sleep(2); + sleep(1); - $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$person['body']['$id']}/attributes/xxx", array_merge([ + $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$person['body']['$id']}/attributes/libraries", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ])); - var_dump($attribute); - -die; - $this->assertEquals(200, $attribute['headers']['status-code']); $this->assertEquals('available', $attribute['body']['status']); - $this->assertEquals('people', $attribute['body']['key']); + $this->assertEquals('libraries', $attribute['body']['key']); $this->assertEquals('relationship', $attribute['body']['type']); $this->assertEquals(false, $attribute['body']['required']); $this->assertEquals(false, $attribute['body']['array']); $this->assertEquals('oneToMany', $attribute['body']['relationType']); - $this->assertEquals(false, $attribute['body']['twoWay']); + $this->assertEquals(true, $attribute['body']['twoWay']); + $this->assertEquals('personId', $attribute['body']['twoWayKey']); $this->assertEquals('restrict', $attribute['body']['onUpdate']); $this->assertEquals('restrict', $attribute['body']['onDelete']); -var_dump($attribute); - die; + $person2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'person10', + 'data' => [ + 'libraries' => [ + [ + '$id' => 'library10', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'libraryName' => 'Library 10', + ], + [ + '$id' => 'library11', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'libraryName' => 'Library 11', + ] + ], + ], + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ] + ]); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/' . $person2['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertArrayNotHasKey('$collection', $response['body']); + $this->assertArrayHasKey('libraries', $response['body']); + $this->assertEquals(2, count($response['body']['libraries'])); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $library['body']['$id'] . '/documents/library11', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertArrayHasKey('personId', $response['body']); + $this->assertEquals('person10', $response['body']['personId']['$id']); + + $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/libraries/relationship', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'twoWayKey' => 'personIdNew', + 'twoWay' => false, + 'onUpdate' => 'cascade', + 'onDelete' => 'cascade', + ]); + + var_dump($response); +die; return []; }