@hed-hog/core 0.0.366 → 0.0.368

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 (87) hide show
  1. package/dist/file/file.service.d.ts +1 -0
  2. package/dist/file/file.service.d.ts.map +1 -1
  3. package/dist/file/file.service.js +1 -1
  4. package/dist/file/file.service.js.map +1 -1
  5. package/dist/integration/services/outbox-polling.coordinator.d.ts +2 -0
  6. package/dist/integration/services/outbox-polling.coordinator.d.ts.map +1 -1
  7. package/dist/integration/services/outbox-polling.coordinator.js +14 -4
  8. package/dist/integration/services/outbox-polling.coordinator.js.map +1 -1
  9. package/dist/integration/services/outbox.processor.d.ts.map +1 -1
  10. package/dist/integration/services/outbox.processor.js +4 -3
  11. package/dist/integration/services/outbox.processor.js.map +1 -1
  12. package/dist/locale/locale.controller.d.ts +1 -1
  13. package/dist/locale/locale.controller.d.ts.map +1 -1
  14. package/dist/locale/locale.controller.js +4 -3
  15. package/dist/locale/locale.controller.js.map +1 -1
  16. package/dist/locale/locale.service.d.ts +1 -1
  17. package/dist/locale/locale.service.d.ts.map +1 -1
  18. package/dist/locale/locale.service.js +6 -6
  19. package/dist/locale/locale.service.js.map +1 -1
  20. package/dist/notification/events/notification-stale-progress-check.event.d.ts +6 -0
  21. package/dist/notification/events/notification-stale-progress-check.event.d.ts.map +1 -0
  22. package/dist/notification/events/notification-stale-progress-check.event.js +11 -0
  23. package/dist/notification/events/notification-stale-progress-check.event.js.map +1 -0
  24. package/dist/notification/notification.service.d.ts +4 -1
  25. package/dist/notification/notification.service.d.ts.map +1 -1
  26. package/dist/notification/notification.service.js +9 -26
  27. package/dist/notification/notification.service.js.map +1 -1
  28. package/dist/task/task.service.d.ts +19 -0
  29. package/dist/task/task.service.d.ts.map +1 -1
  30. package/dist/task/task.service.js +46 -0
  31. package/dist/task/task.service.js.map +1 -1
  32. package/dist/user/user.controller.d.ts +23 -5
  33. package/dist/user/user.controller.d.ts.map +1 -1
  34. package/dist/user/user.controller.js +32 -15
  35. package/dist/user/user.controller.js.map +1 -1
  36. package/dist/user/user.service.d.ts +28 -8
  37. package/dist/user/user.service.d.ts.map +1 -1
  38. package/dist/user/user.service.js +120 -8
  39. package/dist/user/user.service.js.map +1 -1
  40. package/hedhog/data/integration_event_catalog.yaml +118 -118
  41. package/hedhog/data/integration_provider.yaml +25 -0
  42. package/hedhog/data/integration_type.yaml +6 -0
  43. package/hedhog/data/locale.yaml +5 -1
  44. package/hedhog/data/route.yaml +9 -0
  45. package/hedhog/data/setting_group.yaml +116 -106
  46. package/hedhog/data/setting_subgroup.yaml +32 -0
  47. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +56 -0
  48. package/hedhog/frontend/app/roles/page.tsx.ejs +4 -1
  49. package/hedhog/frontend/app/users/user-logs.tsx.ejs +190 -0
  50. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +15 -1
  51. package/hedhog/frontend/messages/en.json +17 -0
  52. package/hedhog/frontend/messages/pt.json +17 -0
  53. package/hedhog/table/ai_instruction.yaml +1 -1
  54. package/hedhog/table/event_webhook.yaml +55 -55
  55. package/hedhog/table/event_webhook_delivery_log.yaml +66 -66
  56. package/hedhog/table/event_webhook_event.yaml +26 -26
  57. package/hedhog/table/integration_event_catalog.yaml +41 -41
  58. package/hedhog/table/mcp_conversation.yaml +12 -12
  59. package/hedhog/table/mcp_conversation_message.yaml +23 -23
  60. package/hedhog/table/mcp_tool_call_log.yaml +22 -22
  61. package/hedhog/table/route.yaml +33 -33
  62. package/hedhog/table/user_change_log.yaml +40 -0
  63. package/hedhog/table/webhook_call_log.yaml +46 -46
  64. package/hedhog/table/webhook_integration.yaml +43 -43
  65. package/package.json +4 -4
  66. package/src/file/file.service.ts +2 -1
  67. package/src/integration/services/outbox-polling.coordinator.ts +16 -4
  68. package/src/integration/services/outbox.processor.ts +14 -8
  69. package/src/integration/types/event.types.ts +4 -4
  70. package/src/locale/locale.controller.ts +2 -1
  71. package/src/locale/locale.service.ts +7 -6
  72. package/src/mcp/decorators/mcp-tool.decorator.ts +7 -7
  73. package/src/mcp/guards/mcp-permission.guard.ts +51 -51
  74. package/src/mcp/interfaces/mcp-tool-definition.interface.ts +6 -6
  75. package/src/mcp/mcp-server.service.ts +87 -87
  76. package/src/mcp/types/mcp-context.type.ts +6 -6
  77. package/src/mcp-chat/dto/create-conversation.dto.ts +7 -7
  78. package/src/mcp-chat/dto/send-message.dto.ts +7 -7
  79. package/src/notification/events/notification-stale-progress-check.event.ts +5 -0
  80. package/src/notification/notification.service.ts +12 -25
  81. package/src/route/dto/create.dto.ts +32 -32
  82. package/src/route/dto/update.dto.ts +31 -31
  83. package/src/task/task.service.ts +52 -0
  84. package/src/user/user.controller.ts +23 -9
  85. package/src/user/user.service.ts +183 -8
  86. package/src/webhook-integration/integration-event-catalog.controller.ts +50 -50
  87. package/src/webhook-integration/integration-event-catalog.service.ts +117 -117
