@hed-hog/core 0.0.347 → 0.0.350

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 (126) hide show
  1. package/dist/auth/auth.controller.d.ts +1 -1
  2. package/dist/auth/auth.service.d.ts +1 -1
  3. package/dist/core.module.d.ts.map +1 -1
  4. package/dist/core.module.js +3 -0
  5. package/dist/core.module.js.map +1 -1
  6. package/dist/dashboard/dashboard-user/dashboard-user.controller.d.ts +3 -3
  7. package/dist/dashboard/dashboard-user/dashboard-user.service.d.ts +3 -3
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +4 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp/guards/mcp-auth.guard.d.ts +6 -1
  13. package/dist/mcp/guards/mcp-auth.guard.d.ts.map +1 -1
  14. package/dist/mcp/guards/mcp-auth.guard.js +29 -2
  15. package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
  16. package/dist/mcp/mcp-api-key.controller.d.ts +32 -0
  17. package/dist/mcp/mcp-api-key.controller.d.ts.map +1 -0
  18. package/dist/mcp/mcp-api-key.controller.js +64 -0
  19. package/dist/mcp/mcp-api-key.controller.js.map +1 -0
  20. package/dist/mcp/mcp-api-key.service.d.ts +27 -0
  21. package/dist/mcp/mcp-api-key.service.d.ts.map +1 -0
  22. package/dist/mcp/mcp-api-key.service.js +74 -0
  23. package/dist/mcp/mcp-api-key.service.js.map +1 -0
  24. package/dist/mcp/mcp-registry.service.d.ts +1 -2
  25. package/dist/mcp/mcp-registry.service.d.ts.map +1 -1
  26. package/dist/mcp/mcp-registry.service.js +6 -2
  27. package/dist/mcp/mcp-registry.service.js.map +1 -1
  28. package/dist/mcp/mcp.module.d.ts.map +1 -1
  29. package/dist/mcp/mcp.module.js +8 -1
  30. package/dist/mcp/mcp.module.js.map +1 -1
  31. package/dist/mcp-chat/mcp-chat.controller.d.ts +4 -0
  32. package/dist/mcp-chat/mcp-chat.controller.d.ts.map +1 -1
  33. package/dist/mcp-chat/mcp-chat.controller.js +46 -0
  34. package/dist/mcp-chat/mcp-chat.controller.js.map +1 -1
  35. package/dist/mcp-chat/mcp-chat.service.d.ts +6 -1
  36. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  37. package/dist/mcp-chat/mcp-chat.service.js +125 -17
  38. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  39. package/dist/notification/dto/create-notification.dto.d.ts +26 -0
  40. package/dist/notification/dto/create-notification.dto.d.ts.map +1 -0
  41. package/dist/notification/dto/create-notification.dto.js +86 -0
  42. package/dist/notification/dto/create-notification.dto.js.map +1 -0
  43. package/dist/notification/dto/update-progress.dto.d.ts +6 -0
  44. package/dist/notification/dto/update-progress.dto.d.ts.map +1 -0
  45. package/dist/notification/dto/update-progress.dto.js +33 -0
  46. package/dist/notification/dto/update-progress.dto.js.map +1 -0
  47. package/dist/notification/notification.controller.d.ts +108 -0
  48. package/dist/notification/notification.controller.d.ts.map +1 -0
  49. package/dist/notification/notification.controller.js +118 -0
  50. package/dist/notification/notification.controller.js.map +1 -0
  51. package/dist/notification/notification.module.d.ts +3 -0
  52. package/dist/notification/notification.module.d.ts.map +1 -0
  53. package/dist/notification/notification.module.js +29 -0
  54. package/dist/notification/notification.module.js.map +1 -0
  55. package/dist/notification/notification.service.d.ts +98 -0
  56. package/dist/notification/notification.service.d.ts.map +1 -0
  57. package/dist/notification/notification.service.js +123 -0
  58. package/dist/notification/notification.service.js.map +1 -0
  59. package/dist/profile/profile.controller.d.ts +1 -1
  60. package/dist/profile/profile.service.d.ts +1 -1
  61. package/dist/setting/dto/update-setting-list.dto.d.ts +9 -0
  62. package/dist/setting/dto/update-setting-list.dto.d.ts.map +1 -0
  63. package/dist/setting/dto/update-setting-list.dto.js +44 -0
  64. package/dist/setting/dto/update-setting-list.dto.js.map +1 -0
  65. package/dist/setting/setting.controller.d.ts +19 -0
  66. package/dist/setting/setting.controller.d.ts.map +1 -1
  67. package/dist/setting/setting.controller.js +12 -0
  68. package/dist/setting/setting.controller.js.map +1 -1
  69. package/dist/setting/setting.service.d.ts +19 -0
  70. package/dist/setting/setting.service.d.ts.map +1 -1
  71. package/dist/setting/setting.service.js +79 -0
  72. package/dist/setting/setting.service.js.map +1 -1
  73. package/dist/task/task.service.d.ts +12 -0
  74. package/dist/task/task.service.d.ts.map +1 -1
  75. package/dist/task/task.service.js +60 -0
  76. package/dist/task/task.service.js.map +1 -1
  77. package/dist/user/user.controller.d.ts +2 -2
  78. package/dist/user/user.service.d.ts +3 -3
  79. package/dist/webhook-integration/webhook-integration.service.d.ts +4 -0
  80. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  81. package/dist/webhook-integration/webhook-integration.service.js +184 -34
  82. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  83. package/dist/webhook-integration/webhook-utils.d.ts +1 -0
  84. package/dist/webhook-integration/webhook-utils.d.ts.map +1 -1
  85. package/dist/webhook-integration/webhook-utils.js +38 -0
  86. package/dist/webhook-integration/webhook-utils.js.map +1 -1
  87. package/hedhog/data/menu.yaml +48 -4
  88. package/hedhog/data/route.yaml +133 -0
  89. package/hedhog/data/setting_group.yaml +45 -0
  90. package/hedhog/data/setting_subgroup.yaml +16 -0
  91. package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +285 -0
  92. package/hedhog/frontend/app/account/layout.tsx.ejs +32 -55
  93. package/hedhog/frontend/app/account/mcp-tokens/page.tsx.ejs +5 -0
  94. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +2098 -1835
  95. package/hedhog/frontend/app/mcp_chat/chat-components.tsx.ejs +595 -0
  96. package/hedhog/frontend/app/mcp_chat/chat-types.ts.ejs +16 -0
  97. package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +554 -383
  98. package/hedhog/frontend/app/setting-list/page.tsx.ejs +649 -0
  99. package/hedhog/frontend/app/users/page.tsx.ejs +445 -203
  100. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +16 -37
  101. package/hedhog/frontend/messages/en.json +241 -2
  102. package/hedhog/frontend/messages/pt.json +241 -2
  103. package/hedhog/table/mcp_api_key.yaml +30 -0
  104. package/hedhog/table/notification.yaml +72 -0
  105. package/hedhog/table/webhook_action_log.yaml +49 -0
  106. package/package.json +2 -2
  107. package/src/core.module.ts +3 -0
  108. package/src/index.ts +5 -0
  109. package/src/mcp/guards/mcp-auth.guard.ts +64 -32
  110. package/src/mcp/mcp-api-key.controller.ts +42 -0
  111. package/src/mcp/mcp-api-key.service.ts +66 -0
  112. package/src/mcp/mcp-registry.service.ts +75 -69
  113. package/src/mcp/mcp.module.ts +43 -36
  114. package/src/mcp-chat/mcp-chat.controller.ts +113 -60
  115. package/src/mcp-chat/mcp-chat.service.ts +494 -332
  116. package/src/notification/dto/create-notification.dto.ts +71 -0
  117. package/src/notification/dto/update-progress.dto.ts +16 -0
  118. package/src/notification/notification.controller.ts +71 -0
  119. package/src/notification/notification.module.ts +16 -0
  120. package/src/notification/notification.service.ts +124 -0
  121. package/src/setting/dto/update-setting-list.dto.ts +32 -0
  122. package/src/setting/setting.controller.ts +9 -0
  123. package/src/setting/setting.service.ts +91 -0
  124. package/src/task/task.service.ts +72 -0
  125. package/src/webhook-integration/webhook-integration.service.ts +934 -735
  126. package/src/webhook-integration/webhook-utils.ts +136 -79
