From 04d12bd661a6347dc4f56dfc382522f9408f27b5 Mon Sep 17 00:00:00 2001 From: Wilco Louwerse Date: Mon, 7 Oct 2024 16:37:27 +0200 Subject: [PATCH 01/38] Upload json to create schema (& basics for Download schema as json) --- appinfo/routes.php | 5 +- lib/Controller/SchemasController.php | 155 ++++++++++++++++++++++++--- lib/Service/DownloadService.php | 26 +++++ lib/Service/UploadService.php | 39 +++++++ 4 files changed, 207 insertions(+), 18 deletions(-) create mode 100644 lib/Service/DownloadService.php create mode 100644 lib/Service/UploadService.php diff --git a/appinfo/routes.php b/appinfo/routes.php index 93948ea..687dfbb 100755 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -8,7 +8,10 @@ 'Objects' => ['url' => 'api/objects'], ], 'routes' => [ - ['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'], + ['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'], ['name' => 'registers#objects', 'url' => '/api/registers-objects/{register}/{schema}', 'verb' => 'GET'], + ['name' => 'schemas#upload', 'url' => '/api/schemas/upload', 'verb' => 'POST'], +// ['name' => 'schemas#upload', 'url' => '/api/schemas/upload', 'verb' => 'PUT'], + ['name' => 'schemas#download', 'url' => '/api/schemas/{id}/download', 'verb' => 'GET'], ], ]; diff --git a/lib/Controller/SchemasController.php b/lib/Controller/SchemasController.php index 82f4223..5455136 100644 --- a/lib/Controller/SchemasController.php +++ b/lib/Controller/SchemasController.php @@ -2,10 +2,12 @@ namespace OCA\OpenRegister\Controller; +use OCA\OpenRegister\Service\DownloadService; use OCA\OpenRegister\Service\ObjectService; use OCA\OpenRegister\Service\SearchService; use OCA\OpenRegister\Db\Schema; use OCA\OpenRegister\Db\SchemaMapper; +use OCA\OpenRegister\Service\UploadService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\JSONResponse; @@ -25,7 +27,9 @@ public function __construct( $appName, IRequest $request, private readonly IAppConfig $config, - private readonly SchemaMapper $schemaMapper + private readonly SchemaMapper $schemaMapper, + private readonly DownloadService $downloadService, + private readonly UploadService $uploadService ) { parent::__construct($appName, $request); @@ -33,7 +37,7 @@ public function __construct( /** * Returns the template of the main app's page - * + * * This method renders the main page of the application, adding any necessary data to the template. * * @NoAdminRequired @@ -42,17 +46,17 @@ public function __construct( * @return TemplateResponse The rendered template response */ public function page(): TemplateResponse - { + { return new TemplateResponse( 'openconnector', 'index', [] ); } - + /** * Retrieves a list of all schemas - * + * * This method returns a JSON response containing an array of all schemas in the system. * * @NoAdminRequired @@ -69,12 +73,12 @@ public function index(ObjectService $objectService, SearchService $searchService $searchConditions = $searchService->createMySQLSearchConditions(filters: $filters, fieldsToSearch: $fieldsToSearch); $filters = $searchService->unsetSpecialQueryParams(filters: $filters); - return new JSONResponse(['results' => $this->schemaMapper->findAll(limit: null, offset: null, filters: $filters, searchConditions: $searchConditions, searchParams: $searchParams)]); + return new JSONResponse(['results' => $this->schemaMapper->findAll(filters: $filters, searchConditions: $searchConditions, searchParams: $searchParams)]); } /** * Retrieves a single schema by its ID - * + * * This method returns a JSON response containing the details of a specific schema. * * @NoAdminRequired @@ -94,7 +98,7 @@ public function show(string $id): JSONResponse /** * Creates a new schema - * + * * This method creates a new schema based on POST data. * * @NoAdminRequired @@ -111,23 +115,23 @@ public function create(): JSONResponse unset($data[$key]); } } - + if (isset($data['id'])) { unset($data['id']); } - + return new JSONResponse($this->schemaMapper->createFromArray(object: $data)); } /** * Updates an existing schema - * + * * This method updates an existing schema based on its ID. * * @NoAdminRequired * @NoCSRFRequired * - * @param string $id The ID of the schema to update + * @param int $id The ID of the schema to update * @return JSONResponse A JSON response containing the updated schema details */ public function update(int $id): JSONResponse @@ -142,24 +146,141 @@ public function update(int $id): JSONResponse if (isset($data['id'])) { unset($data['id']); } - return new JSONResponse($this->schemaMapper->updateFromArray(id: (int) $id, object: $data)); + return new JSONResponse($this->schemaMapper->updateFromArray(id: $id, object: $data)); } /** * Deletes a schema - * + * * This method deletes a schema based on its ID. * * @NoAdminRequired * @NoCSRFRequired * - * @param string $id The ID of the schema to delete + * @param int $id The ID of the schema to delete * @return JSONResponse An empty JSON response */ public function destroy(int $id): JSONResponse { - $this->schemaMapper->delete($this->schemaMapper->find((int) $id)); + $this->schemaMapper->delete($this->schemaMapper->find(id: $id)); return new JSONResponse([]); } -} \ No newline at end of file + + /** + * Transforms a php array (decoded json input) to a php array that can be used to create a new Schema object. + * + * @param array $jsonArray A php array (decoded json input) to translate/map. + * + * @return array The transformed array. + */ + private function jsonToSchema(array $jsonArray): array + { + $schemaObject = $this->uploadService->mapJsonSchema($jsonArray); + + // @todo Should we do custom mappings here or in the 'abstract' function in UploadService? + $schemaObject = array_merge($schemaObject, [ + 'summary' => $jsonArray['description'], + 'required' => $jsonArray['required'], + 'properties' => $jsonArray['properties'] + ]); + + return $schemaObject; + } + + /** + * Creates a new Schema object using a json text/string as input. Uses 'json' from POST body. + * @todo Optionally a 'url' can be used instead to get a json file from somewhere else and use that instead. + * @todo Or a .json file can be uploaded using key 'file'. + * @todo move most of this code to a (new?) UploadService and make it even more abstract and reusable? + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @return JSONResponse + */ + public function upload(): JSONResponse + { + $data = $this->request->getParams(); + + foreach ($data as $key => $value) { + if (str_starts_with($key, '_')) { + unset($data[$key]); + } + } + + // Define the allowed keys + $allowedKeys = ['file', 'url', 'json']; + + // Find which of the allowed keys are in the array + $matchingKeys = array_intersect_key($data, array_flip($allowedKeys)); + + // Check if there is exactly one matching key + if (count($matchingKeys) === 0) { + return new JSONResponse(data: ['error' => 'Missing one of these keys in your POST body: file, url or json.'], statusCode: 400); + } + if (count($matchingKeys) > 1) { + return new JSONResponse(data: ['error' => 'Please use one of these keys: file, url or json, not multiple.'], statusCode: 400); + } + + if (empty($data['file']) === false) { + // @todo use .json file content from POST as $json + $data['json'] = []; + } + + if (empty($data['url']) === false) { + // @todo get .json file using CallService and use that as $json + $data['json'] = []; + } + + $jsonArray = $data['json']; + + $schemaObject = $this->JsonToSchema(jsonArray: $jsonArray); + + $schema = $this->schemaMapper->createFromArray(object: $schemaObject); + + return new JSONResponse($schema); + } + + /** + * Creates and return a json file for a Schema. + * @todo move most of this code to DownloadService and make it even more Abstract using Entity->jsonSerialize instead of Schema->jsonSerialize, etc. + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param int $id The ID of the schema to return json file for + * @return JSONResponse A json Response containing the json + */ + public function download(int $id): JSONResponse + { + try { + $schema = $this->schemaMapper->find($id); + } catch (DoesNotExistException $exception) { + return new JSONResponse(data: ['error' => 'Not Found'], statusCode: 404); + } + + $contentType = $this->request->getHeader('Content-Type'); + + if (empty($contentType) === true) { + return new JSONResponse(data: ['error' => 'Request is missing header Content-Type'], statusCode: 400); + } + + switch ($contentType) { + case 'application/json': + $type = 'json'; + $responseData = [ + 'jsonArray' => $schema->jsonSerialize(), + 'jsonString' => json_encode($schema->jsonSerialize()) + ]; + break; + default: + return new JSONResponse(data: ['error' => "The Content-Type $contentType is not supported."], statusCode: 400); + } + + // @todo Create a downloadable json file and return it. + $file = $this->downloadService->download(type: $type); + + return new JSONResponse($responseData); + } +} diff --git a/lib/Service/DownloadService.php b/lib/Service/DownloadService.php new file mode 100644 index 0000000..981b62d --- /dev/null +++ b/lib/Service/DownloadService.php @@ -0,0 +1,26 @@ + $input['title'] ?? '', + 'reference' => $input['$id'] ?? '', + 'version' => $input['version'] ?? '', + 'description' => $input['description'] ?? '' + ]; + } + +} From a900f9d603085a937ae62fb417ed410286d184b4 Mon Sep 17 00:00:00 2001 From: Wilco Louwerse Date: Mon, 7 Oct 2024 16:39:02 +0200 Subject: [PATCH 02/38] Inline this variable in SchemasController --- lib/Controller/SchemasController.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Controller/SchemasController.php b/lib/Controller/SchemasController.php index 5455136..4a59788 100644 --- a/lib/Controller/SchemasController.php +++ b/lib/Controller/SchemasController.php @@ -179,13 +179,11 @@ private function jsonToSchema(array $jsonArray): array $schemaObject = $this->uploadService->mapJsonSchema($jsonArray); // @todo Should we do custom mappings here or in the 'abstract' function in UploadService? - $schemaObject = array_merge($schemaObject, [ + return array_merge($schemaObject, [ 'summary' => $jsonArray['description'], 'required' => $jsonArray['required'], 'properties' => $jsonArray['properties'] ]); - - return $schemaObject; } /** From b09232d580c27d028aa638b9330aa6bf23c1e14d Mon Sep 17 00:00:00 2001 From: Wilco Louwerse Date: Mon, 7 Oct 2024 16:56:12 +0200 Subject: [PATCH 03/38] add working code for url instead of json in post schemas/upload --- lib/Controller/SchemasController.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/Controller/SchemasController.php b/lib/Controller/SchemasController.php index 4a59788..5aff521 100644 --- a/lib/Controller/SchemasController.php +++ b/lib/Controller/SchemasController.php @@ -2,6 +2,7 @@ namespace OCA\OpenRegister\Controller; +use GuzzleHttp\Client; use OCA\OpenRegister\Service\DownloadService; use OCA\OpenRegister\Service\ObjectService; use OCA\OpenRegister\Service\SearchService; @@ -32,6 +33,7 @@ public function __construct( private readonly UploadService $uploadService ) { + $this->client = new Client([]); parent::__construct($appName, $request); } @@ -227,8 +229,20 @@ public function upload(): JSONResponse } if (empty($data['url']) === false) { - // @todo get .json file using CallService and use that as $json - $data['json'] = []; + // @todo move to function (cleanup) + try { + $response = $this->client->request('GET', $data['url']); + } catch (GuzzleHttp\Exception\BadResponseException $e) { + $response = $e->getResponse(); + return new JSONResponse(data: ['error' => 'Failed to do a GET api-call on url: '.$data['url']], statusCode: 400); + } + + $responseBody = $response->getBody()->getContents(); + // @todo use Conten-Type header in response instead? + if (is_string($responseBody) === true) { + $responseBody = json_decode(json: $responseBody, associative: true); + } + $data['json'] = $responseBody; } $jsonArray = $data['json']; From 2673120a67163fb5f28efc4e6569b7b635d5277e Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 7 Oct 2024 21:24:16 +0200 Subject: [PATCH 04/38] All the changes --- appinfo/routes.php | 1 + lib/Controller/ObjectsController.php | 41 ++++++- lib/Db/ObjectAuditLog.php | 78 ++++++++++++++ lib/Db/ObjectAuditLogMapper.php | 85 +++++++++++++++ lib/Db/ObjectEntity.php | 3 + lib/Db/ObjectEntityMapper.php | 13 ++- lib/Db/Register.php | 6 ++ lib/Db/RegisterMapper.php | 23 ++-- lib/Db/Schema.php | 11 +- lib/Db/SchemaMapper.php | 22 ++-- lib/Db/Source.php | 6 ++ lib/Db/SourceMapper.php | 22 ++-- lib/Migration/Version1Date20240924200009.php | 31 +++++- lib/Service/ValidationService.php | 22 ++++ src/modals/Modals.vue | 3 + src/modals/schema/EditSchema.vue | 41 +------ src/modals/schema/UploadSchema.vue | 108 +++++++++++++++++++ src/store/modules/schema.js | 39 +++++++ src/views/object/ObjectDetails.vue | 2 +- src/views/schema/SchemaDetails.vue | 18 ++++ 20 files changed, 503 insertions(+), 72 deletions(-) create mode 100644 lib/Db/ObjectAuditLog.php create mode 100644 lib/Db/ObjectAuditLogMapper.php create mode 100755 lib/Service/ValidationService.php create mode 100644 src/modals/schema/UploadSchema.vue diff --git a/appinfo/routes.php b/appinfo/routes.php index 93948ea..6a2440b 100755 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -10,5 +10,6 @@ 'routes' => [ ['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'], ['name' => 'registers#objects', 'url' => '/api/registers-objects/{register}/{schema}', 'verb' => 'GET'], + ['name' => 'objects#logs', 'url' => '/api/objects-logs/{id}', 'verb' => 'GET'], ], ]; diff --git a/lib/Controller/ObjectsController.php b/lib/Controller/ObjectsController.php index 9cc6802..032a90e 100644 --- a/lib/Controller/ObjectsController.php +++ b/lib/Controller/ObjectsController.php @@ -6,6 +6,7 @@ use OCA\OpenRegister\Service\SearchService; use OCA\OpenRegister\Db\ObjectEntity; use OCA\OpenRegister\Db\ObjectEntityMapper; +use OCA\OpenRegister\Db\ObjectAuditLogMapper; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\JSONResponse; @@ -25,7 +26,8 @@ public function __construct( $appName, IRequest $request, private readonly IAppConfig $config, - private readonly ObjectEntityMapper $objectEntityMapper + private readonly ObjectEntityMapper $objectEntityMapper, + private readonly ObjectAuditLogMapper $objectAuditLogMapper ) { parent::__construct($appName, $request); @@ -162,4 +164,41 @@ public function destroy(int $id): JSONResponse return new JSONResponse([]); } + + /** + * Retrieves call logs for a object + * + * This method returns all the call logs associated with a object based on its ID. + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param int $id The ID of the object to retrieve logs for + * @return JSONResponse A JSON response containing the call logs + */ + public function contracts(int $id): JSONResponse + { + // @todo + } + + /** + * Retrieves call logs for a object + * + * This method returns all the call logs associated with a object based on its ID. + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param int $id The ID of the object to retrieve logs for + * @return JSONResponse A JSON response containing the call logs + */ + public function logs(int $id): JSONResponse + { + try { + $jobLogs = $this->objectAuditLogMapper->findAll(null, null, ['object_id' => $id]); + return new JSONResponse($jobLogs); + } catch (DoesNotExistException $e) { + return new JSONResponse(['error' => 'Logs not found'], 404); + } + } } \ No newline at end of file diff --git a/lib/Db/ObjectAuditLog.php b/lib/Db/ObjectAuditLog.php new file mode 100644 index 0000000..211bc3f --- /dev/null +++ b/lib/Db/ObjectAuditLog.php @@ -0,0 +1,78 @@ +addType(fieldName:'uuid', type: 'string'); + $this->addType(fieldName:'objectId', type: 'string'); + $this->addType(fieldName:'changes', type: 'json'); + $this->addType(fieldName:'expires', type: 'datetime'); + $this->addType(fieldName:'created', type: 'datetime'); + $this->addType(fieldName:'userId', type: 'string'); + } + + public function getJsonFields(): array + { + return array_keys( + array_filter($this->getFieldTypes(), function ($field) { + return $field === 'json'; + }) + ); + } + + public function hydrate(array $object): self + { + $jsonFields = $this->getJsonFields(); + + foreach($object as $key => $value) { + if (in_array($key, $jsonFields) === true && $value === []) { + $value = null; + } + + $method = 'set'.ucfirst($key); + + try { + $this->$method($value); + } catch (\Exception $exception) { + } + } + + return $this; + } + + public function jsonSerialize(): array + { + return [ + 'id' => $this->id, + 'uuid' => $this->uuid, + 'objectId' => $this->objectId, + 'changes' => $this->changes, + 'expires' => isset($this->expires) ? $this->expires->format('c') : null, + 'created' => isset($this->created) ? $this->created->format('c') : null, + 'userId' => $this->userId + ]; + } +} \ No newline at end of file diff --git a/lib/Db/ObjectAuditLogMapper.php b/lib/Db/ObjectAuditLogMapper.php new file mode 100644 index 0000000..1399de9 --- /dev/null +++ b/lib/Db/ObjectAuditLogMapper.php @@ -0,0 +1,85 @@ +db->getQueryBuilder(); + + $qb->select('*') + ->from('openregister_object_audit_logs') + ->where( + $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) + ); + + return $this->findEntity(query: $qb); + } + + public function findAll(?int $limit = null, ?int $offset = null, ?array $filters = [], ?array $searchConditions = [], ?array $searchParams = []): array + { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from('openregister_object_audit_logs') + ->setMaxResults($limit) + ->setFirstResult($offset); + + foreach($filters as $filter => $value) { + if ($value === 'IS NOT NULL') { + $qb->andWhere($qb->expr()->isNotNull($filter)); + } elseif ($value === 'IS NULL') { + $qb->andWhere($qb->expr()->isNull($filter)); + } else { + $qb->andWhere($qb->expr()->eq($filter, $qb->createNamedParameter($value))); + } + } + + if (!empty($searchConditions)) { + $qb->andWhere('(' . implode(' OR ', $searchConditions) . ')'); + foreach ($searchParams as $param => $value) { + $qb->setParameter($param, $value); + } + } + + return $this->findEntities(query: $qb); + } + + public function createFromArray(array $object): ObjectAuditLog + { + $obj = new ObjectAuditLog(); + $obj->hydrate($object); + // Set uuid + if($obj->getUuid() === null){ + $obj->setUuid(Uuid::v4()); + } + + return $this->insert(entity: $obj); + } + + public function updateFromArray(int $id, array $object): ObjectAuditLog + { + $obj = $this->find($id); + $obj->hydrate($object); + + // Set or update the version + $version = explode('.', $obj->getVersion()); + $version[2] = (int)$version[2] + 1; + $obj->setVersion(implode('.', $version)); + + return $this->update($obj); + } +} diff --git a/lib/Db/ObjectEntity.php b/lib/Db/ObjectEntity.php index b8a56e0..6cb3359 100644 --- a/lib/Db/ObjectEntity.php +++ b/lib/Db/ObjectEntity.php @@ -9,6 +9,7 @@ class ObjectEntity extends Entity implements JsonSerializable { protected ?string $uuid = null; + protected ?string $version = null; protected ?string $register = null; protected ?string $schema = null; protected ?array $object = []; @@ -17,6 +18,7 @@ class ObjectEntity extends Entity implements JsonSerializable public function __construct() { $this->addType(fieldName:'uuid', type: 'string'); + $this->addType(fieldName:'version', type: 'string'); $this->addType(fieldName:'register', type: 'string'); $this->addType(fieldName:'schema', type: 'string'); $this->addType(fieldName:'object', type: 'json'); @@ -64,6 +66,7 @@ public function jsonSerialize(): array 'id' => $this->id, 'uuid' => $this->uuid, 'register' => $this->register, + 'version' => $this->version, 'schema' => $this->schema, 'object' => $this->object, 'updated' => isset($this->updated) ? $this->updated->format('c') : null, diff --git a/lib/Db/ObjectEntityMapper.php b/lib/Db/ObjectEntityMapper.php index 59bc128..5af4b1f 100644 --- a/lib/Db/ObjectEntityMapper.php +++ b/lib/Db/ObjectEntityMapper.php @@ -131,20 +131,23 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters public function createFromArray(array $object): ObjectEntity { $obj = new ObjectEntity(); - $obj->hydrate(object: $object); + $obj->hydrate($object); + // Set uuid if($obj->getUuid() === null){ $obj->setUuid(Uuid::v4()); } - return $this->insert(entity: $obj); + return $this->insert($obj); } public function updateFromArray(int $id, array $object): ObjectEntity { $obj = $this->find($id); $obj->hydrate($object); - if($obj->getUuid() === null){ - $obj->setUuid(Uuid::v4()); - } + + // Set or update the version + $version = explode('.', $obj->getVersion()); + $version[2] = (int)$version[2] + 1; + $obj->setVersion(implode('.', $version)); return $this->update($obj); } diff --git a/lib/Db/Register.php b/lib/Db/Register.php index 4d289fc..6ec97d4 100644 --- a/lib/Db/Register.php +++ b/lib/Db/Register.php @@ -8,8 +8,10 @@ class Register extends Entity implements JsonSerializable { + protected ?string $uuid = null; protected ?string $title = null; protected ?string $description = null; + protected ?string $version = null; protected ?array $schemas = []; protected ?string $source = null; protected ?string $tablePrefix = null; @@ -17,8 +19,10 @@ class Register extends Entity implements JsonSerializable protected ?DateTime $created = null; public function __construct() { + $this->addType('uuid', 'string'); $this->addType(fieldName: 'title', type: 'string'); $this->addType(fieldName: 'description', type: 'string'); + $this->addType(fieldName: 'version', type: 'string'); $this->addType(fieldName: 'schemas', type: 'json'); $this->addType(fieldName: 'source', type: 'string'); $this->addType(fieldName: 'tablePrefix', type: 'string'); @@ -64,8 +68,10 @@ public function jsonSerialize(): array { return [ 'id' => $this->id, + 'uuid' => $this->uuid, 'title' => $this->title, 'description' => $this->description, + 'version' => $this->version, 'schemas' => $this->schemas, 'source' => $this->source, 'tablePrefix' => $this->tablePrefix, diff --git a/lib/Db/RegisterMapper.php b/lib/Db/RegisterMapper.php index 2e16cb2..8e18fac 100644 --- a/lib/Db/RegisterMapper.php +++ b/lib/Db/RegisterMapper.php @@ -7,6 +7,7 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use Symfony\Component\Uid\Uuid; class RegisterMapper extends QBMapper { @@ -59,16 +60,26 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters public function createFromArray(array $object): Register { - $register = new Register(); - $register->hydrate(object: $object); - return $this->insert(entity: $register); + $obj = new Register(); + $obj->hydrate($object); + // Set uuid + if($obj->getUuid() === null){ + $obj->setUuid(Uuid::v4()); + } + + return $this->insert(entity: $obj); } public function updateFromArray(int $id, array $object): Register { - $register = $this->find($id); - $register->hydrate($object); + $obj = $this->find($id); + $obj->hydrate($object); + + // Set or update the version + $version = explode('.', $obj->getVersion()); + $version[2] = (int)$version[2] + 1; + $obj->setVersion(implode('.', $version)); - return $this->update($register); + return $this->update($obj); } } diff --git a/lib/Db/Schema.php b/lib/Db/Schema.php index 91afec6..80e3efb 100644 --- a/lib/Db/Schema.php +++ b/lib/Db/Schema.php @@ -8,21 +8,21 @@ class Schema extends Entity implements JsonSerializable { + protected ?string $uuid = null; protected ?string $title = null; - protected ?string $version = null; protected ?string $description = null; + protected ?string $version = null; protected ?string $summary = null; protected ?array $required = []; protected ?array $properties = []; - protected ?array $archive = []; - protected ?string $source = null; protected ?DateTime $updated = null; protected ?DateTime $created = null; public function __construct() { + $this->addType('uuid', 'string'); $this->addType(fieldName: 'title', type: 'string'); - $this->addType(fieldName: 'version', type: 'string'); $this->addType(fieldName: 'description', type: 'string'); + $this->addType(fieldName: 'version', type: 'string'); $this->addType(fieldName: 'summary', type: 'string'); $this->addType(fieldName: 'required', type: 'json'); $this->addType(fieldName: 'properties', type: 'json'); @@ -93,9 +93,10 @@ public function jsonSerialize(): array $array = [ 'id' => $this->id, + 'uuid' => $this->uuid, 'title' => $this->title, - 'version' => $this->version, 'description' => $this->description, + 'version' => $this->version, 'summary' => $this->summary, 'required' => $this->required, 'properties' => $properties, diff --git a/lib/Db/SchemaMapper.php b/lib/Db/SchemaMapper.php index 5750eea..3f7761f 100644 --- a/lib/Db/SchemaMapper.php +++ b/lib/Db/SchemaMapper.php @@ -7,6 +7,7 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use Symfony\Component\Uid\Uuid; class SchemaMapper extends QBMapper { @@ -59,16 +60,25 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters public function createFromArray(array $object): Schema { - $schema = new Schema(); - $schema->hydrate(object: $object); - return $this->insert(entity: $schema); + $obj = new Schema(); + $obj->hydrate($object); + // Set uuid + if($obj->getUuid() === null){ + $obj->setUuid(Uuid::v4()); + } + return $this->insert(entity: $obj); } public function updateFromArray(int $id, array $object): Schema { - $schema = $this->find($id); - $schema->hydrate($object); + $obj = $this->find($id); + $obj->hydrate($object); + + // Set or update the version + $version = explode('.', $obj->getVersion()); + $version[2] = (int)$version[2] + 1; + $obj->setVersion(implode('.', $version)); - return $this->update($schema); + return $this->update($obj); } } diff --git a/lib/Db/Source.php b/lib/Db/Source.php index 1a8b928..697e7f2 100644 --- a/lib/Db/Source.php +++ b/lib/Db/Source.php @@ -8,16 +8,20 @@ class Source extends Entity implements JsonSerializable { + protected ?string $uuid = null; protected ?string $title = null; protected ?string $description = null; + protected ?string $version = null; protected ?string $databaseUrl = null; protected ?string $type = null; protected ?DateTime $updated = null; protected ?DateTime $created = null; public function __construct() { + $this->addType('uuid', 'string'); $this->addType(fieldName: 'title', type: 'string'); $this->addType(fieldName: 'description', type: 'string'); + $this->addType(fieldName: 'version', type: 'string'); $this->addType(fieldName: 'databaseUrl', type: 'string'); $this->addType(fieldName: 'type', type: 'string'); $this->addType(fieldName: 'updated', type: 'datetime'); @@ -60,8 +64,10 @@ public function jsonSerialize(): array { return [ 'id' => $this->id, + 'uuid' => $this->uuid, 'title' => $this->title, 'description' => $this->description, + 'version' => $this->version, 'databaseUrl' => $this->databaseUrl, 'type' => $this->type, 'updated' => isset($this->updated) ? $this->updated->format('c') : null, diff --git a/lib/Db/SourceMapper.php b/lib/Db/SourceMapper.php index 65034c7..cebef6e 100644 --- a/lib/Db/SourceMapper.php +++ b/lib/Db/SourceMapper.php @@ -7,6 +7,7 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use Symfony\Component\Uid\Uuid; class SourceMapper extends QBMapper { @@ -59,16 +60,25 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters public function createFromArray(array $object): Source { - $source = new Source(); - $source->hydrate(object: $object); - return $this->insert(entity: $source); + $obj = new Source(); + $obj->hydrate($object); + // Set uuid + if($obj->getUuid() === null){ + $obj->setUuid(Uuid::v4()); + } + return $this->insert(entity: $obj); } public function updateFromArray(int $id, array $object): Source { - $source = $this->find($id); - $source->hydrate($object); + $obj = $this->find($id); + $obj->hydrate($object); + + // Set or update the version + $version = explode('.', $obj->getVersion()); + $version[2] = (int)$version[2] + 1; + $obj->setVersion(implode('.', $version)); - return $this->update($source); + return $this->update($obj); } } diff --git a/lib/Migration/Version1Date20240924200009.php b/lib/Migration/Version1Date20240924200009.php index de0db7a..cb226f8 100755 --- a/lib/Migration/Version1Date20240924200009.php +++ b/lib/Migration/Version1Date20240924200009.php @@ -41,8 +41,10 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt if (!$schema->hasTable('openregister_sources')) { $table = $schema->createTable('openregister_sources'); $table->addColumn('id', Types::BIGINT, ['autoincrement' => true, 'notnull' => true]); + $table->addColumn('uuid', Types::STRING, ['notnull' => true, 'length' => 255]); $table->addColumn('title', Types::STRING, ['notnull' => true, 'length' => 255]); $table->addColumn('description', Types::TEXT, ['notnull' => false]); + $table->addColumn('version', Types::STRING, ['notnull' => true, 'length' => 255, 'default' => '0.0.1']); $table->addColumn('database_url', Types::STRING, ['notnull' => true, 'length' => 255]); $table->addColumn('type', Types::STRING, ['notnull' => true, 'length' => 64]); $table->addColumn('updated', Types::DATETIME, ['notnull' => true, 'default' => 'CURRENT_TIMESTAMP']); @@ -51,13 +53,15 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table->setPrimaryKey(['id']); $table->addIndex(['title'], 'register_sources_title_index'); $table->addIndex(['type'], 'register_sources_type_index'); + $table->addIndex(['uuid'], 'register_sources_uuid_index'); } if (!$schema->hasTable('openregister_schemas')) { $table = $schema->createTable('openregister_schemas'); $table->addColumn('id', Types::BIGINT, ['autoincrement' => true, 'notnull' => true]); + $table->addColumn('uuid', Types::STRING, ['notnull' => true, 'length' => 255]); + $table->addColumn('version', Types::STRING, ['notnull' => true, 'length' => 255, 'default' => '0.0.1']); $table->addColumn('title', Types::STRING, ['notnull' => true, 'length' => 255]); - $table->addColumn('version', Types::STRING, ['notnull' => true, 'length' => 64]); $table->addColumn('description', Types::TEXT, ['notnull' => false]); $table->addColumn('summary', Types::TEXT, ['notnull' => false]); $table->addColumn('required', Types::JSON, ['notnull' => false]); @@ -67,12 +71,14 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table->setPrimaryKey(['id']); $table->addIndex(['title'], 'register_schemas_title_index'); - $table->addIndex(['source'], 'register_schemas_source_index'); + $table->addIndex(['uuid'], 'register_schemas_uuid_index'); } if (!$schema->hasTable('openregister_registers')) { $table = $schema->createTable('openregister_registers'); $table->addColumn('id', Types::BIGINT, ['autoincrement' => true, 'notnull' => true]); + $table->addColumn('uuid', Types::STRING, ['notnull' => true, 'length' => 255]); + $table->addColumn('version', Types::STRING, ['notnull' => true, 'length' => 255, 'default' => '0.0.1']); $table->addColumn('title', Types::STRING, ['notnull' => true, 'length' => 255]); $table->addColumn('description', Types::TEXT, ['notnull' => false]); $table->addColumn('schemas', Types::JSON, ['notnull' => false]); @@ -84,12 +90,14 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table->setPrimaryKey(['id']); $table->addIndex(['title'], 'registers_title_index'); $table->addIndex(['source'], 'registers_source_index'); + $table->addIndex(['uuid'], 'registers_uuid_index'); } if (!$schema->hasTable('openregister_objects')) { $table = $schema->createTable('openregister_objects'); $table->addColumn('id', Types::BIGINT, ['autoincrement' => true, 'notnull' => true]); $table->addColumn('uuid', Types::STRING, ['notnull' => true, 'length' => 255]); + $table->addColumn('version', Types::STRING, ['notnull' => true, 'length' => 255, 'default' => '0.0.1']); $table->addColumn('register', Types::STRING, ['notnull' => true, 'length' => 255]); $table->addColumn('schema', Types::STRING, ['notnull' => true, 'length' => 255]); $table->addColumn('object', Types::JSON, ['notnull' => false]); @@ -101,6 +109,25 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table->addIndex(['schema'], 'object_entity_schema'); } + if (!$schema->hasTable('openregister_object_audit_logs')) { + $table = $schema->createTable('openregister_object_audit_logs'); + $table->addColumn('id', Types::BIGINT, ['autoincrement' => true, 'notnull' => true]); + $table->addColumn('uuid', Types::STRING, ['notnull' => true, 'length' => 255]); + $table->addColumn('schema_id', Types::STRING, ['notnull' => true, 'length' => 255]); + $table->addColumn('object_id', Types::STRING, ['notnull' => true, 'length' => 255]); + $table->addColumn('user_id', Types::STRING, ['notnull' => false, 'length' => 255]); + $table->addColumn('session_id', Types::STRING, ['notnull' => false, 'length' => 255]); + $table->addColumn('changes', Types::JSON, ['notnull' => false]); + $table->addColumn('expires', Types::DATETIME, ['notnull' => false]); + $table->addColumn('created', Types::DATETIME, ['notnull' => true, 'default' => 'CURRENT_TIMESTAMP']); + + $table->setPrimaryKey(['id']); + $table->addIndex(['uuid'], 'object_audit_log_uuid'); + $table->addIndex(['schema_id'], 'object_audit_log_schema_id'); + $table->addIndex(['object_id'], 'object_audit_log_object_id'); + $table->addIndex(['user_id'], 'object_audit_log_user_id'); + } + return $schema; } diff --git a/lib/Service/ValidationService.php b/lib/Service/ValidationService.php new file mode 100755 index 0000000..2cbf75b --- /dev/null +++ b/lib/Service/ValidationService.php @@ -0,0 +1,22 @@ + + * + * @license EUPL + * + * @category Service + */ +class ValidationService +{ + + +} diff --git a/src/modals/Modals.vue b/src/modals/Modals.vue index 5841d55..f2e77b0 100755 --- a/src/modals/Modals.vue +++ b/src/modals/Modals.vue @@ -5,6 +5,7 @@ + @@ -19,6 +20,7 @@ import EditRegister from './register/EditRegister.vue' import DeleteRegister from './register/DeleteRegister.vue' import EditSchema from './schema/EditSchema.vue' import DeleteSchema from './schema/DeleteSchema.vue' +import UploadSchema from './schema/UploadSchema.vue' import EditSchemaProperty from './schema/EditSchemaProperty.vue' import DeleteSchemaProperty from './schema/DeleteSchemaProperty.vue' import EditSource from './source/EditSource.vue' @@ -33,6 +35,7 @@ export default { DeleteRegister, EditSchema, DeleteSchema, + UploadSchema, EditSchemaProperty, DeleteSchemaProperty, EditSource, diff --git a/src/modals/schema/EditSchema.vue b/src/modals/schema/EditSchema.vue index 185887d..8f68c7e 100644 --- a/src/modals/schema/EditSchema.vue +++ b/src/modals/schema/EditSchema.vue @@ -1,5 +1,5 @@