rl-core-api 0.2.0 → 0.4.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.
Files changed (89) hide show
  1. package/README.md +2 -2
  2. package/dist/core/config/env.schema.d.ts +2 -2
  3. package/dist/core/config/env.schema.js +3 -3
  4. package/dist/core/config/env.service.d.ts +1 -0
  5. package/dist/core/config/env.service.js +3 -0
  6. package/dist/core/context/requestLog.context.d.ts +10 -0
  7. package/dist/core/context/requestLog.context.js +26 -0
  8. package/dist/core/database/migrations/1710000000000-InitialSchema.js +63 -36
  9. package/dist/core/database/migrations/index.js +1 -9
  10. package/dist/core/events/appEvent.enum.d.ts +18 -1
  11. package/dist/core/events/appEvent.enum.js +1 -0
  12. package/dist/core/filters/allExceptions.filter.d.ts +6 -0
  13. package/dist/core/filters/allExceptions.filter.js +47 -2
  14. package/dist/core/query/parseFilter.util.js +6 -4
  15. package/dist/core/utils/ip.util.d.ts +1 -0
  16. package/dist/core/utils/ip.util.js +8 -0
  17. package/dist/coreEntities.d.ts +2 -3
  18. package/dist/coreEntities.js +2 -4
  19. package/dist/features/audit/audit.module.js +4 -7
  20. package/dist/features/audit/domain/audit.service.d.ts +6 -15
  21. package/dist/features/audit/domain/audit.service.js +9 -32
  22. package/dist/features/audit/domain/classifyError.util.d.ts +1 -1
  23. package/dist/features/audit/domain/classifyError.util.js +8 -8
  24. package/dist/features/audit/domain/enums/errorType.enum.d.ts +9 -0
  25. package/dist/features/audit/domain/enums/errorType.enum.js +13 -0
  26. package/dist/features/audit/domain/enums/requestOutcome.enum.d.ts +5 -0
  27. package/dist/features/audit/domain/enums/requestOutcome.enum.js +9 -0
  28. package/dist/features/audit/domain/index.d.ts +2 -0
  29. package/dist/features/audit/domain/index.js +2 -0
  30. package/dist/features/audit/infra/auditData.subscriber.js +17 -9
  31. package/dist/features/audit/infra/index.d.ts +2 -4
  32. package/dist/features/audit/infra/index.js +2 -4
  33. package/dist/features/audit/infra/repositories/requestLog.repository.d.ts +7 -0
  34. package/dist/features/audit/infra/repositories/{auditChange.repository.js → requestLog.repository.js} +10 -10
  35. package/dist/features/audit/infra/schema/auditDataChange.schema.d.ts +3 -3
  36. package/dist/features/audit/infra/schema/auditDataChange.schema.js +8 -7
  37. package/dist/features/audit/infra/schema/requestLog.schema.d.ts +20 -0
  38. package/dist/features/audit/infra/schema/requestLog.schema.js +92 -0
  39. package/dist/features/audit/presentation/audit.controller.d.ts +4 -8
  40. package/dist/features/audit/presentation/audit.controller.js +8 -31
  41. package/dist/features/audit/presentation/audit.interceptor.d.ts +5 -1
  42. package/dist/features/audit/presentation/audit.interceptor.js +47 -28
  43. package/dist/features/audit/presentation/decorators/auditable.decorator.d.ts +3 -0
  44. package/dist/features/audit/presentation/decorators/auditable.decorator.js +7 -0
  45. package/dist/features/audit/presentation/filters/audit.filters.d.ts +1 -2
  46. package/dist/features/audit/presentation/filters/audit.filters.js +65 -49
  47. package/dist/features/audit/presentation/httpError.listener.d.ts +8 -0
  48. package/dist/features/audit/presentation/httpError.listener.js +61 -0
  49. package/dist/features/audit/presentation/index.d.ts +2 -0
  50. package/dist/features/audit/presentation/index.js +2 -0
  51. package/dist/features/audit/presentation/responses/auditDataChange.response.js +1 -1
  52. package/dist/features/audit/presentation/responses/log.response.d.ts +12 -27
  53. package/dist/features/audit/presentation/responses/log.response.js +66 -88
  54. package/dist/features/auth/infra/schema/authToken.schema.d.ts +5 -5
  55. package/dist/features/auth/infra/schema/authToken.schema.js +7 -7
  56. package/dist/features/auth/presentation/auth.controller.js +10 -0
  57. package/dist/features/maintenance/domain/cleanup.service.d.ts +1 -4
  58. package/dist/features/maintenance/domain/cleanup.service.js +1 -21
  59. package/dist/features/maintenance/maintenance.module.js +1 -2
  60. package/dist/features/notifications/domain/enums/channelType.enum.d.ts +5 -5
  61. package/dist/features/notifications/domain/enums/channelType.enum.js +5 -5
  62. package/dist/features/notifications/domain/enums/deliveryStatus.enum.d.ts +6 -6
  63. package/dist/features/notifications/domain/enums/deliveryStatus.enum.js +6 -6
  64. package/dist/features/notifications/domain/enums/notificationType.enum.d.ts +4 -4
  65. package/dist/features/notifications/domain/enums/notificationType.enum.js +4 -4
  66. package/dist/features/notifications/domain/enums/scopeType.enum.d.ts +3 -3
  67. package/dist/features/notifications/domain/enums/scopeType.enum.js +3 -3
  68. package/dist/features/notifications/infra/schema/notification.schema.js +2 -2
  69. package/dist/features/notifications/infra/schema/notificationScope.schema.js +2 -2
  70. package/dist/features/notifications/presentation/commands/createNotification.command.js +2 -2
  71. package/dist/features/notifications/presentation/responses/notification.response.js +1 -1
  72. package/dist/features/rbac/presentation/rbac.controller.js +5 -0
  73. package/dist/features/users/presentation/users.controller.js +5 -0
  74. package/dist/index.d.ts +5 -3
  75. package/dist/index.js +10 -6
  76. package/package.json +1 -1
  77. package/dist/core/database/migrations/1786060800000-FirstAccessTokenType.d.ts +0 -6
  78. package/dist/core/database/migrations/1786060800000-FirstAccessTokenType.js +0 -19
  79. package/dist/core/database/migrations/1786147200000-PermanentAccountLock.d.ts +0 -6
  80. package/dist/core/database/migrations/1786147200000-PermanentAccountLock.js +0 -15
  81. package/dist/core/database/migrations/1786233600000-Notifications.d.ts +0 -6
  82. package/dist/core/database/migrations/1786233600000-Notifications.js +0 -59
  83. package/dist/features/audit/infra/repositories/auditChange.repository.d.ts +0 -7
  84. package/dist/features/audit/infra/repositories/errorLog.repository.d.ts +0 -7
  85. package/dist/features/audit/infra/repositories/errorLog.repository.js +0 -35
  86. package/dist/features/audit/infra/schema/auditChange.schema.d.ts +0 -14
  87. package/dist/features/audit/infra/schema/auditChange.schema.js +0 -68
  88. package/dist/features/audit/infra/schema/errorLog.schema.d.ts +0 -21
  89. package/dist/features/audit/infra/schema/errorLog.schema.js +0 -69
