@hed-hog/core 0.0.300 → 0.0.302

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 (40) hide show
  1. package/dist/ai/ai.service.d.ts +13 -2
  2. package/dist/ai/ai.service.d.ts.map +1 -1
  3. package/dist/ai/ai.service.js +104 -2
  4. package/dist/ai/ai.service.js.map +1 -1
  5. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +26 -9
  6. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  7. package/dist/dashboard/dashboard-core/dashboard-core.controller.js +11 -5
  8. package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
  9. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +34 -10
  10. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  11. package/dist/dashboard/dashboard-core/dashboard-core.service.js +196 -69
  12. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/integration/services/integration-link.service.d.ts +5 -1
  18. package/dist/integration/services/integration-link.service.d.ts.map +1 -1
  19. package/dist/integration/services/integration-link.service.js +141 -53
  20. package/dist/integration/services/integration-link.service.js.map +1 -1
  21. package/dist/mail/mail.service.d.ts +9 -2
  22. package/dist/mail/mail.service.d.ts.map +1 -1
  23. package/dist/mail/mail.service.js +56 -4
  24. package/dist/mail/mail.service.js.map +1 -1
  25. package/dist/setting/setting.service.d.ts +6 -1
  26. package/dist/setting/setting.service.d.ts.map +1 -1
  27. package/dist/setting/setting.service.js +188 -15
  28. package/dist/setting/setting.service.js.map +1 -1
  29. package/hedhog/data/setting_group.yaml +28 -0
  30. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +305 -75
  31. package/hedhog/frontend/messages/en.json +15 -3
  32. package/hedhog/frontend/messages/pt.json +15 -3
  33. package/package.json +5 -5
  34. package/src/ai/ai.service.ts +129 -1
  35. package/src/dashboard/dashboard-core/dashboard-core.controller.ts +9 -2
  36. package/src/dashboard/dashboard-core/dashboard-core.service.ts +276 -75
  37. package/src/index.ts +7 -6
  38. package/src/integration/services/integration-link.service.ts +190 -55
  39. package/src/mail/mail.service.ts +67 -3
  40. package/src/setting/setting.service.ts +222 -15
@@ -11,11 +11,21 @@ import {
11
11
  NotFoundException
12
12
  } from '@nestjs/common';
13
13
  import * as pako from 'pako';
14
+ import { IntegrationDeveloperApiService } from '../integration/services/integration-developer-api.service';
14
15
  import { CreateDTO } from './dto/create.dto';
15
16
  import { DeleteDTO } from './dto/delete.dto';
16
17
  import { SettingDTO } from './dto/setting.dto';
17
18
  import { UpdateDTO } from './dto/update.dto';
18
19
 
20
+ type SettingChangeEventInput = {
21
+ settingId?: number | null;
22
+ slug: string;
23
+ type?: string | null;
24
+ oldValue?: any;
25
+ newValue?: any;
26
+ source: string;
27
+ };
28
+
19
29
  @Injectable()
20
30
  export class SettingService {
21
31
 
@@ -29,6 +39,8 @@ export class SettingService {
29
39
  private readonly paginationService: PaginationService,
30
40
  @Inject(forwardRef(() => LocaleService))
31
41
  private readonly localeService: LocaleService,
42
+ @Inject(forwardRef(() => IntegrationDeveloperApiService))
43
+ private readonly integrationApi: IntegrationDeveloperApiService,
32
44
  ) { }
33
45
 
34
46
  async exportSettings(includeSecrets = false): Promise<Buffer> {
@@ -108,32 +120,47 @@ export class SettingService {
108
120
  id: true,
109
121
  slug: true,
110
122
  type: true,
123
+ value: true,
111
124
  },
112
125
  });
113
126
 
114
127
  const transaction = [];
128
+ const changedSettings: SettingChangeEventInput[] = [];
129
+
115
130
  for (const setting of settings) {
116
131
  const existingSetting = existingSettings.find((s) => s.slug === setting.slug);
117
132
  if (existingSetting) {
133
+ const nextValue = this.setValueFormattedByType(existingSetting.type, setting.value);
134
+
118
135
  transaction.push(
119
136
  this.prismaService.setting.update({
120
137
  where: {
121
138
  id: existingSetting.id,
122
139
  },
123
140
  data: {
124
- value: this.setValueFormattedByType(existingSetting.type, setting.value),
141
+ value: nextValue,
125
142
  },
126
143
  }),
127
144
  );
145
+
146
+ changedSettings.push({
147
+ settingId: existingSetting.id,
148
+ slug: existingSetting.slug,
149
+ type: existingSetting.type,
150
+ oldValue: existingSetting.value,
151
+ newValue: nextValue,
152
+ source: 'confirmImport',
153
+ });
128
154
  }
129
155
  }
130
156
 
131
157
  if (transaction.length > 0) {
132
158
  await this.prismaService.$transaction(transaction);
159
+ await this.emitSettingChangedEvents(changedSettings);
133
160
  }
134
161
 
135
- return {
136
- success: true,
162
+ return {
163
+ success: true,
137
164
  updatedCount: transaction.length,
138
165
  };
139
166
  }
