From 81ede67b21c79d65cef25520cdbb1b79a1de1fd5 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 4 Sep 2025 07:20:20 +0200 Subject: [PATCH 1/5] Drop required fields --- lib/Settings/softwarecatalogus_register.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Settings/softwarecatalogus_register.json b/lib/Settings/softwarecatalogus_register.json index 337f4318..10f7bac2 100644 --- a/lib/Settings/softwarecatalogus_register.json +++ b/lib/Settings/softwarecatalogus_register.json @@ -1042,7 +1042,7 @@ "version": "0.0.22", "summary": "", "icon": "AccountMultiple", - "required": ["organisatie", "e-mailadres"], + "required": ["e-mailadres"], "properties": { "voornaam": { "type": "string", @@ -1111,7 +1111,6 @@ "handling": "related-object" }, "$ref": "#/components/schemas/organisatie", - "required": true, "order": 9 }, "username": { From 2bf1014d7d5067ac315a9ed2ea89469f5b43b3b7 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 4 Sep 2025 12:15:51 +0200 Subject: [PATCH 2/5] Voorereiding op gebruik accepteren --- .../AangebodenGebruikController.php | 502 ++++++++++++++++++ lib/Service/AangebodenGebruikService.php | 485 +++++++++++++++++ 2 files changed, 987 insertions(+) create mode 100644 lib/Controller/AangebodenGebruikController.php create mode 100644 lib/Service/AangebodenGebruikService.php diff --git a/lib/Controller/AangebodenGebruikController.php b/lib/Controller/AangebodenGebruikController.php new file mode 100644 index 00000000..5f612a05 --- /dev/null +++ b/lib/Controller/AangebodenGebruikController.php @@ -0,0 +1,502 @@ + + * @copyright 2024 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @version 1.0.0 + * @link https://github.com/ConductionNL/SoftwareCatalog + */ + +namespace OCA\SoftwareCatalog\Controller; + +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\JSONResponse; +use OCP\IRequest; +use OCA\SoftwareCatalog\Service\AangebodenGebruikService; +use Psr\Log\LoggerInterface; + +/** + * Controller for handling offered usage (aangeboden gebruik) API operations + * + * This controller provides REST API endpoints for managing gebruiks objects where + * the active organization is involved either as afnemer (consumer) or in deelnemers + * (participants), and for updating the @self property of gebruiks objects. + * + * @category Controller + * @package OCA\SoftwareCatalog\Controller + * @author Conduction b.v. + * @copyright 2024 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @version 1.0.0 + * @link https://github.com/ConductionNL/SoftwareCatalog + */ +class AangebodenGebruikController extends Controller +{ + /** + * Constructor for AangebodenGebruikController + * + * @param string $appName The name of the app + * @param IRequest $request The HTTP request object + * @param AangebodenGebruikService $aangebodenGebruikService The business logic service + * @param LoggerInterface $logger The logger service for debugging and error reporting + */ + public function __construct( + string $appName, + IRequest $request, + private readonly AangebodenGebruikService $aangebodenGebruikService, + private readonly LoggerInterface $logger + ) { + parent::__construct($appName, $request); + } + + /** + * Get all gebruiks objects where the active organization is the afnemer (consumer) + * + * API Endpoint: GET /api/aangeboden-gebruik/afnemer + * + * Query Parameters: + * - limit (int): Maximum number of results to return + * - offset (int): Number of results to skip for pagination + * - status (string): Filter by usage status + * - product (string): Filter by product ID + * - startDate (string): Filter by start date (ISO 8601 format) + * - endDate (string): Filter by end date (ISO 8601 format) + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + * + * @return JSONResponse JSON response with gebruiks array where org is afnemer + */ + public function getGebruiksWhereAfnemer(): JSONResponse + { + $this->logger->info('API: Getting gebruiks where active org is afnemer', [ + 'endpoint' => '/api/aangeboden-gebruik/afnemer', + 'method' => 'GET', + 'query_params' => $this->request->getParams() + ]); + + try { + // Parse query parameters for filtering options + $options = $this->parseQueryOptions(); + + // Get gebruiks from service where org is afnemer + $result = $this->aangebodenGebruikService->getGebruiksWhereAfnemer($options); + + // Determine appropriate HTTP status code + $statusCode = $result['success'] ? 200 : 500; + + $this->logger->info('API: Afnemer gebruiks request completed', [ + 'success' => $result['success'], + 'gebruiks_count' => $result['count'] ?? 0, + 'organisation' => $result['organisation'] ?? 'unknown' + ]); + + return new JSONResponse($result, $statusCode); + + } catch (\Exception $e) { + $this->logger->error('API: Failed to get afnemer gebruiks', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return new JSONResponse([ + 'success' => false, + 'error' => 'Internal server error: ' . $e->getMessage(), + 'gebruiks' => [], + 'count' => 0 + ], 500); + } + } + + /** + * Get all gebruiks objects where the active organization is in deelnemers (participants) + * + * API Endpoint: GET /api/aangeboden-gebruik/deelnemers + * + * Query Parameters: + * - limit (int): Maximum number of results to return + * - offset (int): Number of results to skip for pagination + * - status (string): Filter by usage status + * - product (string): Filter by product ID + * - startDate (string): Filter by start date (ISO 8601 format) + * - endDate (string): Filter by end date (ISO 8601 format) + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + * + * @return JSONResponse JSON response with gebruiks array where org is in deelnemers + */ + public function getGebruiksWhereDeelnemers(): JSONResponse + { + $this->logger->info('API: Getting gebruiks where active org is in deelnemers', [ + 'endpoint' => '/api/aangeboden-gebruik/deelnemers', + 'method' => 'GET', + 'query_params' => $this->request->getParams() + ]); + + try { + // Parse query parameters for filtering options + $options = $this->parseQueryOptions(); + + // Get gebruiks from service where org is in deelnemers + $result = $this->aangebodenGebruikService->getGebruiksWhereDeelnemers($options); + + // Determine appropriate HTTP status code + $statusCode = $result['success'] ? 200 : 500; + + $this->logger->info('API: Deelnemers gebruiks request completed', [ + 'success' => $result['success'], + 'gebruiks_count' => $result['count'] ?? 0, + 'organisation' => $result['organisation'] ?? 'unknown' + ]); + + return new JSONResponse($result, $statusCode); + + } catch (\Exception $e) { + $this->logger->error('API: Failed to get deelnemers gebruiks', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return new JSONResponse([ + 'success' => false, + 'error' => 'Internal server error: ' . $e->getMessage(), + 'gebruiks' => [], + 'count' => 0 + ], 500); + } + } + + /** + * Set the @self property of a gebruik to the active organization + * + * API Endpoint: PUT /api/aangeboden-gebruik/{gebruikId}/set-self + * + * This endpoint allows setting the @self.organisation property of a specific gebruik + * object to the active organization, but only if the active organization is the + * afnemer (consumer) for that gebruik. + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + * + * @param string $gebruikId The UUID of the gebruik object to update + * @return JSONResponse JSON response with success status and updated object + */ + public function setGebruikSelfToActiveOrg(string $gebruikId): JSONResponse + { + $this->logger->info('API: Setting gebruik @self property to active org', [ + 'endpoint' => "/api/aangeboden-gebruik/{$gebruikId}/set-self", + 'method' => 'PUT', + 'gebruik_id' => $gebruikId + ]); + + try { + // Validate input + if (empty($gebruikId)) { + return new JSONResponse([ + 'success' => false, + 'error' => 'Gebruik ID is required', + 'gebruik' => null + ], 400); + } + + // Parse any additional options from request body + $options = []; + $requestBody = $this->request->getParams(); + if (!empty($requestBody)) { + $options = array_filter($requestBody, function($key) { + return !in_array($key, ['gebruikId']); // Exclude path parameters + }, ARRAY_FILTER_USE_KEY); + } + + // Update gebruik @self property via service + $result = $this->aangebodenGebruikService->setGebruikSelfToActiveOrg($gebruikId, $options); + + // Determine appropriate HTTP status code + $statusCode = $result['success'] ? 200 : ($result['error'] === 'Gebruik object not found' ? 404 : + ($result['error'] === 'Operation not allowed: active organization is not the afnemer' ? 403 : 500)); + + $this->logger->info('API: Set gebruik @self property request completed', [ + 'gebruik_id' => $gebruikId, + 'success' => $result['success'], + 'status_code' => $statusCode + ]); + + return new JSONResponse($result, $statusCode); + + } catch (\Exception $e) { + $this->logger->error('API: Failed to set gebruik @self property', [ + 'gebruik_id' => $gebruikId, + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return new JSONResponse([ + 'success' => false, + 'error' => 'Internal server error: ' . $e->getMessage(), + 'gebruik' => null + ], 500); + } + } + + /** + * Get API documentation for AangebodenGebruik endpoints + * + * API Endpoint: GET /api/aangeboden-gebruik/docs + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + * + * @return JSONResponse JSON response with API documentation + */ + public function getApiDocumentation(): JSONResponse + { + $documentation = [ + 'api_version' => '1.0.0', + 'description' => 'SoftwareCatalog AangebodenGebruik API - Manage gebruiks objects where active organization is involved', + 'base_url' => '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/api/aangeboden-gebruik', + 'endpoints' => [ + [ + 'method' => 'GET', + 'path' => '/api/aangeboden-gebruik/afnemer', + 'description' => 'Get all gebruiks objects where the active organization is the afnemer (consumer)', + 'parameters' => [ + [ + 'name' => 'limit', + 'type' => 'integer', + 'required' => false, + 'description' => 'Maximum number of results to return' + ], + [ + 'name' => 'offset', + 'type' => 'integer', + 'required' => false, + 'description' => 'Number of results to skip for pagination' + ], + [ + 'name' => 'status', + 'type' => 'string', + 'required' => false, + 'description' => 'Filter by usage status' + ], + [ + 'name' => 'product', + 'type' => 'string', + 'required' => false, + 'description' => 'Filter by product ID' + ], + [ + 'name' => 'startDate', + 'type' => 'string', + 'required' => false, + 'description' => 'Filter by start date (ISO 8601 format)' + ], + [ + 'name' => 'endDate', + 'type' => 'string', + 'required' => false, + 'description' => 'Filter by end date (ISO 8601 format)' + ] + ], + 'response_example' => [ + 'success' => true, + 'gebruiks' => [ + [ + 'id' => 'usage-uuid-123', + 'afnemer' => 'org-uuid', + 'product' => 'product-uuid', + 'status' => 'actief', + '_filter_type' => 'afnemer', + '_schema_id' => 'schema-id' + ] + ], + 'count' => 1, + 'filter_type' => 'afnemer', + 'organisation' => 'org-uuid' + ] + ], + [ + 'method' => 'GET', + 'path' => '/api/aangeboden-gebruik/deelnemers', + 'description' => 'Get all gebruiks objects where the active organization is in deelnemers (participants)', + 'parameters' => [ + [ + 'name' => 'limit', + 'type' => 'integer', + 'required' => false, + 'description' => 'Maximum number of results to return' + ], + [ + 'name' => 'offset', + 'type' => 'integer', + 'required' => false, + 'description' => 'Number of results to skip for pagination' + ], + [ + 'name' => 'status', + 'type' => 'string', + 'required' => false, + 'description' => 'Filter by usage status' + ], + [ + 'name' => 'product', + 'type' => 'string', + 'required' => false, + 'description' => 'Filter by product ID' + ], + [ + 'name' => 'startDate', + 'type' => 'string', + 'required' => false, + 'description' => 'Filter by start date (ISO 8601 format)' + ], + [ + 'name' => 'endDate', + 'type' => 'string', + 'required' => false, + 'description' => 'Filter by end date (ISO 8601 format)' + ] + ], + 'response_example' => [ + 'success' => true, + 'gebruiks' => [ + [ + 'id' => 'usage-uuid-456', + 'afnemer' => 'other-org-uuid', + 'deelnemers' => ['org-uuid', 'another-org-uuid'], + 'product' => 'product-uuid', + 'status' => 'actief', + '_filter_type' => 'deelnemers', + '_schema_id' => 'schema-id' + ] + ], + 'count' => 1, + 'filter_type' => 'deelnemers', + 'organisation' => 'org-uuid' + ] + ], + [ + 'method' => 'PUT', + 'path' => '/api/aangeboden-gebruik/{gebruikId}/set-self', + 'description' => 'Set the @self property of a gebruik to the active organization (only allowed if active org is afnemer)', + 'parameters' => [ + [ + 'name' => 'gebruikId', + 'type' => 'string', + 'required' => true, + 'description' => 'The UUID of the gebruik object to update (in URL path)' + ] + ], + 'response_example' => [ + 'success' => true, + 'message' => 'Gebruik @self property updated successfully', + 'gebruik' => [ + 'id' => 'usage-uuid-123', + 'afnemer' => 'org-uuid', + '@self' => [ + 'organisation' => 'org-uuid', + 'register' => 'register-id', + 'schema' => 'schema-id' + ] + ], + 'updated_fields' => ['@self.organisation'] + ] + ], + [ + 'method' => 'GET', + 'path' => '/api/aangeboden-gebruik/docs', + 'description' => 'Get this API documentation', + 'parameters' => [], + 'response_example' => '(this response)' + ] + ], + 'security' => [ + 'afnemer_filtering' => 'Uses standard RBAC filtering based on organization association', + 'deelnemers_filtering' => 'Uses RBAC-disabled search to find participation records', + 'self_update_permission' => 'Only allowed if active organization is the afnemer for the specific gebruik' + ], + 'error_codes' => [ + 400 => 'Bad Request - Invalid parameters or missing required fields', + 403 => 'Forbidden - Operation not allowed (e.g., org is not afnemer for @self update)', + 404 => 'Not Found - Gebruik object not found', + 500 => 'Internal Server Error - Server-side error occurred' + ] + ]; + + return new JSONResponse($documentation, 200); + } + + /** + * Parse query parameters into options array + * + * This method extracts and validates query parameters for filtering, + * pagination, and other options. + * + * @return array Parsed options array + */ + private function parseQueryOptions(): array + { + $options = []; + + // Parse pagination parameters + $limit = $this->request->getParam('limit'); + if ($limit !== null && is_numeric($limit)) { + $options['limit'] = (int)$limit; + } + + $offset = $this->request->getParam('offset'); + if ($offset !== null && is_numeric($offset)) { + $options['offset'] = (int)$offset; + } + + // Parse filter parameters + $status = $this->request->getParam('status'); + if ($status !== null && !empty(trim($status))) { + $options['status'] = trim($status); + } + + $product = $this->request->getParam('product'); + if ($product !== null && !empty(trim($product))) { + $options['product'] = trim($product); + } + + $startDate = $this->request->getParam('startDate'); + if ($startDate !== null && !empty(trim($startDate))) { + $options['startDate'] = trim($startDate); + } + + $endDate = $this->request->getParam('endDate'); + if ($endDate !== null && !empty(trim($endDate))) { + $options['endDate'] = trim($endDate); + } + + $this->logger->debug('Parsed query options for AangebodenGebruik', [ + 'raw_params' => [ + 'limit' => $limit, + 'offset' => $offset, + 'status' => $status, + 'product' => $product, + 'startDate' => $startDate, + 'endDate' => $endDate + ], + 'parsed_options' => $options + ]); + + return $options; + } +} diff --git a/lib/Service/AangebodenGebruikService.php b/lib/Service/AangebodenGebruikService.php new file mode 100644 index 00000000..4ea51496 --- /dev/null +++ b/lib/Service/AangebodenGebruikService.php @@ -0,0 +1,485 @@ + + * @copyright 2024 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @version 1.0.0 + * @link https://github.com/ConductionNL/SoftwareCatalog + */ + +namespace OCA\SoftwareCatalog\Service; + +use OCA\OpenRegister\Service\ObjectService; +use OCP\App\IAppManager; +use OCP\IAppConfig; +use OCP\IUserSession; +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; +use Exception; + +/** + * Service for managing offered usage (aangeboden gebruik) operations + * + * This service provides operations for querying gebruiks objects where the active + * organization is involved either as the afnemer (consumer) or in the deelnemers + * (participants) array, and for updating the @self property of gebruiks objects. + * + * @category Service + * @package OCA\SoftwareCatalog\Service + * @author Conduction b.v. + * @copyright 2024 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * @version 1.0.0 + * @link https://github.com/ConductionNL/SoftwareCatalog + */ +class AangebodenGebruikService +{ + /** + * Constructor for AangebodenGebruikService + * + * @param IAppConfig $config Nextcloud app configuration service + * @param IAppManager $appManager App manager service for checking available apps + * @param ContainerInterface $container PSR-11 container interface for dependency injection + * @param LoggerInterface $logger Logger service for debugging and error reporting + * @param SettingsService $settingsService Settings service for retrieving configuration + * @param IUserSession $userSession User session service for current user context + */ + public function __construct( + private readonly IAppConfig $config, + private readonly IAppManager $appManager, + private readonly ContainerInterface $container, + private readonly LoggerInterface $logger, + private readonly SettingsService $settingsService, + private readonly IUserSession $userSession + ) { + } + + /** + * Get all gebruiks objects where the active organization is the afnemer (consumer) + * + * This method retrieves all gebruiks objects where the active organization + * appears as the afnemer using standard RBAC filtering. + * + * @param array $options Additional query options (limit, offset, filters, etc.) + * @return array Array with success status, gebruiks data, and metadata + * @throws Exception When OpenRegister service is not available + */ + public function getGebruiksWhereAfnemer(array $options = []): array + { + $this->logger->info('Getting gebruiks objects where active org is afnemer', [ + 'options' => $options + ]); + + try { + // Get ObjectService from OpenRegister + $objectService = $this->getObjectService(); + + // Get current organization + $currentOrg = $this->getCurrentOrganisation(); + if (!$currentOrg) { + $this->logger->warning('No current organization available for afnemer filtering'); + return [ + 'success' => true, + 'gebruiks' => [], + 'count' => 0, + 'message' => 'No current organization available' + ]; + } + + // Get configuration for gebruiks register/schema + $gebruiksConfig = $this->getGebruiksConfiguration(); + + $allGebruiks = []; + + // Search each configured schema for gebruiks where org is afnemer + foreach ($gebruiksConfig['schemas'] as $schemaId) { + if (!$schemaId) continue; + + try { + // Build query for afnemer filtering with RBAC enabled + $query = [ + '@self' => [ + 'register' => $gebruiksConfig['register_id'], + 'schema' => $schemaId, + 'organisation' => $currentOrg // Standard RBAC filtering + ] + ]; + + // Add additional filters from options + $query = $this->addQueryFilters($query, $options); + + // Execute search with RBAC enabled (default behavior) + $gebruikItems = $objectService->searchObjects($query); + + // Process and add to results + foreach ($gebruikItems as $gebruik) { + $gebruik['_filter_type'] = 'afnemer'; + $gebruik['_schema_id'] = $schemaId; + $allGebruiks[] = $gebruik; + } + + $this->logger->debug('Retrieved afnemer gebruiks from schema', [ + 'schema_id' => $schemaId, + 'count' => count($gebruikItems), + 'organisation' => $currentOrg + ]); + + } catch (Exception $e) { + $this->logger->warning('Failed to get afnemer gebruiks from schema', [ + 'schema_id' => $schemaId, + 'error' => $e->getMessage() + ]); + } + } + + return [ + 'success' => true, + 'gebruiks' => $allGebruiks, + 'count' => count($allGebruiks), + 'filter_type' => 'afnemer', + 'organisation' => $currentOrg + ]; + + } catch (Exception $e) { + $this->logger->error('Failed to get afnemer gebruiks', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'success' => false, + 'error' => 'Failed to retrieve gebruiks: ' . $e->getMessage(), + 'gebruiks' => [], + 'count' => 0 + ]; + } + } + + /** + * Get all gebruiks objects where the active organization is in deelnemers (participants) + * + * This method retrieves all gebruiks objects where the active organization + * appears in the deelnemers array, using RBAC-disabled search. + * + * @param array $options Additional query options (limit, offset, filters, etc.) + * @return array Array with success status, gebruiks data, and metadata + * @throws Exception When OpenRegister service is not available + */ + public function getGebruiksWhereDeelnemers(array $options = []): array + { + $this->logger->info('Getting gebruiks objects where active org is in deelnemers', [ + 'options' => $options + ]); + + try { + // Get ObjectService from OpenRegister + $objectService = $this->getObjectService(); + + // Get current organization + $currentOrg = $this->getCurrentOrganisation(); + if (!$currentOrg) { + $this->logger->warning('No current organization available for deelnemers filtering'); + return [ + 'success' => true, + 'gebruiks' => [], + 'count' => 0, + 'message' => 'No current organization available' + ]; + } + + // Get configuration for gebruiks register/schema + $gebruiksConfig = $this->getGebruiksConfiguration(); + + $allGebruiks = []; + + // Search each configured schema for gebruiks where org is in deelnemers + foreach ($gebruiksConfig['schemas'] as $schemaId) { + if (!$schemaId) continue; + + try { + // Build query for deelnemers filtering + $query = [ + '@self' => [ + 'register' => $gebruiksConfig['register_id'], + 'schema' => $schemaId + ], + 'deelnemers' => $currentOrg // Search where current org is in deelnemers + ]; + + // Add additional filters from options + $query = $this->addQueryFilters($query, $options); + + // Execute search with RBAC disabled to find deelnemers + $gebruikItems = $objectService->searchObjects($query, rbac: false); + + // Process and add to results + foreach ($gebruikItems as $gebruik) { + $gebruik['_filter_type'] = 'deelnemers'; + $gebruik['_schema_id'] = $schemaId; + $allGebruiks[] = $gebruik; + } + + $this->logger->debug('Retrieved deelnemers gebruiks from schema', [ + 'schema_id' => $schemaId, + 'count' => count($gebruikItems), + 'organisation_in_deelnemers' => $currentOrg + ]); + + } catch (Exception $e) { + $this->logger->warning('Failed to get deelnemers gebruiks from schema', [ + 'schema_id' => $schemaId, + 'error' => $e->getMessage() + ]); + } + } + + return [ + 'success' => true, + 'gebruiks' => $allGebruiks, + 'count' => count($allGebruiks), + 'filter_type' => 'deelnemers', + 'organisation' => $currentOrg + ]; + + } catch (Exception $e) { + $this->logger->error('Failed to get deelnemers gebruiks', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'success' => false, + 'error' => 'Failed to retrieve gebruiks: ' . $e->getMessage(), + 'gebruiks' => [], + 'count' => 0 + ]; + } + } + + /** + * Set the @self property of a gebruik to the active organization + * + * This method updates the @self.organisation property of a specific gebruik + * object, but only if the active organization is the afnemer for that gebruik. + * + * @param string $gebruikId The UUID of the gebruik object to update + * @param array $options Additional update options + * @return array Result with success status and updated object data + * @throws Exception When OpenRegister service is not available or operation fails + */ + public function setGebruikSelfToActiveOrg(string $gebruikId, array $options = []): array + { + $this->logger->info('Setting gebruik @self property to active organisation', [ + 'gebruik_id' => $gebruikId, + 'options' => $options + ]); + + try { + // Validate input + if (empty($gebruikId)) { + return [ + 'success' => false, + 'error' => 'Gebruik ID is required', + 'gebruik' => null + ]; + } + + // Get ObjectService from OpenRegister + $objectService = $this->getObjectService(); + + // Get current organization + $currentOrg = $this->getCurrentOrganisation(); + if (!$currentOrg) { + return [ + 'success' => false, + 'error' => 'No current organization available', + 'gebruik' => null + ]; + } + + // Get the existing gebruik object + $existingGebruik = $objectService->getObject($gebruikId); + if (!$existingGebruik) { + return [ + 'success' => false, + 'error' => 'Gebruik object not found', + 'gebruik' => null + ]; + } + + // Verify that the active organization is the afnemer + $gebruikData = $existingGebruik->getObject(); + $afnemerInfo = $gebruikData['afnemer'] ?? null; + + // Check various ways the afnemer might be stored (UUID, object, or string) + $afnemerId = null; + if (is_array($afnemerInfo) && isset($afnemerInfo['id'])) { + $afnemerId = $afnemerInfo['id']; + } elseif (is_string($afnemerInfo)) { + $afnemerId = $afnemerInfo; + } + + if (!$afnemerId || $afnemerId !== $currentOrg) { + return [ + 'success' => false, + 'error' => 'Operation not allowed: active organization is not the afnemer', + 'gebruik' => null, + 'debug' => [ + 'afnemer_in_object' => $afnemerInfo, + 'resolved_afnemer_id' => $afnemerId, + 'current_org' => $currentOrg + ] + ]; + } + + // Update the @self.organisation property + $selfData = $gebruikData['@self'] ?? []; + $selfData['organisation'] = $currentOrg; + $gebruikData['@self'] = $selfData; + + // Save the updated object + $existingGebruik->setObject($gebruikData); + $updatedGebruik = $objectService->saveObject($existingGebruik); + + $this->logger->info('Successfully updated gebruik @self property', [ + 'gebruik_id' => $gebruikId, + 'organisation' => $currentOrg, + 'afnemer_verified' => $afnemerId + ]); + + return [ + 'success' => true, + 'message' => 'Gebruik @self property updated successfully', + 'gebruik' => $updatedGebruik->getObject(), + 'updated_fields' => ['@self.organisation'] + ]; + + } catch (Exception $e) { + $this->logger->error('Failed to update gebruik @self property', [ + 'gebruik_id' => $gebruikId, + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'success' => false, + 'error' => 'Failed to update gebruik: ' . $e->getMessage(), + 'gebruik' => null + ]; + } + } + + /** + * Get current active organisation for filtering + * + * @return string|null Current organisation identifier or null if no user session + */ + private function getCurrentOrganisation(): ?string + { + $user = $this->userSession->getUser(); + if (!$user) { + return null; + } + + // Get user's organization from configuration or session + // This follows the same pattern as ViewService + $userOrg = $this->config->getUserValue( + $user->getUID(), + 'softwarecatalog', + 'organisation', + null + ); + + return $userOrg; + } + + /** + * Get ObjectService from OpenRegister app + * + * @return ObjectService The OpenRegister object service + * @throws Exception When OpenRegister service is not available + */ + private function getObjectService(): ObjectService + { + if (!in_array('openregister', $this->appManager->getInstalledApps())) { + throw new Exception('OpenRegister app is not installed'); + } + + try { + return $this->container->get('OCA\OpenRegister\Service\ObjectService'); + } catch (Exception $e) { + throw new Exception('Failed to get OpenRegister service: ' . $e->getMessage()); + } + } + + /** + * Get configuration for gebruiks objects (register ID and schema IDs) + * + * @return array Configuration with register_id and schemas array + */ + private function getGebruiksConfiguration(): array + { + // Get AMEF configuration which includes gebruiks schemas + $amefConfig = $this->settingsService->getAmefConfig(); + + return [ + 'register_id' => $amefConfig['register_id'] ?? null, + 'schemas' => $amefConfig['gebruik_schemas'] ?? [] + ]; + } + + /** + * Add query filters from options to the base query + * + * This method processes additional filter options and adds them to the query. + * Supported filters: limit, offset, status, product, etc. + * + * @param array $baseQuery The base query to extend + * @param array $options Filter options to apply + * @return array Extended query with additional filters + */ + private function addQueryFilters(array $baseQuery, array $options): array + { + // Add limit if specified + if (isset($options['limit']) && is_numeric($options['limit'])) { + $baseQuery['@limit'] = (int)$options['limit']; + } + + // Add offset if specified + if (isset($options['offset']) && is_numeric($options['offset'])) { + $baseQuery['@offset'] = (int)$options['offset']; + } + + // Add status filter if specified + if (isset($options['status']) && !empty($options['status'])) { + $baseQuery['status'] = $options['status']; + } + + // Add product filter if specified + if (isset($options['product']) && !empty($options['product'])) { + $baseQuery['product'] = $options['product']; + } + + // Add date filters if specified + if (isset($options['startDate']) && !empty($options['startDate'])) { + $baseQuery['startDate'] = $options['startDate']; + } + + if (isset($options['endDate']) && !empty($options['endDate'])) { + $baseQuery['endDate'] = $options['endDate']; + } + + return $baseQuery; + } +} From d5b81fd5f2f6060d7ac177054e50906a684e5b7d Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 4 Sep 2025 14:36:21 +0200 Subject: [PATCH 3/5] Verder op aanbod en gebruik --- appinfo/routes.php | 10 + lib/Settings/softwarecatalogus_register.json | 10 +- test_aangeboden_gebruik_api.sh | 70 ++++++ website/docs/aangeboden-gebruik-api.md | 248 +++++++++++++++++++ 4 files changed, 333 insertions(+), 5 deletions(-) create mode 100644 test_aangeboden_gebruik_api.sh create mode 100644 website/docs/aangeboden-gebruik-api.md diff --git a/appinfo/routes.php b/appinfo/routes.php index bf6d028b..938793ee 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -140,5 +140,15 @@ ['name' => 'view#getApiDocumentation', 'url' => '/api/views/docs', 'verb' => 'GET'], ['name' => 'view#getView', 'url' => '/api/views/{viewId}', 'verb' => 'GET'], + // ======================================================================== + // AANGEBODEN GEBRUIK API ENDPOINTS - Custom Objects API for Gebruiks + // ======================================================================== + + // AangebodenGebruik API endpoints for filtering gebruiks by organization involvement + ['name' => 'aangebodenGebruik#getGebruiksWhereAfnemer', 'url' => '/api/aangeboden-gebruik/afnemer', 'verb' => 'GET'], + ['name' => 'aangebodenGebruik#getGebruiksWhereDeelnemers', 'url' => '/api/aangeboden-gebruik/deelnemers', 'verb' => 'GET'], + ['name' => 'aangebodenGebruik#setGebruikSelfToActiveOrg', 'url' => '/api/aangeboden-gebruik/{gebruikId}/set-self', 'verb' => 'PUT'], + ['name' => 'aangebodenGebruik#getApiDocumentation', 'url' => '/api/aangeboden-gebruik/docs', 'verb' => 'GET'], + ], ]; diff --git a/lib/Settings/softwarecatalogus_register.json b/lib/Settings/softwarecatalogus_register.json index 10f7bac2..d259f587 100644 --- a/lib/Settings/softwarecatalogus_register.json +++ b/lib/Settings/softwarecatalogus_register.json @@ -1037,7 +1037,7 @@ "contactpersoon": { "uri": null, "slug": "contactpersoon", - "title": "Contact Persoon", + "title": "Contactpersoon", "description": "Contactgegevens van een persoon", "version": "0.0.22", "summary": "", @@ -1213,9 +1213,7 @@ "enum": [ "Aanbod-beheerder", "Gebruik-beheerder", - "Gebruik-raadpleger", "Functioneel-beheerder", - "VNG-raadpleger", "Organisatie-beheerder" ], "example": "Bijvoorbeeld: [\"Aanbod-beheerder\", \"Functioneel-beheerder\"]" @@ -1704,7 +1702,8 @@ "title": "Status", "type": "string", "default": "concept", - "visible": true, + "visible": false, + "hideOnCollection": true, "facetable": false, "order": 17, "minLength": null, @@ -1733,7 +1732,8 @@ "description": "Type samenwerking van de organisatie", "title": "Samenwerkingstype", "type": "string", - "visible": true, + "visible": false, + "hideOnCollection": true, "facetable": true, "order": 14, "minLength": null, diff --git a/test_aangeboden_gebruik_api.sh b/test_aangeboden_gebruik_api.sh new file mode 100644 index 00000000..d5b43263 --- /dev/null +++ b/test_aangeboden_gebruik_api.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# Test script for AangebodenGebruik API endpoints +# This script tests the new custom objects endpoint for managing gebruiks objects + +echo "===================================" +echo "Testing AangebodenGebruik API" +echo "===================================" + +BASE_URL="http://localhost/index.php/apps/softwarecatalog/api/aangeboden-gebruik" +AUTH="admin:admin" + +echo "" +echo "1. Testing API Documentation endpoint..." +echo "GET ${BASE_URL}/docs" +docker-compose exec nextcloud curl -s -X GET "${BASE_URL}/docs" \ + -H "Content-Type: application/json" \ + -u ${AUTH} | jq . + +echo "" +echo "2. Testing Afnemer Gebruiks endpoint..." +echo "GET ${BASE_URL}/afnemer" +docker-compose exec nextcloud curl -s -X GET "${BASE_URL}/afnemer" \ + -H "Content-Type: application/json" \ + -u ${AUTH} | jq . + +echo "" +echo "3. Testing Afnemer Gebruiks endpoint with limit..." +echo "GET ${BASE_URL}/afnemer?limit=5" +docker-compose exec nextcloud curl -s -X GET "${BASE_URL}/afnemer?limit=5" \ + -H "Content-Type: application/json" \ + -u ${AUTH} | jq . + +echo "" +echo "4. Testing Deelnemers Gebruiks endpoint..." +echo "GET ${BASE_URL}/deelnemers" +docker-compose exec nextcloud curl -s -X GET "${BASE_URL}/deelnemers" \ + -H "Content-Type: application/json" \ + -u ${AUTH} | jq . + +echo "" +echo "5. Testing Deelnemers Gebruiks endpoint with status filter..." +echo "GET ${BASE_URL}/deelnemers?status=actief" +docker-compose exec nextcloud curl -s -X GET "${BASE_URL}/deelnemers?status=actief" \ + -H "Content-Type: application/json" \ + -u ${AUTH} | jq . + +echo "" +echo "6. Testing Set @self Property endpoint (will need a valid UUID)..." +echo "Note: Replace USAGE_UUID with an actual usage UUID from the previous responses" +echo "PUT ${BASE_URL}/USAGE_UUID/set-self" +echo "Example command (uncomment and replace UUID):" +echo "# docker-compose exec nextcloud curl -s -X PUT \"${BASE_URL}/USAGE_UUID/set-self\" \\" +echo "# -H \"Content-Type: application/json\" \\" +echo "# -u ${AUTH} | jq ." + +echo "" +echo "===================================" +echo "Testing Complete" +echo "===================================" +echo "" +echo "To test the @self property update:" +echo "1. Look for a 'gebruikId' or 'id' in the afnemer response above" +echo "2. Replace USAGE_UUID in the curl command with that ID" +echo "3. Run the command manually" +echo "" +echo "Expected behaviors:" +echo "- Afnemer endpoint: Returns gebruiks where active org is the consumer" +echo "- Deelnemers endpoint: Returns gebruiks where active org is a participant" +echo "- Set @self endpoint: Only works if active org is the afnemer" diff --git a/website/docs/aangeboden-gebruik-api.md b/website/docs/aangeboden-gebruik-api.md new file mode 100644 index 00000000..dc3439b0 --- /dev/null +++ b/website/docs/aangeboden-gebruik-api.md @@ -0,0 +1,248 @@ +# AangebodenGebruik API Documentation + +## Overview + +The AangebodenGebruik API provides endpoints to manage gebruiks (usage) objects where the active organization is involved either as an afnemer (consumer) or in the deelnemers (participants) list. This API allows organizations to: + +1. Retrieve gebruiks objects where they are the afnemer +2. Retrieve gebruiks objects where they are listed in deelnemers +3. Update the '@self' property of a gebruik to claim ownership (only if they are the afnemer) + +## Base URL + +All endpoints are prefixed with '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/api/aangeboden-gebruik' + +## Authentication + +These endpoints require user authentication and use standard Nextcloud RBAC (Role-Based Access Control) for the afnemer endpoints. The deelnemers endpoint uses RBAC-disabled search to find participation records. + +## Endpoints + +### 1. Get Gebruiks Where Active Organization is Afnemer + +**Endpoint:** 'GET /api/aangeboden-gebruik/afnemer' + +**Description:** Returns all gebruiks objects where the active organization is the afnemer (consumer). + +**Query Parameters:** +- 'limit' (integer, optional): Maximum number of results to return +- 'offset' (integer, optional): Number of results to skip for pagination +- 'status' (string, optional): Filter by usage status +- 'product' (string, optional): Filter by product ID +- 'startDate' (string, optional): Filter by start date (ISO 8601 format) +- 'endDate' (string, optional): Filter by end date (ISO 8601 format) + +**Response Example:** +```json +{ + 'success': true, + 'gebruiks': [ + { + 'id': 'usage-uuid-123', + 'afnemer': 'org-uuid', + 'product': 'product-uuid', + 'status': 'actief', + '_filter_type': 'afnemer', + '_schema_id': 'schema-id' + } + ], + 'count': 1, + 'filter_type': 'afnemer', + 'organisation': 'org-uuid' +} +``` + +### 2. Get Gebruiks Where Active Organization is in Deelnemers + +**Endpoint:** 'GET /api/aangeboden-gebruik/deelnemers' + +**Description:** Returns all gebruiks objects where the active organization appears in the deelnemers (participants) array. + +**Query Parameters:** +- 'limit' (integer, optional): Maximum number of results to return +- 'offset' (integer, optional): Number of results to skip for pagination +- 'status' (string, optional): Filter by usage status +- 'product' (string, optional): Filter by product ID +- 'startDate' (string, optional): Filter by start date (ISO 8601 format) +- 'endDate' (string, optional): Filter by end date (ISO 8601 format) + +**Response Example:** +```json +{ + 'success': true, + 'gebruiks': [ + { + 'id': 'usage-uuid-456', + 'afnemer': 'other-org-uuid', + 'deelnemers': ['org-uuid', 'another-org-uuid'], + 'product': 'product-uuid', + 'status': 'actief', + '_filter_type': 'deelnemers', + '_schema_id': 'schema-id' + } + ], + 'count': 1, + 'filter_type': 'deelnemers', + 'organisation': 'org-uuid' +} +``` + +### 3. Set Gebruik @self Property to Active Organization + +**Endpoint:** 'PUT /api/aangeboden-gebruik/{gebruikId}/set-self' + +**Description:** Sets the '@self.organisation' property of a specific gebruik object to the active organization. This operation is only allowed if the active organization is the afnemer for that gebruik. + +**Path Parameters:** +- 'gebruikId' (string, required): The UUID of the gebruik object to update + +**Security:** This endpoint verifies that the active organization is the afnemer before allowing the update. + +**Response Example (Success):** +```json +{ + 'success': true, + 'message': 'Gebruik @self property updated successfully', + 'gebruik': { + 'id': 'usage-uuid-123', + 'afnemer': 'org-uuid', + '@self': { + 'organisation': 'org-uuid', + 'register': 'register-id', + 'schema': 'schema-id' + } + }, + 'updated_fields': ['@self.organisation'] +} +``` + +**Response Example (Permission Denied):** +```json +{ + 'success': false, + 'error': 'Operation not allowed: active organization is not the afnemer', + 'gebruik': null +} +``` + +### 4. Get API Documentation + +**Endpoint:** 'GET /api/aangeboden-gebruik/docs' + +**Description:** Returns comprehensive API documentation including all endpoints, parameters, and examples. + +## Error Handling + +The API uses standard HTTP status codes: + +- **200 OK**: Request successful +- **400 Bad Request**: Invalid parameters or missing required fields +- **403 Forbidden**: Operation not allowed (e.g., organization is not afnemer for @self update) +- **404 Not Found**: Gebruik object not found +- **500 Internal Server Error**: Server-side error occurred + +## Security Model + +### Afnemer Filtering +- Uses standard RBAC filtering based on organization association +- Only returns gebruiks where the active organization has proper access rights + +### Deelnemers Filtering +- Uses RBAC-disabled search to find participation records +- Searches across all gebruiks to find those where the active organization appears in the deelnemers array + +### @self Update Permission +- Verifies that the active organization is the afnemer before allowing updates +- Prevents unauthorized modification of gebruik ownership + +## Usage Examples + +### Get Gebruiks as Afnemer with Pagination +```bash +curl -X GET 'http://localhost/index.php/apps/softwarecatalog/api/aangeboden-gebruik/afnemer?limit=10&offset=0' \ + -H 'Content-Type: application/json' \ + -u admin:admin +``` + +### Get Gebruiks as Deelnemers with Status Filter +```bash +curl -X GET 'http://localhost/index.php/apps/softwarecatalog/api/aangeboden-gebruik/deelnemers?status=actief' \ + -H 'Content-Type: application/json' \ + -u admin:admin +``` + +### Set Gebruik @self Property +```bash +curl -X PUT 'http://localhost/index.php/apps/softwarecatalog/api/aangeboden-gebruik/usage-uuid-123/set-self' \ + -H 'Content-Type: application/json' \ + -u admin:admin +``` + +## Docker Testing Commands + +When testing in the Docker environment, use the docker-compose exec command: + +### Test Afnemer Endpoint +```bash +cd /home/rubenlinde/nextcloud-docker-dev +docker-compose exec nextcloud curl -X GET 'http://localhost/index.php/apps/softwarecatalog/api/aangeboden-gebruik/afnemer' \ + -H 'Content-Type: application/json' \ + -u admin:admin +``` + +### Test Deelnemers Endpoint +```bash +docker-compose exec nextcloud curl -X GET 'http://localhost/index.php/apps/softwarecatalog/api/aangeboden-gebruik/deelnemers' \ + -H 'Content-Type: application/json' \ + -u admin:admin +``` + +### Test Set @self Property +```bash +docker-compose exec nextcloud curl -X PUT 'http://localhost/index.php/apps/softwarecatalog/api/aangeboden-gebruik/USAGE_UUID/set-self' \ + -H 'Content-Type: application/json' \ + -u admin:admin +``` + +Replace 'USAGE_UUID' with an actual usage object UUID from your system. + +## Configuration Requirements + +The AangebodenGebruik API relies on the following configuration: + +1. **OpenRegister App**: Must be installed and available +2. **AMEF Configuration**: Must be configured with proper register_id and gebruik_schemas +3. **User Organization**: Active user must have an organization associated with their account + +## Technical Implementation + +### Service Layer +- **AangebodenGebruikService**: Handles business logic for filtering gebruiks and updating @self properties +- **SettingsService**: Provides configuration data for gebruiks schemas and register IDs + +### Controller Layer +- **AangebodenGebruikController**: Handles HTTP requests and responses, parameter validation, and error handling + +### Data Flow +1. Controller receives HTTP request and parses parameters +2. Service layer retrieves configuration and current organization +3. Service queries OpenRegister with appropriate filters (RBAC enabled/disabled) +4. Results are processed and returned via controller + +## Troubleshooting + +### Common Issues + +**No results returned**: +- Verify that the active user has an organization configured +- Check that gebruiks objects exist with proper afnemer or deelnemers relationships +- Ensure AMEF configuration includes valid gebruik_schemas + +**Permission denied on @self update**: +- Verify that the active organization is the afnemer for the specific gebruik +- Check that the gebruik object exists and is accessible + +**Configuration errors**: +- Ensure OpenRegister app is installed and enabled +- Verify AMEF configuration in SoftwareCatalog settings +- Check that register_id and schema IDs are correctly configured From dde13ca418e4c6b89697b44de11686db7985b31a Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 8 Sep 2025 12:33:22 +0200 Subject: [PATCH 4/5] Make organisaties own themself --- .../AangebodenGebruikController.php | 2 + lib/Service/AangebodenGebruikService.php | 2 + lib/Service/OrganizationSyncService.php | 73 +++++++++++++++++++ lib/Settings/softwarecatalogus_register.json | 24 ++++-- test_aangeboden_gebruik_api.sh | 2 + test_anonymous_registration.sh | 0 test_anonymous_registration_cli.sh | 0 test_archimate_api.sh | 0 test_archimate_export.sh | 0 test_authenticated_registration.sh | 0 test_comprehensive_anonymous_registration.sh | 0 test_optimized_api.sh | 0 website/docs/aangeboden-gebruik-api.md | 2 + 13 files changed, 97 insertions(+), 8 deletions(-) mode change 100644 => 100755 test_anonymous_registration.sh mode change 100644 => 100755 test_anonymous_registration_cli.sh mode change 100644 => 100755 test_archimate_api.sh mode change 100644 => 100755 test_archimate_export.sh mode change 100644 => 100755 test_authenticated_registration.sh mode change 100644 => 100755 test_comprehensive_anonymous_registration.sh mode change 100644 => 100755 test_optimized_api.sh diff --git a/lib/Controller/AangebodenGebruikController.php b/lib/Controller/AangebodenGebruikController.php index 5f612a05..7490f022 100644 --- a/lib/Controller/AangebodenGebruikController.php +++ b/lib/Controller/AangebodenGebruikController.php @@ -500,3 +500,5 @@ private function parseQueryOptions(): array return $options; } } + + diff --git a/lib/Service/AangebodenGebruikService.php b/lib/Service/AangebodenGebruikService.php index 4ea51496..ae2735c2 100644 --- a/lib/Service/AangebodenGebruikService.php +++ b/lib/Service/AangebodenGebruikService.php @@ -483,3 +483,5 @@ private function addQueryFilters(array $baseQuery, array $options): array return $baseQuery; } } + + diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index 705786dc..273fc5f6 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -534,6 +534,11 @@ private function ensureOrganisationEntity(object $organisatieObject, array &$sta 'status' => $objectData['status'] ?? 'Unknown' ]); + // Get configuration for object updates + $voorzieningenConfig = $this->settingsService->getVoorzieningenConfig(); + $register = $voorzieningenConfig['register'] ?? ''; + $organizationSchema = $voorzieningenConfig['organisatie_schema'] ?? ''; + // Try to find existing organisation entity $organisationMapper = \OC::$server->get('OCA\OpenRegister\Db\OrganisationMapper'); @@ -584,6 +589,9 @@ private function ensureOrganisationEntity(object $organisatieObject, array &$sta } } + // Update organisatie object owner to organisation entity UUID + $this->updateOrganisatieObjectOwner($organisatieObject, $organisationEntity, $register, $organizationSchema); + return $organisationEntity; } catch (\OCP\AppFramework\Db\DoesNotExistException $e) { @@ -619,6 +627,9 @@ private function ensureOrganisationEntity(object $organisatieObject, array &$sta 'organisatieId' => $organisatieId ]); } + + // Update organisatie object owner to organisation entity UUID + $this->updateOrganisatieObjectOwner($organisatieObject, $organisationEntity, $register, $organizationSchema); } else { $this->logger->error('❌ ORGANISATION ENTITY CREATION FAILED', [ 'app' => 'softwarecatalog', @@ -1819,4 +1830,66 @@ public function getSyncStatusWithErrorHandling(int $minutesBack = 10): array ]; } } + + /** + * Updates the organisatie object's @self metadata to set owner to the organisation entity UUID + * + * @param object $organisatieObject The organisatie object to update + * @param object $organisationEntity The organisation entity + * @param string $register The register ID + * @param string $organizationSchema The organization schema ID + * @return void + */ + private function updateOrganisatieObjectOwner(object $organisatieObject, object $organisationEntity, string $register, string $organizationSchema): void + { + try { + $organisatieId = $organisatieObject->getUuid(); + $organisationEntityUuid = $organisationEntity->getUuid(); + + $this->logger->info('OrganizationSyncService: Updating organisatie object owner', [ + 'organisatieId' => $organisatieId, + 'organisationEntityUuid' => $organisationEntityUuid, + 'register' => $register, + 'schema' => $organizationSchema + ]); + + // Get the current object data + $currentObject = $organisatieObject->getObject(); + + // Get current @self metadata or create new + $selfMetadata = $currentObject['@self'] ?? []; + + // Update the owner field to the organisation entity UUID + $selfMetadata['owner'] = $organisationEntityUuid; + + // Update the object with the new @self metadata + $currentObject['@self'] = $selfMetadata; + $organisatieObject->setObject($currentObject); + + // Save the updated object using ObjectService + $objectService = \OC::$server->get('OCA\OpenRegister\Service\ObjectService'); + $objectService->saveObject( + object: $organisatieObject, + register: $register, + schema: $organizationSchema, + rbac: false, + multi: false + ); + + $this->logger->info('OrganizationSyncService: Successfully updated organisatie object owner', [ + 'organisatieId' => $organisatieId, + 'organisationEntityUuid' => $organisationEntityUuid, + 'ownerSet' => $selfMetadata['owner'] + ]); + + } catch (\Exception $e) { + $this->logger->error('OrganizationSyncService: Failed to update organisatie object owner', [ + 'organisatieId' => $organisatieObject->getUuid(), + 'organisationEntityUuid' => $organisationEntity->getUuid(), + 'exception' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine() + ]); + } + } } diff --git a/lib/Settings/softwarecatalogus_register.json b/lib/Settings/softwarecatalogus_register.json index d259f587..68b10d7f 100644 --- a/lib/Settings/softwarecatalogus_register.json +++ b/lib/Settings/softwarecatalogus_register.json @@ -627,7 +627,8 @@ "objectDescriptionField": "beschrijvingLang", "objectImageField": "logo", "allowFiles": true, - "allowedTags": ["DPIA", "Handleiding"] + "allowedTags": ["DPIA", "Handleiding"], + "autoPublish": true } }, "dienst": { @@ -882,7 +883,8 @@ "ISO-27001", "ISO-16075", "Verklaring van toepasselijkheid" - ] + ], + "autoPublish": true } }, "kwetsbaarheid": { @@ -1318,7 +1320,8 @@ "deleted": null, "configuration": { "objectNameField": "achternaam", - "objectDescriptionField": "functie" + "objectDescriptionField": "functie", + "autoPublish": true } }, "organisatie": { @@ -1869,7 +1872,8 @@ "BRL-certificaten", "CE-markering documenten", "Aanbestedingsdocumentatie" - ] + ], + "autoPublish": true } }, "gebruik": { @@ -2704,7 +2708,8 @@ "deleted": null, "configuration": { "objectNameField": "type", - "objectDescriptionField": "beschrijvingKort" + "objectDescriptionField": "beschrijvingKort", + "autoPublish": true } }, "beoordeeling": { @@ -4603,7 +4608,8 @@ "objectDescriptionField": "beschrijvingLang", "objectImageField": "logo", "allowFiles": true, - "allowedTags": ["Documentatie", "Handleiding", "Technische specificatie"] + "allowedTags": ["Documentatie", "Handleiding", "Technische specificatie"], + "autoPublish": true } }, "compliancy": { @@ -4679,7 +4685,8 @@ "objectSummaryField": "standaardversie", "objectDescriptionField": "module", "allowFiles": true, - "allowedTags": ["testraport"] + "allowedTags": ["testraport"], + "autoPublish": true } }, "moduleVersie": { @@ -4800,7 +4807,8 @@ "configuration": { "objectNameField": "versie", "objectSummaryField": "beschrijvingKort", - "objectDescriptionField": "beschrijvingLang" + "objectDescriptionField": "beschrijvingLang", + "autoPublish": true } } }, diff --git a/test_aangeboden_gebruik_api.sh b/test_aangeboden_gebruik_api.sh index d5b43263..61f3f4dc 100644 --- a/test_aangeboden_gebruik_api.sh +++ b/test_aangeboden_gebruik_api.sh @@ -68,3 +68,5 @@ echo "Expected behaviors:" echo "- Afnemer endpoint: Returns gebruiks where active org is the consumer" echo "- Deelnemers endpoint: Returns gebruiks where active org is a participant" echo "- Set @self endpoint: Only works if active org is the afnemer" + + diff --git a/test_anonymous_registration.sh b/test_anonymous_registration.sh old mode 100644 new mode 100755 diff --git a/test_anonymous_registration_cli.sh b/test_anonymous_registration_cli.sh old mode 100644 new mode 100755 diff --git a/test_archimate_api.sh b/test_archimate_api.sh old mode 100644 new mode 100755 diff --git a/test_archimate_export.sh b/test_archimate_export.sh old mode 100644 new mode 100755 diff --git a/test_authenticated_registration.sh b/test_authenticated_registration.sh old mode 100644 new mode 100755 diff --git a/test_comprehensive_anonymous_registration.sh b/test_comprehensive_anonymous_registration.sh old mode 100644 new mode 100755 diff --git a/test_optimized_api.sh b/test_optimized_api.sh old mode 100644 new mode 100755 diff --git a/website/docs/aangeboden-gebruik-api.md b/website/docs/aangeboden-gebruik-api.md index dc3439b0..a94e0a60 100644 --- a/website/docs/aangeboden-gebruik-api.md +++ b/website/docs/aangeboden-gebruik-api.md @@ -246,3 +246,5 @@ The AangebodenGebruik API relies on the following configuration: - Ensure OpenRegister app is installed and enabled - Verify AMEF configuration in SoftwareCatalog settings - Check that register_id and schema IDs are correctly configured + + From 10b3c9895cf7d288e55cb96e5595e00d8a1c774c Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 8 Sep 2025 12:38:36 +0200 Subject: [PATCH 5/5] Make Contactpersoonen be properly owned --- lib/Service/ContactpersoonService.php | 76 ++++++++++++++++++++ lib/Service/OrganizationSyncService.php | 67 +++++++++++++++++ test_anonymous_registration.sh | 0 test_anonymous_registration_cli.sh | 0 test_archimate_api.sh | 0 test_archimate_export.sh | 0 test_authenticated_registration.sh | 0 test_comprehensive_anonymous_registration.sh | 0 test_optimized_api.sh | 0 9 files changed, 143 insertions(+) mode change 100755 => 100644 test_anonymous_registration.sh mode change 100755 => 100644 test_anonymous_registration_cli.sh mode change 100755 => 100644 test_archimate_api.sh mode change 100755 => 100644 test_archimate_export.sh mode change 100755 => 100644 test_authenticated_registration.sh mode change 100755 => 100644 test_comprehensive_anonymous_registration.sh mode change 100755 => 100644 test_optimized_api.sh diff --git a/lib/Service/ContactpersoonService.php b/lib/Service/ContactpersoonService.php index 89548a11..422441c6 100644 --- a/lib/Service/ContactpersoonService.php +++ b/lib/Service/ContactpersoonService.php @@ -133,6 +133,9 @@ public function processContactpersoon(object $contactpersoonObject, bool $isUpda // Link user to organization entity $this->contactPersonHandler->addUserToOrganizationEntity($contactpersoonObject, $username); + // Update contactpersoon object owner to user UID + $this->updateContactpersoonObjectOwner($contactpersoonObject, $username); + $this->logger->info('ContactpersoonService: Successfully created user account', [ 'contactId' => $contactId, 'username' => $username @@ -466,4 +469,77 @@ public function getContactPersonsForOrganization(string $organizationUuid): arra return []; } } + + /** + * Updates the contactpersoon object's @self metadata to set owner to the user UID + * + * @param object $contactObject The contactpersoon object to update + * @param string $userUID The user UID to set as owner + * @return void + */ + private function updateContactpersoonObjectOwner(object $contactObject, string $userUID): void + { + try { + $contactId = $contactObject->getUuid(); + + // Get configuration for register and schema + $voorzieningenConfig = $this->settingsService->getVoorzieningenConfig(); + $register = $voorzieningenConfig['register'] ?? ''; + $contactSchema = $voorzieningenConfig['contactpersoon_schema'] ?? ''; + + if (empty($register) || empty($contactSchema)) { + $this->logger->warning('ContactpersoonService: Cannot update object owner - missing configuration', [ + 'contactId' => $contactId, + 'register' => $register, + 'contactSchema' => $contactSchema + ]); + return; + } + + $this->logger->info('ContactpersoonService: Updating contactpersoon object owner', [ + 'contactId' => $contactId, + 'userUID' => $userUID, + 'register' => $register, + 'schema' => $contactSchema + ]); + + // Get the current object data + $currentObject = $contactObject->getObject(); + + // Get current @self metadata or create new + $selfMetadata = $currentObject['@self'] ?? []; + + // Update the owner field to the user UID + $selfMetadata['owner'] = $userUID; + + // Update the object with the new @self metadata + $currentObject['@self'] = $selfMetadata; + $contactObject->setObject($currentObject); + + // Save the updated object using ObjectService + $objectService = \OC::$server->get('OCA\OpenRegister\Service\ObjectService'); + $objectService->saveObject( + object: $contactObject, + register: $register, + schema: $contactSchema, + rbac: false, + multi: false + ); + + $this->logger->info('ContactpersoonService: Successfully updated contactpersoon object owner', [ + 'contactId' => $contactId, + 'userUID' => $userUID, + 'ownerSet' => $selfMetadata['owner'] + ]); + + } catch (\Exception $e) { + $this->logger->error('ContactpersoonService: Failed to update contactpersoon object owner', [ + 'contactId' => $contactObject->getUuid(), + 'userUID' => $userUID, + 'exception' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine() + ]); + } + } } \ No newline at end of file diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index 273fc5f6..eeb83042 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -1412,6 +1412,9 @@ private function createOrUpdateContactPersonObject(array $contactData, string $o // Add user to organization entity in database $this->contactpersonHandler->addUserToOrganizationEntity($contactObject, $user->getUID()); + // Update contactpersoon object owner to user UID + $this->updateContactpersoonObjectOwner($contactObject, $user->getUID(), $register, $contactSchema); + $this->logger->critical('🎉 USER ACCOUNT CREATED SUCCESS', [ 'app' => 'softwarecatalog', 'contactId' => $contactObject->getUuid(), @@ -1532,6 +1535,9 @@ public function processSpecificContactPerson($contactObject): array // Add user to organization entity in database $this->contactpersonHandler->addUserToOrganizationEntity($contactObject, $user->getUID()); + // Update contactpersoon object owner to user UID + $this->updateContactpersoonObjectOwner($contactObject, $user->getUID(), $register, $contactSchema); + $stats['usersCreated']++; } else { $this->logger->info('[EVENT] OrganizationSyncService: Skipping user creation - organization not active or not found in entity table', [ @@ -1892,4 +1898,65 @@ private function updateOrganisatieObjectOwner(object $organisatieObject, object ]); } } + + /** + * Updates the contactpersoon object's @self metadata to set owner to the user UID + * + * @param object $contactObject The contactpersoon object to update + * @param string $userUID The user UID to set as owner + * @param string $register The register ID + * @param string $contactSchema The contact schema ID + * @return void + */ + private function updateContactpersoonObjectOwner(object $contactObject, string $userUID, string $register, string $contactSchema): void + { + try { + $contactId = $contactObject->getUuid(); + + $this->logger->info('OrganizationSyncService: Updating contactpersoon object owner', [ + 'contactId' => $contactId, + 'userUID' => $userUID, + 'register' => $register, + 'schema' => $contactSchema + ]); + + // Get the current object data + $currentObject = $contactObject->getObject(); + + // Get current @self metadata or create new + $selfMetadata = $currentObject['@self'] ?? []; + + // Update the owner field to the user UID + $selfMetadata['owner'] = $userUID; + + // Update the object with the new @self metadata + $currentObject['@self'] = $selfMetadata; + $contactObject->setObject($currentObject); + + // Save the updated object using ObjectService + $objectService = \OC::$server->get('OCA\OpenRegister\Service\ObjectService'); + $objectService->saveObject( + object: $contactObject, + register: $register, + schema: $contactSchema, + rbac: false, + multi: false + ); + + $this->logger->info('OrganizationSyncService: Successfully updated contactpersoon object owner', [ + 'contactId' => $contactId, + 'userUID' => $userUID, + 'ownerSet' => $selfMetadata['owner'] + ]); + + } catch (\Exception $e) { + $this->logger->error('OrganizationSyncService: Failed to update contactpersoon object owner', [ + 'contactId' => $contactObject->getUuid(), + 'userUID' => $userUID, + 'exception' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine() + ]); + } + } } diff --git a/test_anonymous_registration.sh b/test_anonymous_registration.sh old mode 100755 new mode 100644 diff --git a/test_anonymous_registration_cli.sh b/test_anonymous_registration_cli.sh old mode 100755 new mode 100644 diff --git a/test_archimate_api.sh b/test_archimate_api.sh old mode 100755 new mode 100644 diff --git a/test_archimate_export.sh b/test_archimate_export.sh old mode 100755 new mode 100644 diff --git a/test_authenticated_registration.sh b/test_authenticated_registration.sh old mode 100755 new mode 100644 diff --git a/test_comprehensive_anonymous_registration.sh b/test_comprehensive_anonymous_registration.sh old mode 100755 new mode 100644 diff --git a/test_optimized_api.sh b/test_optimized_api.sh old mode 100755 new mode 100644