@@ -1,87 +1,87 @@
1
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
- import {
4
- CallToolRequestSchema,
5
- ListToolsRequestSchema,
6
- } from '@modelcontextprotocol/sdk/types.js';
7
- import { Injectable } from '@nestjs/common';
8
- import { Request, Response } from 'express';
9
- import { McpAuditService } from './mcp-audit.service';
10
- import { McpRegistryService } from './mcp-registry.service';
11
- import { McpContext } from './types/mcp-context.type';
12
-
13
- @Injectable()
14
- export class McpServerService {
15
- constructor(
16
- private readonly registry: McpRegistryService,
17
- private readonly audit: McpAuditService,
18
- ) {}
19
-
20
- async handleRequest(req: Request, res: Response, context: McpContext): Promise<void> {
21
- const server = this.buildServer(context);
22
- const transport = new StreamableHTTPServerTransport({
23
- sessionIdGenerator: undefined,
24
- });
25
- await server.connect(transport);
26
- await transport.handleRequest(req, res, req.body);
27
- }
28
-
29
- private buildServer(context: McpContext): Server {
30
- const server = new Server(
31
- { name: 'hedhog-mcp', version: '1.0.0' },
32
- { capabilities: { tools: {} } },
33
- );
34
-
35
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
36
- tools: this.registry.getAll().map((e) => ({
37
- name: e.definition.name,
38
- description: e.definition.description,
39
- inputSchema: e.definition.inputSchema ?? {
40
- type: 'object',
41
- properties: {},
42
- },
43
- })),
44
- }));
45
-
46
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
47
- const toolName = request.params.name;
48
- const args = (request.params.arguments ?? {}) as Record<string, any>;
49
- const entry = this.registry.get(toolName);
50
-
51
- if (!entry) {
52
- return {
53
- isError: true,
54
- content: [{ type: 'text', text: `Tool '${toolName}' not found` }],
55
- };
56
- }
57
-
58
- const start = Date.now();
59
- try {
60
- const result = await entry.handler(args, { ...context, toolName });
61
- await this.audit.record({
62
- userId: context.userId,
63
- toolName,
64
- input: args,
65
- success: true,
66
- durationMs: Date.now() - start,
67
- });
68
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
69
- } catch (e: any) {
70
- await this.audit.record({
71
- userId: context.userId,
72
- toolName,
73
- input: args,
74
- success: false,
75
- error: e.message,
76
- durationMs: Date.now() - start,
77
- });
78
- return {
79
- isError: true,
80
- content: [{ type: 'text', text: e.message }],
81
- };
82
- }
83
- });
84
-
85
- return server;
86
- }
87
- }
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ } from '@modelcontextprotocol/sdk/types.js';
7
+ import { Injectable } from '@nestjs/common';
8
+ import { Request, Response } from 'express';
9
+ import { McpAuditService } from './mcp-audit.service';
10
+ import { McpRegistryService } from './mcp-registry.service';
11
+ import { McpContext } from './types/mcp-context.type';
12
+
13
+ @Injectable()
14
+ export class McpServerService {
15
+ constructor(
16
+ private readonly registry: McpRegistryService,
17
+ private readonly audit: McpAuditService,
18
+ ) {}
19
+
20
+ async handleRequest(req: Request, res: Response, context: McpContext): Promise<void> {
21
+ const server = this.buildServer(context);
22
+ const transport = new StreamableHTTPServerTransport({
23
+ sessionIdGenerator: undefined,
24
+ });
25
+ await server.connect(transport);
26
+ await transport.handleRequest(req, res, req.body);
27
+ }
28
+
29
+ private buildServer(context: McpContext): Server {
30
+ const server = new Server(
31
+ { name: 'hedhog-mcp', version: '1.0.0' },
32
+ { capabilities: { tools: {} } },
33
+ );
34
+
35
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
36
+ tools: this.registry.getAll().map((e) => ({
37
+ name: e.definition.name,
38
+ description: e.definition.description,
39
+ inputSchema: e.definition.inputSchema ?? {
40
+ type: 'object',
41
+ properties: {},
42
+ },
43
+ })),
44
+ }));
45
+
46
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
47
+ const toolName = request.params.name;
48
+ const args = (request.params.arguments ?? {}) as Record<string, any>;
49
+ const entry = this.registry.get(toolName);
50
+
51
+ if (!entry) {
52
+ return {
53
+ isError: true,
54
+ content: [{ type: 'text', text: `Tool '${toolName}' not found` }],
55
+ };
56
+ }
57
+
58
+ const start = Date.now();
59
+ try {
60
+ const result = await entry.handler(args, { ...context, toolName });
61
+ await this.audit.record({
62
+ userId: context.userId,
63
+ toolName,
64
+ input: args,
65
+ success: true,
66
+ durationMs: Date.now() - start,
67
+ });
68
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
69
+ } catch (e: any) {
70
+ await this.audit.record({
71
+ userId: context.userId,
72
+ toolName,
73
+ input: args,
74
+ success: false,
75
+ error: e.message,
76
+ durationMs: Date.now() - start,
77
+ });
78
+ return {
79
+ isError: true,
80
+ content: [{ type: 'text', text: e.message }],
81
+ };
82
+ }
83
+ });
84
+
85
+ return server;
86
+ }
87
+ }
@@ -1,6 +1,6 @@
1
- export type McpContext = {
2
- userId: number;
3
- sessionId: string;
4
- locale: string;
5
- toolName: string;
6
- };
1
+ export type McpContext = {
2
+ userId: number;
3
+ sessionId: string;
4
+ locale: string;
5
+ toolName: string;
6
+ };
@@ -1,7 +1,7 @@
1
- import { IsOptional, IsString } from 'class-validator';
2
-
3
- export class CreateConversationDTO {
4
- @IsOptional()
5
- @IsString()
6
- title?: string;
7
- }
1
+ import { IsOptional, IsString } from 'class-validator';
2
+
3
+ export class CreateConversationDTO {
4
+ @IsOptional()
5
+ @IsString()
6
+ title?: string;
7
+ }
@@ -1,7 +1,7 @@
1
- import { IsNotEmpty, IsString } from 'class-validator';
2
-
3
- export class SendMessageDTO {
4
- @IsString()
5
- @IsNotEmpty()
6
- message: string;
7
- }
1
+ import { IsNotEmpty, IsString } from 'class-validator';
2
+
3
+ export class SendMessageDTO {
4
+ @IsString()
5
+ @IsNotEmpty()
6
+ message: string;
7
+ }
@@ -0,0 +1,5 @@
1
+ export const NOTIFICATION_STALE_PROGRESS_CHECK = 'notification.stale-progress-check';
2
+
3
+ export class NotificationStaleProgressCheckEvent {
4
+ constructor(public readonly userId: number) {}
5
+ }
@@ -11,12 +11,17 @@ import {
11
11
  OnModuleDestroy,
12
12
  OnModuleInit,
13
13
  } from '@nestjs/common';