@@ -178,6 +205,8 @@ export class SettingService {
178
205
  'password-min-uppercase',
179
206
  'password-min-length',
180
207
  'mfa-email-code-length',
208
+ 'ai-openai-api-key-enabled',
209
+ 'ai-gemini-api-key-enabled',
181
210
  'date-format',
182
211
  'time-format',
183
212
  'timezone',
@@ -233,21 +262,52 @@ export class SettingService {
233
262
  async setManySettings(data: SettingDTO) {
234
263
  this.clearCache();
235
264
  const transaction = [];
265
+ const existingSettings = await this.prismaService.setting.findMany({
266
+ where: {
267
+ slug: {
268
+ in: data.setting.map((item) => item.slug),
269
+ },
270
+ },
271
+ select: {
272
+ id: true,
273
+ slug: true,
274
+ type: true,
275
+ value: true,
276
+ },
277
+ });
278
+ const changedSettings: SettingChangeEventInput[] = [];
236
279
 
237
280
  for (const { slug, value } of data.setting) {
281
+ const existingSetting = existingSettings.find((item) => item.slug === slug);
282
+ if (!existingSetting) {
283
+ continue;
284
+ }
285
+
286
+ const nextValue = this.setValueFormattedByType(existingSetting.type, value);
287
+
238
288
  transaction.push(
239
289
  this.prismaService.setting.updateMany({
240
290
  where: {
241
291
  slug,
242
292
  },
243
293
  data: {
244
- value,
294
+ value: nextValue,
245
295
  },
246
296
  }),
247
297
  );
298
+
299
+ changedSettings.push({
300
+ settingId: existingSetting.id,
301
+ slug: existingSetting.slug,
302
+ type: existingSetting.type,
303
+ oldValue: existingSetting.value,
304
+ newValue: nextValue,
305
+ source: 'setManySettings',
306
+ });
248
307
  }
249
308
 
250
309
  await this.prismaService.$transaction(transaction);
310
+ await this.emitSettingChangedEvents(changedSettings);
251
311
  return { success: true };
252
312
  }
253
313
 
@@ -477,7 +537,7 @@ export class SettingService {
477
537
 
478
538
  async create(data: CreateDTO, _locale: string) {
479
539
  this.clearCache();
480
- return this.prismaService.setting.create({
540
+ const createdSetting = await this.prismaService.setting.create({
481
541
  data: {
482
542
  slug: data.slug,
483
543
  type: data.type as any,
@@ -490,21 +550,42 @@ export class SettingService {
490
550
  },
491
551
  },
492
552
  });
553
+
554
+ await this.emitSettingChangedEvents([
555
+ {
556
+ settingId: createdSetting.id,
557
+ slug: createdSetting.slug,
558
+ type: createdSetting.type,
559
+ oldValue: undefined,
560
+ newValue: createdSetting.value,
561
+ source: 'create',
562
+ },
563
+ ]);
564
+
565
+ return createdSetting;
493
566
  }
494
567
 
495
568
  async update({ id, data }: { id: number; data: UpdateDTO }) {
496
569
  this.clearCache();
497
570
 
498
- const { type } = await this.prismaService.setting.findFirst({
571
+ const currentSetting = await this.prismaService.setting.findFirst({
499
572
  where: {
500
573
  id,
501
574
  },
575
+ select: {
576
+ id: true,
577
+ slug: true,
578
+ type: true,
579
+ value: true,
580
+ },
502
581
  });
503
582
 
504
- if (!type) {
583
+ if (!currentSetting?.type) {
505
584
  throw new NotFoundException(`Setting not found.`);
506
585
  }
507
586
 
587
+ const { type } = currentSetting;
588
+
508
589
  // Build update data object with only provided fields
509
590
  const updateData: any = {};
510
591
 
@@ -541,6 +622,17 @@ export class SettingService {
541
622
  data: updateData,
542
623
  });
543
624
 
625
+ await this.emitSettingChangedEvents([
626
+ {
627
+ settingId: updatedSetting.id,
628
+ slug: updatedSetting.slug,
629
+ type: updatedSetting.type,
630
+ oldValue: currentSetting.value,
631
+ newValue: updatedSetting.value,
632
+ source: 'update',
633
+ },
634
+ ]);
635
+
544
636
  // Garantir que o value sobrescreve o valor original
545
637
  const result = { ...updatedSetting };
546
638
  result.value = this.getValueFormattedByType(updatedSetting.type, updatedSetting.value);
@@ -549,28 +641,123 @@ export class SettingService {
549
641
 
550
642
  async updateFromSlug(slug: string, data: UpdateDTO) {
551
643
  this.clearCache();
552
- const { id, type } = await this.prismaService.setting.findFirst({
644
+ const currentSetting = await this.prismaService.setting.findFirst({
553
645
  where: {
554
646
  slug,
555
647
  },
648
+ select: {
649
+ id: true,
650
+ slug: true,
651
+ type: true,
652
+ value: true,
653
+ },
556
654
  });
557
655
 
558
- if (!id) {
656
+ if (!currentSetting?.id) {
559
657
  throw new NotFoundException(`Setting with slug ${slug} not found.`);
560
658
  }
561
659
 
660
+ const nextValue = this.setValueFormattedByType(currentSetting.type, data.value);
661
+
562
662
  const updatedSetting = await this.prismaService.setting.update({
563
- where: { id },
663
+ where: { id: currentSetting.id },
564
664
  data: {
565
- value: this.setValueFormattedByType(type, data.value),
665
+ value: nextValue,
566
666
  },
567
667
  });
568
668
 
669
+ await this.emitSettingChangedEvents([
670
+ {
671
+ settingId: updatedSetting.id,
672
+ slug: updatedSetting.slug,
673
+ type: updatedSetting.type,
674
+ oldValue: currentSetting.value,
675
+ newValue: updatedSetting.value,
676
+ source: 'updateFromSlug',
677
+ },
678
+ ]);
679
+
569
680
  const result = { ...updatedSetting };
570
681
  result.value = this.getValueFormattedByType(updatedSetting.type, updatedSetting.value);
571
682
  return result;
572
683
  }
573
684
 
685
+ private normalizeValueForComparison(value: any) {
686
+ if (value === undefined) {
687
+ return '__undefined__';
688
+ }
689
+
690
+ if (value === null) {
691
+ return null;
692
+ }
693
+
694
+ if (typeof value === 'string') {
695
+ return value.trim();
696
+ }
697
+
698
+ try {
699
+ return JSON.stringify(value);
700
+ } catch {
701
+ return String(value);
702
+ }
703
+ }
704
+
705
+ private isValueDefined(value: any) {
706
+ if (value === undefined || value === null) {
707
+ return false;
708
+ }
709
+
710
+ if (typeof value === 'string') {
711
+ return value.trim().length > 0;
712
+ }
713
+
714
+ if (Array.isArray(value)) {
715
+ return value.length > 0;
716
+ }
717
+
718
+ return true;
719
+ }
720
+
721
+ private async emitSettingChangedEvents(changes: SettingChangeEventInput[]) {
722
+ const events = changes
723
+ .filter((change) => Boolean(change.slug))
724
+ .filter(
725
+ (change) =>
726
+ this.normalizeValueForComparison(change.oldValue) !==
727
+ this.normalizeValueForComparison(change.newValue),
728
+ )
729
+ .map((change) => ({
730
+ eventName: 'core.setting.changed',
731
+ sourceModule: 'core',
732
+ aggregateType: 'setting',
733
+ aggregateId: String(change.settingId ?? change.slug),
734
+ payload: {
735
+ settingId: change.settingId ?? null,
736
+ slug: change.slug,
737
+ type: change.type ?? null,
738
+ source: change.source,
739
+ hasValue: this.isValueDefined(change.newValue),
740
+ },
741
+ metadata: {
742
+ settingSlug: change.slug,
743
+ source: change.source,
744
+ },
745
+ }));
746
+
747
+ if (events.length === 0) {
748
+ return;
749
+ }
750
+
751
+ try {
752
+ await this.integrationApi.publishEvents(events);
753
+ } catch (error) {
754
+ this.logger.error(
755
+ `Failed to publish ${events.length} setting change event(s).`,
756
+ error instanceof Error ? error.stack : String(error),
757
+ );
758
+ }
759
+ }
760
+
574
761
  setValueFormattedByType(type: string, value: any) {
575
762
  switch (type) {
576
763
  case 'boolean':
@@ -829,23 +1016,43 @@ export class SettingService {
829
1016
  async setValue(slug: string, value: string) {
830
1017
  this.clearCache();
831
1018
 
832
- const { id } = await this.prismaService.setting.findFirst({
1019
+ const currentSetting = await this.prismaService.setting.findFirst({
833
1020
  where: {
834
1021
  slug,
835
1022
  },
1023
+ select: {
1024
+ id: true,
1025
+ slug: true,
1026
+ type: true,
1027
+ value: true,
1028
+ },
836
1029
  });
837
1030
 
838
- if (!id) {
1031
+ if (!currentSetting?.id) {
839
1032
  throw new NotFoundException(`Setting with slug ${slug} not found.`);
840
1033
  }
841
1034
 
842
- return this.prismaService.setting.updateMany({
1035
+ const nextValue = this.setValueFormattedByType(currentSetting.type, value);
1036
+ const result = await this.prismaService.setting.updateMany({
843
1037
  where: {
844
1038
  slug,
845
1039
  },
846
1040
  data: {
847
- value,
1041
+ value: nextValue,
848
1042
  },
849
1043
  });
1044
+
1045
+ await this.emitSettingChangedEvents([
1046
+ {
1047
+ settingId: currentSetting.id,
1048
+ slug: currentSetting.slug,
1049
+ type: currentSetting.type,
1050
+ oldValue: currentSetting.value,
1051
+ newValue: nextValue,
1052
+ source: 'setValue',
1053
+ },
1054
+ ]);
1055
+
1056
+ return result;
850
1057
  }
851
1058
  }