From 75678f5715e8760933d9930aef0104ca446a5a6c Mon Sep 17 00:00:00 2001 From: Barry Brands Date: Thu, 12 Dec 2024 16:54:15 +0100 Subject: [PATCH 1/2] added cascade delete in frontend --- src/modals/schema/EditSchemaProperty.vue | 25 +++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/modals/schema/EditSchemaProperty.vue b/src/modals/schema/EditSchemaProperty.vue index b46abb5..4648910 100644 --- a/src/modals/schema/EditSchemaProperty.vue +++ b/src/modals/schema/EditSchemaProperty.vue @@ -181,6 +181,13 @@ import { navigationStore, schemaStore } from '../../store/store.js' :loading="loading" :error="!verifyJsonValidity(properties.default)" :helper-text="!verifyJsonValidity(properties.default) ? 'This is not valid JSON' : ''" /> + + + Cascade delete + + - - Required - + + Required + + + + Cascade delete + Date: Thu, 12 Dec 2024 16:54:24 +0100 Subject: [PATCH 2/2] Added cascade delete --- lib/Db/ObjectEntityMapper.php | 23 ++++++++ lib/Service/ObjectService.php | 100 +++++++++++++++++++++++++++++++--- 2 files changed, 115 insertions(+), 8 deletions(-) diff --git a/lib/Db/ObjectEntityMapper.php b/lib/Db/ObjectEntityMapper.php index 860523d..9c064f0 100644 --- a/lib/Db/ObjectEntityMapper.php +++ b/lib/Db/ObjectEntityMapper.php @@ -91,6 +91,29 @@ public function findByUuid(Register $register, Schema $schema, string $uuid): Ob } } + /** + * Find an object by UUID only + * + * @param string $uuid The UUID of the object to find + * @return ObjectEntity The object + */ + public function findByUuidOnly(string $uuid): ObjectEntity|null + { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from('openregister_objects') + ->where( + $qb->expr()->eq('uuid', $qb->createNamedParameter($uuid)) + ); + + try { + return $this->findEntity($qb); + } catch (\OCP\AppFramework\Db\DoesNotExistException $e) { + return null; + } + } + /** * Find objects by register and schema * diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php index 6356c4b..63a1294 100755 --- a/lib/Service/ObjectService.php +++ b/lib/Service/ObjectService.php @@ -205,8 +205,8 @@ public function delete(array|\JsonSerializable $object): bool } return $this->deleteObject( - register: $this->registerMapper->find($this->getRegister()), - schema: $this->schemaMapper->find($this->getSchema()), + register: $this->getRegister(), + schema: $this->getSchema(), uuid: $object['id'] ); } @@ -762,21 +762,105 @@ public function getObject(Register $register, Schema $schema, string $uuid, ?arr throw new Exception('Unsupported source type'); } + /** + * Check if a string contains a dot and get the substring before the first dot. + * + * @param string $input The input string. + * + * @return string The substring before the first dot, or the original string if no dot is found. + */ + private function getStringBeforeDot(string $input): string + { + // Find the position of the first dot + $dotPosition = strpos($input, '.'); + + // Return the substring before the dot, or the original string if no dot is found + return $dotPosition !== false ? substr($input, 0, $dotPosition) : $input; + } + + /** + * Get the substring after the last slash in a string. + * + * @param string $input The input string. + * + * @return string The substring after the last slash. + */ + function getStringAfterLastSlash(string $input): string + { + // Find the position of the last slash + $lastSlashPos = strrpos($input, '/'); + + // Return the substring after the last slash, or the original string if no slash is found + return $lastSlashPos !== false ? substr($input, $lastSlashPos + 1) : $input; + } + + /** + * Cascade delete related objects based on schema properties. + * + * This method identifies properties in the schema marked for cascade deletion and deletes + * related objects associated with those properties in the given object. + * + * @param Register $register The register containing the objects. + * @param Schema $schema The schema defining the properties and relationships. + * @param ObjectEntity $object The object entity whose related objects should be deleted. + * + * @return void + * + * @throws Exception If any errors occur during the deletion process. + */ + private function cascadeDeleteObjects(Register $register, Schema $schema, ObjectEntity $object, string $originalObjectId): void + { + $cascadeDeleteProperties = []; + foreach ($schema->getProperties() as $propertyName => $property) { + if ((isset($property['cascadeDelete']) === true && $property['cascadeDelete'] === true) || (isset($property['items']['cascadeDelete']) === true && $property['items']['cascadeDelete'] === true)) { + $cascadeDeleteProperties[] = $propertyName; + } + } + + foreach ($object->getRelations() as $relationName => $relation) { + $relationName = $this->getStringBeforeDot(input: $relationName); + $relatedObjectId = $this->getStringAfterLastSlash(input: $relation); + // Check if this sub object has cacsadeDelete = true and is not the original object that started this delete streakt + if (in_array(needle: $relationName, haystack: $cascadeDeleteProperties) === true && $relatedObjectId !== $originalObjectId) { + $this->deleteObject(register: $register->getId(), schema: $schema->getId(), uuid: $relatedObjectId, originalObjectId: $originalObjectId); + } + } + } + /** * Delete an object * - * @param Register $register The register to delete from - * @param Schema $schema The schema of the object - * @param string $uuid The UUID of the object to delete + * @param string|int $register The register to delete from + * @param string|int $schema The schema of the object + * @param string $uuid The UUID of the object to delete + * @param string|null $originalObjectId The UUID of the parent object so we dont delete the object we come from and cause a loop * - * @return bool True if deletion was successful + * @return bool True if deletion was successful * @throws Exception If source type is unsupported */ - public function deleteObject(Register $register, Schema $schema, string $uuid): bool + public function deleteObject($register, $schema, string $uuid, ?string $originalObjectId = null): bool { + $register = $this->registerMapper->find($register); + $schema = $this->schemaMapper->find($schema); + // Handle internal source if ($register->getSource() === 'internal' || $register->getSource() === '') { - $object = $this->objectEntityMapper->findByUuid(register: $register, schema: $schema, uuid: $uuid); + $object = $this->objectEntityMapper->findByUuidOnly(uuid: $uuid); + + if ($object === null) { + return false; + } + + // If internal register and schema should be found from the object himself. Makes it possible to delete cascaded objects. + $register = $this->registerMapper->find($object->getRegister()); + $schema = $this->schemaMapper->find($object->getSchema()); + + if ($originalObjectId === null) { + $originalObjectId = $object->getUuid(); + } + + $this->cascadeDeleteObjects(register: $register, schema: $schema, object: $object, originalObjectId: $originalObjectId); + $this->objectEntityMapper->delete($object); return true; }