diff --git a/lib/Controller/ObjectsController.php b/lib/Controller/ObjectsController.php index aa15c91..253288a 100644 --- a/lib/Controller/ObjectsController.php +++ b/lib/Controller/ObjectsController.php @@ -83,7 +83,7 @@ public function index(ObjectService $objectService, SearchService $searchService foreach ($results as $key => $result) { $results[$key] = $result->getObjectArray(); } - + return new JSONResponse(['results' => $results]); } @@ -101,7 +101,7 @@ public function index(ObjectService $objectService, SearchService $searchService public function show(string $id): JSONResponse { try { - return new JSONResponse($this->objectEntityMapper->find(id: (int) $id)); + return new JSONResponse($this->objectEntityMapper->find(idOrUuid: (int) $id)->getObjectArray()); } catch (DoesNotExistException $exception) { return new JSONResponse(data: ['error' => 'Not Found'], statusCode: 404); } @@ -138,7 +138,7 @@ public function create(): JSONResponse $this->auditTrailMapper->createAuditTrail(new: $objectEntity); - return new JSONResponse($objectEntity); + return new JSONResponse($objectEntity->getObjectArray()); } /** @@ -172,7 +172,7 @@ public function update(int $id): JSONResponse $this->auditTrailMapper->createAuditTrail(new: $objectEntity, old: $oldObject); - return new JSONResponse($objectEntity); + return new JSONResponse($objectEntity->getOBjectArray()); } /** @@ -201,7 +201,7 @@ public function destroy(int $id): JSONResponse * Retrieves a list of logs for an object * * This method returns a JSON response containing the logs for a specific object. - * + * * @NoAdminRequired * @NoCSRFRequired * diff --git a/src/entities/auditTrail/auditTrail.mock.ts b/src/entities/auditTrail/auditTrail.mock.ts new file mode 100644 index 0000000..5b99e30 --- /dev/null +++ b/src/entities/auditTrail/auditTrail.mock.ts @@ -0,0 +1,39 @@ +import { AuditTrail } from './auditTrail' +import { TAuditTrail } from './auditTrail.types' + +export const mockAuditTrailData = (): TAuditTrail[] => [ + { + id: '1234a1e5-b54d-43ad-abd1-4b5bff5fcd3f', + uuid: 'uuid-1234a1e5-b54d-43ad-abd1-4b5bff5fcd3f', + schema: 1, + register: 1, + object: 1, + action: 'create', + changed: JSON.stringify({ key: 'value' }), + user: 'user1', + userName: 'User One', + session: 'session1', + request: 'request1', + ipAddress: '127.0.0.1', + version: '1.0', + created: new Date().toISOString(), + }, + { + id: '5678a1e5-b54d-43ad-abd1-4b5bff5fcd3f', + uuid: 'uuid-5678a1e5-b54d-43ad-abd1-4b5bff5fcd3f', + schema: 2, + register: 2, + object: 2, + action: 'update', + changed: JSON.stringify({ key: 'value' }), + user: 'user2', + userName: 'User Two', + session: 'session2', + request: 'request2', + ipAddress: '127.0.0.2', + version: '1.1', + created: new Date().toISOString(), + }, +] + +export const mockAuditTrail = (data: TAuditTrail[] = mockAuditTrailData()): TAuditTrail[] => data.map(item => new AuditTrail(item)) diff --git a/src/entities/auditTrail/auditTrail.spec.ts b/src/entities/auditTrail/auditTrail.spec.ts new file mode 100644 index 0000000..ae52468 --- /dev/null +++ b/src/entities/auditTrail/auditTrail.spec.ts @@ -0,0 +1,45 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { AuditTrail } from './auditTrail' +import { mockAuditTrailData } from './auditTrail.mock' + +describe('AuditTrail Entity', () => { + it('should create an AuditTrail entity with full data', () => { + const auditTrail = new AuditTrail(mockAuditTrailData()[0]) + + expect(auditTrail).toBeInstanceOf(AuditTrail) + expect(auditTrail).toEqual(mockAuditTrailData()[0]) + expect(auditTrail.validate().success).toBe(true) + }) + + it('should create an AuditTrail entity with partial data', () => { + const auditTrail = new AuditTrail(mockAuditTrailData()[0]) + + expect(auditTrail).toBeInstanceOf(AuditTrail) + expect(auditTrail.id).toBe(null) + expect(auditTrail.uuid).toBe(mockAuditTrailData()[0].uuid) + expect(auditTrail.register).toBe(mockAuditTrailData()[0].register) + expect(auditTrail.schema).toBe(mockAuditTrailData()[0].schema) + expect(auditTrail.object).toBe(mockAuditTrailData()[0].object) + expect(auditTrail.action).toBe(mockAuditTrailData()[0].action) + expect(auditTrail.changed).toBe(mockAuditTrailData()[0].changed) + expect(auditTrail.user).toBe(mockAuditTrailData()[0].user) + expect(auditTrail.userName).toBe(mockAuditTrailData()[0].userName) + expect(auditTrail.session).toBe(mockAuditTrailData()[0].session) + expect(auditTrail.request).toBe(mockAuditTrailData()[0].request) + expect(auditTrail.ipAddress).toBe(mockAuditTrailData()[0].ipAddress) + expect(auditTrail.version).toBe(mockAuditTrailData()[0].version) + expect(auditTrail.created).toBe(mockAuditTrailData()[0].created) + expect(auditTrail.validate().success).toBe(true) + }) + + it('should fail validation with invalid data', () => { + const auditTrail = new AuditTrail(mockAuditTrailData()[1]) + + expect(auditTrail).toBeInstanceOf(AuditTrail) + expect(auditTrail.validate().success).toBe(false) + expect(auditTrail.validate().error?.issues).toContainEqual(expect.objectContaining({ + path: ['id'], + message: 'Expected string, received null', + })) + }) +}) diff --git a/src/entities/auditTrail/auditTrail.ts b/src/entities/auditTrail/auditTrail.ts new file mode 100644 index 0000000..0d24c6d --- /dev/null +++ b/src/entities/auditTrail/auditTrail.ts @@ -0,0 +1,59 @@ +import { SafeParseReturnType, z } from 'zod' +import { TAuditTrail } from './auditTrail.types' + +export class AuditTrail implements TAuditTrail { + + public id: string + public uuid: string + public schema: number + public register: number + public object: number + public action: string + public changed: string + public user: string + public userName: string + public session: string + public request: string + public ipAddress: string + public version: string + public created: string + + constructor(auditTrail: TAuditTrail) { + this.id = auditTrail.id || null + this.uuid = auditTrail.uuid || null + this.schema = auditTrail.schema || 0 + this.register = auditTrail.register || 0 + this.object = auditTrail.object || 0 + this.action = auditTrail.action || '' + this.changed = auditTrail.changed || '' + this.user = auditTrail.user || '' + this.userName = auditTrail.userName || '' + this.session = auditTrail.session || '' + this.request = auditTrail.request || '' + this.ipAddress = auditTrail.ipAddress || '' + this.version = auditTrail.version || '' + this.created = auditTrail.created || '' + } + + public validate(): SafeParseReturnType { + const schema = z.object({ + id: z.string().nullable(), + uuid: z.string().uuid().nullable(), + schema: z.number(), + register: z.number(), + object: z.number(), + action: z.string(), + changed: z.string(), + user: z.string(), + userName: z.string(), + session: z.string(), + request: z.string(), + ipAddress: z.string(), + version: z.string(), + created: z.string(), + }) + + return schema.safeParse(this) + } + +} diff --git a/src/entities/auditTrail/auditTrail.types.ts b/src/entities/auditTrail/auditTrail.types.ts new file mode 100644 index 0000000..a600169 --- /dev/null +++ b/src/entities/auditTrail/auditTrail.types.ts @@ -0,0 +1,16 @@ +export type TAuditTrail = { + id: string + uuid: string + schema: number // schema ID + register: number // register ID + object: number // object ID + action: string + changed: string // JSON object + user: string + userName: string + session: string + request: string + ipAddress: string + version: string + created: string +} diff --git a/src/entities/auditTrail/index.js b/src/entities/auditTrail/index.js new file mode 100644 index 0000000..98de241 --- /dev/null +++ b/src/entities/auditTrail/index.js @@ -0,0 +1,4 @@ +export * from './auditTrail.ts' +export * from './auditTrail.types.ts' +export * from './auditTrail.mock.ts' + diff --git a/src/entities/index.js b/src/entities/index.js index 5bda5a3..6082526 100755 --- a/src/entities/index.js +++ b/src/entities/index.js @@ -3,3 +3,4 @@ export * from './schema/index.js' export * from './register/index.js' export * from './source/index.js' export * from './object/index.js' +export * from './auditTrail/index.js' diff --git a/src/modals/Modals.vue b/src/modals/Modals.vue index 5841d55..a2d5cf3 100755 --- a/src/modals/Modals.vue +++ b/src/modals/Modals.vue @@ -1,3 +1,7 @@ + + @@ -25,6 +30,7 @@ import EditSource from './source/EditSource.vue' import DeleteSource from './source/DeleteSource.vue' import EditObject from './object/EditObject.vue' import DeleteObject from './object/DeleteObject.vue' +import ViewObjectAuditTrail from './objectAuditTrail/ViewObjectAuditTrail.vue' export default { name: 'Modals', @@ -39,6 +45,7 @@ export default { DeleteSource, EditObject, DeleteObject, + ViewObjectAuditTrail, }, } diff --git a/src/modals/objectAuditTrail/ViewObjectAuditTrail.vue b/src/modals/objectAuditTrail/ViewObjectAuditTrail.vue new file mode 100644 index 0000000..229a393 --- /dev/null +++ b/src/modals/objectAuditTrail/ViewObjectAuditTrail.vue @@ -0,0 +1,98 @@ + + + + + + + diff --git a/src/store/modules/object.js b/src/store/modules/object.js index 8ef2d29..20fbff0 100644 --- a/src/store/modules/object.js +++ b/src/store/modules/object.js @@ -1,13 +1,13 @@ /* eslint-disable no-console */ import { defineStore } from 'pinia' -import { ObjectEntity } from '../../entities/index.js' +import { AuditTrail, ObjectEntity } from '../../entities/index.js' export const useObjectStore = defineStore('object', { state: () => ({ objectItem: false, objectList: [], auditTrailItem: false, - auditTrails: [], + auditTrails: [], }), actions: { setObjectItem(objectItem) { @@ -119,38 +119,46 @@ export const useObjectStore = defineStore('object', { // change updated to current date as a singular iso date string objectItem.updated = new Date().toISOString() - try { - const response = await fetch( - endpoint, - { - method, - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(objectItem), + const response = await fetch( + endpoint, + { + method, + headers: { + 'Content-Type': 'application/json', }, - ) + body: JSON.stringify(objectItem), + }, + ) - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } - const responseData = await response.json() + const data = new ObjectEntity(await response.json()) - if (!responseData || typeof responseData !== 'object') { - throw new Error('Invalid response data') - } + this.refreshObjectList() + this.setObjectItem(data) - const data = new ObjectEntity(responseData) + return { response, data } + }, + // AUDIT TRAILS + async getAuditTrails(id) { + if (!id) { + throw new Error('No object id to get audit trails for') + } - this.setObjectItem(data) - this.refreshObjectList() + const endpoint = `/index.php/apps/openregister/api/audit-trails/${id}` - return { response, data } - } catch (error) { - console.error('Error saving object:', error) - throw new Error(`Failed to save object: ${error.message}`) - } + const response = await fetch(endpoint, { + method: 'GET', + }) + + const responseData = await response.json() + const data = responseData.map((auditTrail) => new AuditTrail(auditTrail)) + + this.setAuditTrails(data) + + return { response, data } }, }, }) diff --git a/src/views/object/ObjectDetails.vue b/src/views/object/ObjectDetails.vue index 463c721..98083f7 100644 --- a/src/views/object/ObjectDetails.vue +++ b/src/views/object/ObjectDetails.vue @@ -52,54 +52,43 @@ import { objectStore, navigationStore } from '../../store/store.js'
-
-								{{ JSON.stringify(objectStore.objectItem.object, null, 2) }}
-							
+
{{ JSON.stringify(objectStore.objectItem.object, null, 2) }}
+                            
No synchronizations found
- -
- +
+ +
-
- - - - - - - - - - - - - - -
TijdstipGebruikerActieDetails
{{ new Date(auditTrail.created).toLocaleString() }}{{ auditTrail.userName }}{{ auditTrail.action }} - - - Bekijk details - -
+
+ No audit trails found
@@ -110,12 +99,19 @@ import { objectStore, navigationStore } from '../../store/store.js' diff --git a/src/views/object/ObjectsList.vue b/src/views/object/ObjectsList.vue index 14a9ffd..53a7e77 100644 --- a/src/views/object/ObjectsList.vue +++ b/src/views/object/ObjectsList.vue @@ -33,7 +33,7 @@ import { objectStore, navigationStore, searchStore } from '../../store/store.js'