@@ -0,0 +1,71 @@
1
+ import {
2
+ IsBoolean,
3
+ IsDateString,
4
+ IsEnum,
5
+ IsNumber,
6
+ IsObject,
7
+ IsOptional,
8
+ IsString,
9
+ Max,
10
+ Min,
11
+ } from 'class-validator';
12
+
13
+ export enum NotificationType {
14
+ Info = 'info',
15
+ Success = 'success',
16
+ Warning = 'warning',
17
+ Error = 'error',
18
+ Progress = 'progress',
19
+ }
20
+
21
+ export enum NotificationActionType {
22
+ Url = 'url',
23
+ Sheet = 'sheet',
24
+ Modal = 'modal',
25
+ }
26
+
27
+ export class CreateNotificationDto {
28
+ @IsNumber()
29
+ user_id: number;
30
+
31
+ @IsString()
32
+ title: string;
33
+
34
+ @IsOptional()
35
+ @IsString()
36
+ body?: string;
37
+
38
+ @IsOptional()
39
+ @IsString()
40
+ icon?: string;
41
+
42
+ @IsOptional()
43
+ @IsEnum(NotificationType)
44
+ type?: NotificationType;
45
+
46
+ @IsOptional()
47
+ @IsEnum(NotificationActionType)
48
+ action_type?: NotificationActionType;
49
+
50
+ @IsOptional()
51
+ @IsObject()
52
+ action_data?: Record<string, unknown>;
53
+
54
+ @IsOptional()
55
+ @IsString()
56
+ action_url?: string;
57
+
58
+ @IsOptional()
59
+ @IsBoolean()
60
+ auto_remove?: boolean;
61
+
62
+ @IsOptional()
63
+ @IsNumber()
64
+ @Min(0)
65
+ @Max(100)
66
+ progress?: number;
67
+
68
+ @IsOptional()
69
+ @IsDateString()
70
+ started_at?: string;
71
+ }
@@ -0,0 +1,16 @@
1
+ import { IsBoolean, IsNumber, IsOptional, IsString, Max, Min } from 'class-validator';
2
+
3
+ export class UpdateProgressDto {
4
+ @IsNumber()
5
+ @Min(0)
6
+ @Max(100)
7
+ progress: number;
8
+
9
+ @IsOptional()
10
+ @IsString()
11
+ body?: string;
12
+
13
+ @IsOptional()
14
+ @IsBoolean()
15
+ success?: boolean;
16
+ }
@@ -0,0 +1,71 @@
1
+ import { Role, User } from '@hed-hog/api';
2
+ import { Pagination } from '@hed-hog/api-pagination';
3
+ import {
4
+ Body,
5
+ Controller,
6
+ Delete,
7
+ Get,
8
+ Param,
9
+ ParseIntPipe,
10
+ Patch,
11
+ Post,
12
+ } from '@nestjs/common';
13
+ import { CreateNotificationDto } from './dto/create-notification.dto';
14
+ import { UpdateProgressDto } from './dto/update-progress.dto';
15
+ import { NotificationService } from './notification.service';
16
+
17
+ @Role()
18
+ @Controller('notification')
19
+ export class NotificationController {
20
+ constructor(private readonly notificationService: NotificationService) {}
21
+
22
+ @Get()
23
+ async list(@User() { id }, @Pagination() paginationParams) {
24
+ return this.notificationService.list(id, paginationParams);
25
+ }
26
+
27
+ @Get('unread-count')
28
+ async unreadCount(@User() { id }) {
29
+ return this.notificationService.unreadCount(id);
30
+ }
31
+
32
+ @Patch('read-all')
33
+ async markAllRead(@User() { id }) {
34
+ return this.notificationService.markAllRead(id);
35
+ }
36
+
37
+ @Patch(':id/read')
38
+ async markRead(
39
+ @User() { id: userId },
40
+ @Param('id', ParseIntPipe) id: number,
41
+ ) {
42
+ return this.notificationService.markRead(userId, id);
43
+ }
44
+
45
+ @Patch(':id/progress')
46
+ async updateProgress(
47
+ @User() { id: userId },
48
+ @Param('id', ParseIntPipe) id: number,
49
+ @Body() dto: UpdateProgressDto,
50
+ ) {
51
+ return this.notificationService.updateProgress(userId, id, dto);
52
+ }
53
+
54
+ @Delete()
55
+ async deleteAll(@User() { id }) {
56
+ return this.notificationService.deleteAll(id);
57
+ }
58
+
59
+ @Delete(':id')
60
+ async delete(
61
+ @User() { id: userId },
62
+ @Param('id', ParseIntPipe) id: number,
63
+ ) {
64
+ return this.notificationService.delete(userId, id);
65
+ }
66
+
67
+ @Post()
68
+ async create(@Body() data: CreateNotificationDto) {
69
+ return this.notificationService.create(data);
70
+ }
71
+ }
@@ -0,0 +1,16 @@
1
+ import { PaginationModule } from '@hed-hog/api-pagination';
2
+ import { PrismaModule } from '@hed-hog/api-prisma';
3
+ import { forwardRef, Module } from '@nestjs/common';
4
+ import { NotificationController } from './notification.controller';
5
+ import { NotificationService } from './notification.service';
6
+
7
+ @Module({
8
+ imports: [
9
+ forwardRef(() => PrismaModule),
10
+ forwardRef(() => PaginationModule),
11
+ ],
12
+ controllers: [NotificationController],
13
+ providers: [NotificationService],
14
+ exports: [NotificationService],
15
+ })
16
+ export class NotificationModule {}
@@ -0,0 +1,124 @@
1
+ import { PaginationService } from '@hed-hog/api-pagination';
2
+ import { PrismaService } from '@hed-hog/api-prisma';
3
+ import {
4
+ ForbiddenException,
5
+ forwardRef,
6
+ Inject,
7
+ Injectable,
8
+ NotFoundException,
9
+ } from '@nestjs/common';
10
+ import { CreateNotificationDto } from './dto/create-notification.dto';
11
+ import { UpdateProgressDto } from './dto/update-progress.dto';
12
+
13
+ @Injectable()
14
+ export class NotificationService {
15
+ constructor(
16
+ @Inject(forwardRef(() => PrismaService))
17
+ private readonly prisma: PrismaService,
18
+ @Inject(forwardRef(() => PaginationService))
19
+ private readonly pagination: PaginationService,
20
+ ) {}
21
+
22
+ async list(userId: number, paginationParams: any) {
23
+ return this.pagination.paginate(
24
+ this.prisma.notification,
25
+ paginationParams,
26
+ {
27
+ where: { user_id: userId },
28
+ orderBy: { created_at: 'desc' },
29
+ },
30
+ );
31
+ }
32
+
33
+ async unreadCount(userId: number) {
34
+ const count = await this.prisma.notification.count({
35
+ where: { user_id: userId, status: 'unread' },
36
+ });
37
+ return { count };
38
+ }
39
+
40
+ async markRead(userId: number, id: number) {
41
+ const notification = await this.assertOwnership(userId, id);
42
+ if (notification.auto_remove) {
43
+ return this.prisma.notification.delete({ where: { id } });
44
+ }
45
+ return this.prisma.notification.update({
46
+ where: { id },
47
+ data: { status: 'read' },
48
+ });
49
+ }
50
+
51
+ async markAllRead(userId: number) {
52
+ await this.prisma.notification.deleteMany({
53
+ where: { user_id: userId, status: 'unread', auto_remove: true },
54
+ });
55
+ return this.prisma.notification.updateMany({
56
+ where: { user_id: userId, status: 'unread' },
57
+ data: { status: 'read' },
58
+ });
59
+ }
60
+
61
+ async delete(userId: number, id: number) {
62
+ await this.assertOwnership(userId, id);
63
+ return this.prisma.notification.delete({ where: { id } });
64
+ }
65
+
66
+ async deleteAll(userId: number) {
67
+ return this.prisma.notification.deleteMany({ where: { user_id: userId } });
68
+ }
69
+
70
+ async create(data: CreateNotificationDto) {
71
+ return this.prisma.notification.create({
72
+ data: {
73
+ user_id: data.user_id,
74
+ title: data.title,
75
+ body: data.body,
76
+ icon: data.icon,
77
+ type: (data.type as any) ?? 'info',
78
+ action_type: (data.action_type as any) ?? 'url',
79
+ action_data: data.action_data as any,
80
+ action_url: data.action_url,
81
+ auto_remove: data.auto_remove ?? false,
82
+ progress: data.progress,
83
+ started_at: data.started_at ? new Date(data.started_at) : undefined,
84
+ },
85
+ });
86
+ }
87
+
88
+ async updateProgress(userId: number, id: number, dto: UpdateProgressDto) {
89
+ await this.assertOwnership(userId, id);
90
+ const finished = dto.progress >= 100;
91
+ return this.prisma.notification.update({
92
+ where: { id },
93
+ data: {
94
+ progress: dto.progress,
95
+ body: dto.body,
96
+ type: finished ? (dto.success === false ? 'error' : 'success') as any : undefined,
97
+ finished_at: finished ? new Date() : undefined,
98
+ },
99
+ });
100
+ }
101
+
102
+ async clearOldNotifications(retentionDays: number, batchLimit: number) {
103
+ return this.prisma.$executeRawUnsafe(
104
+ `WITH del AS (
105
+ SELECT id FROM notification
106
+ WHERE created_at < NOW() - INTERVAL '${retentionDays} days'
107
+ ORDER BY id
108
+ LIMIT ${batchLimit}
109
+ )
110
+ DELETE FROM notification n
111
+ USING del WHERE n.id = del.id`,
112
+ );
113
+ }
114
+
115
+ private async assertOwnership(userId: number, id: number) {
116
+ const notification = await this.prisma.notification.findUnique({
117
+ where: { id },
118
+ select: { user_id: true, auto_remove: true },
119
+ });
120
+ if (!notification) throw new NotFoundException();
121
+ if (notification.user_id !== userId) throw new ForbiddenException();
122
+ return notification;
123
+ }
124
+ }
@@ -0,0 +1,32 @@
1
+ import { Type } from 'class-transformer';
2
+ import {
3
+ IsArray,
4
+ IsInt,
5
+ IsOptional,
6
+ IsString,
7
+ Length,
8
+ ValidateNested,
9
+ } from 'class-validator';
10
+
11
+ export class UpdateSettingListItemDTO {
12
+ @IsOptional()
13
+ @Type(() => Number)
14
+ @IsInt()
15
+ id?: number;
16
+
17
+ @IsString()
18
+ @Length(1, 255)
19
+ value: string;
20
+
21
+ @IsOptional()
22
+ @Type(() => Number)
23
+ @IsInt()
24
+ order?: number;
25
+ }
26
+
27
+ export class UpdateSettingListDTO {
28
+ @IsArray()
29
+ @ValidateNested({ each: true })
30
+ @Type(() => UpdateSettingListItemDTO)
31
+ options: UpdateSettingListItemDTO[];
32
+ }
@@ -27,6 +27,7 @@ import { DeleteDTO } from './dto/delete.dto';
27
27
  import { ExportDto } from './dto/export.dto';