package/README.md CHANGED
@@ -58,7 +58,7 @@ migrations do core vêm no pacote — você nunca copia nem reescreve nenhuma.
58
58
  |---|---|
59
59
  | **Auth** | login, refresh por cookie httpOnly, 2FA obrigatório (TOTP + fallback por e-mail + códigos de backup), primeiro acesso, reset de senha, bloqueio por tentativas, expiração de senha |
60
60
  | **RBAC** | resources, actions, scopes, permissions, roles e groups, com guard por `resource:action:scope` |
61
- | **Auditoria** | trilha de alterações de dados, log de requisições e log de erros, com retenção configurável |
61
+ | **Auditoria** | trilha de alterações de dados, log de requisições e log de erros, mantidos indefinidamente (sem retenção automática) |
62
62
  | **Notificações** | WebSocket com handshake por cookie ou Bearer, escopo resolvido na leitura |
63
63
  | **Listagens** | paginação, ordenação e filtro dinâmico com catálogo por listagem (`$AND`/`$OR` aninhados, 17 operadores) |
64
64
  | **Infra** | config validada com Zod, mailer, agendador, rate-limit, CSRF, filtro global de exceções, logger Winston |
@@ -78,7 +78,7 @@ Swagger, o remetente dos emails e o emissor exibido no app autenticador:
78
78
  |---|---|---|
79
79
  | `APP_NAME` | Nome do sistema | `Core App` |
80
80
  | `APP_LOGO_URL` | Logo no cabeçalho dos emails — **URL absoluta e pública**, porque o cliente de email busca a imagem do servidor dele | só o nome, em texto |
81
- | `APP_BRAND_COLOR` | Cor dos botões e do cabeçalho dos emails (`#RRGGBB`) | `#007bff` |
81
+ | `APP_BRAND_COLOR` | Cor **dos emails** — botões, código 2FA e o nome no cabeçalho. Não é a cor da interface, que vem do `tailwind-preset`. Escreva entre aspas: `APP_BRAND_COLOR="#16A34A"`, senão o dotenv lê o `#` como comentário e o valor chega vazio | `#007bff` |
82
82
  | `TOTP_ISSUER` | Emissor no app autenticador | segue o `APP_NAME` |
83
83
  | `EMAIL_FROM_NAME` | Nome do remetente | segue o `APP_NAME` |
84
84
 
