From 8c60c6562d855c775390b31dcf8226fe2b8d6c33 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 16 Oct 2024 10:18:15 +0000 Subject: [PATCH 01/17] Bump version to 0.6.19 --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index dc4f3111..4ef96354 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,7 +22,7 @@ Create a [bug report](https://github.com/OpenCatalogi/.github/issues/new/choose) Create a [feature request](https://github.com/OpenCatalogi/.github/issues/new/choose) ]]> - 0.6.18 + 0.6.19 agpl Conduction Acato From 09e5b6b1bc467ebf679bd9b6060722feb0db1450 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 18 Oct 2024 12:28:26 +0000 Subject: [PATCH 02/17] Bump version to 0.6.20 --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 4ef96354..1675ea8e 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,7 +22,7 @@ Create a [bug report](https://github.com/OpenCatalogi/.github/issues/new/choose) Create a [feature request](https://github.com/OpenCatalogi/.github/issues/new/choose) ]]> - 0.6.19 + 0.6.20 agpl Conduction Acato From 644892906cba5c9e55b2f1641aee855ba0342775 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 18 Oct 2024 16:15:19 +0000 Subject: [PATCH 03/17] Bump version to 0.6.21 --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 1675ea8e..c475b76f 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,7 +22,7 @@ Create a [bug report](https://github.com/OpenCatalogi/.github/issues/new/choose) Create a [feature request](https://github.com/OpenCatalogi/.github/issues/new/choose) ]]> - 0.6.20 + 0.6.21 agpl Conduction Acato From 65002eca16e4cca2b93c5c38198cac62f342a4fd Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 19 Oct 2024 08:19:40 +0000 Subject: [PATCH 04/17] Bump version to 0.6.22 --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index c475b76f..dce17510 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,7 +22,7 @@ Create a [bug report](https://github.com/OpenCatalogi/.github/issues/new/choose) Create a [feature request](https://github.com/OpenCatalogi/.github/issues/new/choose) ]]> - 0.6.21 + 0.6.22 agpl Conduction Acato From 0b0515a27c8bf79b7f957b706f38fc3f214313b8 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 19 Oct 2024 20:49:12 +0000 Subject: [PATCH 05/17] Bump version to 0.6.23 --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index dce17510..4edc332a 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,7 +22,7 @@ Create a [bug report](https://github.com/OpenCatalogi/.github/issues/new/choose) Create a [feature request](https://github.com/OpenCatalogi/.github/issues/new/choose) ]]> - 0.6.22 + 0.6.23 agpl Conduction Acato From a57e07b6227ac1f1d1b2dc205818b9dfd90db7ad Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 20 Oct 2024 10:46:08 +0200 Subject: [PATCH 06/17] Add routing for syncing publication types --- appinfo/routes.php | 8 ++- docs/installatie/instructies.md | 3 + docs/installatie/upgrade.png | Bin 0 -> 30745 bytes lib/Controller/DirectoryController.php | 5 +- lib/Controller/PublicationTypesController.php | 23 +++++++ lib/Service/DirectoryService.php | 56 ++++++++++++++++++ 6 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 docs/installatie/upgrade.png diff --git a/appinfo/routes.php b/appinfo/routes.php index 1449c5ce..862602a1 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -11,12 +11,14 @@ 'listings' => ['url' => '/api/listings'], ], 'routes' => [ - // Custom + // Directory ['name' => 'listing#synchronise', 'url' => '/api/listings/synchronise/{id?}', 'verb' => 'POST'], ['name' => 'directory#index', 'url' => '/api/directory', 'verb' => 'GET'], - ['name' => 'directory#show', 'url' => '/api/directory/{id}', 'verb' => 'GET'], + ['name' => 'directory#show', 'url' => '/api/directory/{id}', 'verb' => 'GET', 'requirements' => ['path' => '.+']], ['name' => 'directory#update', 'url' => '/api/directory', 'verb' => 'POST'], - ['name' => 'directory#publicationType', 'url' => '/api/directory/publication_type/{id}', 'verb' => 'GET'], + ['name' => 'directory#syncPublicationType', 'url' => '/api/directory/copy_pubpication_type', 'verb' => 'POST'], // Should be in directory becouse its public + // Publication + ['name' => 'synchronise#synchronise', 'url' => '/api/publication_types/synchronise/{id}', 'verb' => 'POST'], // Dashboard ['name' => 'dashboard#index', 'url' => '/index', 'verb' => 'GET'], ['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'], diff --git a/docs/installatie/instructies.md b/docs/installatie/instructies.md index f5ca4de6..85917fb7 100644 --- a/docs/installatie/instructies.md +++ b/docs/installatie/instructies.md @@ -33,3 +33,6 @@ Let op! Je hebt hier een admin-account voor nodig. Dit werkt mogelijk niet met t
Met deze stappen ben je klaar om Nextcloud en OpenCatalogi te gebruiken zonder dat je een eigen server hoeft op te zetten. Als je nog vragen hebt of hulp nodig hebt, raadpleeg dan de [officiƫle Nextcloud documentatie](https://docs.nextcloud.com/) of stuur een mail naar support@conduction.nl. + +** OpenCatalogi Bijwerken** +![alt text](upgrade.png) diff --git a/docs/installatie/upgrade.png b/docs/installatie/upgrade.png new file mode 100644 index 0000000000000000000000000000000000000000..1ec452eb85b6af1f3d64793cb7a5411934690468 GIT binary patch literal 30745 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 literal 0 HcmV?d00001 diff --git a/lib/Controller/DirectoryController.php b/lib/Controller/DirectoryController.php index 2ea14264..7104cbb7 100644 --- a/lib/Controller/DirectoryController.php +++ b/lib/Controller/DirectoryController.php @@ -106,9 +106,9 @@ public function show(string|int $id): JSONResponse } /** - * Get a specific publication type + * Get a specific publication type, used by external applications to synchronyse * - * @NoAdminRequired + * @PublicPage * @NoCSRFRequired * @param string|int $id The ID of the publication type to retrieve * @return JSONResponse The JSON response containing the publication type details @@ -124,4 +124,5 @@ public function publicationType(string|int $id): JSONResponse return new JSONResponse(['error' => 'An error occurred while retrieving the publication type'], 500); } } + } diff --git a/lib/Controller/PublicationTypesController.php b/lib/Controller/PublicationTypesController.php index 2d65c987..593366a5 100644 --- a/lib/Controller/PublicationTypesController.php +++ b/lib/Controller/PublicationTypesController.php @@ -157,4 +157,27 @@ public function destroy(string|int $id): JSONResponse // Return the result as a JSON response return new JSONResponse(['success' => $result], $result === true ? '200' : '404'); } + + /** + * Copy or update a publication type from an external directory + * + * @PublicPage + * @NoCSRFRequired + * @return JSONResponse The JSON response containing the copied publication type or error message + */ + public function synchronise(): JSONResponse + { + $url = $this->request->getParam('publicationType'); + + if (empty($url)) { + return new JSONResponse(['error' => 'publicationType parameter is required'], 400); + } + + try { + $copiedPublicationType = $this->directoryService->syncPublicationType($url); + return new JSONResponse($copiedPublicationType); + } catch (\Exception $e) { + return new JSONResponse(['error' => 'An error occurred while copying the publication type: ' . $e->getMessage()], 500); + } + } } diff --git a/lib/Service/DirectoryService.php b/lib/Service/DirectoryService.php index 14bcb9b5..89f4e45b 100644 --- a/lib/Service/DirectoryService.php +++ b/lib/Service/DirectoryService.php @@ -374,4 +374,60 @@ public function synchronise(?string $id = null): array return $object; } + + /** + * Copy or update a publication type from an external URL + * + * @param string $url The URL of the publication type to copy or update + * @return array The copied or updated publication type + * @throws GuzzleException + * @throws DoesNotExistException + * @throws MultipleObjectsReturnedException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws \InvalidArgumentException If the URL is invalid + */ + public function syncPublicationType(string $url): array + { + // Validate the URL + if (!filter_var($url, FILTER_VALIDATE_URL)) { + throw new \InvalidArgumentException('Invalid URL provided'); + } + + // Fetch the publication type data from the external URL + try { + $response = $this->client->get($url); + } catch (GuzzleException $e) { + throw new \InvalidArgumentException('Unable to fetch data from the provided URL: ' . $e->getMessage()); + } + + $publicationType = json_decode($response->getBody()->getContents(), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \InvalidArgumentException('Invalid JSON data received from the URL'); + } + + // Check if a publication type with the same name already exists + $existingPublicationType = $this->objectService->getObjects( + objectType: 'publicationType', + limit: 1, + filters: [ + ['source' => $url] + ] + ); + + // Prevent against malicious input + unset($publicationType['id']); + unset($publicationType['uuid']); + + if (!empty($existingPublicationType)) { + // Update the existing publication type + $updatedPublicationType = $this->objectService->updateObject('publicationType', $existingPublicationType[0]['id'], $publicationType); + return $updatedPublicationType->jsonSerialize(); + } else { + // Save the new publication type + $newPublicationType = $this->objectService->saveObject('publicationType', $publicationType); + return $newPublicationType->jsonSerialize(); + } + } } From b46e6a80d24e0a34eee3f7330f27cc43ac345bec Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 20 Oct 2024 08:47:51 +0000 Subject: [PATCH 07/17] Bump version to 0.6.24 --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 4edc332a..bdd76222 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,7 +22,7 @@ Create a [bug report](https://github.com/OpenCatalogi/.github/issues/new/choose) Create a [feature request](https://github.com/OpenCatalogi/.github/issues/new/choose) ]]> - 0.6.23 + 0.6.24 agpl Conduction Acato From ca907082d8fd4d418d3c616d3dde99d597681687 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 20 Oct 2024 10:56:06 +0200 Subject: [PATCH 08/17] small route fixes --- appinfo/routes.php | 4 ++-- lib/Service/DirectoryService.php | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 862602a1..a03872fb 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -16,9 +16,9 @@ ['name' => 'directory#index', 'url' => '/api/directory', 'verb' => 'GET'], ['name' => 'directory#show', 'url' => '/api/directory/{id}', 'verb' => 'GET', 'requirements' => ['path' => '.+']], ['name' => 'directory#update', 'url' => '/api/directory', 'verb' => 'POST'], - ['name' => 'directory#syncPublicationType', 'url' => '/api/directory/copy_pubpication_type', 'verb' => 'POST'], // Should be in directory becouse its public + ['name' => 'directory#publicationType', 'url' => '/api/directory/publication_types/{id}', 'verb' => 'GET'], // Should be in directory becouse its public // Publication - ['name' => 'synchronise#synchronise', 'url' => '/api/publication_types/synchronise/{id}', 'verb' => 'POST'], + ['name' => 'synchronise#synchronise', 'url' => '/api/publication_types/synchronise', 'verb' => 'POST'], // Dashboard ['name' => 'dashboard#index', 'url' => '/index', 'verb' => 'GET'], ['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'], diff --git a/lib/Service/DirectoryService.php b/lib/Service/DirectoryService.php index 89f4e45b..46b2265d 100644 --- a/lib/Service/DirectoryService.php +++ b/lib/Service/DirectoryService.php @@ -406,6 +406,9 @@ public function syncPublicationType(string $url): array if (json_last_error() !== JSON_ERROR_NONE) { throw new \InvalidArgumentException('Invalid JSON data received from the URL'); } + + // Set the source to the URL + $publicationType['source'] = $url; // Check if a publication type with the same name already exists $existingPublicationType = $this->objectService->getObjects( From 364e3ddf93565ca974bf633b5ec58c984620812c Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 20 Oct 2024 11:58:21 +0200 Subject: [PATCH 09/17] Added ownership and listed on the directory --- lib/Service/DirectoryService.php | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/Service/DirectoryService.php b/lib/Service/DirectoryService.php index 46b2265d..228de0cf 100644 --- a/lib/Service/DirectoryService.php +++ b/lib/Service/DirectoryService.php @@ -124,6 +124,18 @@ private function getDirectoryFromListing(Listing|array $listing): array // $listing['uuid'], //@todo this breaks stuff when trying to find and update a listing $listing['hash']); + // Process publication types + if (isset($listing['publicationTypes']) && is_array($listing['publicationTypes'])) { + foreach ($listing['publicationTypes'] as &$publicationType) { + // Convert publicationType to array if it's an object + if ($publicationType instanceof \JsonSerializable) { + $publicationType = $publicationType->jsonSerialize(); + } + + + } + } + // TODO: This should be mapped to the stoplight documentation return $listing; } @@ -158,6 +170,21 @@ private function getDirectoryFromCatalog(Catalog|array $catalog): array $catalog['search'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("opencatalogi.search.index")); $catalog['directory'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("opencatalogi.directory.index")); + // Process publication types + if (isset($catalog['publicationTypes']) && is_array($catalog['publicationTypes'])) { + foreach ($catalog['publicationTypes'] as &$publicationType) { + // Convert publicationType to array if it's an object + if ($publicationType instanceof \JsonSerializable) { + $publicationType = $publicationType->jsonSerialize(); + } + $publicationType['listed'] = true; + $publicationType['owner'] = true; + if (!isset($publicationType['source']) || empty($publicationType['source'])) { + $publicationType['source'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("opencatalogi.directory.publicationType", ['id' => $publicationType['id']])); + } + } + } + // TODO: This should be mapped to the stoplight documentation return $catalog; } @@ -200,7 +227,7 @@ public function getDirectories(): array // TODO: Define when a listed item should not be shown (e.g. when secret or trusted is true), this is a product decision // Get all the catalogi - $catalogi = $this->objectService->getObjects(objectType: 'catalog', extend: ['publicationTypes','organization']); + $catalogi = $this->objectService->getObjects(objectType: 'catalog', extend: ['publicationTypes', 'organization']); $catalogi = array_map([$this, 'getDirectoryFromCatalog'], $catalogi); // Filter out the catalogi that are not listed From 002d611cb99fa7548118a86ded424a7b7a166341 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 20 Oct 2024 09:59:28 +0000 Subject: [PATCH 10/17] Bump version to 0.6.25 --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index bdd76222..ce908e73 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,7 +22,7 @@ Create a [bug report](https://github.com/OpenCatalogi/.github/issues/new/choose) Create a [feature request](https://github.com/OpenCatalogi/.github/issues/new/choose) ]]> - 0.6.24 + 0.6.25 agpl Conduction Acato From e276a0fc4b19e2babee2fe333e856eb860968ac8 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 20 Oct 2024 15:38:00 +0200 Subject: [PATCH 11/17] Give proper feedback on publicationType usage --- lib/Controller/DirectoryController.php | 2 +- lib/Service/DirectoryService.php | 38 ++++++++++++++++++++- src/sidebars/directory/DirectorySideBar.vue | 20 +++++++---- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/lib/Controller/DirectoryController.php b/lib/Controller/DirectoryController.php index 7104cbb7..a9a003fc 100644 --- a/lib/Controller/DirectoryController.php +++ b/lib/Controller/DirectoryController.php @@ -116,7 +116,7 @@ public function show(string|int $id): JSONResponse public function publicationType(string|int $id): JSONResponse { try { - $publicationType = $this->objectService->get('publicationType', $id); + $publicationType = $this->objectService->getObject('publicationType', $id); return new JSONResponse($publicationType); } catch (DoesNotExistException $e) { return new JSONResponse(['error' => 'Publication type not found'], 404); diff --git a/lib/Service/DirectoryService.php b/lib/Service/DirectoryService.php index 228de0cf..b3ccbc44 100644 --- a/lib/Service/DirectoryService.php +++ b/lib/Service/DirectoryService.php @@ -28,6 +28,9 @@ class DirectoryService /** @var Client The HTTP client for making requests */ private Client $client; + /** @var array The list of external publication types that are used by this instance */ + private array $externalPublicationTypes = []; + /** * Constructor for DirectoryService * @@ -103,6 +106,22 @@ public function updateExternalDirectory(string $directoryUrl): int } } + /** + * Get the list of external publication types that are used by this instance + * + * @return array The list of external publication types + */ + private function getExternalPublicationTypes(): array + { + if (empty($this->externalPublicationTypes)) { + $result = $this->objectService->getObjects('publicationType'); + $this->externalPublicationTypes = array_filter($result, function($pt) { + return !empty($pt['source']); + }); + } + return $this->externalPublicationTypes; + } + /** * Convert a listing object or array to a directory array * @@ -131,8 +150,25 @@ private function getDirectoryFromListing(Listing|array $listing): array if ($publicationType instanceof \JsonSerializable) { $publicationType = $publicationType->jsonSerialize(); } + + // set listed and owner to false by default + $publicationType['listed'] = false; + $publicationType['owner'] = false; - + // check if this publication type is used by this instance + if (isset($publicationType['source'])) { + // Get all external publication types used by this instance + $externalPublicationTypes = $this->getExternalPublicationTypes(); + + // Filter external types to find matches with the current publication type + $matchingTypes = array_filter($externalPublicationTypes, function($externalType) use ($publicationType) { + // Check if the external type has a source and if it matches the current publication type's source + return isset($externalType['source']) && $externalType['source'] === $publicationType['source']; + }); + + // Set 'listed' to true if there are any matching types, false otherwise + $publicationType['listed'] = !empty($matchingTypes); + } } } diff --git a/src/sidebars/directory/DirectorySideBar.vue b/src/sidebars/directory/DirectorySideBar.vue index a54e1501..909128f4 100644 --- a/src/sidebars/directory/DirectorySideBar.vue +++ b/src/sidebars/directory/DirectorySideBar.vue @@ -106,12 +106,19 @@ import { navigationStore, directoryStore, publicationTypeStore } from '../../sto Welke publicatietype zou u uit deze catalogus willen overnemen?
- - {{ publicationType.title ?? publicationType.source ?? publicationType }} - +
@@ -128,6 +135,7 @@ import CogOutline from 'vue-material-design-icons/CogOutline.vue' import FileTreeOutline from 'vue-material-design-icons/FileTreeOutline.vue' import InformationSlabSymbol from 'vue-material-design-icons/InformationSlabSymbol.vue' import CertificateOutline from 'vue-material-design-icons/CertificateOutline.vue' +import Check from 'vue-material-design-icons/Check.vue' export default { name: 'DirectorySideBar', From 744f274fb6eb4ec336e6363075d9d1a0c02010da Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 20 Oct 2024 17:07:00 +0200 Subject: [PATCH 12/17] Final tests on the add external publicationtype workflow --- appinfo/routes.php | 2 +- lib/Controller/PublicationTypesController.php | 58 ++++++++++++++++--- src/sidebars/directory/DirectorySideBar.vue | 31 +++++++++- 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index a03872fb..427b3af4 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -18,7 +18,7 @@ ['name' => 'directory#update', 'url' => '/api/directory', 'verb' => 'POST'], ['name' => 'directory#publicationType', 'url' => '/api/directory/publication_types/{id}', 'verb' => 'GET'], // Should be in directory becouse its public // Publication - ['name' => 'synchronise#synchronise', 'url' => '/api/publication_types/synchronise', 'verb' => 'POST'], + ['name' => 'publication_types#synchronise', 'url' => '/api/publication_types/synchronise', 'verb' => 'POST'], // Dashboard ['name' => 'dashboard#index', 'url' => '/index', 'verb' => 'GET'], ['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'], diff --git a/lib/Controller/PublicationTypesController.php b/lib/Controller/PublicationTypesController.php index 593366a5..10eff662 100644 --- a/lib/Controller/PublicationTypesController.php +++ b/lib/Controller/PublicationTypesController.php @@ -4,6 +4,7 @@ use OCA\OpenCatalogi\Db\PublicationTypeMapper; use OCA\OpenCatalogi\Service\ObjectService; +use OCA\OpenCatalogi\Service\DirectoryService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\JSONResponse; @@ -25,13 +26,15 @@ class PublicationTypesController extends Controller * @param IAppConfig $config The app configuration * @param PublicationTypeMapper $publicationTypeMapper The publication type mapper * @param ObjectService $objectService The object service + * @param DirectoryService $directoryService The directory service */ public function __construct( $appName, IRequest $request, private readonly IAppConfig $config, private readonly PublicationTypeMapper $publicationTypeMapper, - private readonly ObjectService $objectService + private readonly ObjectService $objectService, + private readonly DirectoryService $directoryService ) { parent::__construct($appName, $request); @@ -159,25 +162,62 @@ public function destroy(string|int $id): JSONResponse } /** - * Copy or update a publication type from an external directory + * Synchronize or delete a publication type based on listing status * * @PublicPage * @NoCSRFRequired - * @return JSONResponse The JSON response containing the copied publication type or error message + * @return JSONResponse The JSON response containing the result of the operation */ public function synchronise(): JSONResponse { - $url = $this->request->getParam('publicationType'); + // Get the source and listed parameters from the request + $source = $this->request->getParam('source'); + $listed = $this->request->getParam('listed', false); - if (empty($url)) { - return new JSONResponse(['error' => 'publicationType parameter is required'], 400); + // Check if the source parameter is provided + if (empty($source)) { + return new JSONResponse(['error' => 'source parameter is required'], 400); } try { - $copiedPublicationType = $this->directoryService->syncPublicationType($url); - return new JSONResponse($copiedPublicationType); + if ($listed) { + // If listed is true, synchronize the publication type + $syncedPublicationType = $this->directoryService->syncPublicationType($source); + return new JSONResponse($syncedPublicationType); + } else { + // If listed is false, attempt to delete the publication type + // @todo: we cant get a single object by parameters yet but we can use the find method and grab the first array result + // Check if a publication type with the same name already exists + $publicationTypes = $this->objectService->getObjects( + objectType: 'publicationType', + filters: [ + ['source' => $source] + ] + ); + + // Check the number of publication types found + if (count($publicationTypes) === 1) { + $publicationType = $publicationTypes[0]; + } elseif (count($publicationTypes) > 1) { + // If multiple publication types are found, return an error + return new JSONResponse(['error' => 'Multiple publication types found for the given source'], 409); + } else { + // If no publication types are found, return an error + return new JSONResponse(['error' => 'Publication type not found'], 404); + } + + // If a publication type is found, attempt to delete it + if ($publicationType) { + $result = $this->objectService->deleteObject('publicationType', $publicationType['id']); + return new JSONResponse(['success' => $result], $result === true ? 200 : 404); + } + + // If no publication type is found (this should not be reached due to earlier check) + return new JSONResponse(['message' => 'Publication type not found'], 404); + } } catch (\Exception $e) { - return new JSONResponse(['error' => 'An error occurred while copying the publication type: ' . $e->getMessage()], 500); + // If an exception occurs, return an error response + return new JSONResponse(['error' => 'An error occurred: ' . $e->getMessage()], 500); } } } diff --git a/src/sidebars/directory/DirectorySideBar.vue b/src/sidebars/directory/DirectorySideBar.vue index 909128f4..55cdb449 100644 --- a/src/sidebars/directory/DirectorySideBar.vue +++ b/src/sidebars/directory/DirectorySideBar.vue @@ -115,7 +115,8 @@ import { navigationStore, directoryStore, publicationTypeStore } from '../../sto v-else :key="`${publicationType}${i}`" :checked="publicationType.listed" - type="switch"> + type="switch" + @update:checked="togglePublicationType(publicationType)"> {{ publicationType.title ?? publicationType.source ?? publicationType }} @@ -154,6 +155,7 @@ export default { listing: '', loading: false, syncLoading: false, + publicationTypeLoading: false, } }, computed: { @@ -341,6 +343,33 @@ export default { this.syncLoading = false }) }, + togglePublicationType(publicationType) { + publicationType.listed = !publicationType.listed + this.synchronizePublicationType(publicationType) + }, + synchronizePublicationType(publicationType) { + this.publicationTypeLoading = true + fetch( + `/index.php/apps/opencatalogi/api/publication_types/synchronise`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + listed: publicationType.listed, + source: publicationType.source + }), + }, + ) + .then(() => { + this.publicationTypeLoading = false + }) + .catch((err) => { + this.error = err + this.publicationTypeLoading = false + }) + }, }, } From ae7ad1378bc3bfdaeba9a0629120be3da8b5adf1 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 20 Oct 2024 15:08:17 +0000 Subject: [PATCH 13/17] Bump version to 0.6.26 --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index ce908e73..8f5e68ce 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,7 +22,7 @@ Create a [bug report](https://github.com/OpenCatalogi/.github/issues/new/choose) Create a [feature request](https://github.com/OpenCatalogi/.github/issues/new/choose) ]]> - 0.6.25 + 0.6.26 agpl Conduction Acato From e1863a26d983dae43166e9a74fe52ed9a1a2093a Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 20 Oct 2024 18:00:51 +0200 Subject: [PATCH 14/17] First fixes on the synchronysation --- lib/Controller/PublicationTypesController.php | 30 +++++++++++++++---- lib/Service/DirectoryService.php | 24 +++++++++++---- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/lib/Controller/PublicationTypesController.php b/lib/Controller/PublicationTypesController.php index 10eff662..e720ecb5 100644 --- a/lib/Controller/PublicationTypesController.php +++ b/lib/Controller/PublicationTypesController.php @@ -188,19 +188,39 @@ public function synchronise(): JSONResponse // If listed is false, attempt to delete the publication type // @todo: we cant get a single object by parameters yet but we can use the find method and grab the first array result // Check if a publication type with the same name already exists - $publicationTypes = $this->objectService->getObjects( + //$publicationTypes = $this->objectService->getObjects( + // objectType: 'publicationType', + // filters: [ + // ['source' => $source] + // ] + //); + + // Todo: we need to get the object by the source parameter, but that filter returns an empty array so know we get all objects and then filter them here and then array filter them. PRIORITY: LOW + + // Get all publication types + $allPublicationTypes = $this->objectService->getObjects( objectType: 'publicationType', - filters: [ - ['source' => $source] - ] ); + + // Filter publication types to only include those with a matching source + $publicationTypes = array_filter($allPublicationTypes, function($publicationType) use ($source) { + // Check if the publication type has a 'source' property and if it matches the given source + return isset($publicationType['source']) && $publicationType['source'] === $source; + }); + + //var_dump($publicationTypes); + //var_dump($publicationTypes); // Check the number of publication types found if (count($publicationTypes) === 1) { $publicationType = $publicationTypes[0]; } elseif (count($publicationTypes) > 1) { // If multiple publication types are found, return an error - return new JSONResponse(['error' => 'Multiple publication types found for the given source'], 409); + //return new JSONResponse(['error' => 'Multiple publication types found for the given source'], 409); + // TODO: Discus if we want this? + foreach ($publicationTypes as $publicationType) { + $this->objectService->deleteObject('publicationType', $publicationType['id']); + } } else { // If no publication types are found, return an error return new JSONResponse(['error' => 'Publication type not found'], 404); diff --git a/lib/Service/DirectoryService.php b/lib/Service/DirectoryService.php index b3ccbc44..5bff3f5c 100644 --- a/lib/Service/DirectoryService.php +++ b/lib/Service/DirectoryService.php @@ -474,6 +474,7 @@ public function syncPublicationType(string $url): array $publicationType['source'] = $url; // Check if a publication type with the same name already exists + /* $existingPublicationType = $this->objectService->getObjects( objectType: 'publicationType', limit: 1, @@ -481,16 +482,29 @@ public function syncPublicationType(string $url): array ['source' => $url] ] ); + */ - // Prevent against malicious input - unset($publicationType['id']); - unset($publicationType['uuid']); + // TODO: THis is a hacky workaround for failing filters: PRIORITY: High + $existingPublicationTypes = $this->objectService->getObjects( + objectType: 'publicationType', + ); + // Filter publication types to only include those with a matching source + $existingPublicationTypes = array_filter($existingPublicationTypes, function($publicationType) use ($source) { + // Check if the publication type has a 'source' property and if it matches the given source + return isset($publicationType['source']) && $publicationType['source'] === $source; + }); - if (!empty($existingPublicationType)) { + if (!empty($existingPublicationTypes)) { + // Prevent against malicious input + unset($publicationType[0]['id']); + unset($publicationType[0]['uuid']); // Update the existing publication type - $updatedPublicationType = $this->objectService->updateObject('publicationType', $existingPublicationType[0]['id'], $publicationType); + $updatedPublicationType = $this->objectService->updateObject('publicationType', $existingPublicationTypes[0]['id'], $publicationType); return $updatedPublicationType->jsonSerialize(); } else { + //// Prevent against malicious input + unset($publicationType[0]['id']); + unset($publicationType[0]['uuid']); // Save the new publication type $newPublicationType = $this->objectService->saveObject('publicationType', $publicationType); return $newPublicationType->jsonSerialize(); From 1d76c555acb480d33ef2b5900e1468d16a9c20a3 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 20 Oct 2024 18:29:06 +0200 Subject: [PATCH 15/17] Final fixes on sync for the publication types --- lib/Controller/PublicationTypesController.php | 61 ++++++------------- lib/Service/DirectoryService.php | 23 ++++--- 2 files changed, 31 insertions(+), 53 deletions(-) diff --git a/lib/Controller/PublicationTypesController.php b/lib/Controller/PublicationTypesController.php index e720ecb5..e63484f5 100644 --- a/lib/Controller/PublicationTypesController.php +++ b/lib/Controller/PublicationTypesController.php @@ -186,54 +186,29 @@ public function synchronise(): JSONResponse return new JSONResponse($syncedPublicationType); } else { // If listed is false, attempt to delete the publication type - // @todo: we cant get a single object by parameters yet but we can use the find method and grab the first array result - // Check if a publication type with the same name already exists - //$publicationTypes = $this->objectService->getObjects( - // objectType: 'publicationType', - // filters: [ - // ['source' => $source] - // ] - //); - - // Todo: we need to get the object by the source parameter, but that filter returns an empty array so know we get all objects and then filter them here and then array filter them. PRIORITY: LOW - - // Get all publication types - $allPublicationTypes = $this->objectService->getObjects( - objectType: 'publicationType', - ); - - // Filter publication types to only include those with a matching source - $publicationTypes = array_filter($allPublicationTypes, function($publicationType) use ($source) { - // Check if the publication type has a 'source' property and if it matches the given source - return isset($publicationType['source']) && $publicationType['source'] === $source; - }); - - //var_dump($publicationTypes); - //var_dump($publicationTypes); - + // Get all publication types + $allPublicationTypes = $this->objectService->getObjects( + objectType: 'publicationType', + ); + + // Filter publication types to only include those with a matching source + $publicationTypes = array_filter($allPublicationTypes, function($publicationType) use ($source) { + // Check if the publication type has a 'source' property and if it matches the given source + return isset($publicationType['source']) && $publicationType['source'] === $source; + }); + // Check the number of publication types found - if (count($publicationTypes) === 1) { - $publicationType = $publicationTypes[0]; - } elseif (count($publicationTypes) > 1) { - // If multiple publication types are found, return an error - //return new JSONResponse(['error' => 'Multiple publication types found for the given source'], 409); - // TODO: Discus if we want this? - foreach ($publicationTypes as $publicationType) { - $this->objectService->deleteObject('publicationType', $publicationType['id']); - } + if (!empty($publicationTypes)) { + $result = true; + foreach ($publicationTypes as $publicationType) { + $deleteResult = $this->objectService->deleteObject('publicationType', $publicationType['id']); + $result = $result && $deleteResult; + } + return new JSONResponse(['success' => $result], $result ? 200 : 500); } else { // If no publication types are found, return an error return new JSONResponse(['error' => 'Publication type not found'], 404); } - - // If a publication type is found, attempt to delete it - if ($publicationType) { - $result = $this->objectService->deleteObject('publicationType', $publicationType['id']); - return new JSONResponse(['success' => $result], $result === true ? 200 : 404); - } - - // If no publication type is found (this should not be reached due to earlier check) - return new JSONResponse(['message' => 'Publication type not found'], 404); } } catch (\Exception $e) { // If an exception occurs, return an error response diff --git a/lib/Service/DirectoryService.php b/lib/Service/DirectoryService.php index 5bff3f5c..7e846f89 100644 --- a/lib/Service/DirectoryService.php +++ b/lib/Service/DirectoryService.php @@ -472,6 +472,10 @@ public function syncPublicationType(string $url): array // Set the source to the URL $publicationType['source'] = $url; + + // Prevent against malicious input + unset($publicationType['id']); + unset($publicationType['uuid']); // Check if a publication type with the same name already exists /* @@ -494,20 +498,19 @@ public function syncPublicationType(string $url): array return isset($publicationType['source']) && $publicationType['source'] === $source; }); + if (!empty($existingPublicationTypes)) { - // Prevent against malicious input - unset($publicationType[0]['id']); - unset($publicationType[0]['uuid']); - // Update the existing publication type - $updatedPublicationType = $this->objectService->updateObject('publicationType', $existingPublicationTypes[0]['id'], $publicationType); - return $updatedPublicationType->jsonSerialize(); + // Update existing publication types + $updatedPublicationTypes = []; + foreach ($existingPublicationTypes as $existingType) { + $updatedType = $this->objectService->updateObject('publicationType', $existingType['id'], $publicationType); + $updatedPublicationTypes[] = $updatedType->jsonSerialize(); + } + return $updatedPublicationTypes; } else { - //// Prevent against malicious input - unset($publicationType[0]['id']); - unset($publicationType[0]['uuid']); // Save the new publication type $newPublicationType = $this->objectService->saveObject('publicationType', $publicationType); - return $newPublicationType->jsonSerialize(); + return [$newPublicationType->jsonSerialize()]; } } } From 5183d0da9fdd702b54dd400bf0ea3cf9f0335140 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 21 Oct 2024 07:20:00 +0200 Subject: [PATCH 16/17] Fix updating directories --- lib/Service/DirectoryService.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Service/DirectoryService.php b/lib/Service/DirectoryService.php index 7e846f89..0e54dbaa 100644 --- a/lib/Service/DirectoryService.php +++ b/lib/Service/DirectoryService.php @@ -234,7 +234,7 @@ private function getDirectoryFromCatalog(Catalog|array $catalog): array private function getListings(): array { // Get all the listings - $listings = $this->objectService->getObjects(objectType: 'listing', extend: ['publicationTypes','organization']); + $listings = $this->objectService->getObjects(objectType: 'listing'); $listings = array_map([$this, 'getDirectoryFromListing'], $listings); // TODO: Define when a listed item should not be shown (e.g. when secret or trusted is true), this is a product decision @@ -472,7 +472,7 @@ public function syncPublicationType(string $url): array // Set the source to the URL $publicationType['source'] = $url; - + // Prevent against malicious input unset($publicationType['id']); unset($publicationType['uuid']); From 95a68df4dc99c8af18567d952bc7b976af81b411 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 21 Oct 2024 05:49:08 +0000 Subject: [PATCH 17/17] Bump version to 0.6.27 --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 8f5e68ce..4ce65e42 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,7 +22,7 @@ Create a [bug report](https://github.com/OpenCatalogi/.github/issues/new/choose) Create a [feature request](https://github.com/OpenCatalogi/.github/issues/new/choose) ]]> - 0.6.26 + 0.6.27 agpl Conduction Acato