14
+ import { EventEmitter2 } from '@nestjs/event-emitter';
14
15
  import type { Request, Response } from 'express';
15
16
  import { randomUUID } from 'node:crypto';
16
17
  import { EventEmitter } from 'node:events';
17
18
  import { Client, type Notification as PgNotification } from 'pg';
18
19
  import { CreateNotificationDto } from './dto/create-notification.dto';
19
20
  import { UpdateProgressDto } from './dto/update-progress.dto';
21
+ import {
22
+ NOTIFICATION_STALE_PROGRESS_CHECK,
23
+ NotificationStaleProgressCheckEvent,
24
+ } from './events/notification-stale-progress-check.event';
20
25
 
21
26
  type NotificationStreamEventType =
22
27
  | 'ready'
@@ -47,6 +52,7 @@ export class NotificationService implements OnModuleInit, OnModuleDestroy {
47
52
  private readonly prisma: PrismaService,
48
53
  @Inject(forwardRef(() => PaginationService))
49
54
  private readonly pagination: PaginationService,
55
+ private readonly eventEmitter: EventEmitter2,
50
56
  ) {
51
57
  this.notificationEvents.setMaxListeners(0);
52
58
  }
@@ -106,32 +112,13 @@ export class NotificationService implements OnModuleInit, OnModuleDestroy {
106
112
  }