28
28
  import { SettingUserDTO } from './dto/setting-user.dto';
29
29
  import { SettingDTO } from './dto/setting.dto';
30
+ import { UpdateSettingListDTO } from './dto/update-setting-list.dto';
30
31
  import { UpdateDTO } from './dto/update.dto';
31
32
  import { SettingService } from './setting.service';
32
33
 
@@ -124,6 +125,14 @@ export class SettingsController {
124
125
  return this.settingService.updateFromSlug(slug, data);
125
126
  }
126
127
 
128
+ @Put(':settingId/options')
129
+ async updateSettingListOptions(
130
+ @Param('settingId', ParseIntPipe) settingId: number,
131
+ @Body() data: UpdateSettingListDTO,
132
+ ) {
133
+ return this.settingService.updateSettingListOptions(settingId, data);
134
+ }
135
+
127
136
  @Put()
128
137
  async setManySettings(@Body() data: SettingDTO) {
129
138
  return this.settingService.setManySettings(data);
@@ -15,6 +15,7 @@ import { IntegrationDeveloperApiService } from '../integration/services/integrat
15
15
  import { CreateDTO } from './dto/create.dto';
16
16
  import { DeleteDTO } from './dto/delete.dto';
17
17
  import { SettingDTO } from './dto/setting.dto';
18
+ import { UpdateSettingListDTO } from './dto/update-setting-list.dto';
18
19
  import { UpdateDTO } from './dto/update.dto';
19
20
 
20
21
  type SettingChangeEventInput = {
@@ -497,6 +498,21 @@ export class SettingService {
497
498
  },
498
499
  },
499
500
  },
