@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
@@ -430,6 +430,170 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
430
430
  }
431
431
  return value.map((provider) => String(provider).toLowerCase());
432
432
  }
433
+ slugifyDashboardName(value) {
434
+ const normalized = value
435
+ .normalize('NFD')
436
+ .replace(/[\u0300-\u036f]/g, '')
437
+ .toLowerCase()
438
+ .replace(/[^a-z0-9]+/g, '-')
439
+ .replace(/^-+|-+$/g, '');
440
+ return normalized || `dashboard-${Date.now()}`;
441
+ }
442
+ getDashboardDisplayName(dashboard) {
443
+ var _a, _b;
444
+ return ((_b = (_a = dashboard.dashboard_locale) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.name) || dashboard.slug;
445
+ }
446
+ async buildUniqueDashboardSlug(baseSlug) {
447
+ let slug = baseSlug;
448
+ let suffix = 2;
449
+ while (await this.prismaService.dashboard.findFirst({
450
+ where: { slug },
451
+ select: { id: true },
452
+ })) {
453
+ slug = `${baseSlug}-${suffix}`;
454
+ suffix += 1;
455
+ }
456
+ return slug;
457
+ }
458
+ async getDashboardUserOrThrow(userId, slug, locale) {
459
+ const dashboardUser = await this.prismaService.dashboard_user.findFirst({
460
+ where: {
461
+ user_id: userId,
462
+ dashboard: { slug },
463
+ },
464
+ include: {
465
+ dashboard: {
466
+ include: {
467
+ dashboard_locale: {
468
+ where: {
469
+ locale: {
470
+ code: locale,
471
+ },
472
+ },
473
+ },
474
+ },
475
+ },
476
+ },
477
+ });
478
+ if (!dashboardUser) {
479
+ throw new common_1.ForbiddenException((0, api_locale_1.getLocaleText)('dashboardNotFound', locale, 'Dashboard not found.'));
480
+ }
481
+ return dashboardUser;
482
+ }
483
+ async getUserRoleIds(userId) {
484
+ const roleUsers = await this.prismaService.role_user.findMany({
485
+ where: { user_id: userId },
486
+ select: { role_id: true },
487
+ });
488
+ return roleUsers.map((roleUser) => roleUser.role_id);
489
+ }
490
+ async getDashboardComponentRoleRequirements(dashboardId) {
491
+ const dashboardItems = await this.prismaService.dashboard_item.findMany({
492
+ where: {
493
+ dashboard_id: dashboardId,
494
+ },
495
+ select: {
496
+ component_id: true,
497
+ dashboard_component: {
498
+ select: {
499
+ dashboard_component_role: {
500
+ select: {
501
+ role_id: true,
502
+ },
503
+ },
504
+ },
505
+ },
506
+ },
507
+ });
508
+ const uniqueByComponentId = new Map();
509
+ for (const item of dashboardItems) {
510
+ if (uniqueByComponentId.has(item.component_id)) {
511
+ continue;
512
+ }
513
+ uniqueByComponentId.set(item.component_id, item.dashboard_component.dashboard_component_role.map((relation) => relation.role_id));
514
+ }
515
+ return Array.from(uniqueByComponentId.values());
516
+ }
517
+ userHasRequiredRolesForDashboard(componentRoleRequirements, userRoleIds) {
518
+ if (componentRoleRequirements.length === 0) {
519
+ return true;
520
+ }
521
+ if (userRoleIds.length === 0) {
522
+ return componentRoleRequirements.every((requiredRoles) => requiredRoles.length === 0);
523
+ }
524
+ const userRoleIdSet = new Set(userRoleIds);
525
+ return componentRoleRequirements.every((requiredRoles) => requiredRoles.length === 0 ||
526
+ requiredRoles.some((roleId) => userRoleIdSet.has(roleId)));
527
+ }
528
+ async getDashboardRoleAccessState(dashboardId, userId) {
529
+ const [componentRoleRequirements, userRoleIds] = await Promise.all([
530
+ this.getDashboardComponentRoleRequirements(dashboardId),
531
+ this.getUserRoleIds(userId),
532
+ ]);
533
+ const hasRequiredRoles = this.userHasRequiredRolesForDashboard(componentRoleRequirements, userRoleIds);
534
+ return {
535
+ hasRequiredRoles,
536
+ accessStatus: hasRequiredRoles ? 'allowed' : 'missing-roles',
537
+ };
538
+ }
539
+ async assertDashboardRoleAccess(dashboardId, userId) {
540
+ const { hasRequiredRoles } = await this.getDashboardRoleAccessState(dashboardId, userId);
541
+ if (!hasRequiredRoles) {
542
+ throw new common_1.ForbiddenException('Access denied to this dashboard');
543
+ }
544
+ }
545
+ async getAccessibleTemplateOrThrow(userId, templateSlug, locale) {
546
+ const userRoleIds = await this.getUserRoleIds(userId);
547
+ const templateAccessFilter = userRoleIds.length > 0
548
+ ? {
549
+ OR: [
550
+ {
551
+ dashboard_role: {
552
+ some: {
553
+ role_id: {
554
+ in: userRoleIds,
555
+ },
556
+ },
557
+ },
558
+ },
559
+ {
560
+ dashboard_role: {
561
+ none: {},
562
+ },
563
+ },
564
+ ],
565
+ }
566
+ : {
567
+ dashboard_role: {
568
+ none: {},
569
+ },
570
+ };
571
+ const template = await this.prismaService.dashboard.findFirst({
572
+ where: Object.assign({ slug: templateSlug, is_template: true }, templateAccessFilter),
573
+ include: {
574
+ dashboard_locale: {
575
+ where: {
576
+ locale: {
577
+ code: locale,
578
+ },
579
+ },
580
+ },
581
+ dashboard_item: {
582
+ select: {
583
+ component_id: true,
584
+ width: true,
585
+ height: true,
586
+ x_axis: true,
587
+ y_axis: true,
588
+ },
589
+ },
590
+ },
591
+ });
592
+ if (!template) {
593
+ throw new common_1.ForbiddenException((0, api_locale_1.getLocaleText)('dashboardNotFound', locale, 'Dashboard template not found.'));
594
+ }
595
+ return template;
596
+ }
433
597
  async getHome(userId, locale) {
434
598
  const user = await this.prismaService.user.findUnique({
435
599
  where: { id: userId },
@@ -712,6 +876,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
712
876
  if (!canAccess) {
713
877
  return [];
714
878
  }
879
+ await this.assertDashboardRoleAccess(dashboard.id, userId);
715
880
  const dashboardItems = await this.prismaService.dashboard_item.findMany({
716
881
  where: {
717
882
  dashboard_id: dashboard.id,
@@ -771,26 +936,30 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
771
936
  if (!canAccess) {
772
937
  throw new common_1.ForbiddenException('Access denied to this dashboard');
773
938
  }
774
- for (const item of layout) {
775
- const itemId = parseInt(item.i.replace('widget-', ''));
776
- const dashboardItem = await this.prismaService.dashboard_item.findFirst({
777
- where: {
778
- id: itemId,
779
- dashboard_id: dashboard.id,
780
- },
781
- });
782
- if (!dashboardItem) {
783
- continue;
939
+ await this.assertDashboardRoleAccess(dashboard.id, userId);
940
+ const layoutUpdates = layout.flatMap((item) => {
941
+ const itemId = Number.parseInt(item.i.replace('widget-', ''), 10);
942
+ if (Number.isNaN(itemId)) {
943
+ this.logger.warn(`Skipping dashboard layout item with invalid id: ${item.i}`);
944
+ return [];
784
945
  }
785
- await this.prismaService.dashboard_item.update({
786
- where: { id: dashboardItem.id },
787
- data: {
788
- x_axis: item.x,
789
- y_axis: item.y,
790
- width: item.w,
791
- height: item.h,
792
- },
793
- });
946
+ return [
947
+ this.prismaService.dashboard_item.updateMany({
948
+ where: {
949
+ id: itemId,
950
+ dashboard_id: dashboard.id,
951
+ },
952
+ data: {
953
+ x_axis: item.x,
954
+ y_axis: item.y,
955
+ width: item.w,
956
+ height: item.h,
957
+ },
958
+ }),
959
+ ];
960
+ });
961
+ if (layoutUpdates.length > 0) {
962
+ await this.prismaService.$transaction(layoutUpdates);
794
963
  }
795
964
  return { success: true };
796
965
  }
@@ -812,6 +981,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
812
981
  if (!canAccess) {
813
982
  throw new common_1.ForbiddenException('Access denied to this dashboard');
814
983
  }
984
+ await this.assertDashboardRoleAccess(dashboard.id, userId);
815
985
  const userRoles = await this.prismaService.role_user.findMany({
816
986
  where: { user_id: userId },
817
987
  select: { role_id: true },
@@ -927,6 +1097,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
927
1097
  if (!canAccess) {
928
1098
  throw new common_1.ForbiddenException('Access denied to this dashboard');
929
1099
  }
1100
+ await this.assertDashboardRoleAccess(dashboard.id, userId);
930
1101
  const parsedWidgetId = Number(widgetId.replace(/^widget-/, ''));
931
1102
  if (!Number.isInteger(parsedWidgetId) || parsedWidgetId <= 0) {
932
1103
  throw new common_1.BadRequestException('Invalid widget id');
@@ -975,9 +1146,18 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
975
1146
  user_id: userId,
976
1147
  },
977
1148
  });
978
- const hasAccess = Boolean(dashboardUser);
1149
+ if (!dashboardUser) {
1150
+ return {
1151
+ hasAccess: false,
1152
+ accessStatus: 'not-shared',
1153
+ dashboard: null,
1154
+ };
1155
+ }
1156
+ const roleAccess = await this.getDashboardRoleAccessState(dashboard.id, userId);
1157
+ const hasAccess = roleAccess.hasRequiredRoles;
979
1158
  return {
980
1159
  hasAccess,
1160
+ accessStatus: roleAccess.accessStatus,
981
1161
  dashboard: hasAccess
982
1162
  ? {
983
1163
  id: dashboard.id,
@@ -988,6 +1168,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
988
1168
  };
989
1169
  }
990
1170
  async getUserDashboards(userId, locale) {
1171
+ await this.getHome(userId, locale);
991
1172
  const dashboardUsers = await this.prismaService.dashboard_user.findMany({
992
1173
  where: { user_id: userId },
993
1174
  include: {
@@ -1003,6 +1184,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
1003
1184
  },
1004
1185
  },
1005
1186
  },
1187
+ orderBy: [{ is_home: 'desc' }, { id: 'asc' }],
1006
1188
  });
1007
1189
  const uniqueByDashboardId = new Map();
1008
1190
  for (const dashboardUser of dashboardUsers) {
@@ -1013,9 +1195,461 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
1013
1195
  return Array.from(uniqueByDashboardId.values()).map((dashboardUser) => ({
1014
1196
  id: dashboardUser.dashboard.id,
1015
1197
  slug: dashboardUser.dashboard.slug,
1198
+ name: this.getDashboardDisplayName(dashboardUser.dashboard),
1199
+ icon: dashboardUser.dashboard.icon,
1200
+ is_home: dashboardUser.is_home,
1016
1201
  dashboard_locale: dashboardUser.dashboard.dashboard_locale,
1017
1202
  }));
1018
1203
  }
1204
+ async getAvailableTemplates(userId, locale) {
1205
+ const userRoleIds = await this.getUserRoleIds(userId);
1206
+ const templateAccessFilter = userRoleIds.length > 0
1207
+ ? {
1208
+ OR: [
1209
+ {
1210
+ dashboard_role: {
1211
+ some: {
1212
+ role_id: {
1213
+ in: userRoleIds,
1214
+ },
1215
+ },
1216
+ },
1217
+ },
1218
+ {
1219
+ dashboard_role: {
1220
+ none: {},
1221
+ },
1222
+ },
1223
+ ],
1224
+ }
1225
+ : {
1226
+ dashboard_role: {
1227
+ none: {},
1228
+ },
1229
+ };
1230
+ const templates = await this.prismaService.dashboard.findMany({
1231
+ where: Object.assign({ is_template: true }, templateAccessFilter),
1232
+ include: {
1233
+ dashboard_locale: {
1234
+ where: {
1235
+ locale: {
1236
+ code: locale,
1237
+ },
1238
+ },
1239
+ },
1240
+ dashboard_item: {
1241
+ select: { id: true },
1242
+ },
1243
+ },
1244
+ orderBy: [{ id: 'asc' }],
1245
+ });
1246
+ return templates.map((template) => ({
1247
+ id: template.id,
1248
+ slug: template.slug,
1249
+ name: this.getDashboardDisplayName(template),
1250
+ icon: template.icon,
1251
+ itemCount: template.dashboard_item.length,
1252
+ }));
1253
+ }
1254
+ async createUserDashboard(userId, data, locale) {
1255
+ var _a, _b, _c, _d;
1256
+ const templateSlug = this.toNullableString(data === null || data === void 0 ? void 0 : data.templateSlug);
1257
+ const template = templateSlug
1258
+ ? await this.getAccessibleTemplateOrThrow(userId, templateSlug, locale)
1259
+ : null;
1260
+ const templateName = template ? this.getDashboardDisplayName(template) : null;
1261
+ const name = this.toNullableString(data === null || data === void 0 ? void 0 : data.name) || templateName;
1262
+ if (!name) {
1263
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.fieldRequired', locale, 'Dashboard name is required.'));
1264
+ }
1265
+ const requestedSlug = this.toNullableString(data === null || data === void 0 ? void 0 : data.slug);
1266
+ const baseSlug = this.slugifyDashboardName(requestedSlug || name);
1267
+ const slug = await this.buildUniqueDashboardSlug(baseSlug);
1268
+ const icon = (data === null || data === void 0 ? void 0 : data.icon) === undefined
1269
+ ? (_a = template === null || template === void 0 ? void 0 : template.icon) !== null && _a !== void 0 ? _a : null
1270
+ : (_c = (_b = this.toNullableString(data === null || data === void 0 ? void 0 : data.icon)) !== null && _b !== void 0 ? _b : template === null || template === void 0 ? void 0 : template.icon) !== null && _c !== void 0 ? _c : null;
1271
+ const [localeRecord, existingCount] = await Promise.all([
1272
+ this.prismaService.locale.findFirst({
1273
+ where: { code: locale },
1274
+ select: { id: true },
1275
+ }),
1276
+ this.prismaService.dashboard_user.count({
1277
+ where: { user_id: userId },
1278
+ }),
1279
+ ]);
1280
+ const dashboard = await this.prismaService.dashboard.create({
1281
+ data: {
1282
+ slug,
1283
+ icon,
1284
+ },
1285
+ });
1286
+ if (localeRecord) {
1287
+ await this.prismaService.dashboard_locale.create({
1288
+ data: {
1289
+ dashboard_id: dashboard.id,
1290
+ locale_id: localeRecord.id,
1291
+ name,
1292
+ },
1293
+ });
1294
+ }
1295
+ await this.prismaService.dashboard_user.create({
1296
+ data: {
1297
+ dashboard_id: dashboard.id,
1298
+ user_id: userId,
1299
+ is_home: existingCount === 0,
1300
+ },
1301
+ });
1302
+ if ((_d = template === null || template === void 0 ? void 0 : template.dashboard_item) === null || _d === void 0 ? void 0 : _d.length) {
1303
+ await this.prismaService.dashboard_item.createMany({
1304
+ data: template.dashboard_item.map((item) => ({
1305
+ dashboard_id: dashboard.id,
1306
+ component_id: item.component_id,
1307
+ width: item.width,
1308
+ height: item.height,
1309
+ x_axis: item.x_axis,
1310
+ y_axis: item.y_axis,
1311
+ })),
1312
+ });
1313
+ }
1314
+ return {
1315
+ id: dashboard.id,
1316
+ slug,
1317
+ name,
1318
+ icon,
1319
+ is_home: existingCount === 0,
1320
+ };
1321
+ }
1322
+ async renameUserDashboard(userId, slug, data, locale) {
1323
+ var _a, _b;
1324
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1325
+ const name = this.toNullableString(data === null || data === void 0 ? void 0 : data.name);
1326
+ const normalizedIcon = (data === null || data === void 0 ? void 0 : data.icon) === undefined ? undefined : this.toNullableString(data === null || data === void 0 ? void 0 : data.icon);
1327
+ if (!name && (data === null || data === void 0 ? void 0 : data.icon) === undefined) {
1328
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.fieldRequired', locale, 'Dashboard name or icon is required.'));
1329
+ }
1330
+ if ((data === null || data === void 0 ? void 0 : data.icon) !== undefined) {
1331
+ await this.prismaService.dashboard.update({
1332
+ where: { id: dashboardUser.dashboard_id },
1333
+ data: {
1334
+ icon: normalizedIcon !== null && normalizedIcon !== void 0 ? normalizedIcon : null,
1335
+ },
1336
+ });
1337
+ }
1338
+ if (name) {
1339
+ const localeRecord = await this.prismaService.locale.findFirst({
1340
+ where: { code: locale },
1341
+ select: { id: true },
1342
+ });
1343
+ if (!localeRecord) {
1344
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('localeNotFound', locale, 'Locale not found.'));
1345
+ }
1346
+ const existingLocale = await this.prismaService.dashboard_locale.findFirst({
1347
+ where: {
1348
+ dashboard_id: dashboardUser.dashboard_id,
1349
+ locale_id: localeRecord.id,
1350
+ },
1351
+ select: { id: true },
1352
+ });
1353
+ if (existingLocale) {
1354
+ await this.prismaService.dashboard_locale.update({
1355
+ where: { id: existingLocale.id },
1356
+ data: { name },
1357
+ });
1358
+ }
1359
+ else {
1360
+ await this.prismaService.dashboard_locale.create({
1361
+ data: {
1362
+ dashboard_id: dashboardUser.dashboard_id,
1363
+ locale_id: localeRecord.id,
1364
+ name,
1365
+ },
1366
+ });
1367
+ }
1368
+ }
1369
+ const updatedDashboard = await this.prismaService.dashboard.findUnique({
1370
+ where: { id: dashboardUser.dashboard_id },
1371
+ include: {
1372
+ dashboard_locale: {
1373
+ where: {
1374
+ locale: {
1375
+ code: locale,
1376
+ },
1377
+ },
1378
+ },
1379
+ },
1380
+ });
1381
+ return {
1382
+ success: true,
1383
+ slug,
1384
+ name: name ||
1385
+ (updatedDashboard
1386
+ ? this.getDashboardDisplayName(updatedDashboard)
1387
+ : slug),
1388
+ icon: (_b = (_a = updatedDashboard === null || updatedDashboard === void 0 ? void 0 : updatedDashboard.icon) !== null && _a !== void 0 ? _a : normalizedIcon) !== null && _b !== void 0 ? _b : null,
1389
+ };
1390
+ }
1391
+ async setHomeDashboard(userId, slug, locale) {
1392
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1393
+ await this.prismaService.dashboard_user.updateMany({
1394
+ where: { user_id: userId },
1395
+ data: { is_home: false },
1396
+ });
1397
+ await this.prismaService.dashboard_user.update({
1398
+ where: { id: dashboardUser.id },
1399
+ data: { is_home: true },
1400
+ });
1401
+ return {
1402
+ success: true,
1403
+ slug,
1404
+ };
1405
+ }
1406
+ async getDashboardShares(userId, slug, locale) {
1407
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1408
+ const dashboardRoleRequirements = await this.getDashboardComponentRoleRequirements(dashboardUser.dashboard_id);
1409
+ const sharedUsers = await this.prismaService.dashboard_user.findMany({
1410
+ where: {
1411
+ dashboard_id: dashboardUser.dashboard_id,
1412
+ },
1413
+ include: {
1414
+ user: {
1415
+ select: {
1416
+ id: true,
1417
+ name: true,
1418
+ role_user: {
1419
+ select: {
1420
+ role_id: true,
1421
+ },
1422
+ },
1423
+ user_identifier: {
1424
+ where: {
1425
+ type: 'email',
1426
+ },
1427
+ select: {
1428
+ value: true,
1429
+ },
1430
+ take: 1,
1431
+ },
1432
+ },
1433
+ },
1434
+ },
1435
+ orderBy: {
1436
+ id: 'asc',
1437
+ },
1438
+ });
1439
+ return sharedUsers.map((sharedDashboardUser) => {
1440
+ var _a, _b;
1441
+ const userRoleIds = sharedDashboardUser.user.role_user.map((roleUser) => roleUser.role_id);
1442
+ const hasRequiredRoles = this.userHasRequiredRolesForDashboard(dashboardRoleRequirements, userRoleIds);
1443
+ return {
1444
+ id: sharedDashboardUser.user.id,
1445
+ name: sharedDashboardUser.user.name,
1446
+ email: (_b = (_a = sharedDashboardUser.user.user_identifier[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : null,
1447
+ isCurrentUser: sharedDashboardUser.user.id === userId,
1448
+ isHome: sharedDashboardUser.is_home,
1449
+ hasRequiredRoles,
1450
+ accessStatus: hasRequiredRoles ? 'allowed' : 'missing-roles',
1451
+ };
1452
+ });
1453
+ }
1454
+ async getShareableUsers(userId, slug, options, locale) {
1455
+ var _a, _b;
1456
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1457
+ const normalizedSearch = this.toNullableString(options === null || options === void 0 ? void 0 : options.search);
1458
+ const requestedPage = Number.parseInt(String((_a = options === null || options === void 0 ? void 0 : options.page) !== null && _a !== void 0 ? _a : '1'), 10);
1459
+ const requestedPageSize = Number.parseInt(String((_b = options === null || options === void 0 ? void 0 : options.pageSize) !== null && _b !== void 0 ? _b : '10'), 10);
1460
+ const page = Number.isFinite(requestedPage) && requestedPage > 0 ? requestedPage : 1;
1461
+ const pageSize = Number.isFinite(requestedPageSize) && requestedPageSize > 0
1462
+ ? Math.min(requestedPageSize, 50)
1463
+ : 10;
1464
+ const [existingUsers, dashboardRoleRequirements] = await Promise.all([
1465
+ this.prismaService.dashboard_user.findMany({
1466
+ where: {
1467
+ dashboard_id: dashboardUser.dashboard_id,
1468
+ },
1469
+ select: {
1470
+ user_id: true,
1471
+ },
1472
+ }),
1473
+ this.getDashboardComponentRoleRequirements(dashboardUser.dashboard_id),
1474
+ ]);
1475
+ const where = Object.assign({ id: {
1476
+ notIn: existingUsers.map((item) => item.user_id),
1477
+ } }, (normalizedSearch
1478
+ ? {
1479
+ OR: [
1480
+ {
1481
+ name: {
1482
+ contains: normalizedSearch,
1483
+ mode: 'insensitive',
1484
+ },
1485
+ },
1486
+ {
1487
+ user_identifier: {
1488
+ some: {
1489
+ type: 'email',
1490
+ value: {
1491
+ contains: normalizedSearch,
1492
+ mode: 'insensitive',
1493
+ },
1494
+ },
1495
+ },
1496
+ },
1497
+ ],
1498
+ }
1499
+ : {}));
1500
+ const total = await this.prismaService.user.count({ where });
1501
+ const lastPage = total > 0 ? Math.ceil(total / pageSize) : 1;
1502
+ const safePage = Math.min(page, lastPage);
1503
+ const users = await this.prismaService.user.findMany({
1504
+ where,
1505
+ select: {
1506
+ id: true,
1507
+ name: true,
1508
+ role_user: {
1509
+ select: {
1510
+ role_id: true,
1511
+ },
1512
+ },
1513
+ user_identifier: {
1514
+ where: {
1515
+ type: 'email',
1516
+ },
1517
+ select: {
1518
+ value: true,
1519
+ },
1520
+ take: 1,
1521
+ },
1522
+ },
1523
+ skip: (safePage - 1) * pageSize,
1524
+ take: pageSize,
1525
+ orderBy: [{ name: 'asc' }, { id: 'asc' }],
1526
+ });
1527
+ return {
1528
+ data: users.map((candidateUser) => {
1529
+ var _a, _b;
1530
+ const userRoleIds = candidateUser.role_user.map((roleUser) => roleUser.role_id);
1531
+ const hasRequiredRoles = this.userHasRequiredRolesForDashboard(dashboardRoleRequirements, userRoleIds);
1532
+ return {
1533
+ id: candidateUser.id,
1534
+ name: candidateUser.name,
1535
+ email: (_b = (_a = candidateUser.user_identifier[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : null,
1536
+ hasRequiredRoles,
1537
+ accessStatus: hasRequiredRoles ? 'allowed' : 'missing-roles',
1538
+ };
1539
+ }),
1540
+ total,
1541
+ page: safePage,
1542
+ pageSize,
1543
+ lastPage,
1544
+ prev: safePage > 1 ? safePage - 1 : null,
1545
+ next: safePage < lastPage ? safePage + 1 : null,
1546
+ };
1547
+ }
1548
+ async shareDashboard(userId, slug, sharedUserIds, sharedUserId, locale) {
1549
+ const requestedIds = Array.from(new Set([
1550
+ ...(Array.isArray(sharedUserIds) ? sharedUserIds : []),
1551
+ ...(sharedUserId !== undefined ? [sharedUserId] : []),
1552
+ ]
1553
+ .map((value) => Number(value))
1554
+ .filter((value) => Number.isInteger(value) && value > 0)));
1555
+ if (requestedIds.length === 0) {
1556
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.fieldRequired', locale, 'User is required.'));
1557
+ }
1558
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1559
+ const sanitizedUserIds = requestedIds.filter((candidateUserId) => candidateUserId !== userId);
1560
+ if (sanitizedUserIds.length === 0) {
1561
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.invalidValue', locale, 'You already have access to this dashboard.'));
1562
+ }
1563
+ const targetUsers = await this.prismaService.user.findMany({
1564
+ where: {
1565
+ id: {
1566
+ in: sanitizedUserIds,
1567
+ },
1568
+ },
1569
+ select: {
1570
+ id: true,
1571
+ },
1572
+ });
1573
+ if (targetUsers.length !== sanitizedUserIds.length) {
1574
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
1575
+ }
1576
+ const existingShares = await this.prismaService.dashboard_user.findMany({
1577
+ where: {
1578
+ dashboard_id: dashboardUser.dashboard_id,
1579
+ user_id: {
1580
+ in: sanitizedUserIds,
1581
+ },
1582
+ },
1583
+ select: {
1584
+ user_id: true,
1585
+ },
1586
+ });
1587
+ const alreadySharedUserIds = existingShares.map((item) => item.user_id);
1588
+ const alreadySharedSet = new Set(alreadySharedUserIds);
1589
+ const newUserIds = sanitizedUserIds.filter((candidateUserId) => !alreadySharedSet.has(candidateUserId));
1590
+ if (newUserIds.length > 0) {
1591
+ await this.prismaService.dashboard_user.createMany({
1592
+ data: newUserIds.map((candidateUserId) => ({
1593
+ dashboard_id: dashboardUser.dashboard_id,
1594
+ user_id: candidateUserId,
1595
+ is_home: false,
1596
+ })),
1597
+ });
1598
+ }
1599
+ return {
1600
+ success: true,
1601
+ sharedCount: newUserIds.length,
1602
+ sharedUserIds: newUserIds,
1603
+ alreadySharedUserIds,
1604
+ };
1605
+ }
1606
+ async revokeDashboardShare(userId, slug, sharedUserId, locale) {
1607
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1608
+ if (sharedUserId === userId) {
1609
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.invalidValue', locale, 'Use the remove dashboard action to leave this tab.'));
1610
+ }
1611
+ await this.prismaService.dashboard_user.deleteMany({
1612
+ where: {
1613
+ dashboard_id: dashboardUser.dashboard_id,
1614
+ user_id: sharedUserId,
1615
+ },
1616
+ });
1617
+ return {
1618
+ success: true,
1619
+ };
1620
+ }
1621
+ async removeUserDashboard(userId, slug, locale) {
1622
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1623
+ await this.prismaService.dashboard_user.delete({
1624
+ where: { id: dashboardUser.id },
1625
+ });
1626
+ if (dashboardUser.is_home) {
1627
+ const nextDashboard = await this.prismaService.dashboard_user.findFirst({
1628
+ where: { user_id: userId },
1629
+ orderBy: { id: 'asc' },
1630
+ });
1631
+ if (nextDashboard) {
1632
+ await this.prismaService.dashboard_user.update({
1633
+ where: { id: nextDashboard.id },
1634
+ data: { is_home: true },
1635
+ });
1636
+ }
1637
+ }
1638
+ const remainingShares = await this.prismaService.dashboard_user.count({
1639
+ where: {
1640
+ dashboard_id: dashboardUser.dashboard_id,
1641
+ },
1642
+ });
1643
+ if (remainingShares === 0) {
1644
+ await this.prismaService.dashboard.delete({
1645
+ where: { id: dashboardUser.dashboard_id },
1646
+ });
1647
+ }
1648
+ return {
1649
+ success: true,
1650
+ removedSlug: slug,
1651
+ };
1652
+ }
1019
1653
  async getAccountSecurity(userId) {
1020
1654
  const now = new Date();
1021
1655
  const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);