diff --git a/.github/workflows/pull-request-from-branch-check.yaml b/.github/workflows/pull-request-from-branch-check.yaml
new file mode 100644
index 0000000..77d1970
--- /dev/null
+++ b/.github/workflows/pull-request-from-branch-check.yaml
@@ -0,0 +1,18 @@
+name: Main Branch Protection
+
+on:
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ check-branch:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check branch
+ run: |
+ if [[ ${GITHUB_HEAD_REF} != development ]] && [[ ${GITHUB_HEAD_REF} != documentation ]] && ! [[ ${GITHUB_HEAD_REF} =~ ^hotfix/ ]];
+ then
+ echo "Error: Pull request must come from 'development', 'documentation' or 'hotfix/' branch"
+ exit 1
+ fi
diff --git a/.github/workflows/pull-request-lint-check.yaml b/.github/workflows/pull-request-lint-check.yaml
new file mode 100644
index 0000000..4a8c874
--- /dev/null
+++ b/.github/workflows/pull-request-lint-check.yaml
@@ -0,0 +1,21 @@
+name: Lint Check
+
+on:
+ pull_request:
+ branches:
+ - development
+ - main
+
+jobs:
+ lint-check:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: Install dependencies
+ run: npm i
+
+ - name: Linting
+ run: npm run lint
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 9567c1a..307a966 100755
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -11,6 +11,8 @@
['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', 'requirements' => ['id' => '[^/]+']],
+ ['name' => 'objects#mappings', 'url' => '/api/objects/mappings', 'verb' => 'GET'],
+ ['name' => 'objects#auditTrails', 'url' => '/api/objects/audit-trails/{id}', 'verb' => 'GET', 'requirements' => ['id' => '[^/]+']],
['name' => 'schemas#upload', 'url' => '/api/schemas/upload', 'verb' => 'POST'],
['name' => 'schemas#uploadUpdate', 'url' => '/api/schemas/{id}/upload', 'verb' => 'PUT', 'requirements' => ['id' => '[^/]+']],
['name' => 'schemas#download', 'url' => '/api/schemas/{id}/download', 'verb' => 'GET', 'requirements' => ['id' => '[^/]+']],
diff --git a/docker-compose.yml b/docker-compose.yml
index 375daca..20bbdf0 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -47,3 +47,6 @@ services:
- TZ=Europe/Amsterdam
- NEXTCLOUD_ADMIN_USER=admin
- NEXTCLOUD_ADMIN_PASSWORD=admin
+ depends_on:
+ init-ubuntu:
+ condition: service_completed_successfully
diff --git a/lib/Controller/ObjectsController.php b/lib/Controller/ObjectsController.php
index 12da7d9..f5fc177 100644
--- a/lib/Controller/ObjectsController.php
+++ b/lib/Controller/ObjectsController.php
@@ -13,9 +13,13 @@
use OCP\AppFramework\Http\JSONResponse;
use OCP\DB\Exception;
use OCP\IAppConfig;
-use OCP\IRequest;
+use OCP\IRequest;
+use OCP\App\IAppManager;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\NotFoundExceptionInterface;
use Opis\JsonSchema\Errors\ErrorFormatter;
use Symfony\Component\Uid\Uuid;
+use Psr\Container\ContainerInterface;
class ObjectsController extends Controller
{
@@ -32,6 +36,8 @@ public function __construct(
$appName,
IRequest $request,
private readonly IAppConfig $config,
+ private readonly IAppManager $appManager,
+ private readonly ContainerInterface $container,
private readonly ObjectEntityMapper $objectEntityMapper,
private readonly AuditTrailMapper $auditTrailMapper,
private readonly ObjectAuditLogMapper $objectAuditLogMapper
@@ -111,20 +117,24 @@ public function show(string $id): JSONResponse
}
}
- /**
- * Creates a new object
- *
- * This method creates a new object based on POST data.
- *
- * @NoAdminRequired
- * @NoCSRFRequired
- *
- * @return JSONResponse A JSON response containing the created object
- */
+ /**
+ * Creates a new object
+ *
+ * This method creates a new object based on POST data.
+ *
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ *
+ * @return JSONResponse A JSON response containing the created object
+ * @throws Exception
+ */
public function create(ObjectService $objectService): JSONResponse
{
$data = $this->request->getParams();
$object = $data['object'];
+ $mapping = $data['mapping'] ?? null;
+ $register = $data['register'];
+ $schema = $data['schema'];
foreach ($data as $key => $value) {
if (str_starts_with($key, '_')) {
@@ -136,6 +146,17 @@ public function create(ObjectService $objectService): JSONResponse
unset($data['id']);
}
+ // If mapping ID is provided, transform the object using the mapping
+ $mappingService = $this->getOpenConnectorMappingService();
+
+ if ($mapping !== null && $mappingService !== null) {
+ $mapping = $mappingService->getMapping($mapping);
+
+ $object = $mappingService->executeMapping($mapping, $object);
+ $data['register'] = $register;
+ $data['schema'] = $schema;
+ }
+
// Save the object
try {
$objectEntity = $objectService->saveObject(register: $data['register'], schema: $data['schema'], object: $object);
@@ -163,6 +184,7 @@ public function update(int $id): JSONResponse
{
$data = $this->request->getParams();
$object = $data['object'];
+ $mapping = $data['mapping'] ?? null;
foreach ($data as $key => $value) {
if (str_starts_with($key, '_')) {
@@ -173,6 +195,14 @@ public function update(int $id): JSONResponse
unset($data['id']);
}
+ // If mapping ID is provided, transform the object using the mapping
+ $mappingService = $this->getOpenConnectorMappingService();
+
+ if ($mapping !== null && $mappingService !== null) {
+ $mapping = $mappingService->getMapping($mapping);
+ $data = $mappingService->executeMapping($mapping, $object);
+ }
+
// save it
$oldObject = $this->objectEntityMapper->find($id);
$objectEntity = $this->objectEntityMapper->updateFromArray(id: $id, object: $data);
@@ -265,4 +295,57 @@ public function logs(int $id): JSONResponse
return new JSONResponse(['error' => 'Logs not found'], 404);
}
}
+
+
+
+ /**
+ * Retrieves all available mappings
+ *
+ * This method returns a JSON response containing all available mappings in the system.
+ *
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ *
+ * @return JSONResponse A JSON response containing the list of mappings
+ */
+ public function mappings(): JSONResponse
+ {
+ // Get mapping service, which will return null based on implementation
+ $mappingService = $this->getOpenConnectorMappingService();
+
+ // Initialize results array
+ $results = [];
+
+ // If mapping service exists, get all mappings using find() method
+ if ($mappingService !== null) {
+ $results = $mappingService->getMappings();
+ }
+
+ // Return response with results array and total count
+ return new JSONResponse([
+ 'results' => $results,
+ 'total' => count($results)
+ ]);
+ }
+
+ /**
+ * Attempts to retrieve the OpenRegister service from the container.
+ *
+ * @return mixed|null The OpenRegister service if available, null otherwise.
+ * @throws ContainerExceptionInterface|NotFoundExceptionInterface
+ */
+ public function getOpenConnectorMappingService(): ?\OCA\OpenConnector\Service\MappingService
+ {
+ if (in_array(needle: 'openconnector', haystack: $this->appManager->getInstalledApps()) === true) {
+ try {
+ // Attempt to get the OpenRegister service from the container
+ return $this->container->get('OCA\OpenConnector\Service\MappingService');
+ } catch (Exception $e) {
+ // If the service is not available, return null
+ return null;
+ }
+ }
+
+ return null;
+ }
}
diff --git a/lib/Db/ObjectEntityMapper.php b/lib/Db/ObjectEntityMapper.php
index a883fba..a64af65 100644
--- a/lib/Db/ObjectEntityMapper.php
+++ b/lib/Db/ObjectEntityMapper.php
@@ -16,7 +16,7 @@
/**
* The ObjectEntityMapper class
- *
+ *
* @package OCA\OpenRegister\Db
*/
class ObjectEntityMapper extends QBMapper
@@ -35,7 +35,7 @@ public function __construct(IDBConnection $db, MySQLJsonService $mySQLJsonServic
{
parent::__construct($db, 'openregister_objects');
- if($db->getDatabasePlatform() instanceof MySQLPlatform === true) {
+ if ($db->getDatabasePlatform() instanceof MySQLPlatform === true) {
$this->databaseJsonService = $mySQLJsonService;
}
}
@@ -235,15 +235,15 @@ public function updateFromArray(int $id, array $object): ObjectEntity
*/
public function getFacets(array $filters = [], ?string $search = null)
{
- if(key_exists(key: 'register', array: $filters) === true) {
+ if (key_exists(key: 'register', array: $filters) === true) {
$register = $filters['register'];
}
- if(key_exists(key: 'schema', array: $filters) === true) {
+ if (key_exists(key: 'schema', array: $filters) === true) {
$schema = $filters['schema'];
}
$fields = [];
- if(isset($filters['_queries'])) {
+ if (isset($filters['_queries'])) {
$fields = $filters['_queries'];
}
diff --git a/lib/Db/SchemaMapper.php b/lib/Db/SchemaMapper.php
index 2ca6725..2b83986 100644
--- a/lib/Db/SchemaMapper.php
+++ b/lib/Db/SchemaMapper.php
@@ -11,7 +11,7 @@
/**
* The SchemaMapper class
- *
+ *
* @package OCA\OpenRegister\Db
*/
class SchemaMapper extends QBMapper
@@ -54,7 +54,7 @@ public function find(int $id): Schema
public function findMultiple(array $ids): array
{
$result = [];
- foreach($ids as $id) {
+ foreach ($ids as $id) {
$result[] = $this->find($id);
}
@@ -116,7 +116,7 @@ public function createFromArray(array $object): Schema
if ($schema->getUuid() === null) {
$schema->setUuid(Uuid::v4());
}
-
+
return $this->insert(entity: $schema);
}
diff --git a/lib/Service/MySQLJsonService.php b/lib/Service/MySQLJsonService.php
index f0ff964..3331ef1 100644
--- a/lib/Service/MySQLJsonService.php
+++ b/lib/Service/MySQLJsonService.php
@@ -7,7 +7,7 @@
/**
* Service class for handling MySQL JSON operations
- *
+ *
* This class provides methods for querying and filtering JSON data stored in MySQL,
* including complex filtering, searching, ordering and aggregation functionality.
*/
@@ -23,7 +23,7 @@ class MySQLJsonService implements IDatabaseJsonService
public function orderJson(IQueryBuilder $builder, array $order = []): IQueryBuilder
{
// Loop through each ordering field and direction
- foreach($order as $item=>$direction) {
+ foreach ($order as $item=>$direction) {
// Create parameters for the JSON path and sort direction
$builder->createNamedParameter(value: "$.$item", placeHolder: ":path$item");
$builder->createNamedParameter(value: $direction, placeHolder: ":direction$item");
@@ -108,7 +108,7 @@ private function jsonFilterArray(IQueryBuilder $builder, string $filter, array $
private function getMultipleContains (array $values, string $filter, IQueryBuilder $builder): string
{
$orString = '';
- foreach($values as $key=>$value)
+ foreach ($values as $key=>$value)
{
// Create parameter for each value
$builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR, placeHolder: ":value$filter$key");
@@ -126,7 +126,7 @@ private function getMultipleContains (array $values, string $filter, IQueryBuild
* - Complex filters (after/before)
* - Array filters
* - Simple equality filters
- *
+ *
* @param IQueryBuilder $builder The query builder instance
* @param array $filters Array of filters to apply
* @return IQueryBuilder The modified query builder
@@ -136,7 +136,7 @@ public function filterJson(IQueryBuilder $builder, array $filters): IQueryBuilde
// Remove special system fields from filters
unset($filters['register'], $filters['schema'], $filters['updated'], $filters['created'], $filters['_queries']);
- foreach($filters as $filter=>$value) {
+ foreach ($filters as $filter=>$value) {
// Create parameter for JSON path
$builder->createNamedParameter(value: "$.$filter", placeHolder: ":path$filter");
@@ -179,7 +179,7 @@ public function getAggregations(IQueryBuilder $builder, array $fields, int $regi
{
$facets = [];
- foreach($fields as $field) {
+ foreach ($fields as $field) {
// Create parameter for JSON path
$builder->createNamedParameter(value: "$.$field", placeHolder: ":$field");
diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php
index 90d5113..7be4d13 100755
--- a/lib/Service/ObjectService.php
+++ b/lib/Service/ObjectService.php
@@ -15,6 +15,7 @@
use OCA\OpenRegister\Db\AuditTrailMapper;
use OCA\OpenRegister\Exception\ValidationException;
use OCA\OpenRegister\Formats\BsnFormat;
+use OCP\DB\Exception;
use OCP\IURLGenerator;
use Opis\JsonSchema\ValidationResult;
use Opis\JsonSchema\Validator;
@@ -24,20 +25,20 @@
/**
* Service class for handling object operations
- *
+ *
* This service provides methods for CRUD operations on objects, including:
* - Creating, reading, updating and deleting objects
* - Finding objects by ID/UUID
* - Getting audit trails
* - Extending objects with related data
- *
+ *
* @package OCA\OpenRegister\Service
*/
class ObjectService
{
/** @var int The current register ID */
private int $register;
-
+
/** @var int The current schema ID */
private int $schema;
@@ -58,7 +59,8 @@ public function __construct(
ObjectEntityMapper $objectEntityMapper,
RegisterMapper $registerMapper,
SchemaMapper $schemaMapper,
- AuditTrailMapper $auditTrailMapper
+ AuditTrailMapper $auditTrailMapper,
+ private readonly IURLGenerator $urlGenerator
)
{
$this->objectEntityMapper = $objectEntityMapper;
@@ -67,7 +69,31 @@ public function __construct(
$this->auditTrailMapper = $auditTrailMapper;
}
- /**
+ /**
+ * Validate an object with a schema.
+ * If schema is not given and schemaObject is filled, the object will validate to the schemaObject.
+ *
+ * @param array $object The object to validate.
+ * @param int|null $schemaId The id of the schema to validate to.
+ * @param object $schemaObject A schema object to validate to.
+ *
+ * @return ValidationResult The validation result from opis/json-schema.
+ */
+ public function validateObject(array $object, ?int $schemaId = null, object $schemaObject = new stdClass()): ValidationResult
+ {
+ if ($schemaObject === new stdClass() || $schemaId !== null) {
+ $schemaObject = $this->schemaMapper->find($schemaId)->getSchemaObject($this->urlGenerator);
+ }
+
+ $validator = new Validator();
+ $validator->setMaxErrors(100);
+ $validator->parser()->getFormatResolver()->register('string', 'bsn', new BsnFormat());
+
+ return $validator->validate(data: json_decode(json_encode($object)), schema: $schemaObject);
+
+ }
+
+ /**
* Find an object by ID or UUID
*
* @param int|string $id The ID or UUID to search for
@@ -123,7 +149,7 @@ public function updateFromArray(string $id, array $object, bool $updatedObject)
public function delete(array|\JsonSerializable $object): bool
{
// Convert JsonSerializable objects to array
- if($object instanceof \JsonSerializable === true) {
+ if ($object instanceof \JsonSerializable === true) {
$object = $object->jsonSerialize();
}
@@ -169,11 +195,11 @@ public function findAll(?int $limit = null, ?int $offset = null, array $filters
public function count(array $filters = [], ?string $search = null): int
{
// Add register and schema filters if set
- if($this->getSchema() !== null && $this->getRegister() !== null) {
+ if ($this->getSchema() !== null && $this->getRegister() !== null) {
$filters['register'] = $this->getRegister();
$filters['schema'] = $this->getSchema();
}
-
+
return $this->objectEntityMapper
->countAll(filters: $filters, search: $search);
}
@@ -187,7 +213,7 @@ public function count(array $filters = [], ?string $search = null): int
public function findMultiple(array $ids): array
{
$result = [];
- foreach($ids as $id) {
+ foreach ($ids as $id) {
$result[] = $this->find($id);
}
@@ -242,7 +268,7 @@ private function getDataFromObject(mixed $object) {
public function getObjects(?string $objectType = null, ?int $register = null, ?int $schema = null, ?int $limit = null, ?int $offset = null, array $filters = [], array $sort = [], ?string $search = null): array
{
// Set object type and filters if register and schema are provided
- if($objectType === null && $register !== null && $schema !== null) {
+ if ($objectType === null && $register !== null && $schema !== null) {
$objectType = 'objectEntity';
$filters['register'] = $register;
$filters['schema'] = $schema;
@@ -255,15 +281,17 @@ public function getObjects(?string $objectType = null, ?int $register = null, ?i
return $mapper->findAll(limit: $limit, offset: $offset, filters: $filters, sort: $sort, search: $search);
}
- /**
- * Save an object
- *
- * @param Register|string $register The register to save the object to.
- * @param Schema|string $schema The schema to save the object to.
- * @param array $object The data to be saved.
- *
- * @return ObjectEntity The resulting object.
- */
+ /**
+ * Save an object
+ *
+ * @param int $register The register to save the object to.
+ * @param int $schema The schema to save the object to.
+ * @param array $object The data to be saved.
+ *
+ * @return ObjectEntity The resulting object.
+ * @throws ValidationException When the validation fails and returns an error.
+ * @throws Exception
+ */
public function saveObject(int $register, int $schema, array $object): ObjectEntity
{
// Convert register and schema to their respective objects if they are strings
@@ -275,16 +303,18 @@ public function saveObject(int $register, int $schema, array $object): ObjectEnt
}
// Check if object already exists
- if(isset($object['id']) === true) {
+ if (isset($object['id']) === true) {
$objectEntity = $this->objectEntityMapper->findByUuid(
- $this->registerMapper->find($register),
- $this->schemaMapper->find($schema),
+ $this->registerMapper->find($register),
+ $this->schemaMapper->find($schema),
$object['id']
);
}
+ $validationResult = $this->validateObject(object: $object, schemaId: $schema);
+
// Create new entity if none exists
- if($objectEntity === null){
+ if ($objectEntity === null) {
$objectEntity = new ObjectEntity();
$objectEntity->setRegister($register);
$objectEntity->setSchema($schema);
@@ -301,21 +331,25 @@ public function saveObject(int $register, int $schema, array $object): ObjectEnt
// Store old version for audit trail
$oldObject = clone $objectEntity;
$objectEntity->setObject($object);
-
+
// Ensure UUID exists
if (empty($objectEntity->getUuid())) {
$objectEntity->setUuid(Uuid::v4());
}
- // Update or insert based on whether ID exists
- if($objectEntity->getId()){
- $objectEntity = $this->objectEntityMapper->update($objectEntity);
- $this->auditTrailMapper->createAuditTrail(new: $objectEntity, old: $oldObject);
- }
- else {
- $objectEntity = $this->objectEntityMapper->insert($objectEntity);
- $this->auditTrailMapper->createAuditTrail(new: $objectEntity);
- }
+ $schemaObject = $this->schemaMapper->find($schema);
+
+ if ($objectEntity->getId() && ($schemaObject->getHardValidation() === false || $validationResult->isValid() === true)){
+ $objectEntity = $this->objectEntityMapper->update($objectEntity);
+ $this->auditTrailMapper->createAuditTrail(new: $objectEntity, old: $oldObject);
+ } else if ($schemaObject->getHardValidation() === false || $validationResult->isValid() === true) {
+ $objectEntity = $this->objectEntityMapper->insert($objectEntity);
+ $this->auditTrailMapper->createAuditTrail(new: $objectEntity);
+ }
+
+ if ($validationResult->isValid() === false) {
+ throw new ValidationException(message: 'The object could not be validated', errors: $validationResult->error());
+ }
return $objectEntity;
}
@@ -378,7 +412,7 @@ public function deleteObject(Register $register, Schema $schema, string $uuid):
public function getMapper(?string $objectType = null, ?int $register = null, ?int $schema = null)
{
// Return self if register and schema provided
- if($register !== null && $schema !== null) {
+ if ($register !== null && $schema !== null) {
$this->setSchema($schema);
$this->setRegister($register);
return $this;
@@ -443,7 +477,7 @@ public function getMultipleObjects(string $objectType, array $ids)
public function extendEntity(array $entity, array $extend): array
{
// Convert entity to array if needed
- if(is_array($entity)) {
+ if (is_array($entity)) {
$result = $entity;
} else {
$result = $entity->jsonSerialize();
@@ -509,7 +543,7 @@ public function getRegisters(): array
// Extend with schemas
$extend = ['schemas'];
- if(empty($extend) === false) {
+ if (empty($extend) === false) {
$registers = array_map(function($object) use ($extend) {
return $this->extendEntity(entity: $object, extend: $extend);
}, $registers);
diff --git a/package-lock.json b/package-lock.json
index 3d59b6b..551c1c9 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -30,7 +30,6 @@
"remark-preset-lint-consistent": "^6.0.0",
"remark-preset-lint-recommended": "^7.0.0",
"style-loader": "^3.3.3",
- "uuid": "^10.0.0",
"vue": "^2.7.14",
"vue-apexcharts": "^1.6.2",
"vue-codemirror6": "^1.3.4",
@@ -21213,18 +21212,6 @@
"node": ">= 0.4.0"
}
},
- "node_modules/uuid": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
- "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
@@ -37943,11 +37930,6 @@
"dev": true,
"peer": true
},
- "uuid": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
- "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="
- },
"v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
diff --git a/package.json b/package.json
index 5fae2c2..d199420 100755
--- a/package.json
+++ b/package.json
@@ -41,7 +41,6 @@
"remark-preset-lint-consistent": "^6.0.0",
"remark-preset-lint-recommended": "^7.0.0",
"style-loader": "^3.3.3",
- "uuid": "^10.0.0",
"vue": "^2.7.14",
"vue-apexcharts": "^1.6.2",
"vue-codemirror6": "^1.3.4",
diff --git a/src/entities/auditTrail/index.js b/src/entities/auditTrail/index.js
index 98de241..b49eb51 100644
--- a/src/entities/auditTrail/index.js
+++ b/src/entities/auditTrail/index.js
@@ -1,4 +1,3 @@
export * from './auditTrail.ts'
export * from './auditTrail.types.ts'
export * from './auditTrail.mock.ts'
-
diff --git a/src/entities/schema/schema.ts b/src/entities/schema/schema.ts
index 4239d09..4dcd6d9 100644
--- a/src/entities/schema/schema.ts
+++ b/src/entities/schema/schema.ts
@@ -31,7 +31,7 @@ export class Schema implements TSchema {
const schema = z.object({
id: z.string().min(1),
title: z.string().min(1),
- version: z.string(),
+ version: z.string().regex(/^(?:\d+\.){2}\d+$/g, 'Invalid version format'),
description: z.string(),
summary: z.string(),
required: z.array(z.string()),
diff --git a/src/modals/Modals.vue b/src/modals/Modals.vue
index 3bb513b..fc22305 100755
--- a/src/modals/Modals.vue
+++ b/src/modals/Modals.vue
@@ -8,15 +8,16 @@ import { navigationStore } from '../store/store.js'
Object successfully uploaded {{ error }} Object
- Wil je {{ registerStore.registerItem.title }} definitief verwijderen? Deze actie kan niet ongedaan worden gemaakt. + Wil je {{ registerStore.registerItem?.title }} definitief verwijderen? Deze actie kan niet ongedaan worden gemaakt.
- Do you want to permanently delete {{ schemaStore.schemaItem.title }}? This action cannot be undone. + Do you want to permanently delete {{ schemaStore.schemaItem?.title }}? This action cannot be undone.
- Do you want to permanently delete {{ sourceStore.sourceItem.title }}? This action cannot be undone. + Do you want to permanently delete {{ sourceStore.sourceItem?.title }}? This action cannot be undone.