501
+ setting_subgroup: {
502
+ include: {
503
+ setting_subgroup_locale: {
504
+ where: {
505
+ locale: {
506
+ code: locale,
507
+ },
508
+ },
509
+ select: {
510
+ name: true,
511
+ description: true,
512
+ },
513
+ },
514
+ },
515
+ },
500
516
  setting_locale: {
501
517
  where: {
502
518
  locale: {
@@ -508,6 +524,16 @@ export class SettingService {
508
524
  description: true,
509
525
  },
510
526
  },
527
+ setting_list: {
528
+ select: {
529
+ id: true,
530
+ order: true,
531
+ value: true,
532
+ },
533
+ orderBy: {
534
+ order: 'asc',
535
+ },
536
+ },
511
537
  },
512
538
  },
513
539
  'setting_locale',
@@ -518,12 +544,77 @@ export class SettingService {
518
544
  'setting_group_locale',
519
545
  setting.setting_group,
520
546
  );
547
+
548
+ if (setting.setting_subgroup) {
549
+ setting.setting_subgroup = itemTranslations(
550
+ 'setting_subgroup_locale',
551
+ setting.setting_subgroup,
552
+ );
553
+ }
554
+
555
+ setting.subgroupId ??=
556
+ setting.subgroup_id ?? setting.setting_subgroup?.id ?? null;
557
+
521
558
  return setting;