107
113
 
108
114
  private async cleanupStaleProgressNotifications(userId: number) {
109
- const stale = await this.prisma.$queryRaw<{ id: number }[]>`
110
- SELECT n.id
111
- FROM notification n
112
- WHERE n.user_id = ${userId}
113
- AND n.progress IS NOT NULL
114
- AND n.progress < 100
115
- AND n.finished_at IS NULL
116
- AND (n.action_data->>'queueJobId') IS NOT NULL
117
- AND NOT EXISTS (
118
- SELECT 1 FROM queue_job qj
119
- WHERE qj.id = (n.action_data->>'queueJobId')::int
120
- )
121
- `;
122
-
123
- if (!stale.length) return;
124
-
125
- await this.prisma.notification.updateMany({
126
- where: { id: { in: stale.map((n) => n.id) } },
127
- data: {
128
- progress: 100,
129
- type: 'error' as any,
130
- body: 'O processamento foi interrompido (job não encontrado).',
131
- finished_at: new Date(),
132
- },
133
- });
115
+ await this.eventEmitter.emitAsync(
116
+ NOTIFICATION_STALE_PROGRESS_CHECK,
117
+ new NotificationStaleProgressCheckEvent(userId),
118
+ );
119
+ }
134
120
 
121
+ async notifyUpdated(userId: number) {
135
122
  await this.broadcastNotificationEvent(userId, { type: 'notification.updated' });
136
123
  }
137
124
 
