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 00000000..a5476c9c --- /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 \ No newline at end of file diff --git a/appinfo/routes.php b/appinfo/routes.php index 27548e58..f96805d7 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -22,6 +22,8 @@ ['name' => 'synchronizations#test', 'url' => '/api/synchronizations-test/{id}', 'verb' => 'POST'], // Mapping endpoints ['name' => 'mappings#test', 'url' => '/api/mappings/test', 'verb' => 'POST'], + ['name' => 'mappings#saveObject', 'url' => '/api/mappings/objects', 'verb' => 'POST'], + ['name' => 'mappings#getObjects', 'url' => '/api/mappings/objects', 'verb' => 'GET'], // Running endpoints ['name' => 'endpoints#run', 'url' => '/api/v1/{endpoint}', 'verb' => 'GET'], ['name' => 'endpoints#run', 'url' => '/api/v1/{endpoint}', 'verb' => 'PUT'], diff --git a/lib/Controller/MappingsController.php b/lib/Controller/MappingsController.php index f3ab9032..bc48a2b6 100644 --- a/lib/Controller/MappingsController.php +++ b/lib/Controller/MappingsController.php @@ -31,7 +31,8 @@ public function __construct( IRequest $request, private readonly IAppConfig $config, private readonly MappingMapper $mappingMapper, - private readonly MappingService $mappingService + private readonly MappingService $mappingService, + private readonly ObjectService $objectService ) { parent::__construct($appName, $request); @@ -291,4 +292,47 @@ public function test(ObjectService $objectService, IURLGenerator $urlGenerator): 'validationErrors' => $validationErrors ]); } + + /** + * Saves a mapping object + * + * This method saves a mapping object based on POST data. + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function saveObject(): JSONResponse + { + // Check if the OpenRegister service is available + $openRegisters = $this->objectService->getOpenRegisters(); + if ($openRegisters !== null) { + $data = $this->request->getParams(); + return new JSONResponse($openRegisters->saveObject($data['register'], $data['schema'], $data['object'])); + } + } + + /** + * Retrieves a list of objects to map to + * + * This method retrieves a list of objects to map to based on GET data. + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function getObjects(): JSONResponse + { + // Check if the OpenRegister service is available + $openRegisters = $this->objectService->getOpenRegisters(); + $data = []; + if ($openRegisters !== null) { + $data['openRegisters'] = true; + $data['availableRegisters'] = $openRegisters->getRegisters(); + } + else { + $data['openRegisters'] = false; + } + + return new JSONResponse($data); + + } } diff --git a/lib/Db/CallLog.php b/lib/Db/CallLog.php index c8c40c83..9dc69b53 100644 --- a/lib/Db/CallLog.php +++ b/lib/Db/CallLog.php @@ -80,8 +80,9 @@ public function jsonSerialize(): array 'synchronizationId' => $this->synchronizationId, 'userId' => $this->userId, 'sessionId' => $this->sessionId, - 'expires' => $this->expires, - 'created' => $this->created, + 'expires' => isset($this->expires) ? $this->expires->format('c') : null, + 'created' => isset($this->created) ? $this->created->format('c') : null, + ]; } } diff --git a/lib/Service/SynchronizationService.php b/lib/Service/SynchronizationService.php index 3acc7d77..920ec6e8 100644 --- a/lib/Service/SynchronizationService.php +++ b/lib/Service/SynchronizationService.php @@ -83,6 +83,9 @@ public function __construct( */ public function synchronize(Synchronization $synchronization, ?bool $isTest = false): array { + if (empty($synchronization->getSourceId()) === true) { + throw new Exception('sourceId of synchronziation cannot be empty. Canceling synchronization..'); + } $objectList = $this->getAllObjectsFromSource(synchronization: $synchronization, isTest: $isTest); @@ -151,7 +154,7 @@ private function getOriginId(Synchronization $synchronization, array $object): i $sourceConfig = $synchronization->getSourceConfig(); // Check if a custom ID position is defined in the source configuration - if (isset($sourceConfig['idPosition']) === true) { + if (isset($sourceConfig['idPosition']) === true && empty($sourceConfig['idPosition']) === false) { // Override default with custom ID position from config $originIdPosition = $sourceConfig['idPosition']; } @@ -391,19 +394,21 @@ public function getAllObjectsFromApi(Synchronization $synchronization, ?bool $is // Current page is 2 because the first call made above is page 1. $currentPage = 2; - $usedNextEndpoint = false; + $useNextEndpoint = false; + if (array_key_exists('next', $body)) { + $useNextEndpoint = true; + } // Continue making API calls if there are more pages from 'next' the response body or if paginationQuery is set - while($nextEndpoint = $this->getNextEndpoint(body: $body, url: $source->getLocation(), sourceConfig: $sourceConfig, currentPage: $currentPage)) { - $usedNextEndpoint = true; + while($useNextEndpoint === true && $nextEndpoint = $this->getNextEndpoint(body: $body, url: $source->getLocation(), sourceConfig: $sourceConfig, currentPage: $currentPage)) { // Do not pass $config here becuase it overwrites the query attached to nextEndpoint $response = $this->callService->call(source: $source, endpoint: $nextEndpoint)->getResponse(); $body = json_decode($response['body'], true); $objects = array_merge($objects, $this->getAllObjectsFromArray($body, $synchronization)); } - if ($usedNextEndpoint === false) { + if ($useNextEndpoint === false) { do { $config = $this->getNextPage(config: $config, sourceConfig: $sourceConfig, currentPage: $currentPage); $response = $this->callService->call(source: $source, endpoint: $endpoint, method: 'GET', config: $config)->getResponse(); @@ -523,7 +528,6 @@ public function getAllObjectsFromArray(array $array, Synchronization $synchroniz */ public function getNextlinkFromCall(array $body): ?string { - // Check if the 'next' key exists in the response body return $body['next'] ?? null; } } diff --git a/src/components/CreateEndpointDialog.vue b/src/components/CreateEndpointDialog.vue index 0519ecba..e69de29b 100644 --- a/src/components/CreateEndpointDialog.vue +++ b/src/components/CreateEndpointDialog.vue @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/CreateWebhookDialog.vue b/src/components/CreateWebhookDialog.vue index 0519ecba..e69de29b 100644 --- a/src/components/CreateWebhookDialog.vue +++ b/src/components/CreateWebhookDialog.vue @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/EditEndpointDialog.vue b/src/components/EditEndpointDialog.vue index 0519ecba..e69de29b 100644 --- a/src/components/EditEndpointDialog.vue +++ b/src/components/EditEndpointDialog.vue @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/EditWebhookDialog.vue b/src/components/EditWebhookDialog.vue index 0519ecba..e69de29b 100644 --- a/src/components/EditWebhookDialog.vue +++ b/src/components/EditWebhookDialog.vue @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/entities/callLog/index.js b/src/entities/callLog/index.js index 7b97bc11..ec8b4474 100644 --- a/src/entities/callLog/index.js +++ b/src/entities/callLog/index.js @@ -1,4 +1,3 @@ export * from './callLog.ts' export * from './callLog.types.ts' export * from './callLog.mock.ts' - diff --git a/src/entities/endpoint/index.js b/src/entities/endpoint/index.js index d9efff0e..5689c47b 100644 --- a/src/entities/endpoint/index.js +++ b/src/entities/endpoint/index.js @@ -1,4 +1,3 @@ export * from './endpoint.ts' export * from './endpoint.types.ts' export * from './endpoint.mock.ts' - diff --git a/src/entities/index.js b/src/entities/index.js index 9e9bf6ee..99d426c6 100644 --- a/src/entities/index.js +++ b/src/entities/index.js @@ -8,4 +8,4 @@ export * from './webhook/index.js' export * from './mapping/index.js' export * from './synchronization/index.js' export * from './source/index.js' -export * from './callLog/index.js' +export * from './callLog/index.js' diff --git a/src/entities/jobLog/index.js b/src/entities/jobLog/index.js index df9f468f..25968947 100644 --- a/src/entities/jobLog/index.js +++ b/src/entities/jobLog/index.js @@ -1,4 +1,3 @@ export * from './jobLog.types.ts' export * from './jobLog.types.ts' export * from './jobLog.mock.ts' - diff --git a/src/entities/mapping/mapping.ts b/src/entities/mapping/mapping.ts index 984092ce..0574e29f 100644 --- a/src/entities/mapping/mapping.ts +++ b/src/entities/mapping/mapping.ts @@ -13,7 +13,7 @@ export class Mapping extends ReadonlyBaseClass implements TMapping { public readonly name: string public readonly description: string public readonly mapping: Record - public readonly unset: any[] + public readonly unset: string[] public readonly cast: Record public readonly passThrough: boolean public readonly dateCreated: string @@ -47,7 +47,7 @@ export class Mapping extends ReadonlyBaseClass implements TMapping { name: z.string().max(255), description: z.string(), mapping: z.record(z.any()), - unset: z.array(z.any()), + unset: z.array(z.string()), cast: z.record(z.any()), passThrough: z.boolean(), dateCreated: z.string().or(z.literal('')), diff --git a/src/entities/mapping/mapping.types.ts b/src/entities/mapping/mapping.types.ts index 92e69b53..5d7a9eda 100644 --- a/src/entities/mapping/mapping.types.ts +++ b/src/entities/mapping/mapping.types.ts @@ -7,7 +7,7 @@ export type TMapping = { name: string description: string mapping: Record - unset: any[] + unset: string[] cast: Record passThrough: boolean dateCreated: string diff --git a/src/entities/source/source.mock.ts b/src/entities/source/source.mock.ts index d4aec985..3be4fa9c 100644 --- a/src/entities/source/source.mock.ts +++ b/src/entities/source/source.mock.ts @@ -4,6 +4,7 @@ import { TSource } from './source.types' export const mockSourceData = (): TSource[] => [ { id: '5137a1e5-b54d-43ad-abd1-4b5bff5fcd3f', + uuid: '5137a1e5-b54d-43ad-abd1-4b5bff5fcd3f', name: 'Test Source 1', description: 'A test source for demonstration', location: 'https://api.test1.com', @@ -43,6 +44,7 @@ export const mockSourceData = (): TSource[] => [ }, { id: '4c3edd34-a90d-4d2a-8894-adb5836ecde8', + uuid: '4c3edd34-a90d-4d2a-8894-adb5836ecde8', name: 'Test Source 2', description: 'Another test source', location: 'https://api.test2.com', diff --git a/src/entities/source/source.ts b/src/entities/source/source.ts index f842a0ca..7846c2da 100644 --- a/src/entities/source/source.ts +++ b/src/entities/source/source.ts @@ -7,6 +7,7 @@ import ReadonlyBaseClass from '../ReadonlyBaseClass.js' export class Source extends ReadonlyBaseClass implements TSource { public readonly id: string + public readonly uuid: string public readonly name: string public readonly description: string public readonly reference: string @@ -47,6 +48,7 @@ export class Source extends ReadonlyBaseClass implements TSource { constructor(source: TSource) { const processedSource: TSource = { id: source.id || null, + uuid: source.uuid || '', name: source.name || '', description: source.description || '', reference: source.reference || '', @@ -91,6 +93,7 @@ export class Source extends ReadonlyBaseClass implements TSource { public validate(): SafeParseReturnType { const schema = z.object({ id: z.string().nullable(), + uuid: z.string(), name: z.string().max(255), description: z.string(), reference: z.string(), diff --git a/src/entities/source/source.types.ts b/src/entities/source/source.types.ts index 1e4abd8d..a47f21ec 100644 --- a/src/entities/source/source.types.ts +++ b/src/entities/source/source.types.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ export type TSource = { id: string + uuid: string name: string description: string reference: string diff --git a/src/modals/Endpoint/DeleteEndpoint.vue b/src/modals/Endpoint/DeleteEndpoint.vue index 130e212b..77691a5b 100644 --- a/src/modals/Endpoint/DeleteEndpoint.vue +++ b/src/modals/Endpoint/DeleteEndpoint.vue @@ -8,7 +8,7 @@ import { endpointStore, navigationStore } from '../../store/store.js' size="normal" :can-close="false">

- Do you want to delete {{ endpointStore.endpointItem.name }}? This action cannot be undone. + Do you want to delete {{ endpointStore.endpointItem?.name }}? This action cannot be undone.

diff --git a/src/modals/JobArgument/DeleteJobArgument.vue b/src/modals/JobArgument/DeleteJobArgument.vue index 75b779f7..b41001e9 100644 --- a/src/modals/JobArgument/DeleteJobArgument.vue +++ b/src/modals/JobArgument/DeleteJobArgument.vue @@ -22,7 +22,7 @@ import { navigationStore, jobStore } from '../../store/store.js' Do you want to delete {{ jobStore.jobArgumentKey }}? This action cannot be undone.