522
559
  });
523
560
 
524
561
  return result;
525
562
  }
526
563
 
564
+ async updateSettingListOptions(settingId: number, data: UpdateSettingListDTO) {
565
+ this.clearCache();
566
+
567
+ const setting = await this.prismaService.setting.findUnique({
568
+ where: { id: settingId },
569
+ select: {
570
+ id: true,
571
+ component: true,
572
+ },
573
+ });
574
+
575
+ if (!setting) {
576
+ throw new NotFoundException(`Setting with ID ${settingId} not found.`);
577
+ }
578
+
579
+ const component = String(setting.component ?? '').toLowerCase();
580
+ if (component !== 'combobox' && component !== 'checkbox') {
581
+ throw new BadRequestException(
582
+ 'Only combobox and checkbox settings support list options.',
583
+ );
584
+ }
585
+
586
+ const options = (data.options ?? [])
587
+ .map((option, index) => ({
588
+ value: option.value?.trim() ?? '',
589
+ order: option.order ?? index + 1,
590
+ }))
591
+ .filter((option) => option.value.length > 0);
592
+
593
+ const updatedSetting = await this.prismaService.setting.update({
594
+ where: { id: settingId },
595
+ data: {
596
+ setting_list: {
597
+ deleteMany: {},
598
+ create: options,
599
+ },
600
+ },
601
+ include: {
602
+ setting_list: {
603
+ select: {
604
+ id: true,
605
+ order: true,
606
+ value: true,
607
+ },
608
+ orderBy: {
609
+ order: 'asc',
610
+ },
611
+ },
612
+ },
613
+ });
614
+
615
+ return updatedSetting;
616
+ }
617
+
527
618
  async get(settingId: number) {
528
619
  const setting = await this.prismaService.setting.findUnique({
529
620
  where: { id: settingId },
@@ -36,6 +36,12 @@ export class TasksService {
36
36
  await this.clearUnverifiedMfaWithExpiredChallenges();
37
37
  this.logger.verbose('++++++++++++++++++++++++++++++++++');
38
38
 
39
+ await this.clearExpiredWebhookLogs();
40
+ this.logger.verbose('++++++++++++++++++++++++++++++++++');
41
+
42
+ await this.clearOldNotifications();
43
+ this.logger.verbose('++++++++++++++++++++++++++++++++++');
44
+
39
45
  this.logger.verbose('Finished scheduled cleanup tasks');
40
46
  this.logger.verbose('++++++++++++++++++++++++++++++++++');
41
47
  }
@@ -225,4 +231,70 @@ export class TasksService {
225
231
  `Unverified MFA with expired challenges cleaned up in ${Date.now() - startAt}ms`,
226
232
  );
227
233
  }
234
+
235
+ /**
236
+ * Limpa logs antigos de execução de webhooks de entrada com base na política
237
+ * de retenção configurada em settings.
238
+ *
239
+ * Remove registros da tabela webhook_call_log que:
240
+ * - Foram criados há mais dias do que o valor de webhook-log-retention-days
241
+ *
242
+ * Os registros relacionados em webhook_action_log são removidos em cascata
243
+ * pelo relacionamento FK com onDelete CASCADE.
244
+ */
245
+ private async clearExpiredWebhookLogs(): Promise<void> {
246
+ const settings = await this.settingService.getSettingValues([
247
+ 'cleanup-batch-limit',
248
+ 'webhook-log-retention-days',
249
+ ]);
250
+ const batchLimit = Number(settings['cleanup-batch-limit']) || 20000;
251
+ const retentionDays = Number(settings['webhook-log-retention-days']) || 30;
252
+ this.logger.verbose('Starting cleanup of expired webhook call logs');
253
+ const startAt = Date.now();
254
+
255
+ await this.prismaService.$executeRaw`
256
+ WITH del AS (
257
+ SELECT id
258
+ FROM webhook_call_log
259
+ WHERE created_at < (now() - ${Prisma.sql`INTERVAL '${Prisma.raw(String(retentionDays))} days'`})
260
+ ORDER BY id
261
+ LIMIT ${batchLimit}
262
+ )
263
+ DELETE FROM webhook_call_log wcl
264
+ USING del
265
+ WHERE wcl.id = del.id
266
+ `;
267
+
268
+ this.logger.verbose(
269
+ `Expired webhook call logs cleaned up in ${Date.now() - startAt}ms`,
270
+ );
271
+ }
272
+
273
+ private async clearOldNotifications(): Promise<void> {
274
+ const settings = await this.settingService.getSettingValues([
275
+ 'cleanup-batch-limit',
276
+ 'notification-retention-days',
277
+ ]);
278
+ const batchLimit = Number(settings['cleanup-batch-limit']) || 20000;
279
+ const retentionDays = Number(settings['notification-retention-days']) || 30;
280
+ this.logger.verbose('Starting cleanup of old notifications');
281
+ const startAt = Date.now();
282
+
283
+ await this.prismaService.$executeRaw`
284
+ WITH del AS (
285
+ SELECT id
286
+ FROM notification
287
+ WHERE created_at < (now() - ${Prisma.sql`INTERVAL '${Prisma.raw(String(retentionDays))} days'`})
288
+ ORDER BY id
289
+ LIMIT ${batchLimit}
290
+ )
291
+ DELETE FROM notification n
292
+ USING del
293
+ WHERE n.id = del.id
294
+ `;
295
+
296
+ this.logger.verbose(
297
+ `Old notifications cleaned up in ${Date.now() - startAt}ms`,
298
+ );
299
+ }
228
300
  }