@hed-hog/core 0.0.299 → 0.0.301

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 (133) 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/dashboard.controller.d.ts +6 -0
  6. package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
  7. package/dist/dashboard/dashboard/dashboard.service.d.ts +6 -0
  8. package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
  9. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +2 -1
  10. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts.map +1 -1
  11. package/dist/dashboard/dashboard-component/dashboard-component.controller.js +6 -3
  12. package/dist/dashboard/dashboard-component/dashboard-component.controller.js.map +1 -1
  13. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +7 -1
  14. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
  15. package/dist/dashboard/dashboard-component/dashboard-component.service.js +76 -33
  16. package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
  17. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +82 -0
  18. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  19. package/dist/dashboard/dashboard-core/dashboard-core.controller.js +117 -0
  20. package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
  21. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +93 -0
  22. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  23. package/dist/dashboard/dashboard-core/dashboard-core.service.js +654 -20
  24. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  25. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +2 -0
  26. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
  27. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +2 -0
  28. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
  29. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +2 -0
  30. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts.map +1 -1
  31. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +2 -0
  32. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts.map +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/mail/mail.service.d.ts +9 -2
  38. package/dist/mail/mail.service.d.ts.map +1 -1
  39. package/dist/mail/mail.service.js +56 -4
  40. package/dist/mail/mail.service.js.map +1 -1
  41. package/dist/setting/setting.service.d.ts +6 -1
  42. package/dist/setting/setting.service.d.ts.map +1 -1
  43. package/dist/setting/setting.service.js +188 -15
  44. package/dist/setting/setting.service.js.map +1 -1
  45. package/hedhog/data/dashboard.yaml +12 -6
  46. package/hedhog/data/dashboard_component_role.yaml +66 -0
  47. package/hedhog/data/dashboard_role.yaml +2 -8
  48. package/hedhog/data/route.yaml +72 -0
  49. package/hedhog/data/setting_group.yaml +28 -0
  50. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +333 -128
  51. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +277 -53
  52. package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +179 -231
  53. package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +64 -18
  54. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +1619 -0
  55. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +37 -0
  56. package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +1 -1
  57. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +6 -6
  58. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +8 -8
  59. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +3 -3
  60. package/hedhog/frontend/app/dashboard/page.tsx.ejs +3 -25
  61. package/hedhog/frontend/messages/en.json +124 -2
  62. package/hedhog/frontend/messages/pt.json +123 -1
  63. package/hedhog/frontend/widgets/account-security.tsx.ejs +1 -1
  64. package/hedhog/frontend/widgets/active-users-card.tsx.ejs +2 -2
  65. package/hedhog/frontend/widgets/activity-timeline.tsx.ejs +1 -1
  66. package/hedhog/frontend/widgets/email-notifications.tsx.ejs +1 -1
  67. package/hedhog/frontend/widgets/locale-config.tsx.ejs +1 -1
  68. package/hedhog/frontend/widgets/login-history-chart.tsx.ejs +1 -1
  69. package/hedhog/frontend/widgets/mail-config.tsx.ejs +1 -1
  70. package/hedhog/frontend/widgets/mail-sent-card.tsx.ejs +2 -2
  71. package/hedhog/frontend/widgets/mail-sent-chart.tsx.ejs +1 -1
  72. package/hedhog/frontend/widgets/menus-card.tsx.ejs +2 -2
  73. package/hedhog/frontend/widgets/oauth-config.tsx.ejs +1 -1
  74. package/hedhog/frontend/widgets/permissions-card.tsx.ejs +2 -2
  75. package/hedhog/frontend/widgets/permissions-chart.tsx.ejs +1 -1
  76. package/hedhog/frontend/widgets/profile-card.tsx.ejs +1 -1
  77. package/hedhog/frontend/widgets/routes-card.tsx.ejs +2 -2
  78. package/hedhog/frontend/widgets/session-activity-chart.tsx.ejs +1 -1
  79. package/hedhog/frontend/widgets/sessions-today-card.tsx.ejs +2 -2
  80. package/hedhog/frontend/widgets/stat-access-level.tsx.ejs +1 -1
  81. package/hedhog/frontend/widgets/stat-actions-today.tsx.ejs +1 -1
  82. package/hedhog/frontend/widgets/stat-consecutive-days.tsx.ejs +1 -1
  83. package/hedhog/frontend/widgets/stat-online-time.tsx.ejs +1 -1
  84. package/hedhog/frontend/widgets/storage-config.tsx.ejs +1 -1
  85. package/hedhog/frontend/widgets/theme-config.tsx.ejs +1 -1
  86. package/hedhog/frontend/widgets/user-growth-chart.tsx.ejs +1 -1
  87. package/hedhog/frontend/widgets/user-roles.tsx.ejs +1 -1
  88. package/hedhog/frontend/widgets/user-sessions.tsx.ejs +1 -1
  89. package/hedhog/table/dashboard.yaml +6 -0
  90. package/package.json +3 -3
  91. package/src/ai/ai.service.ts +129 -1
  92. package/src/dashboard/dashboard-component/dashboard-component.controller.ts +15 -2
  93. package/src/dashboard/dashboard-component/dashboard-component.service.ts +107 -43
  94. package/src/dashboard/dashboard-core/dashboard-core.controller.ts +119 -1
  95. package/src/dashboard/dashboard-core/dashboard-core.service.ts +876 -20
  96. package/src/index.ts +7 -6
  97. package/src/mail/mail.service.ts +67 -3
  98. package/src/setting/setting.service.ts +222 -15
  99. package/hedhog/frontend/app/dashboard/components/widgets/core..gitkeep.ejs +0 -11
  100. package/hedhog/frontend/app/dashboard/components/widgets/core.account-security.tsx.ejs +0 -192
  101. package/hedhog/frontend/app/dashboard/components/widgets/core.active-users-card.tsx.ejs +0 -58
  102. package/hedhog/frontend/app/dashboard/components/widgets/core.activity-timeline.tsx.ejs +0 -223
  103. package/hedhog/frontend/app/dashboard/components/widgets/core.email-notifications.tsx.ejs +0 -226
  104. package/hedhog/frontend/app/dashboard/components/widgets/core.locale-config.tsx.ejs +0 -168
  105. package/hedhog/frontend/app/dashboard/components/widgets/core.login-history-chart.tsx.ejs +0 -115
  106. package/hedhog/frontend/app/dashboard/components/widgets/core.mail-config.tsx.ejs +0 -199
  107. package/hedhog/frontend/app/dashboard/components/widgets/core.mail-sent-card.tsx.ejs +0 -58
  108. package/hedhog/frontend/app/dashboard/components/widgets/core.mail-sent-chart.tsx.ejs +0 -149
  109. package/hedhog/frontend/app/dashboard/components/widgets/core.menus-card.tsx.ejs +0 -58
  110. package/hedhog/frontend/app/dashboard/components/widgets/core.oauth-config.tsx.ejs +0 -175
  111. package/hedhog/frontend/app/dashboard/components/widgets/core.permissions-card.tsx.ejs +0 -61
  112. package/hedhog/frontend/app/dashboard/components/widgets/core.permissions-chart.tsx.ejs +0 -156
  113. package/hedhog/frontend/app/dashboard/components/widgets/core.profile-card.tsx.ejs +0 -186
  114. package/hedhog/frontend/app/dashboard/components/widgets/core.routes-card.tsx.ejs +0 -58
  115. package/hedhog/frontend/app/dashboard/components/widgets/core.session-activity-chart.tsx.ejs +0 -183
  116. package/hedhog/frontend/app/dashboard/components/widgets/core.sessions-today-card.tsx.ejs +0 -62
  117. package/hedhog/frontend/app/dashboard/components/widgets/core.stat-access-level.tsx.ejs +0 -57
  118. package/hedhog/frontend/app/dashboard/components/widgets/core.stat-actions-today.tsx.ejs +0 -57
  119. package/hedhog/frontend/app/dashboard/components/widgets/core.stat-consecutive-days.tsx.ejs +0 -57
  120. package/hedhog/frontend/app/dashboard/components/widgets/core.stat-online-time.tsx.ejs +0 -57
  121. package/hedhog/frontend/app/dashboard/components/widgets/core.storage-config.tsx.ejs +0 -196
  122. package/hedhog/frontend/app/dashboard/components/widgets/core.theme-config.tsx.ejs +0 -213
  123. package/hedhog/frontend/app/dashboard/components/widgets/core.user-growth-chart.tsx.ejs +0 -210
  124. package/hedhog/frontend/app/dashboard/components/widgets/core.user-roles.tsx.ejs +0 -132
  125. package/hedhog/frontend/app/dashboard/components/widgets/core.user-sessions.tsx.ejs +0 -236
  126. package/hedhog/frontend/app/dashboard/components/widgets/finance.alerts.tsx.ejs +0 -108
  127. package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-balance-kpi.tsx.ejs +0 -66
  128. package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-flow-chart.tsx.ejs +0 -122
  129. package/hedhog/frontend/app/dashboard/components/widgets/finance.default-kpi.tsx.ejs +0 -63
  130. package/hedhog/frontend/app/dashboard/components/widgets/finance.payable-30d-kpi.tsx.ejs +0 -73
  131. package/hedhog/frontend/app/dashboard/components/widgets/finance.receivable-30d-kpi.tsx.ejs +0 -73
  132. package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-payable.tsx.ejs +0 -123
  133. package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-receivable.tsx.ejs +0 -118