@@ -57,8 +57,8 @@ export declare const envSchema: z.ZodObject<{
57
57
  FIRST_ACCESS_TOKEN_EXPIRY: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
58
58
  PASSWORD_MAX_AGE_DAYS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
59
59
  TOKEN_CLEANUP_CRON: z.ZodDefault<z.ZodString>;
60
- AUDIT_LOG_CLEANUP_CRON: z.ZodDefault<z.ZodString>;
61
- AUDIT_LOG_RETENTION_DAYS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
60
+ AUDIT_SLOW_REQUEST_MS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
61
+ AUDIT_IP_SALT: z.ZodOptional<z.ZodString>;
62
62
  NOTIFICATION_RETENTION_DAYS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
63
63
  SEED_ADMIN_NAME: z.ZodDefault<z.ZodString>;
64
64
  SEED_ADMIN_EMAIL: z.ZodDefault<z.ZodString>;
@@ -18,7 +18,7 @@ exports.envSchema = zod_1.z
18
18
  APP_LOGO_URL: zod_1.z.string().url().optional(),
19
19
  APP_BRAND_COLOR: zod_1.z
20
20
  .string()
21
- .regex(/^#[0-9a-fA-F]{6}$/, "APP_BRAND_COLOR deve ser hex (#RRGGBB)")
21
+ .regex(/^#[0-9a-fA-F]{6}$/, 'APP_BRAND_COLOR deve ser hex (#RRGGBB) e entre aspas no .env: APP_BRAND_COLOR="#007bff"')
22
22
  .default("#007bff"),
23
23
  DB_HOST: zod_1.z.string().min(1),
24
24
  DB_PORT: zod_1.z.coerce.number().int().positive().default(3306),
@@ -68,8 +68,8 @@ exports.envSchema = zod_1.z
68
68
  .default(1440),
69
69
  PASSWORD_MAX_AGE_DAYS: zod_1.z.coerce.number().int().positive().default(90),
70
70
  TOKEN_CLEANUP_CRON: zod_1.z.string().default("0 */6 * * *"),
71
- AUDIT_LOG_CLEANUP_CRON: zod_1.z.string().default("0 3 * * *"),
72
- AUDIT_LOG_RETENTION_DAYS: zod_1.z.coerce.number().int().positive().default(30),
71
+ AUDIT_SLOW_REQUEST_MS: zod_1.z.coerce.number().int().positive().default(1000),
72
+ AUDIT_IP_SALT: zod_1.z.string().optional(),
73
73
  NOTIFICATION_RETENTION_DAYS: zod_1.z.coerce
74
74
  .number()
75
75
  .int()
@@ -5,5 +5,6 @@ export declare class EnvService {
5
5
  private readonly config;
6
6
  constructor(config: ConfigService<Env, true>);
7
7
  get<K extends keyof Env>(key: K): Env[K];
8
+ auditIpSalt(): string;
8
9
  brand(): AppBrand;
9
10
  }
@@ -19,6 +19,9 @@ let EnvService = class EnvService {
19
19
  get(key) {
20
20
  return this.config.get(key, { infer: true });
21
21
  }
22
+ auditIpSalt() {
23
+ return this.get("AUDIT_IP_SALT") ?? this.get("JWT_ACCESS_SECRET");
24
+ }
22
25
  brand() {
23
26
  const name = this.get("APP_NAME");
24
27
  return {
@@ -0,0 +1,10 @@
1
+ import { Request } from "express";
2
+ export interface RequestLogContext {
3
+ action: string | null;
4
+ startedAt: number;
5
+ userId: string | null;
6
+ }
7
+ export declare const routePathOf: (request: Request) => string | null;
8
+ export declare const setRequestLogContext: (request: Request, context: RequestLogContext) => void;
9
+ export declare const getRequestLogContext: (request: Request) => RequestLogContext | null;
10
+ export declare const setRequestLogUser: (request: Request, userId: string | null) => void;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setRequestLogUser = exports.getRequestLogContext = exports.setRequestLogContext = exports.routePathOf = void 0;
4
+ const KEY = "__requestLogContext";
5
+ const routePathOf = (request) => {
6
+ const route = request.route;
7
+ if (route && typeof route === "object" && "path" in route) {
8
+ const path = route.path;
9
+ return typeof path === "string" ? path : null;
10
+ }
11
+ return null;
12
+ };
13
+ exports.routePathOf = routePathOf;
14
+ const setRequestLogContext = (request, context) => {
15
+ request[KEY] = context;
16
+ };
17
+ exports.setRequestLogContext = setRequestLogContext;
18
+ const getRequestLogContext = (request) => request[KEY] ?? null;
19
+ exports.getRequestLogContext = getRequestLogContext;
20
+ const setRequestLogUser = (request, userId) => {
21
+ const context = (0, exports.getRequestLogContext)(request);
22
+ if (context) {
23
+ context.userId = userId;
24
+ }
25
+ };
26
+ exports.setRequestLogUser = setRequestLogUser;
@@ -26,7 +26,6 @@ class InitialSchema1710000000000 {
26
26
  two_factor_confirmed_at datetime NULL,
27
27
  backup_codes json NULL,
28
28
  failed_login_attempts int NOT NULL DEFAULT 0,
29
- locked_until datetime NULL,
30
29
  password_changed_at datetime NULL,
31
30
  must_change_password tinyint NOT NULL DEFAULT 0,
32
31
  ${ts},
@@ -49,7 +48,7 @@ class InitialSchema1710000000000 {
49
48
  id char(36) NOT NULL,
50
49
  user_id char(36) NOT NULL,
51
50
  token_hash varchar(255) NOT NULL,
52
- type varchar(30) NOT NULL,
51
+ type tinyint unsigned NOT NULL,
53
52
  family char(36) NULL,
54
53
  expires_at datetime NOT NULL,
55
54
  used tinyint NOT NULL DEFAULT 0,
@@ -192,44 +191,31 @@ class InitialSchema1710000000000 {
192
191
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
193
192
  `);
194
193
  await queryRunner.query(`
195
- CREATE TABLE audit_changes (
194
+ CREATE TABLE request_logs (
196
195
  id char(36) NOT NULL,
197
- user_id char(36) NULL,
198
- action varchar(100) NOT NULL,
199
- entity varchar(100) NULL,
200
- entity_id varchar(36) NULL,
196
+ outcome tinyint unsigned NOT NULL,
197
+ action varchar(100) NULL,
201
198
  method varchar(10) NULL,
202
199
  path varchar(255) NULL,
203
- ip_address varchar(45) NULL,
204
- metadata json NULL,
200
+ route varchar(255) NULL,
205
201
  status_code int NULL,
206
202
  duration_ms int NULL,
207
- request_id char(36) NULL,
208
- ${ts},
209
- PRIMARY KEY (id),
210
- KEY IDX_audit_changes_user (user_id),
211
- KEY IDX_audit_changes_entity (entity),
212
- KEY IDX_audit_changes_request_id (request_id),
213
- KEY IDX_audit_changes_status_code (status_code)
214
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
215
- `);
216
- await queryRunner.query(`
217
- CREATE TABLE error_logs (
218
- id char(36) NOT NULL,
219
- status_code int NULL,
220
- message varchar(255) NOT NULL,
221
- stack text NULL,
222
- method varchar(10) NULL,
223
- path varchar(255) NULL,
224
203
  user_id char(36) NULL,
225
- ip_address varchar(45) NULL,
226
- type enum('VALIDATION','AUTH','NOT_FOUND','RATE_LIMIT','DATABASE','INTEGRATION','INTERNAL') NOT NULL DEFAULT 'INTERNAL',
204
+ ip_hash char(64) NULL,
205
+ user_agent varchar(255) NULL,
227
206
  request_id char(36) NULL,
207
+ error_type tinyint unsigned NULL,
208
+ error_code varchar(100) NULL,
209
+ error_message varchar(255) NULL,
210
+ error_stack text NULL,
228
211
  ${ts},
229
212
  PRIMARY KEY (id),
230
- KEY IDX_error_logs_status (status_code),
231
- KEY IDX_error_logs_type (type),
232
- KEY IDX_error_logs_request_id (request_id)
213
+ KEY IDX_request_logs_created_at (created_at),
214
+ KEY IDX_request_logs_outcome (outcome, created_at),
215
+ KEY IDX_request_logs_status (status_code, created_at),
216
+ KEY IDX_request_logs_user (user_id, created_at),
217
+ KEY IDX_request_logs_route (route, created_at),
218
+ KEY IDX_request_logs_request_id (request_id)
233
219
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
234
220
  `);
235
221
  await queryRunner.query(`
@@ -237,22 +223,63 @@ class InitialSchema1710000000000 {
237
223
  id char(36) NOT NULL,
238
224
  entity varchar(100) NOT NULL,
239
225
  entity_id varchar(36) NULL,
240
- action enum('CREATE','UPDATE','DELETE') NOT NULL,
226
+ action tinyint unsigned NOT NULL,
241
227
  old_data json NULL,
242
228
  new_data json NULL,
243
229
  user_id char(36) NULL,
244
230
  ip_address varchar(45) NULL,
245
231
  ${ts},
246
232
  PRIMARY KEY (id),
247
- KEY IDX_audit_data_changes_entity (entity, entity_id),
248
- KEY IDX_audit_data_changes_user (user_id)
233
+ KEY IDX_audit_data_changes_created_at (created_at),
234
+ KEY IDX_audit_data_changes_entity (entity, entity_id, created_at),
235
+ KEY IDX_audit_data_changes_user (user_id, created_at)
236
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
237
+ `);
238
+ await queryRunner.query(`
239
+ CREATE TABLE notifications (
240
+ id char(36) NOT NULL,
241
+ title varchar(255) NOT NULL,
242
+ body text NOT NULL,
243
+ payload json NULL,
244
+ type tinyint unsigned NOT NULL,
245
+ created_by char(36) NULL,
246
+ ${ts},
247
+ PRIMARY KEY (id),
248
+ KEY IDX_notifications_created_at (created_at)
249
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
250
+ `);
251
+ await queryRunner.query(`
252
+ CREATE TABLE notification_scopes (
253
+ id char(36) NOT NULL,
254
+ notification_id char(36) NOT NULL,
255
+ scope_type tinyint unsigned NOT NULL,
256
+ scope_key varchar(100) NULL,
257
+ ${ts},
258
+ PRIMARY KEY (id),
259
+ KEY IDX_notification_scopes_notification (notification_id),
260
+ KEY IDX_notification_scopes_target (scope_type, scope_key),
261
+ CONSTRAINT FK_notification_scopes_notification
262
+ FOREIGN KEY (notification_id) REFERENCES notifications (id) ON DELETE CASCADE
263
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
264
+ `);
265
+ await queryRunner.query(`
266
+ CREATE TABLE notification_reads (
267
+ notification_id char(36) NOT NULL,
268
+ user_id char(36) NOT NULL,
269
+ read_at datetime NOT NULL,
270
+ PRIMARY KEY (notification_id, user_id),
271
+ KEY IDX_notification_reads_user (user_id),
272
+ CONSTRAINT FK_notification_reads_notification
273
+ FOREIGN KEY (notification_id) REFERENCES notifications (id) ON DELETE CASCADE
249
274
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
250
275
  `);
251
276
  }
252
277
  async down(queryRunner) {
278
+ await queryRunner.query("DROP TABLE IF EXISTS notification_reads;");
279
+ await queryRunner.query("DROP TABLE IF EXISTS notification_scopes;");
280
+ await queryRunner.query("DROP TABLE IF EXISTS notifications;");
253
281
  await queryRunner.query("DROP TABLE IF EXISTS audit_data_changes;");
254
- await queryRunner.query("DROP TABLE IF EXISTS error_logs;");
255
- await queryRunner.query("DROP TABLE IF EXISTS audit_changes;");
282
+ await queryRunner.query("DROP TABLE IF EXISTS request_logs;");
256
283
  await queryRunner.query("DROP TABLE IF EXISTS group_roles;");
257
284
  await queryRunner.query("DROP TABLE IF EXISTS user_groups;");
258
285
  await queryRunner.query("DROP TABLE IF EXISTS user_permissions;");
@@ -2,12 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.coreMigrations = void 0;
4
4
  const _1710000000000_InitialSchema_1 = require("./1710000000000-InitialSchema");
5
- const _1786060800000_FirstAccessTokenType_1 = require("./1786060800000-FirstAccessTokenType");
6
- const _1786147200000_PermanentAccountLock_1 = require("./1786147200000-PermanentAccountLock");
7
- const _1786233600000_Notifications_1 = require("./1786233600000-Notifications");
8
- exports.coreMigrations = [
9
- _1710000000000_InitialSchema_1.InitialSchema1710000000000,
10
- _1786060800000_FirstAccessTokenType_1.FirstAccessTokenType1786060800000,
11
- _1786147200000_PermanentAccountLock_1.PermanentAccountLock1786147200000,
12
- _1786233600000_Notifications_1.Notifications1786233600000,
13
- ];
5
+ exports.coreMigrations = [_1710000000000_InitialSchema_1.InitialSchema1710000000000];
@@ -4,7 +4,8 @@ export declare enum AppEvent {
4
4
  USER_ROLES_CHANGED = "user.roles-changed",
5
5
  USER_PASSWORD_RESET_REQUESTED = "user.password-reset-requested",
6
6
  USER_PASSWORD_EXPIRING = "user.password-expiring",
7
- SYSTEM_CLEANUP_FAILED = "system.cleanup-failed"
7
+ SYSTEM_CLEANUP_FAILED = "system.cleanup-failed",
8
+ HTTP_ERROR = "http.error"
8
9
  }
9
10
  export interface UserLockedEvent {
10
11
  userId: string;
@@ -29,3 +30,19 @@ export interface SystemCleanupFailedEvent {
29
30
  routine: string;
30
31
  reason: string;
31
32
  }
33
+ export interface HttpErrorEvent {
34
+ statusCode: number;
35
+ errorCode: string | null;
36
+ errorName: string | null;
37
+ message: string;
38
+ stack?: string | null;
39
+ method: string;
40
+ path: string;
41
+ route?: string | null;
42
+ action?: string | null;
43
+ durationMs?: number | null;
44
+ userId?: string | null;
45
+ ipHash?: string | null;
46
+ userAgent?: string | null;
47
+ requestId?: string | null;
48
+ }
@@ -9,4 +9,5 @@ var AppEvent;
9
9
  AppEvent["USER_PASSWORD_RESET_REQUESTED"] = "user.password-reset-requested";
10
10
  AppEvent["USER_PASSWORD_EXPIRING"] = "user.password-expiring";
11
11
  AppEvent["SYSTEM_CLEANUP_FAILED"] = "system.cleanup-failed";
12
+ AppEvent["HTTP_ERROR"] = "http.error";
12
13
  })(AppEvent || (exports.AppEvent = AppEvent = {}));
@@ -1,5 +1,11 @@
1
1
  import { ArgumentsHost, ExceptionFilter } from "@nestjs/common";
2
+ import { EventEmitter2 } from "@nestjs/event-emitter";
3
+ import { EnvService } from "../config/env.service";
2
4
  export declare class AllExceptionsFilter implements ExceptionFilter {
5
+ private readonly events;
6
+ private readonly env;
3
7
  private readonly logger;
8
+ constructor(events: EventEmitter2, env: EnvService);
4
9
  catch(exception: unknown, host: ArgumentsHost): void;
10
+ private publish;
5
11
  }
@@ -5,11 +5,21 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
6
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
7
  };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
8
11
  Object.defineProperty(exports, "__esModule", { value: true });
9
12
  exports.AllExceptionsFilter = void 0;
10
13
  const common_1 = require("@nestjs/common");
14
+ const event_emitter_1 = require("@nestjs/event-emitter");
15
+ const env_service_1 = require("../config/env.service");
16
+ const requestLog_context_1 = require("../context/requestLog.context");
17
+ const appEvent_enum_1 = require("../events/appEvent.enum");
18
+ const ip_util_1 = require("../utils/ip.util");
11
19
  let AllExceptionsFilter = class AllExceptionsFilter {
12
- constructor() {
20
+ constructor(events, env) {
21
+ this.events = events;
22
+ this.env = env;
13
23
  this.logger = new common_1.Logger("Exception");
14
24
  }
15
25
  catch(exception, host) {
@@ -44,6 +54,12 @@ let AllExceptionsFilter = class AllExceptionsFilter {
44
54
  this.logger.error(`[${requestId}] ${exception.message}`, exception.stack);
45
55
  }
46
56
  }
57
+ this.publish(exception, request, {
58
+ statusCode: status,
59
+ errorCode,
60
+ message,
61
+ requestId,
62
+ });
47
63
  response.status(status).json({
48
64
  statusCode: status,
49
65
  message,
@@ -54,8 +70,37 @@ let AllExceptionsFilter = class AllExceptionsFilter {
54
70
  requestId,
55
71
  });
56
72
  }
73
+ publish(exception, request, data) {
74
+ try {
75
+ const user = request.user;
76
+ const logContext = (0, requestLog_context_1.getRequestLogContext)(request);
77
+ this.events.emit(appEvent_enum_1.AppEvent.HTTP_ERROR, {
78
+ statusCode: data.statusCode,
79
+ errorCode: data.errorCode,
80
+ errorName: exception?.name ?? null,
81
+ message: typeof data.message === "string"
82
+ ? data.message
83
+ : JSON.stringify(data.message),
84
+ stack: exception instanceof Error ? exception.stack : null,
85
+ method: request.method,
86
+ path: request.path,
87
+ route: (0, requestLog_context_1.routePathOf)(request),
88
+ action: logContext?.action ?? null,
89
+ durationMs: logContext ? Date.now() - logContext.startedAt : null,
90
+ userId: logContext?.userId ?? user?.id ?? null,
91
+ ipHash: (0, ip_util_1.hashIp)((0, ip_util_1.normalizeIp)(request.ip), this.env.auditIpSalt()) ?? null,
92
+ userAgent: request.headers["user-agent"] ?? null,
93
+ requestId: data.requestId,
94
+ });
95
+ }
96
+ catch (err) {
97
+ this.logger.warn(`Falha ao publicar ${appEvent_enum_1.AppEvent.HTTP_ERROR}: ${err.message}`);
98
+ }
99
+ }
57
100
  };
58
101
  exports.AllExceptionsFilter = AllExceptionsFilter;
59
102
  exports.AllExceptionsFilter = AllExceptionsFilter = __decorate([
60
- (0, common_1.Catch)()
103
+ (0, common_1.Catch)(),
104
+ __metadata("design:paramtypes", [event_emitter_1.EventEmitter2,
105
+ env_service_1.EnvService])
61
106
  ], AllExceptionsFilter);
@@ -8,6 +8,7 @@ const filterOperator_enum_1 = require("./filterOperator.enum");
8
8
  const codedError_util_1 = require("../utils/codedError.util");
9
9
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10
10
  const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
11
+ const NUMERIC_ENUM_VALUE = /^\d+$/;
11
12
  const NULLABILITY_OPERATORS = [
12
13
  filterOperator_enum_1.FilterOperator.Is,
13
14
  filterOperator_enum_1.FilterOperator.Not,
@@ -197,14 +198,15 @@ const coerceScalar = (definition, value, upperBound = false) => {
197
198
  return date;
198
199
  }
199
200
  case filterFieldType_enum_1.FilterFieldType.Enum: {
200
- if (typeof value !== "string") {
201
+ if (typeof value !== "string" && typeof value !== "number") {
201
202
  throw invalid(`"${field}" espera um dos valores da lista`);
202
203
  }
204
+ const raw = String(value);
203
205
  const options = definition.options ?? [];
204
- if (!options.some((option) => option.value === value)) {
205
- throw invalid(`"${value}" não é um valor válido para "${field}"`);
206
+ if (!options.some((option) => option.value === raw)) {
207
+ throw invalid(`"${raw}" não é um valor válido para "${field}"`);
206
208
  }
207
- return value;
209
+ return NUMERIC_ENUM_VALUE.test(raw) ? Number(raw) : raw;
208
210
  }
209
211
  case filterFieldType_enum_1.FilterFieldType.Select:
210
212
  if (typeof value !== "string" || value === "") {
@@ -1 +1,2 @@
1
+ export declare function hashIp(ip: string | undefined, secret: string): string | undefined;
1
2
  export declare function normalizeIp(ip: string | undefined): string | undefined;
@@ -1,6 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hashIp = hashIp;
3
4
  exports.normalizeIp = normalizeIp;
5
+ const node_crypto_1 = require("node:crypto");
6
+ function hashIp(ip, secret) {
7
+ if (!ip) {
8
+ return ip;
9
+ }
10
+ return (0, node_crypto_1.createHmac)("sha256", secret).update(ip).digest("hex");
11
+ }
4
12
  function normalizeIp(ip) {
5
13
  if (!ip) {
6
14
  return ip;
@@ -1,6 +1,5 @@
1
- import { AuditChange } from "./features/audit/infra/schema/auditChange.schema";
2
1
  import { AuditDataChange } from "./features/audit/infra/schema/auditDataChange.schema";
3
- import { ErrorLog } from "./features/audit/infra/schema/errorLog.schema";
2
+ import { RequestLog } from "./features/audit/infra/schema/requestLog.schema";
4
3
  import { AuthToken } from "./features/auth/infra/schema/authToken.schema";
5
4
  import { Notification } from "./features/notifications/infra/schema/notification.schema";
6
5
  import { NotificationRead } from "./features/notifications/infra/schema/notificationRead.schema";
@@ -9,4 +8,4 @@ import { Action } from "./features/rbac/infra/schema/action.schema";
9
8
  import { Permission } from "./features/rbac/infra/schema/permission.schema";
10
9
  import { PasswordHistory } from "./features/users/infra/schema/passwordHistory.schema";
11
10
  import { User } from "./features/users/infra/schema/user.schema";
12
- export declare const coreEntities: (typeof AuditChange | typeof AuditDataChange | typeof ErrorLog | typeof AuthToken | typeof NotificationRead | typeof Notification | typeof NotificationScope | typeof Action | typeof Permission | typeof PasswordHistory | typeof User)[];
11
+ export declare const coreEntities: (typeof AuditDataChange | typeof RequestLog | typeof AuthToken | typeof NotificationRead | typeof Notification | typeof NotificationScope | typeof Action | typeof Permission | typeof PasswordHistory | typeof User)[];
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.coreEntities = void 0;
4
- const auditChange_schema_1 = require("./features/audit/infra/schema/auditChange.schema");
5
4
  const auditDataChange_schema_1 = require("./features/audit/infra/schema/auditDataChange.schema");
6
- const errorLog_schema_1 = require("./features/audit/infra/schema/errorLog.schema");
5
+ const requestLog_schema_1 = require("./features/audit/infra/schema/requestLog.schema");
7
6
  const authToken_schema_1 = require("./features/auth/infra/schema/authToken.schema");
8
7
  const notification_schema_1 = require("./features/notifications/infra/schema/notification.schema");
9
8
  const notificationRead_schema_1 = require("./features/notifications/infra/schema/notificationRead.schema");
@@ -26,8 +25,7 @@ exports.coreEntities = [
26
25
  permission_schema_1.Permission,
27
26
  role_schema_1.Role,
28
27
  group_schema_1.Group,
29
- auditChange_schema_1.AuditChange,
30
- errorLog_schema_1.ErrorLog,
28
+ requestLog_schema_1.RequestLog,
31
29
  auditDataChange_schema_1.AuditDataChange,
32
30
  notification_schema_1.Notification,
33
31
  notificationScope_schema_1.NotificationScope,
@@ -20,22 +20,19 @@ let AuditModule = class AuditModule {
20
20
  exports.AuditModule = AuditModule;
21
21
  exports.AuditModule = AuditModule = __decorate([
22
22
  (0, common_1.Module)({
23
- imports: [
24
- typeorm_1.TypeOrmModule.forFeature([infra_1.AuditChange, infra_1.ErrorLog, infra_1.AuditDataChange, user_schema_1.User]),
25
- ],
23
+ imports: [typeorm_1.TypeOrmModule.forFeature([infra_1.RequestLog, infra_1.AuditDataChange, user_schema_1.User])],
26
24
  controllers: [presentation_1.AuditController],
27
25
  providers: [
28
- infra_1.AuditChangeRepository,
29
- infra_1.ErrorLogRepository,
26
+ infra_1.RequestLogRepository,
30
27
  infra_1.AuditDataChangeRepository,
31
28
  user_repository_1.UserRepository,
32
29
  domain_1.AuditService,
33
30
  { provide: core_1.APP_INTERCEPTOR, useClass: presentation_1.AuditInterceptor },
31
+ presentation_1.HttpErrorListener,
34
32
  infra_1.AuditDataSubscriber,
35
33
  ],
36
34
  exports: [
37
- infra_1.AuditChangeRepository,
38
- infra_1.ErrorLogRepository,
35
+ infra_1.RequestLogRepository,
39
36
  infra_1.AuditDataChangeRepository,
40
37
  user_repository_1.UserRepository,
41
38
  domain_1.AuditService,
@@ -1,10 +1,8 @@
1
1
  import { Paginated, PaginationQuery } from "../../../core/utils/pagination.util";
2
- import { AuditChangeRepository } from "../infra/repositories/auditChange.repository";
3
2
  import { AuditDataChangeRepository } from "../infra/repositories/auditDataChange.repository";
4
- import { ErrorLogRepository } from "../infra/repositories/errorLog.repository";
5
- import { AuditChange } from "../infra/schema/auditChange.schema";
3
+ import { RequestLogRepository } from "../infra/repositories/requestLog.repository";
6
4
  import { AuditDataChange } from "../infra/schema/auditDataChange.schema";
7
- import { ErrorLog } from "../infra/schema/errorLog.schema";
5
+ import { RequestLog } from "../infra/schema/requestLog.schema";
8
6
  import { UserRepository } from "../../users/infra/repositories/user.repository";
9
7
  export type WithUserRef<T> = T & {
10
8
  user?: {
@@ -15,20 +13,13 @@ export type WithUserRef<T> = T & {
15
13
  };
16
14
  export type AuditDataChangeWithUser = WithUserRef<Pick<AuditDataChange, "id" | "createdAt" | "updatedAt" | "deletedAt" | "entity" | "entityId" | "action" | "oldData" | "newData" | "userId" | "ipAddress">>;
17
15
  export declare class AuditService {
18
- private readonly changeRepo;
19
- private readonly errorRepo;
16
+ private readonly requestLogRepo;
20
17
  private readonly dataChangeRepo;
21
18
  private readonly userRepo;
22
19
  private readonly logger;
23
- constructor(changeRepo: AuditChangeRepository, errorRepo: ErrorLogRepository, dataChangeRepo: AuditDataChangeRepository, userRepo: UserRepository);
24
- recordChange(data: Partial<AuditChange>): Promise<void>;
25
- recordError(data: Partial<ErrorLog>): Promise<void>;
26
- cleanupOldLogs(retentionDays: number): Promise<{
27
- changes: number;
28
- errors: number;
29
- }>;
30
- listChanges(query: PaginationQuery): Promise<Paginated<WithUserRef<AuditChange>>>;
31
- listErrors(query: PaginationQuery): Promise<Paginated<WithUserRef<ErrorLog>>>;
20
+ constructor(requestLogRepo: RequestLogRepository, dataChangeRepo: AuditDataChangeRepository, userRepo: UserRepository);
21
+ recordRequest(data: Partial<RequestLog>): Promise<void>;
22
+ listRequests(query: PaginationQuery): Promise<Paginated<WithUserRef<RequestLog>>>;
32
23
  listDataChanges(query: PaginationQuery): Promise<Paginated<AuditDataChangeWithUser>>;
33
24
  private enrichWithUser;
34
25
  }
@@ -14,48 +14,26 @@ exports.AuditService = void 0;
14
14
  const common_1 = require("@nestjs/common");
15
15
  const typeorm_1 = require("typeorm");
16
16
  const pagination_util_1 = require("../../../core/utils/pagination.util");
17
- const auditChange_repository_1 = require("../infra/repositories/auditChange.repository");
18
17
  const auditDataChange_repository_1 = require("../infra/repositories/auditDataChange.repository");
19
- const errorLog_repository_1 = require("../infra/repositories/errorLog.repository");
18
+ const requestLog_repository_1 = require("../infra/repositories/requestLog.repository");
20
19
  const user_repository_1 = require("../../users/infra/repositories/user.repository");
21
20
  let AuditService = AuditService_1 = class AuditService {
22
- constructor(changeRepo, errorRepo, dataChangeRepo, userRepo) {
23
- this.changeRepo = changeRepo;
24
- this.errorRepo = errorRepo;
21
+ constructor(requestLogRepo, dataChangeRepo, userRepo) {
22
+ this.requestLogRepo = requestLogRepo;
25
23
  this.dataChangeRepo = dataChangeRepo;
26
24
  this.userRepo = userRepo;
27
25
  this.logger = new common_1.Logger(AuditService_1.name);
28
26
  }
29
- async recordChange(data) {
27
+ async recordRequest(data) {
30
28
  try {
31
- await this.changeRepo.save(this.changeRepo.create(data));
29
+ await this.requestLogRepo.save(this.requestLogRepo.create(data));
32
30
  }
33
31
  catch (err) {
34
- this.logger.warn(`Falha ao gravar AuditChange: ${err.message}`);
32
+ this.logger.warn(`Falha ao gravar RequestLog: ${err.message}`);
35
33
  }
36
34
  }
37
- async recordError(data) {
38
- try {
39
- await this.errorRepo.save(this.errorRepo.create(data));
40
- }
41
- catch (err) {
42
- this.logger.warn(`Falha ao gravar ErrorLog: ${err.message}`);
43
- }
44
- }
45
- async cleanupOldLogs(retentionDays) {
46
- const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);
47
- const changes = await this.changeRepo.delete({
48
- createdAt: (0, typeorm_1.LessThan)(cutoff),
49
- });
50
- const errors = await this.errorRepo.delete({ createdAt: (0, typeorm_1.LessThan)(cutoff) });
51
- return { changes: changes.affected ?? 0, errors: errors.affected ?? 0 };
52
- }
53
- async listChanges(query) {
54
- const [items, total] = await this.changeRepo.findPaginated(query);
55
- return new pagination_util_1.Paginated(await this.enrichWithUser(items), total, query.page, query.limit);
56
- }
57
- async listErrors(query) {
58
- const [items, total] = await this.errorRepo.findPaginated(query);
35
+ async listRequests(query) {
36
+ const [items, total] = await this.requestLogRepo.findPaginated(query);
59
37
  return new pagination_util_1.Paginated(await this.enrichWithUser(items), total, query.page, query.limit);
60
38
  }
61
39
  async listDataChanges(query) {
@@ -86,8 +64,7 @@ let AuditService = AuditService_1 = class AuditService {
86
64
  exports.AuditService = AuditService;
87
65
  exports.AuditService = AuditService = AuditService_1 = __decorate([
88
66
  (0, common_1.Injectable)(),
89
- __metadata("design:paramtypes", [auditChange_repository_1.AuditChangeRepository,
90
- errorLog_repository_1.ErrorLogRepository,
67
+ __metadata("design:paramtypes", [requestLog_repository_1.RequestLogRepository,
91
68
  auditDataChange_repository_1.AuditDataChangeRepository,
92
69
  user_repository_1.UserRepository])
93
70
  ], AuditService);
@@ -1,2 +1,2 @@
1
- import { ErrorType } from "../infra/schema/errorLog.schema";
1
+ import { ErrorType } from "./enums/errorType.enum";
2
2
  export declare function classifyError(exception: unknown, status: number): ErrorType;