Merge pull request #7153 from appwrite/twoWayKey-2

Add twoWayKey checks and multiple many-to-many restrictions
This commit is contained in:
Jake Barnby 2024-02-25 22:44:43 +13:00 committed by GitHub
commit ff26d4c9d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 207 additions and 0 deletions

View file

@ -1616,6 +1616,54 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati
$key ??= $relatedCollectionId;
$twoWayKey ??= $collectionId;
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
$collection = $dbForProject->getCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
if ($collection->isEmpty()) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$relatedCollectionDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId);
$relatedCollection = $dbForProject->getCollection('database_' . $database->getInternalId() . '_collection_' . $relatedCollectionDocument->getInternalId());
if ($relatedCollection->isEmpty()) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$attributes = $collection->getAttribute('attributes', []);
/** @var Document[] $attributes */
foreach ($attributes as $attribute) {
if ($attribute->getAttribute('type') !== Database::VAR_RELATIONSHIP) {
continue;
}
if (\strtolower($attribute->getId()) === \strtolower($key)) {
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS);
}
if (
\strtolower($attribute->getAttribute('options')['twoWayKey']) === \strtolower($twoWayKey) &&
$attribute->getAttribute('options')['relatedCollection'] === $relatedCollection->getId()
) {
// Console should provide a unique twoWayKey input!
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS, 'Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.');
}
if (
$type === Database::RELATION_MANY_TO_MANY &&
$attribute->getAttribute('options')['relationType'] === Database::RELATION_MANY_TO_MANY &&
$attribute->getAttribute('options')['relatedCollection'] === $relatedCollection->getId()
) {
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS, 'Creating more than one "manyToMany" relationship on the same collection is currently not permitted.');
}
}
$attribute = createAttribute(
$databaseId,
$collectionId,

View file

@ -6,6 +6,7 @@ use Tests\E2E\Client;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\SideClient;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@ -316,6 +317,164 @@ class DatabasesCustomClientTest extends Scope
$this->assertEquals('restrict', $collection1RelationAttribute['onDelete']);
}
public function testRelationshipSameTwoWayKey(): void
{
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Same two way key'
]);
$databaseId = $database['body']['$id'];
$collection1 = $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' => 'c1',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
]
]);
$collection2 = $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' => 'c2',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
]
]);
\sleep(2);
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'relatedCollectionId' => $collection2['body']['$id'],
'type' => Database::RELATION_ONE_TO_ONE,
'twoWay' => false,
'onDelete' => 'cascade',
'key' => 'attr1',
'twoWayKey' => 'same_key'
]);
\sleep(2);
$this->assertEquals(202, $relation['headers']['status-code']);
$this->assertEquals('same_key', $relation['body']['twoWayKey']);
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'relatedCollectionId' => $collection2['body']['$id'],
'type' => Database::RELATION_ONE_TO_MANY,
'twoWay' => false,
'onDelete' => 'cascade',
'key' => 'attr2',
'twoWayKey' => 'same_key'
]);
\sleep(2);
$this->assertEquals(409, $relation['body']['code']);
$this->assertEquals('Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.', $relation['body']['message']);
// twoWayKey is null TwoWayKey is default
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'relatedCollectionId' => $collection2['body']['$id'],
'type' => Database::RELATION_ONE_TO_MANY,
'twoWay' => false,
'onDelete' => 'cascade',
'key' => 'attr3',
]);
\sleep(2);
$this->assertEquals(202, $relation['headers']['status-code']);
$this->assertArrayHasKey('twoWayKey', $relation['body']);
// twoWayKey is null, TwoWayKey is default, second POST
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'relatedCollectionId' => $collection2['body']['$id'],
'type' => Database::RELATION_ONE_TO_MANY,
'twoWay' => false,
'onDelete' => 'cascade',
'key' => 'attr4',
]);
\sleep(2);
$this->assertEquals('Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.', $relation['body']['message']);
$this->assertEquals(409, $relation['body']['code']);
// RelationshipManyToMany
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'relatedCollectionId' => $collection2['body']['$id'],
'type' => Database::RELATION_MANY_TO_MANY,
'twoWay' => true,
'onDelete' => 'setNull',
'key' => 'songs',
'twoWayKey' => 'playlist',
]);
\sleep(2);
$this->assertEquals(202, $relation['headers']['status-code']);
$this->assertArrayHasKey('twoWayKey', $relation['body']);
// Second RelationshipManyToMany on Same collections
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'relatedCollectionId' => $collection2['body']['$id'],
'type' => Database::RELATION_MANY_TO_MANY,
'twoWay' => true,
'onDelete' => 'setNull',
'key' => 'songs2',
'twoWayKey' => 'playlist2',
]);
\sleep(2);
$this->assertEquals(409, $relation['body']['code']);
$this->assertEquals('Creating more than one "manyToMany" relationship on the same collection is currently not permitted.', $relation['body']['message']);
}
public function testUpdateWithoutRelationPermission(): void
{
$userId = $this->getUser()['$id'];