@@ -1,32 +1,32 @@
1
- import { getLocaleText } from '@hed-hog/api-locale';
2
- import { IsIn, IsOptional, IsString } from 'class-validator';
3
- import { HttpMethod } from '../../types/http-method';
4
-
5
- export type RouteType = 'HTTP' | 'MCP';
6
-
7
- const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
8
-
9
- export class CreateDTO {
10
- @IsOptional()
11
- @IsString({ message: (args) => getLocaleText('validation.urlMustBeString', args.value) })
12
- url?: string;
13
-
14
- @IsOptional()
15
- @IsString({ message: (args) => getLocaleText('validation.methodMustBeString', args.value) })
16
- @IsIn(HTTP_METHODS, {
17
- message: (args) => getLocaleText('validation.methodMustBeValid', args.value),
18
- })
19
- method?: HttpMethod;
20
-
21
- @IsOptional()
22
- @IsIn(['HTTP', 'MCP'])
23
- type?: RouteType;
24
-
25
- @IsOptional()
26
- @IsString()
27
- tool_name?: string;
28
-
29
- @IsOptional()
30
- @IsString()
31
- name?: string;
32
- }
1
+ import { getLocaleText } from '@hed-hog/api-locale';
2
+ import { IsIn, IsOptional, IsString } from 'class-validator';
3
+ import { HttpMethod } from '../../types/http-method';
4
+
5
+ export type RouteType = 'HTTP' | 'MCP';
6
+
7
+ const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
8
+
9
+ export class CreateDTO {
10
+ @IsOptional()
11
+ @IsString({ message: (args) => getLocaleText('validation.urlMustBeString', args.value) })
12
+ url?: string;
13
+
14
+ @IsOptional()
15
+ @IsString({ message: (args) => getLocaleText('validation.methodMustBeString', args.value) })
16
+ @IsIn(HTTP_METHODS, {
17
+ message: (args) => getLocaleText('validation.methodMustBeValid', args.value),
18
+ })
19
+ method?: HttpMethod;
20
+
21
+ @IsOptional()
22
+ @IsIn(['HTTP', 'MCP'])
23
+ type?: RouteType;
24
+
25
+ @IsOptional()
26
+ @IsString()
27
+ tool_name?: string;
28
+
29
+ @IsOptional()
30
+ @IsString()
31
+ name?: string;
32
+ }
@@ -1,31 +1,31 @@
1
- import { getLocaleText } from '@hed-hog/api-locale';
2
- import { IsIn, IsOptional, IsString } from 'class-validator';
3
- import { HttpMethod } from '../../types/http-method';
4
- import { RouteType } from './create.dto';
5
-
6
- const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
7
-
8
- export class UpdateDTO {
9
- @IsOptional()
10
- @IsString({ message: (args) => getLocaleText('validation.urlMustBeString', args.value) })
11
- url?: string;
12
-
13
- @IsOptional()
14
- @IsString({ message: (args) => getLocaleText('validation.methodMustBeString', args.value) })
15
- @IsIn(HTTP_METHODS, {
16
- message: (args) => getLocaleText('validation.methodMustBeValid', args.value),
17
- })
18
- method?: HttpMethod;
19
-
20
- @IsOptional()
21
- @IsIn(['HTTP', 'MCP'])
22
- type?: RouteType;
23
-
24
- @IsOptional()
25
- @IsString()
26
- tool_name?: string;
27
-
28
- @IsOptional()
29
- @IsString()
30
- name?: string;
31
- }
1
+ import { getLocaleText } from '@hed-hog/api-locale';
2
+ import { IsIn, IsOptional, IsString } from 'class-validator';
3
+ import { HttpMethod } from '../../types/http-method';
4
+ import { RouteType } from './create.dto';
5
+
6
+ const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
7
+
8
+ export class UpdateDTO {
9
+ @IsOptional()
10
+ @IsString({ message: (args) => getLocaleText('validation.urlMustBeString', args.value) })
11
+ url?: string;
12
+
13
+ @IsOptional()
14
+ @IsString({ message: (args) => getLocaleText('validation.methodMustBeString', args.value) })
15
+ @IsIn(HTTP_METHODS, {
16
+ message: (args) => getLocaleText('validation.methodMustBeValid', args.value),
17
+ })
18
+ method?: HttpMethod;
19
+
20
+ @IsOptional()
21
+ @IsIn(['HTTP', 'MCP'])
22
+ type?: RouteType;
23
+
24
+ @IsOptional()
25
+ @IsString()
26
+ tool_name?: string;
27
+
28
+ @IsOptional()
29
+ @IsString()
30
+ name?: string;
31
+ }
@@ -42,6 +42,9 @@ export class TasksService {
42
42
  await this.clearOldNotifications();
43
43
  this.logger.verbose('++++++++++++++++++++++++++++++++++');
44
44
 
45
+ await this.clearStaleAsyncNotifications();
46
+ this.logger.verbose('++++++++++++++++++++++++++++++++++');
47
+
45
48
  await this.clearOldAccessLogs();
46
49
  this.logger.verbose('++++++++++++++++++++++++++++++++++');
47
50
 
@@ -322,4 +325,53 @@ export class TasksService {
322
325
  `Old notifications cleaned up in ${Date.now() - startAt}ms`,
323
326
  );
324
327
  }
328
+
329
+ /**
330
+ * Remove notificações assíncronas que ficaram "travadas" em andamento por tempo demais.
331
+ *
332
+ * Uma notificação assíncrona está em andamento quando possui acompanhamento de progresso
333
+ * (progress IS NOT NULL) que ainda não chegou ao fim (progress < 100) e que nunca foi
334
+ * finalizada (finished_at IS NULL). Se o job que a alimenta morre ou trava, a notificação
335
+ * permanece eternamente "rodando".
336
+ *
337
+ * Remove registros da tabela notification que:
338
+ * - Estão em andamento (progress IS NOT NULL AND progress < 100 AND finished_at IS NULL)
339
+ * - Começaram a rodar há mais horas do que o valor de stale-async-notification-hours,
340
+ * medido por COALESCE(started_at, created_at)
341
+ *
342
+ * Por quê: notificações assíncronas presas poluem a interface (barra de progresso eterna)
343
+ * e o banco. A remoção periódica baseada em tempo as descarta de forma previsível.
344
+ *
345
+ * Limite: cleanup-batch-limit registros por execução para evitar lock prolongado da tabela.
346
+ */
347
+ private async clearStaleAsyncNotifications(): Promise<void> {
348
+ const settings = await this.settingService.getSettingValues([
349
+ 'cleanup-batch-limit',
350
+ 'stale-async-notification-hours',
351
+ ]);
352
+ const batchLimit = Number(settings['cleanup-batch-limit']) || 20000;
353
+ const staleHours = Number(settings['stale-async-notification-hours']) || 24;
354
+ this.logger.verbose('Starting cleanup of stale async notifications');
355
+ const startAt = Date.now();
356
+
357
+ await this.prismaService.$executeRaw`
358
+ WITH del AS (
359
+ SELECT id
360
+ FROM notification
361
+ WHERE progress IS NOT NULL
362
+ AND progress < 100
363
+ AND finished_at IS NULL
364
+ AND COALESCE(started_at, created_at) < (now() - ${Prisma.sql`INTERVAL '${Prisma.raw(String(staleHours))} hours'`})
365
+ ORDER BY id
366
+ LIMIT ${batchLimit}
367
+ )
368
+ DELETE FROM notification n
369
+ USING del
370
+ WHERE n.id = del.id
371
+ `;
372
+
373
+ this.logger.verbose(
374
+ `Stale async notifications cleaned up in ${Date.now() - startAt}ms`,
375
+ );
376
+ }
325
377
  }