@@ -1,8 +1,8 @@
1
1
  import { useWidgetData } from '@/hooks/use-widget-data';
2
2
  import { LayoutList } from 'lucide-react';
3
3
  import { useTranslations } from 'next-intl';
4
- import StatCard from '../stats';
5
- import { WidgetWrapper } from '../widget-wrapper';
4
+ import StatCard from '@/app/(app)/(libraries)/core/dashboard/components/stats';
5
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
6
6
 
7
7
  interface MenusCardProps {
8
8
  widget?: any;
@@ -15,7 +15,7 @@ import type {
15
15
  } from '@/types/widget-data';
16
16
  import { CheckCircle2, KeyRound, XCircle } from 'lucide-react';
17
17
  import { useTranslations } from 'next-intl';
18
- import { WidgetWrapper } from '../widget-wrapper';
18
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
19
19
 
20
20
  const defaultOAuthConfigData: OAuthConfigWidgetData = {
21
21
  status: {
@@ -1,8 +1,8 @@
1
1
  import { useWidgetData } from '@/hooks/use-widget-data';
2
2
  import { Shield } from 'lucide-react';
3
3
  import { useTranslations } from 'next-intl';
4
- import StatCard from '../stats';
5
- import { WidgetWrapper } from '../widget-wrapper';
4
+ import StatCard from '@/app/(app)/(libraries)/core/dashboard/components/stats';
5
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
6
6
 
7
7
  interface PermissionsCardProps {
8
8
  widget?: any;
@@ -11,7 +11,7 @@ import { useWidgetData } from '@/hooks/use-widget-data';
11
11
  import { IconGripVertical } from '@tabler/icons-react';
12
12
  import { useTranslations } from 'next-intl';
13
13
  import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
14
- import { WidgetWrapper } from '../widget-wrapper';
14
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
15
15
 
16
16
  function CustomTooltip({
17
17
  active,
@@ -21,7 +21,7 @@ import {
21
21
  } from 'lucide-react';
22
22
  import { useTranslations } from 'next-intl';
23
23
  import { useRouter } from 'next/navigation';
24
- import { WidgetWrapper } from '../widget-wrapper';
24
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
25
25
 
26
26
  function getInitials(name: string): string {
27
27
  return name
@@ -1,8 +1,8 @@
1
1
  import { useWidgetData } from '@/hooks/use-widget-data';
2
2
  import { Route } from 'lucide-react';
3
3
  import { useTranslations } from 'next-intl';
4
- import StatCard from '../stats';
5
- import { WidgetWrapper } from '../widget-wrapper';
4
+ import StatCard from '@/app/(app)/(libraries)/core/dashboard/components/stats';
5
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
6
6
 
7
7
  interface RoutesCardProps {
8
8
  widget?: any;
@@ -19,7 +19,7 @@ import {
19
19
  XAxis,
20
20
  YAxis,
21
21
  } from 'recharts';
22
- import { WidgetWrapper } from '../widget-wrapper';
22
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
23
23
 
24
24
  function CustomTooltip({
25
25
  active,
@@ -1,8 +1,8 @@
1
1
  import { useWidgetData } from '@/hooks/use-widget-data';
2
2
  import { Activity } from 'lucide-react';
3
3
  import { useTranslations } from 'next-intl';
4
- import StatCard from '../stats';
5
- import { WidgetWrapper } from '../widget-wrapper';
4
+ import StatCard from '@/app/(app)/(libraries)/core/dashboard/components/stats';
5
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
6
6
 
7
7
  interface SessionsTodayProps {
8
8
  widget?: any;
@@ -5,7 +5,7 @@ import { useWidgetData } from '@/hooks/use-widget-data';
5
5
  import type { AllWidgetsData } from '@/types/widget-data';
6
6
  import { Zap } from 'lucide-react';
7
7
  import { useTranslations } from 'next-intl';
8
- import { WidgetWrapper } from '../widget-wrapper';
8
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
9
9
 
10
10
  interface StatAccessLevelProps {
11
11
  widget?: { name?: string };
@@ -5,7 +5,7 @@ import { useWidgetData } from '@/hooks/use-widget-data';
5
5
  import type { AllWidgetsData } from '@/types/widget-data';
6
6
  import { MousePointerClick } from 'lucide-react';
7
7
  import { useTranslations } from 'next-intl';
8
- import { WidgetWrapper } from '../widget-wrapper';
8
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
9
9
 
10
10
  interface StatActionsTodayProps {
11
11
  widget?: { name?: string };
@@ -5,7 +5,7 @@ import { useWidgetData } from '@/hooks/use-widget-data';
5
5
  import type { AllWidgetsData } from '@/types/widget-data';
6
6
  import { CalendarDays } from 'lucide-react';
7
7
  import { useTranslations } from 'next-intl';
8
- import { WidgetWrapper } from '../widget-wrapper';
8
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
9
9
 
10
10
  interface StatConsecutiveDaysProps {
11
11
  widget?: { name?: string };
@@ -5,7 +5,7 @@ import { useWidgetData } from '@/hooks/use-widget-data';
5
5
  import type { AllWidgetsData } from '@/types/widget-data';
6
6
  import { Clock } from 'lucide-react';
7
7
  import { useTranslations } from 'next-intl';
8
- import { WidgetWrapper } from '../widget-wrapper';
8
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
9
9
 
10
10
  interface StatOnlineTimeProps {
11
11
  widget?: { name?: string };
@@ -15,7 +15,7 @@ import type {
15
15
  } from '@/types/widget-data';
16
16
  import { CheckCircle2, HardDrive } from 'lucide-react';
17
17
  import { useTranslations } from 'next-intl';
18
- import { WidgetWrapper } from '../widget-wrapper';
18
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
19
19
 
20
20
  const defaultStorageConfigData: StorageConfigWidgetData = {
21
21
  status: {
@@ -15,7 +15,7 @@ import type {
15
15
  } from '@/types/widget-data';
16
16
  import { Palette } from 'lucide-react';
17
17
  import { useTranslations } from 'next-intl';
18
- import { WidgetWrapper } from '../widget-wrapper';
18
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
19
19
 
20
20
  const emptyPaletteMode = {
21
21
  primary: null,
@@ -19,7 +19,7 @@ import {
19
19
  XAxis,
20
20
  YAxis,
21
21
  } from 'recharts';
22
- import { WidgetWrapper } from '../widget-wrapper';
22
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
23
23
 
24
24
  function CustomTooltip({
25
25
  active,
@@ -12,7 +12,7 @@ import { useWidgetData } from '@/hooks/use-widget-data';
12
12
  import type { AllWidgetsData, RoleData } from '@/types/widget-data';
13
13
  import { Crown, ShieldCheck } from 'lucide-react';
14
14
  import { useTranslations } from 'next-intl';
15
- import { WidgetWrapper } from '../widget-wrapper';
15
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
16
16
 
17
17
  const levelStyles = [
18
18
  {
@@ -29,7 +29,7 @@ import {
29
29
  } from 'lucide-react';
30
30
  import { useTranslations } from 'next-intl';
31
31
  import { useRouter } from 'next/navigation';
32
- import { WidgetWrapper } from '../widget-wrapper';
32
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
33
33
 
34
34
  function detectDeviceType(ua: string): 'desktop' | 'mobile' | 'tablet' {
35
35
  const u = ua.toLowerCase();
@@ -1,11 +1,17 @@
1
1
  columns:
2
2
  - type: pk
3
3
  - type: slug
4
+ - name: icon
5
+ length: 255
6
+ isNullable: true
4
7
  - name: name
5
8
  type: locale_varchar
6
9
  length: 255
7
10
  locale:
8
11
  en: Name
9
12
  pt: Nome
13
+ - name: is_template
14
+ type: boolean
15
+ default: false
10
16
  - type: created_at
11
17
  - type: updated_at
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/core",
3
- "version": "0.0.299",
3
+ "version": "0.0.301",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -32,9 +32,9 @@
32
32
  "uuid": "^11.1.0",
33
33
  "@hed-hog/api": "0.0.6",
34
34
  "@hed-hog/api-types": "0.0.1",
35
- "@hed-hog/api-pagination": "0.0.7",
36
- "@hed-hog/api-locale": "0.0.14",
37
35
  "@hed-hog/api-mail": "0.0.9",
36
+ "@hed-hog/api-locale": "0.0.14",
37
+ "@hed-hog/api-pagination": "0.0.7",
38
38
  "@hed-hog/api-prisma": "0.0.6"
39
39
  },
40
40
  "exports": {
@@ -7,12 +7,14 @@ import {
7
7
  Injectable,
8
8
  Logger,
9
9
  NotFoundException,
10
+ OnModuleInit,
10
11
  } from '@nestjs/common';
11
12
  import axios from 'axios';
12
13
  import { createHash } from 'crypto';
13
14
  import pdfParse from 'pdf-parse';
14
15
  import { DeleteDTO } from '../dto/delete.dto';
15
16
  import { FileService } from '../file/file.service';
17
+ import { IntegrationDeveloperApiService } from '../integration/services/integration-developer-api.service';
16
18
  import { SettingService } from '../setting/setting.service';
17
19
  import { ChatAgentDTO } from './dto/chat-agent.dto';
18
20
  import { ChatDTO } from './dto/chat.dto';
@@ -38,8 +40,10 @@ type AiAttachment = {
38
40
  buffer: Buffer;
39
41
  };
40
42
 
43
+ type AiProviderKeySettingSlug = 'ai-openai-api-key' | 'ai-gemini-api-key';
44
+
41
45
  @Injectable()
42
- export class AiService {
46
+ export class AiService implements OnModuleInit {
43
47
  private readonly logger = new Logger(AiService.name);
44
48
 
45
49
  constructor(
@@ -49,8 +53,29 @@ export class AiService {
49
53
  private readonly settingService: SettingService,
50
54
  @Inject(forwardRef(() => FileService))
51
55
  private readonly fileService: FileService,
56
+ @Inject(forwardRef(() => IntegrationDeveloperApiService))
57
+ private readonly integrationApi: IntegrationDeveloperApiService,
52
58
  ) {}
53
59
 
60
+ onModuleInit(): void {
61
+ this.integrationApi.subscribe({
62
+ eventName: 'core.setting.changed',
63
+ consumerName: 'core.ai-provider-key-validator',
64
+ priority: 0,
65
+ handler: async (event) => {
66
+ const slug = String(event.payload?.slug || '').trim();
67
+
68
+ if (!this.isAiProviderKeySlug(slug)) {
69
+ return;
70
+ }
71
+
72
+ await this.syncProviderEnabledFlag(slug);
73
+ },
74
+ });
75
+
76
+ void this.syncAllProviderEnabledFlags();
77
+ }
78
+
54
79
  async chat(data: ChatDTO, file?: MulterFile) {
55
80
  const provider = data.provider || 'openai';
56
81
  const attachment = await this.resolveAttachment(file, data.file_id);
@@ -398,6 +423,109 @@ export class AiService {
398
423
  return 'gemini-1.5-flash';
399
424
  }
400
425
 
426
+ private isAiProviderKeySlug(slug: string): slug is AiProviderKeySettingSlug {
427
+ return slug === 'ai-openai-api-key' || slug === 'ai-gemini-api-key';
428
+ }
429
+
430
+ private getEnabledSlugFromProviderKey(slug: AiProviderKeySettingSlug) {
431
+ return slug === 'ai-openai-api-key'
432
+ ? 'ai-openai-api-key-enabled'
433
+ : 'ai-gemini-api-key-enabled';
434
+ }
435
+
436
+ private async syncAllProviderEnabledFlags() {
437
+ const results = await Promise.allSettled([
438
+ this.syncProviderEnabledFlag('ai-openai-api-key'),
439
+ this.syncProviderEnabledFlag('ai-gemini-api-key'),
440
+ ]);
441
+
442
+ results.forEach((result, index) => {
443
+ if (result.status === 'rejected') {
444
+ const slug = index === 0 ? 'ai-openai-api-key' : 'ai-gemini-api-key';
445
+ this.logger.warn(
446
+ `Failed to sync provider availability for ${slug}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`,
447
+ );
448
+ }
449
+ });
450
+ }
451
+
452
+ private async syncProviderEnabledFlag(slug: AiProviderKeySettingSlug) {
453
+ const values = await this.settingService.getSettingValues(slug);
454
+ const apiKey = String(values?.[slug] || '').trim();
455
+ const enabledSlug = this.getEnabledSlugFromProviderKey(slug);
456
+
457
+ const isValid = slug === 'ai-openai-api-key'
458
+ ? await this.validateOpenAiKey(apiKey)
459
+ : await this.validateGeminiKey(apiKey);
460
+
461
+ await this.settingService.setValue(enabledSlug, isValid ? 'true' : 'false');
462
+ }
463
+
464
+ private getValidationErrorMessage(error: unknown) {
465
+ if (axios.isAxiosError(error)) {
466
+ const providerMessage =
467
+ error.response?.data?.error?.message ||
468
+ error.response?.data?.message ||
469
+ error.message;
470
+ const status = error.response?.status || 'network';
471
+ return `${status}: ${providerMessage}`;
472
+ }
473
+
474
+ return error instanceof Error ? error.message : String(error);
475
+ }
476
+
477
+ private async validateOpenAiKey(apiKey?: string | null): Promise<boolean> {
478
+ const normalizedKey = String(apiKey || '').trim();
479
+
480
+ if (!normalizedKey) {
481
+ return false;
482
+ }
483
+
484
+ try {
485
+ await axios.get('https://api.openai.com/v1/models', {
486
+ headers: {
487
+ Authorization: `Bearer ${normalizedKey}`,
488
+ },
489
+ params: {
490
+ limit: 1,
491
+ },
492
+ timeout: 5000,
493
+ });
494
+
495
+ return true;
496
+ } catch (error) {
497
+ this.logger.warn(
498
+ `OpenAI key validation failed: ${this.getValidationErrorMessage(error)}`,
499
+ );
500
+ return false;
501
+ }
502
+ }
503
+
504
+ private async validateGeminiKey(apiKey?: string | null): Promise<boolean> {
505
+ const normalizedKey = String(apiKey || '').trim();
506
+
507
+ if (!normalizedKey) {
508
+ return false;
509
+ }
510
+
511
+ try {
512
+ await axios.get('https://generativelanguage.googleapis.com/v1beta/models', {
513
+ params: {
514
+ key: normalizedKey,
515
+ pageSize: 1,
516
+ },
517
+ timeout: 5000,
518
+ });
519
+
520
+ return true;
521
+ } catch (error) {
522
+ this.logger.warn(
523
+ `Gemini key validation failed: ${this.getValidationErrorMessage(error)}`,
524
+ );
525
+ return false;
526
+ }
527
+ }
528
+
401
529
  private async getApiKeys() {
402
530
  const settings = await this.settingService.getSettingValues([
403
531
  'ai-openai-api-key',
@@ -12,6 +12,7 @@ import {
12
12
  ParseIntPipe,
13
13
  Patch,
14
14
  Post,
15
+ Query,
15
16
  UploadedFile,
16
17
  UseInterceptors,
17
18
  forwardRef
@@ -37,8 +38,20 @@ export class DashboardComponentController {
37
38
  }
38
39
 
39
40
  @Get('user')
40
- getAllComponentsByUserRole(@Pagination() paginationParams, @User() { id }) {
41
- return this.dashboardComponentService.getAllComponentsByUserRole(paginationParams, id);
41
+ getAllComponentsByUserRole(
42
+ @Pagination() paginationParams,
43
+ @User() { id },
44
+ @Query('librarySlug') librarySlug?: string,
45
+ @Query('exclude') exclude?: string,
46
+ ) {
47
+ return this.dashboardComponentService.getAllComponentsByUserRole(
48
+ {
49
+ ...paginationParams,
50
+ librarySlug,
51
+ exclude,
52
+ },
53
+ id,
54
+ );
42
55
  }
43
56
 
44
57
  @Get(':id')
@@ -2,19 +2,24 @@ import { getLocaleText } from '@hed-hog/api-locale';
2
2
  import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
3
3
  import { PrismaService } from '@hed-hog/api-prisma';
4
4
  import {
5
- BadRequestException,
6
- ForbiddenException,
7
- forwardRef,
8
- Inject,
9
- Injectable,
10
- NotFoundException
5
+ BadRequestException,
6
+ ForbiddenException,
7
+ forwardRef,
8
+ Inject,
9
+ Injectable,
10
+ NotFoundException,
11
11
  } from '@nestjs/common';
12
12
  import { existsSync, promises as fs } from 'fs';
13
13
  import { join, resolve } from 'path';
14
14
  import {
15
- CreateDashboardComponentDTO,
16
- UpdateDashboardComponentDTO,
15
+ CreateDashboardComponentDTO,
16
+ UpdateDashboardComponentDTO,
17
17
  } from './dto';
18
+ type DashboardComponentUserPagination = PaginationDTO & {
19
+ librarySlug?: string;
20
+ exclude?: string;
21
+ };
22
+
18
23
  @Injectable()
19
24
  export class DashboardComponentService {
20
25
  constructor(
@@ -79,7 +84,10 @@ export class DashboardComponentService {
79
84
  );
80
85
  }
81
86
 
82
- async getAllComponentsByUserRole(paginationParams: PaginationDTO, userId: number) {
87
+ async getAllComponentsByUserRole(
88
+ paginationParams: DashboardComponentUserPagination,
89
+ userId: number,
90
+ ) {
83
91
  const userRoles = await this.prismaService.role_user.findMany({
84
92
  where: { user_id: userId },
85
93
  select: { role_id: true },
@@ -88,7 +96,7 @@ export class DashboardComponentService {
88
96
  const userRoleIds = userRoles.map((ur) => ur.role_id);
89
97
 
90
98
  if (userRoleIds.length === 0) {
91
- return this.paginationService.paginate(
99
+ const emptyResult = await this.paginationService.paginate(
92
100
  this.prismaService.dashboard_component,
93
101
  paginationParams,
94
102
  {
@@ -121,9 +129,30 @@ export class DashboardComponentService {
121
129
  },
122
130
  'dashboardComponent',
123
131
  );
132
+
133
+ return {
134
+ ...emptyResult,
135
+ modules: [],
136
+ };
124
137
  }
125
138
 
126
- const fields = ['slug']
139
+ const requestedLibrarySlug = paginationParams.librarySlug?.trim();
140
+ const excludedComponents = (paginationParams.exclude ?? '')
141
+ .split(',')
142
+ .map((value) => value.trim())
143
+ .filter(Boolean)
144
+ .map((value) => {
145
+ const slugParts = value.split('.').filter(Boolean);
146
+ const baseSlug = slugParts[slugParts.length - 1] || value;
147
+ const librarySlug = slugParts.length > 1 ? slugParts[0] : undefined;
148
+
149
+ return {
150
+ slug: baseSlug,
151
+ ...(librarySlug ? { library_slug: librarySlug } : {}),
152
+ };
153
+ });
154
+
155
+ const fields = ['slug', 'library_slug'];
127
156
  const OR = this.prismaService.createInsensitiveSearch(
128
157
  fields,
129
158
  paginationParams,
@@ -142,52 +171,87 @@ export class DashboardComponentService {
142
171
  });
143
172
  }
144
173
 
145
- return this.paginationService.paginate(
146
- this.prismaService.dashboard_component,
147
- paginationParams,
174
+ const baseFilters = [
148
175
  {
149
- include: {
150
- dashboard_component_locale: {
151
- include: {
152
- locale: true,
176
+ dashboard_component_role: {
177
+ some: {
178
+ role_id: {
179
+ in: userRoleIds,
153
180
  },
154
181
  },
155
- dashboard_component_role: {
156
- include: {
157
- role: {
158
- include: {
159
- role_locale: {
160
- include: {
161
- locale: true,
182
+ },
183
+ },
184
+ ...(OR.length > 0 ? [{ OR }] : []),
185
+ ...(excludedComponents.length > 0
186
+ ? [
187
+ {
188
+ NOT: {
189
+ OR: excludedComponents,
190
+ },
191
+ },
192
+ ]
193
+ : []),
194
+ ];
195
+
196
+ const [paginatedResult, modules] = await Promise.all([
197
+ this.paginationService.paginate(
198
+ this.prismaService.dashboard_component,
199
+ paginationParams,
200
+ {
201
+ include: {
202
+ dashboard_component_locale: {
203
+ include: {
204
+ locale: true,
205
+ },
206
+ },
207
+ dashboard_component_role: {
208
+ include: {
209
+ role: {
210
+ include: {
211
+ role_locale: {
212
+ include: {
213
+ locale: true,
214
+ },
162
215
  },
163
216
  },
164
217
  },
165
218
  },
166
219
  },
167
220
  },
221
+ where: {
222
+ AND: [
223
+ ...baseFilters,
224
+ ...(requestedLibrarySlug
225
+ ? [{ library_slug: requestedLibrarySlug }]
226
+ : []),
227
+ ],
228
+ },
229
+ orderBy: {
230
+ created_at: 'desc',
231
+ },
168
232
  },
233
+ 'dashboardComponent',
234
+ ),
235
+ this.prismaService.dashboard_component.findMany({
169
236
  where: {
170
- AND: [
171
- {
172
- dashboard_component_role: {
173
- some: {
174
- role_id: {
175
- in: userRoleIds,
176
- },
177
- },
178
- },
179
- },
180
- {
181
- OR,
182
- },
183
- ],
237
+ AND: baseFilters,
184
238
  },
239
+ select: {
240
+ library_slug: true,
241
+ },
242
+ distinct: ['library_slug'],
185
243
  orderBy: {
186
- created_at: 'desc',
244
+ library_slug: 'asc',
187
245
  },
188
- },
189
- 'dashboardComponent',
190
- );
246
+ }),
247
+ ]);
248
+
249
+ return {
250
+ ...paginatedResult,
251
+ modules: modules
252
+ .map((component) => component.library_slug)
253
+ .filter((value): value is string => Boolean(value)),
254
+ };
191
255
  }
192
256
 
193
257
  async getComponent(id: number, locale: string, userId?: number) {