@drax/audit-back 0.38.1 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ import uniqueValidator from 'mongoose-unique-validator';
3
3
  import mongoosePaginate from 'mongoose-paginate-v2';
4
4
  const AuditSchema = new mongoose.Schema({
5
5
  entity: { type: String, required: true, index: true, unique: false },
6
+ resourceId: { type: String, required: false, index: true, unique: false },
6
7
  user: {
7
8
  id: { type: String, required: true, index: true, unique: false },
8
9
  username: { type: String, required: true, index: false, unique: false },
@@ -13,15 +14,19 @@ const AuditSchema = new mongoose.Schema({
13
14
  userAgent: { type: String, required: false, index: false, unique: false },
14
15
  changes: [{
15
16
  field: { type: String, required: true, index: false, unique: false },
16
- old: { type: String, required: false, index: false, unique: false },
17
- new: { type: String, required: false, index: false, unique: false }
17
+ old: { type: mongoose.Schema.Types.Mixed, required: false, index: false, unique: false },
18
+ new: { type: mongoose.Schema.Types.Mixed, required: false, index: false, unique: false }
18
19
  }],
19
20
  sessionId: { type: String, required: false, index: true, unique: false },
20
21
  requestId: { type: String, required: false, index: true, unique: false },
21
22
  detail: { type: String, required: false, index: false, unique: false },
22
23
  tenant: {
23
24
  id: { type: String, required: false, index: true, unique: false },
24
- name: { type: String, required: false, index: false, unique: false }
25
+ name: { type: String, required: false, index: true, unique: false }
26
+ },
27
+ apiKey: {
28
+ id: { type: String, required: false, index: true, unique: false },
29
+ name: { type: String, required: false, index: true, unique: false }
25
30
  }
26
31
  }, { timestamps: true });
27
32
  AuditSchema.plugin(uniqueValidator, { message: 'validation.unique' });
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  const AuditBaseSchema = z.object({
3
3
  entity: z.string().min(1, 'validation.required'),
4
+ resourceId: z.string().optional().nullable(),
4
5
  user: z.object({
5
6
  id: z.string().min(1, 'validation.required'),
6
7
  username: z.string().min(1, 'validation.required'),
@@ -11,8 +12,8 @@ const AuditBaseSchema = z.object({
11
12
  userAgent: z.string().optional().nullable(),
12
13
  changes: z.array(z.object({
13
14
  field: z.string().min(1, 'validation.required'),
14
- old: z.string().optional().nullable(),
15
- new: z.string().optional().nullable()
15
+ old: z.any().optional().nullable(),
16
+ new: z.any().optional().nullable()
16
17
  })).optional(),
17
18
  sessionId: z.string().optional().nullable(),
18
19
  requestId: z.string().optional().nullable(),
@@ -20,6 +21,10 @@ const AuditBaseSchema = z.object({
20
21
  tenant: z.object({
21
22
  id: z.string().optional().nullable(),
22
23
  name: z.string().optional().nullable()
24
+ }).optional().nullable(),
25
+ apiKey: z.object({
26
+ id: z.string().optional().nullable(),
27
+ name: z.string().optional().nullable()
23
28
  }).optional().nullable()
24
29
  });
25
30
  const AuditSchema = AuditBaseSchema
@@ -1,16 +1,31 @@
1
1
  import { AuditServiceFactory } from "../factory/services/AuditServiceFactory.js";
2
2
  async function RegisterCrudEvent(crudEventData) {
3
- function diff(preItem, postItem) {
3
+ function diff(preItem = {}, postItem = {}) {
4
4
  const changes = [];
5
- // Si no hay preItem (creación), no hay cambios que registrar
5
+ const ignoredFields = ['_id', 'createdAt', 'updatedAt', 'createdBy', '$__', '$isNew', '$errors'];
6
+ // Si no hay preItem (creación), registrar todos los campos como nuevos
6
7
  if (!preItem) {
8
+ preItem = {};
9
+ if (postItem) {
10
+ Object.keys(postItem).forEach(key => {
11
+ // Ignorar campos internos de MongoDB y timestamps
12
+ if (!key.startsWith('_') && !ignoredFields.includes(key)) {
13
+ changes.push({
14
+ field: key,
15
+ old: undefined,
16
+ new: postItem[key]
17
+ });
18
+ }
19
+ });
20
+ }
7
21
  return changes;
8
22
  }
9
23
  // Si no hay postItem (eliminación), registrar todos los campos como eliminados
10
24
  if (!postItem) {
25
+ postItem = {};
11
26
  Object.keys(preItem).forEach(key => {
12
27
  // Ignorar campos internos de MongoDB y timestamps
13
- if (!key.startsWith('_') && key !== 'createdAt' && key !== 'updatedAt') {
28
+ if (!key.startsWith('_') && !ignoredFields.includes(key)) {
14
29
  changes.push({
15
30
  field: key,
16
31
  old: preItem[key],
@@ -20,17 +35,49 @@ async function RegisterCrudEvent(crudEventData) {
20
35
  });
21
36
  return changes;
22
37
  }
38
+ // Función para comparar valores teniendo en cuenta ObjectId y populated
39
+ function areValuesEqual(oldValue, newValue) {
40
+ // Si son exactamente iguales
41
+ if (JSON.stringify(oldValue) === JSON.stringify(newValue)) {
42
+ return true;
43
+ }
44
+ // Comparar referencias (ObjectId vs objeto poblado)
45
+ const oldId = extractId(oldValue);
46
+ const newId = extractId(newValue);
47
+ if (oldId && newId) {
48
+ return oldId.toString() === newId.toString();
49
+ }
50
+ // Si uno es array y el otro también, comparar elementos
51
+ if (Array.isArray(oldValue) && Array.isArray(newValue)) {
52
+ if (oldValue.length !== newValue.length) {
53
+ return false;
54
+ }
55
+ return oldValue.every((item, index) => areValuesEqual(item, newValue[index]));
56
+ }
57
+ return false;
58
+ }
59
+ function extractId(value) {
60
+ // Si es un ObjectId directo
61
+ if (value && typeof value === 'object' && value._id) {
62
+ return value._id;
63
+ }
64
+ // Si es directamente un ObjectId
65
+ if (value && typeof value === 'object' && value.toString && value.constructor.name === 'ObjectId') {
66
+ return value;
67
+ }
68
+ return null;
69
+ }
23
70
  // Obtener todas las claves únicas de ambos objetos
24
71
  const allKeys = new Set([...Object.keys(preItem), ...Object.keys(postItem)]);
25
72
  allKeys.forEach(key => {
26
73
  // Ignorar campos internos de MongoDB y timestamps
27
- if (key.startsWith('_') || key === 'createdAt' || key === 'updatedAt') {
74
+ if (key.startsWith('_') || ignoredFields.includes(key)) {
28
75
  return;
29
76
  }
30
77
  const oldValue = preItem[key];
31
78
  const newValue = postItem[key];
32
- // Comparar valores
33
- if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
79
+ // Comparar valores usando la nueva función
80
+ if (!areValuesEqual(oldValue, newValue)) {
34
81
  changes.push({
35
82
  field: key,
36
83
  old: oldValue,
@@ -43,23 +90,28 @@ async function RegisterCrudEvent(crudEventData) {
43
90
  let changes = diff(crudEventData.preItem, crudEventData.postItem);
44
91
  let data = {
45
92
  action: crudEventData.action,
93
+ resourceId: crudEventData.resourceId || crudEventData.postItem?._id?.toString() || crudEventData.preItem?._id?.toString(),
46
94
  changes: changes,
47
95
  createdAt: crudEventData.timestamp,
48
- detail: "",
96
+ detail: crudEventData.detail,
49
97
  entity: crudEventData.entity,
50
98
  tenant: {
51
- id: crudEventData.user.tenant.id,
52
- name: crudEventData.user.tenant.name
99
+ id: crudEventData.user?.tenant?.id,
100
+ name: crudEventData.user?.tenant?.name
53
101
  },
54
102
  user: {
55
103
  id: crudEventData.user.id,
56
104
  username: crudEventData.user.username,
57
- rolName: crudEventData.user.role.name,
105
+ rolName: crudEventData.user.role?.name,
106
+ },
107
+ apiKey: {
108
+ id: crudEventData.user?.apiKey?.id,
109
+ name: crudEventData.user?.apiKey?.name
58
110
  },
59
111
  ip: crudEventData.ip,
60
112
  userAgent: crudEventData.userAgent,
61
113
  sessionId: crudEventData.user.session,
62
- requestId: crudEventData.requestId
114
+ requestId: crudEventData.requestId,
63
115
  };
64
116
  return await AuditServiceFactory.instance.create(data);
65
117
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.38.1",
6
+ "version": "0.40.0",
7
7
  "description": "Audit backend",
8
8
  "main": "dist/index.js",
9
9
  "types": "types/index.d.ts",
@@ -22,8 +22,8 @@
22
22
  "author": "Cristian Incarnato & Drax Team",
23
23
  "license": "ISC",
24
24
  "dependencies": {
25
- "@drax/crud-back": "^0.38.1",
26
- "@drax/crud-share": "^0.38.0",
25
+ "@drax/crud-back": "^0.40.0",
26
+ "@drax/crud-share": "^0.40.0",
27
27
  "mongoose": "^8.6.3",
28
28
  "mongoose-paginate-v2": "^1.8.3"
29
29
  },
@@ -39,5 +39,5 @@
39
39
  "tsc-alias": "^1.8.10",
40
40
  "typescript": "^5.6.2"
41
41
  },
42
- "gitHead": "33404c9f1b3df6a3f86624389916d1cae0c56904"
42
+ "gitHead": "269ec7921d83364dfba091cd74bd3f45a8135c26"
43
43
  }
@@ -6,6 +6,7 @@ import type {IAudit} from '@drax/audit-share'
6
6
 
7
7
  const AuditSchema = new mongoose.Schema<IAudit>({
8
8
  entity: {type: String, required: true, index: true, unique: false},
9
+ resourceId: {type: String, required: false, index: true, unique: false},
9
10
  user: {
10
11
  id: {type: String, required: true, index: true, unique: false},
11
12
  username: {type: String, required: true, index: false, unique: false},
@@ -16,15 +17,19 @@ const AuditSchema = new mongoose.Schema<IAudit>({
16
17
  userAgent: {type: String, required: false, index: false, unique: false},
17
18
  changes: [{
18
19
  field: {type: String, required: true, index: false, unique: false},
19
- old: {type: String, required: false, index: false, unique: false},
20
- new: {type: String, required: false, index: false, unique: false}
20
+ old: {type: mongoose.Schema.Types.Mixed, required: false, index: false, unique: false},
21
+ new: {type: mongoose.Schema.Types.Mixed, required: false, index: false, unique: false}
21
22
  }],
22
23
  sessionId: {type: String, required: false, index: true, unique: false},
23
24
  requestId: {type: String, required: false, index: true, unique: false},
24
25
  detail: {type: String, required: false, index: false, unique: false},
25
26
  tenant: {
26
27
  id: {type: String, required: false, index: true, unique: false},
27
- name: {type: String, required: false, index: false, unique: false}
28
+ name: {type: String, required: false, index: true, unique: false}
29
+ },
30
+ apiKey: {
31
+ id: {type: String, required: false, index: true, unique: false},
32
+ name: {type: String, required: false, index: true, unique: false}
28
33
  }
29
34
  }, {timestamps: true});
30
35
 
@@ -3,6 +3,7 @@ import {z} from 'zod';
3
3
 
4
4
  const AuditBaseSchema = z.object({
5
5
  entity: z.string().min(1, 'validation.required'),
6
+ resourceId: z.string().optional().nullable(),
6
7
  user: z.object({
7
8
  id: z.string().min(1, 'validation.required'),
8
9
  username: z.string().min(1, 'validation.required'),
@@ -14,8 +15,8 @@ const AuditBaseSchema = z.object({
14
15
  changes: z.array(
15
16
  z.object({
16
17
  field: z.string().min(1, 'validation.required'),
17
- old: z.string().optional().nullable(),
18
- new: z.string().optional().nullable()
18
+ old: z.any().optional().nullable(),
19
+ new: z.any().optional().nullable()
19
20
  })
20
21
  ).optional(),
21
22
  sessionId: z.string().optional().nullable(),
@@ -24,6 +25,10 @@ const AuditBaseSchema = z.object({
24
25
  tenant: z.object({
25
26
  id: z.string().optional().nullable(),
26
27
  name: z.string().optional().nullable()
28
+ }).optional().nullable(),
29
+ apiKey: z.object({
30
+ id: z.string().optional().nullable(),
31
+ name: z.string().optional().nullable()
27
32
  }).optional().nullable()
28
33
  });
29
34
 
@@ -10,19 +10,35 @@ interface IChange{
10
10
 
11
11
  async function RegisterCrudEvent(crudEventData: IDraxCrudEvent){
12
12
 
13
- function diff(preItem: any, postItem: any): IChange[] {
13
+ function diff(preItem: any = {}, postItem: any = {}): IChange[] {
14
14
  const changes: IChange[] = [];
15
15
 
16
- // Si no hay preItem (creación), no hay cambios que registrar
16
+ const ignoredFields = ['_id', 'createdAt', 'updatedAt', 'createdBy','$__','$isNew', '$errors'];
17
+
18
+ // Si no hay preItem (creación), registrar todos los campos como nuevos
17
19
  if (!preItem) {
20
+ preItem = {}
21
+ if (postItem) {
22
+ Object.keys(postItem).forEach(key => {
23
+ // Ignorar campos internos de MongoDB y timestamps
24
+ if (!key.startsWith('_') && !ignoredFields.includes(key)) {
25
+ changes.push({
26
+ field: key,
27
+ old: undefined,
28
+ new: postItem[key]
29
+ });
30
+ }
31
+ });
32
+ }
18
33
  return changes;
19
34
  }
20
35
 
21
36
  // Si no hay postItem (eliminación), registrar todos los campos como eliminados
22
37
  if (!postItem) {
38
+ postItem = {}
23
39
  Object.keys(preItem).forEach(key => {
24
40
  // Ignorar campos internos de MongoDB y timestamps
25
- if (!key.startsWith('_') && key !== 'createdAt' && key !== 'updatedAt') {
41
+ if (!key.startsWith('_') && !ignoredFields.includes(key)) {
26
42
  changes.push({
27
43
  field: key,
28
44
  old: preItem[key],
@@ -33,20 +49,60 @@ async function RegisterCrudEvent(crudEventData: IDraxCrudEvent){
33
49
  return changes;
34
50
  }
35
51
 
52
+ // Función para comparar valores teniendo en cuenta ObjectId y populated
53
+ function areValuesEqual(oldValue: any, newValue: any): boolean {
54
+ // Si son exactamente iguales
55
+ if (JSON.stringify(oldValue) === JSON.stringify(newValue)) {
56
+ return true;
57
+ }
58
+
59
+ // Comparar referencias (ObjectId vs objeto poblado)
60
+ const oldId = extractId(oldValue);
61
+ const newId = extractId(newValue);
62
+
63
+ if (oldId && newId) {
64
+ return oldId.toString() === newId.toString();
65
+ }
66
+
67
+ // Si uno es array y el otro también, comparar elementos
68
+ if (Array.isArray(oldValue) && Array.isArray(newValue)) {
69
+ if (oldValue.length !== newValue.length) {
70
+ return false;
71
+ }
72
+ return oldValue.every((item, index) =>
73
+ areValuesEqual(item, newValue[index])
74
+ );
75
+ }
76
+
77
+ return false;
78
+ }
79
+
80
+ function extractId(value: any): any {
81
+ // Si es un ObjectId directo
82
+ if (value && typeof value === 'object' && value._id) {
83
+ return value._id;
84
+ }
85
+ // Si es directamente un ObjectId
86
+ if (value && typeof value === 'object' && value.toString && value.constructor.name === 'ObjectId') {
87
+ return value;
88
+ }
89
+ return null;
90
+ }
91
+
36
92
  // Obtener todas las claves únicas de ambos objetos
37
93
  const allKeys = new Set([...Object.keys(preItem), ...Object.keys(postItem)]);
38
94
 
39
95
  allKeys.forEach(key => {
40
96
  // Ignorar campos internos de MongoDB y timestamps
41
- if (key.startsWith('_') || key === 'createdAt' || key === 'updatedAt') {
97
+ if (key.startsWith('_') || ignoredFields.includes(key)) {
42
98
  return;
43
99
  }
44
100
 
45
101
  const oldValue = preItem[key];
46
102
  const newValue = postItem[key];
47
103
 
48
- // Comparar valores
49
- if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
104
+ // Comparar valores usando la nueva función
105
+ if (!areValuesEqual(oldValue, newValue)) {
50
106
  changes.push({
51
107
  field: key,
52
108
  old: oldValue,
@@ -62,23 +118,28 @@ async function RegisterCrudEvent(crudEventData: IDraxCrudEvent){
62
118
 
63
119
  let data: IAuditBase = {
64
120
  action: crudEventData.action,
121
+ resourceId: crudEventData.resourceId || crudEventData.postItem?._id?.toString() || crudEventData.preItem?._id?.toString(),
65
122
  changes: changes,
66
123
  createdAt: crudEventData.timestamp,
67
- detail: "",
124
+ detail: crudEventData.detail,
68
125
  entity: crudEventData.entity,
69
126
  tenant: {
70
- id: crudEventData.user.tenant.id,
71
- name: crudEventData.user.tenant.name
127
+ id: crudEventData.user?.tenant?.id,
128
+ name: crudEventData.user?.tenant?.name
72
129
  },
73
130
  user: {
74
131
  id: crudEventData.user.id,
75
132
  username: crudEventData.user.username,
76
- rolName: crudEventData.user.role.name,
133
+ rolName: crudEventData.user.role?.name,
134
+ },
135
+ apiKey: {
136
+ id: crudEventData.user?.apiKey?.id,
137
+ name: crudEventData.user?.apiKey?.name
77
138
  },
78
139
  ip: crudEventData.ip,
79
140
  userAgent: crudEventData.userAgent,
80
141
  sessionId: crudEventData.user.session,
81
- requestId: crudEventData.requestId
142
+ requestId: crudEventData.requestId,
82
143
  }
83
144
 
84
145
  return await AuditServiceFactory.instance.create(data)