@@ -1,4 +1,4 @@
1
- import { Public, Role } from '@hed-hog/api';
1
+ import { Public, Role, User } from '@hed-hog/api';
2
2
  import { Locale } from '@hed-hog/api-locale';
3
3
  import { Pagination } from '@hed-hog/api-pagination';
4
4
  import {
@@ -64,9 +64,10 @@ export class UserController {
64
64
  async update(
65
65
  @Param('userId', ParseIntPipe) userId: number,
66
66
  @Body() data: UpdateDTO,
67
- @Locale() locale: string
67
+ @Locale() locale: string,
68
+ @User() actor,
68
69
  ) {
69
- return this.userService.update(locale, userId, data);
70
+ return this.userService.update(locale, userId, data, actor?.id);
70
71
  }
71
72
 
72
73
  @Patch(':userId/reset-password')
@@ -74,8 +75,9 @@ export class UserController {
74
75
  @Param('userId', ParseIntPipe) userId: number,
75
76
  @Body() data: ResetPasswordDTO,
76
77
  @Locale() locale: string,
78
+ @User() actor,
77
79
  ) {
78
- return this.userService.resetPassword(locale, userId, data);
80
+ return this.userService.resetPassword(locale, userId, data, actor?.id);
79
81
  }
80
82
 
81
83
  @UseInterceptors(
@@ -93,8 +95,9 @@ export class UserController {
93
95
  @Locale() locale: string,
94
96
  @Param('userId', ParseIntPipe) userId: number,
95
97
  @UploadedFile() avatar: MulterFile,
98
+ @User() actor,
96
99
  ) {
97
- return this.userService.changeAvatar(locale, userId, avatar)
100
+ return this.userService.changeAvatar(locale, userId, avatar, actor?.id)
98
101
  }
99
102
 
100
103
  @Post(':userId/verify-identifier/:identifierId')
@@ -132,21 +135,32 @@ export class UserController {
132
135
  return this.userService.getUserRoutes(locale, userId);
133
136
  }
134
137
 
138
+ @Get(':userId/log')
139
+ async listLogs(
140
+ @Param('userId', ParseIntPipe) userId: number,
141
+ @Pagination() paginationParams,
142
+ @Locale() locale: string,
143
+ ) {
144
+ return this.userService.listUserChangeLogs(locale, userId, paginationParams);
145
+ }
146
+
135
147
  @Post(':userId/role/:roleId')
136
148
  async assignRoleToUser(
137
149
  @Param('userId', ParseIntPipe) userId: number,
138
150
  @Param('roleId', ParseIntPipe) roleId: number,
139
- @Locale() locale: string
151
+ @Locale() locale: string,
152
+ @User() actor,
140
153
  ) {
141
- return this.userService.assignRoleToUser(locale, userId, roleId);
154
+ return this.userService.assignRoleToUser(locale, userId, roleId, actor?.id);
142
155
  }
143
156
 
144
157
  @Delete(':userId/role/:roleId')
145
158
  async removeRoleFromUser(
146
159
  @Param('userId', ParseIntPipe) userId: number,
147
160
  @Param('roleId', ParseIntPipe) roleId: number,
148
- @Locale() locale: string
161
+ @Locale() locale: string,
162
+ @User() actor,
149
163
  ) {
150
- return this.userService.removeRoleFromUser(locale, userId, roleId);
164
+ return this.userService.removeRoleFromUser(locale, userId, roleId, actor?.id);
151
165
  }
152
166
  }