@hed-hog/core 0.0.299 → 0.0.300

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 (112) hide show
  1. package/dist/dashboard/dashboard/dashboard.controller.d.ts +6 -0
  2. package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
  3. package/dist/dashboard/dashboard/dashboard.service.d.ts +6 -0
  4. package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
  5. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +2 -1
  6. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts.map +1 -1
  7. package/dist/dashboard/dashboard-component/dashboard-component.controller.js +6 -3
  8. package/dist/dashboard/dashboard-component/dashboard-component.controller.js.map +1 -1
  9. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +7 -1
  10. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
  11. package/dist/dashboard/dashboard-component/dashboard-component.service.js +76 -33
  12. package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
  13. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +65 -0
  14. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  15. package/dist/dashboard/dashboard-core/dashboard-core.controller.js +111 -0
  16. package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
  17. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +69 -0
  18. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  19. package/dist/dashboard/dashboard-core/dashboard-core.service.js +526 -19
  20. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  21. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +2 -0
  22. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
  23. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +2 -0
  24. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
  25. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +2 -0
  26. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts.map +1 -1
  27. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +2 -0
  28. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts.map +1 -1
  29. package/hedhog/data/dashboard.yaml +12 -6
  30. package/hedhog/data/dashboard_component_role.yaml +66 -0
  31. package/hedhog/data/dashboard_role.yaml +2 -8
  32. package/hedhog/data/route.yaml +72 -0
  33. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +333 -128
  34. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +277 -53
  35. package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +179 -231
  36. package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +64 -18
  37. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +1389 -0
  38. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +37 -0
  39. package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +1 -1
  40. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +6 -6
  41. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +8 -8
  42. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +3 -3
  43. package/hedhog/frontend/app/dashboard/page.tsx.ejs +3 -25
  44. package/hedhog/frontend/messages/en.json +112 -2
  45. package/hedhog/frontend/messages/pt.json +111 -1
  46. package/hedhog/frontend/widgets/account-security.tsx.ejs +1 -1
  47. package/hedhog/frontend/widgets/active-users-card.tsx.ejs +2 -2
  48. package/hedhog/frontend/widgets/activity-timeline.tsx.ejs +1 -1
  49. package/hedhog/frontend/widgets/email-notifications.tsx.ejs +1 -1
  50. package/hedhog/frontend/widgets/locale-config.tsx.ejs +1 -1
  51. package/hedhog/frontend/widgets/login-history-chart.tsx.ejs +1 -1
  52. package/hedhog/frontend/widgets/mail-config.tsx.ejs +1 -1
  53. package/hedhog/frontend/widgets/mail-sent-card.tsx.ejs +2 -2
  54. package/hedhog/frontend/widgets/mail-sent-chart.tsx.ejs +1 -1
  55. package/hedhog/frontend/widgets/menus-card.tsx.ejs +2 -2
  56. package/hedhog/frontend/widgets/oauth-config.tsx.ejs +1 -1
  57. package/hedhog/frontend/widgets/permissions-card.tsx.ejs +2 -2
  58. package/hedhog/frontend/widgets/permissions-chart.tsx.ejs +1 -1
  59. package/hedhog/frontend/widgets/profile-card.tsx.ejs +1 -1
  60. package/hedhog/frontend/widgets/routes-card.tsx.ejs +2 -2
  61. package/hedhog/frontend/widgets/session-activity-chart.tsx.ejs +1 -1
  62. package/hedhog/frontend/widgets/sessions-today-card.tsx.ejs +2 -2
  63. package/hedhog/frontend/widgets/stat-access-level.tsx.ejs +1 -1
  64. package/hedhog/frontend/widgets/stat-actions-today.tsx.ejs +1 -1
  65. package/hedhog/frontend/widgets/stat-consecutive-days.tsx.ejs +1 -1
  66. package/hedhog/frontend/widgets/stat-online-time.tsx.ejs +1 -1
  67. package/hedhog/frontend/widgets/storage-config.tsx.ejs +1 -1
  68. package/hedhog/frontend/widgets/theme-config.tsx.ejs +1 -1
  69. package/hedhog/frontend/widgets/user-growth-chart.tsx.ejs +1 -1
  70. package/hedhog/frontend/widgets/user-roles.tsx.ejs +1 -1
  71. package/hedhog/frontend/widgets/user-sessions.tsx.ejs +1 -1
  72. package/hedhog/table/dashboard.yaml +6 -0
  73. package/package.json +5 -5
  74. package/src/dashboard/dashboard-component/dashboard-component.controller.ts +15 -2
  75. package/src/dashboard/dashboard-component/dashboard-component.service.ts +107 -43
  76. package/src/dashboard/dashboard-core/dashboard-core.controller.ts +112 -1
  77. package/src/dashboard/dashboard-core/dashboard-core.service.ts +674 -19
  78. package/hedhog/frontend/app/dashboard/components/widgets/core..gitkeep.ejs +0 -11
  79. package/hedhog/frontend/app/dashboard/components/widgets/core.account-security.tsx.ejs +0 -192
  80. package/hedhog/frontend/app/dashboard/components/widgets/core.active-users-card.tsx.ejs +0 -58
  81. package/hedhog/frontend/app/dashboard/components/widgets/core.activity-timeline.tsx.ejs +0 -223
  82. package/hedhog/frontend/app/dashboard/components/widgets/core.email-notifications.tsx.ejs +0 -226
  83. package/hedhog/frontend/app/dashboard/components/widgets/core.locale-config.tsx.ejs +0 -168
  84. package/hedhog/frontend/app/dashboard/components/widgets/core.login-history-chart.tsx.ejs +0 -115
  85. package/hedhog/frontend/app/dashboard/components/widgets/core.mail-config.tsx.ejs +0 -199
  86. package/hedhog/frontend/app/dashboard/components/widgets/core.mail-sent-card.tsx.ejs +0 -58
  87. package/hedhog/frontend/app/dashboard/components/widgets/core.mail-sent-chart.tsx.ejs +0 -149
  88. package/hedhog/frontend/app/dashboard/components/widgets/core.menus-card.tsx.ejs +0 -58
  89. package/hedhog/frontend/app/dashboard/components/widgets/core.oauth-config.tsx.ejs +0 -175
  90. package/hedhog/frontend/app/dashboard/components/widgets/core.permissions-card.tsx.ejs +0 -61
  91. package/hedhog/frontend/app/dashboard/components/widgets/core.permissions-chart.tsx.ejs +0 -156
  92. package/hedhog/frontend/app/dashboard/components/widgets/core.profile-card.tsx.ejs +0 -186
  93. package/hedhog/frontend/app/dashboard/components/widgets/core.routes-card.tsx.ejs +0 -58
  94. package/hedhog/frontend/app/dashboard/components/widgets/core.session-activity-chart.tsx.ejs +0 -183
  95. package/hedhog/frontend/app/dashboard/components/widgets/core.sessions-today-card.tsx.ejs +0 -62
  96. package/hedhog/frontend/app/dashboard/components/widgets/core.stat-access-level.tsx.ejs +0 -57
  97. package/hedhog/frontend/app/dashboard/components/widgets/core.stat-actions-today.tsx.ejs +0 -57
  98. package/hedhog/frontend/app/dashboard/components/widgets/core.stat-consecutive-days.tsx.ejs +0 -57
  99. package/hedhog/frontend/app/dashboard/components/widgets/core.stat-online-time.tsx.ejs +0 -57
  100. package/hedhog/frontend/app/dashboard/components/widgets/core.storage-config.tsx.ejs +0 -196
  101. package/hedhog/frontend/app/dashboard/components/widgets/core.theme-config.tsx.ejs +0 -213
  102. package/hedhog/frontend/app/dashboard/components/widgets/core.user-growth-chart.tsx.ejs +0 -210
  103. package/hedhog/frontend/app/dashboard/components/widgets/core.user-roles.tsx.ejs +0 -132
  104. package/hedhog/frontend/app/dashboard/components/widgets/core.user-sessions.tsx.ejs +0 -236
  105. package/hedhog/frontend/app/dashboard/components/widgets/finance.alerts.tsx.ejs +0 -108
  106. package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-balance-kpi.tsx.ejs +0 -66
  107. package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-flow-chart.tsx.ejs +0 -122
  108. package/hedhog/frontend/app/dashboard/components/widgets/finance.default-kpi.tsx.ejs +0 -63
  109. package/hedhog/frontend/app/dashboard/components/widgets/finance.payable-30d-kpi.tsx.ejs +0 -73
  110. package/hedhog/frontend/app/dashboard/components/widgets/finance.receivable-30d-kpi.tsx.ejs +0 -73
  111. package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-payable.tsx.ejs +0 -123
  112. package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-receivable.tsx.ejs +0 -118
@@ -430,6 +430,115 @@ 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 getAccessibleTemplateOrThrow(userId, templateSlug, locale) {
491
+ const userRoleIds = await this.getUserRoleIds(userId);
492
+ const templateAccessFilter = userRoleIds.length > 0
493
+ ? {
494
+ OR: [
495
+ {
496
+ dashboard_role: {
497
+ some: {
498
+ role_id: {
499
+ in: userRoleIds,
500
+ },
501
+ },
502
+ },
503
+ },
504
+ {
505
+ dashboard_role: {
506
+ none: {},
507
+ },
508
+ },
509
+ ],
510
+ }
511
+ : {
512
+ dashboard_role: {
513
+ none: {},
514
+ },
515
+ };
516
+ const template = await this.prismaService.dashboard.findFirst({
517
+ where: Object.assign({ slug: templateSlug, is_template: true }, templateAccessFilter),
518
+ include: {
519
+ dashboard_locale: {
520
+ where: {
521
+ locale: {
522
+ code: locale,
523
+ },
524
+ },
525
+ },
526
+ dashboard_item: {
527
+ select: {
528
+ component_id: true,
529
+ width: true,
530
+ height: true,
531
+ x_axis: true,
532
+ y_axis: true,
533
+ },
534
+ },
535
+ },
536
+ });
537
+ if (!template) {
538
+ throw new common_1.ForbiddenException((0, api_locale_1.getLocaleText)('dashboardNotFound', locale, 'Dashboard template not found.'));
539
+ }
540
+ return template;
541
+ }
433
542
  async getHome(userId, locale) {
434
543
  const user = await this.prismaService.user.findUnique({
435
544
  where: { id: userId },
@@ -771,26 +880,29 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
771
880
  if (!canAccess) {
772
881
  throw new common_1.ForbiddenException('Access denied to this dashboard');
773
882
  }
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;
883
+ const layoutUpdates = layout.flatMap((item) => {
884
+ const itemId = Number.parseInt(item.i.replace('widget-', ''), 10);
885
+ if (Number.isNaN(itemId)) {
886
+ this.logger.warn(`Skipping dashboard layout item with invalid id: ${item.i}`);
887
+ return [];
784
888
  }
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
- });
889
+ return [
890
+ this.prismaService.dashboard_item.updateMany({
891
+ where: {
892
+ id: itemId,
893
+ dashboard_id: dashboard.id,
894
+ },
895
+ data: {
896
+ x_axis: item.x,
897
+ y_axis: item.y,
898
+ width: item.w,
899
+ height: item.h,
900
+ },
901
+ }),
902
+ ];
903
+ });
904
+ if (layoutUpdates.length > 0) {
905
+ await this.prismaService.$transaction(layoutUpdates);
794
906
  }
795
907
  return { success: true };
796
908
  }
@@ -988,6 +1100,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
988
1100
  };
989
1101
  }
990
1102
  async getUserDashboards(userId, locale) {
1103
+ await this.getHome(userId, locale);
991
1104
  const dashboardUsers = await this.prismaService.dashboard_user.findMany({
992
1105
  where: { user_id: userId },
993
1106
  include: {
@@ -1003,6 +1116,7 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
1003
1116
  },
1004
1117
  },
1005
1118
  },
1119
+ orderBy: [{ is_home: 'desc' }, { id: 'asc' }],
1006
1120
  });
1007
1121
  const uniqueByDashboardId = new Map();
1008
1122
  for (const dashboardUser of dashboardUsers) {
@@ -1013,9 +1127,402 @@ let DashboardCoreService = DashboardCoreService_1 = class DashboardCoreService {
1013
1127
  return Array.from(uniqueByDashboardId.values()).map((dashboardUser) => ({
1014
1128
  id: dashboardUser.dashboard.id,
1015
1129
  slug: dashboardUser.dashboard.slug,
1130
+ name: this.getDashboardDisplayName(dashboardUser.dashboard),
1131
+ icon: dashboardUser.dashboard.icon,
1132
+ is_home: dashboardUser.is_home,
1016
1133
  dashboard_locale: dashboardUser.dashboard.dashboard_locale,
1017
1134
  }));
1018
1135
  }
1136
+ async getAvailableTemplates(userId, locale) {
1137
+ const userRoleIds = await this.getUserRoleIds(userId);
1138
+ const templateAccessFilter = userRoleIds.length > 0
1139
+ ? {
1140
+ OR: [
1141
+ {
1142
+ dashboard_role: {
1143
+ some: {
1144
+ role_id: {
1145
+ in: userRoleIds,
1146
+ },
1147
+ },
1148
+ },
1149
+ },
1150
+ {
1151
+ dashboard_role: {
1152
+ none: {},
1153
+ },
1154
+ },
1155
+ ],
1156
+ }
1157
+ : {
1158
+ dashboard_role: {
1159
+ none: {},
1160
+ },
1161
+ };
1162
+ const templates = await this.prismaService.dashboard.findMany({
1163
+ where: Object.assign({ is_template: true }, templateAccessFilter),
1164
+ include: {
1165
+ dashboard_locale: {
1166
+ where: {
1167
+ locale: {
1168
+ code: locale,
1169
+ },
1170
+ },
1171
+ },
1172
+ dashboard_item: {
1173
+ select: { id: true },
1174
+ },
1175
+ },
1176
+ orderBy: [{ id: 'asc' }],
1177
+ });
1178
+ return templates.map((template) => ({
1179
+ id: template.id,
1180
+ slug: template.slug,
1181
+ name: this.getDashboardDisplayName(template),
1182
+ icon: template.icon,
1183
+ itemCount: template.dashboard_item.length,
1184
+ }));
1185
+ }
1186
+ async createUserDashboard(userId, data, locale) {
1187
+ var _a, _b, _c, _d;
1188
+ const templateSlug = this.toNullableString(data === null || data === void 0 ? void 0 : data.templateSlug);
1189
+ const template = templateSlug
1190
+ ? await this.getAccessibleTemplateOrThrow(userId, templateSlug, locale)
1191
+ : null;
1192
+ const templateName = template ? this.getDashboardDisplayName(template) : null;
1193
+ const name = this.toNullableString(data === null || data === void 0 ? void 0 : data.name) || templateName;
1194
+ if (!name) {
1195
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.fieldRequired', locale, 'Dashboard name is required.'));
1196
+ }
1197
+ const requestedSlug = this.toNullableString(data === null || data === void 0 ? void 0 : data.slug);
1198
+ const baseSlug = this.slugifyDashboardName(requestedSlug || name);
1199
+ const slug = await this.buildUniqueDashboardSlug(baseSlug);
1200
+ const icon = (data === null || data === void 0 ? void 0 : data.icon) === undefined
1201
+ ? (_a = template === null || template === void 0 ? void 0 : template.icon) !== null && _a !== void 0 ? _a : null
1202
+ : (_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;
1203
+ const [localeRecord, existingCount] = await Promise.all([
1204
+ this.prismaService.locale.findFirst({
1205
+ where: { code: locale },
1206
+ select: { id: true },
1207
+ }),
1208
+ this.prismaService.dashboard_user.count({
1209
+ where: { user_id: userId },
1210
+ }),
1211
+ ]);
1212
+ const dashboard = await this.prismaService.dashboard.create({
1213
+ data: {
1214
+ slug,
1215
+ icon,
1216
+ },
1217
+ });
1218
+ if (localeRecord) {
1219
+ await this.prismaService.dashboard_locale.create({
1220
+ data: {
1221
+ dashboard_id: dashboard.id,
1222
+ locale_id: localeRecord.id,
1223
+ name,
1224
+ },
1225
+ });
1226
+ }
1227
+ await this.prismaService.dashboard_user.create({
1228
+ data: {
1229
+ dashboard_id: dashboard.id,
1230
+ user_id: userId,
1231
+ is_home: existingCount === 0,
1232
+ },
1233
+ });
1234
+ if ((_d = template === null || template === void 0 ? void 0 : template.dashboard_item) === null || _d === void 0 ? void 0 : _d.length) {
1235
+ await this.prismaService.dashboard_item.createMany({
1236
+ data: template.dashboard_item.map((item) => ({
1237
+ dashboard_id: dashboard.id,
1238
+ component_id: item.component_id,
1239
+ width: item.width,
1240
+ height: item.height,
1241
+ x_axis: item.x_axis,
1242
+ y_axis: item.y_axis,
1243
+ })),
1244
+ });
1245
+ }
1246
+ return {
1247
+ id: dashboard.id,
1248
+ slug,
1249
+ name,
1250
+ icon,
1251
+ is_home: existingCount === 0,
1252
+ };
1253
+ }
1254
+ async renameUserDashboard(userId, slug, data, locale) {
1255
+ var _a, _b;
1256
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1257
+ const name = this.toNullableString(data === null || data === void 0 ? void 0 : data.name);
1258
+ const normalizedIcon = (data === null || data === void 0 ? void 0 : data.icon) === undefined ? undefined : this.toNullableString(data === null || data === void 0 ? void 0 : data.icon);
1259
+ if (!name && (data === null || data === void 0 ? void 0 : data.icon) === undefined) {
1260
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.fieldRequired', locale, 'Dashboard name or icon is required.'));
1261
+ }
1262
+ if ((data === null || data === void 0 ? void 0 : data.icon) !== undefined) {
1263
+ await this.prismaService.dashboard.update({
1264
+ where: { id: dashboardUser.dashboard_id },
1265
+ data: {
1266
+ icon: normalizedIcon !== null && normalizedIcon !== void 0 ? normalizedIcon : null,
1267
+ },
1268
+ });
1269
+ }
1270
+ if (name) {
1271
+ const localeRecord = await this.prismaService.locale.findFirst({
1272
+ where: { code: locale },
1273
+ select: { id: true },
1274
+ });
1275
+ if (!localeRecord) {
1276
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('localeNotFound', locale, 'Locale not found.'));
1277
+ }
1278
+ const existingLocale = await this.prismaService.dashboard_locale.findFirst({
1279
+ where: {
1280
+ dashboard_id: dashboardUser.dashboard_id,
1281
+ locale_id: localeRecord.id,
1282
+ },
1283
+ select: { id: true },
1284
+ });
1285
+ if (existingLocale) {
1286
+ await this.prismaService.dashboard_locale.update({
1287
+ where: { id: existingLocale.id },
1288
+ data: { name },
1289
+ });
1290
+ }
1291
+ else {
1292
+ await this.prismaService.dashboard_locale.create({
1293
+ data: {
1294
+ dashboard_id: dashboardUser.dashboard_id,
1295
+ locale_id: localeRecord.id,
1296
+ name,
1297
+ },
1298
+ });
1299
+ }
1300
+ }
1301
+ const updatedDashboard = await this.prismaService.dashboard.findUnique({
1302
+ where: { id: dashboardUser.dashboard_id },
1303
+ include: {
1304
+ dashboard_locale: {
1305
+ where: {
1306
+ locale: {
1307
+ code: locale,
1308
+ },
1309
+ },
1310
+ },
1311
+ },
1312
+ });
1313
+ return {
1314
+ success: true,
1315
+ slug,
1316
+ name: name ||
1317
+ (updatedDashboard
1318
+ ? this.getDashboardDisplayName(updatedDashboard)
1319
+ : slug),
1320
+ icon: (_b = (_a = updatedDashboard === null || updatedDashboard === void 0 ? void 0 : updatedDashboard.icon) !== null && _a !== void 0 ? _a : normalizedIcon) !== null && _b !== void 0 ? _b : null,
1321
+ };
1322
+ }
1323
+ async setHomeDashboard(userId, slug, locale) {
1324
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1325
+ await this.prismaService.dashboard_user.updateMany({
1326
+ where: { user_id: userId },
1327
+ data: { is_home: false },
1328
+ });
1329
+ await this.prismaService.dashboard_user.update({
1330
+ where: { id: dashboardUser.id },
1331
+ data: { is_home: true },
1332
+ });
1333
+ return {
1334
+ success: true,
1335
+ slug,
1336
+ };
1337
+ }
1338
+ async getDashboardShares(userId, slug, locale) {
1339
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1340
+ const sharedUsers = await this.prismaService.dashboard_user.findMany({
1341
+ where: {
1342
+ dashboard_id: dashboardUser.dashboard_id,
1343
+ },
1344
+ include: {
1345
+ user: {
1346
+ select: {
1347
+ id: true,
1348
+ name: true,
1349
+ user_identifier: {
1350
+ where: {
1351
+ type: 'email',
1352
+ },
1353
+ select: {
1354
+ value: true,
1355
+ },
1356
+ take: 1,
1357
+ },
1358
+ },
1359
+ },
1360
+ },
1361
+ orderBy: {
1362
+ id: 'asc',
1363
+ },
1364
+ });
1365
+ return sharedUsers.map((sharedDashboardUser) => {
1366
+ var _a, _b;
1367
+ return ({
1368
+ id: sharedDashboardUser.user.id,
1369
+ name: sharedDashboardUser.user.name,
1370
+ email: (_b = (_a = sharedDashboardUser.user.user_identifier[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : null,
1371
+ isCurrentUser: sharedDashboardUser.user.id === userId,
1372
+ isHome: sharedDashboardUser.is_home,
1373
+ });
1374
+ });
1375
+ }
1376
+ async getShareableUsers(userId, slug, search, locale) {
1377
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1378
+ const normalizedSearch = this.toNullableString(search);
1379
+ const existingUsers = await this.prismaService.dashboard_user.findMany({
1380
+ where: {
1381
+ dashboard_id: dashboardUser.dashboard_id,
1382
+ },
1383
+ select: {
1384
+ user_id: true,
1385
+ },
1386
+ });
1387
+ const users = await this.prismaService.user.findMany({
1388
+ where: Object.assign({ id: {
1389
+ notIn: existingUsers.map((item) => item.user_id),
1390
+ } }, (normalizedSearch
1391
+ ? {
1392
+ OR: [
1393
+ {
1394
+ name: {
1395
+ contains: normalizedSearch,
1396
+ mode: 'insensitive',
1397
+ },
1398
+ },
1399
+ {
1400
+ user_identifier: {
1401
+ some: {
1402
+ type: 'email',
1403
+ value: {
1404
+ contains: normalizedSearch,
1405
+ mode: 'insensitive',
1406
+ },
1407
+ },
1408
+ },
1409
+ },
1410
+ ],
1411
+ }
1412
+ : {})),
1413
+ select: {
1414
+ id: true,
1415
+ name: true,
1416
+ user_identifier: {
1417
+ where: {
1418
+ type: 'email',
1419
+ },
1420
+ select: {
1421
+ value: true,
1422
+ },
1423
+ take: 1,
1424
+ },
1425
+ },
1426
+ take: 20,
1427
+ orderBy: {
1428
+ id: 'desc',
1429
+ },
1430
+ });
1431
+ return users.map((candidateUser) => {
1432
+ var _a, _b;
1433
+ return ({
1434
+ id: candidateUser.id,
1435
+ name: candidateUser.name,
1436
+ email: (_b = (_a = candidateUser.user_identifier[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : null,
1437
+ });
1438
+ });
1439
+ }
1440
+ async shareDashboard(userId, slug, sharedUserId, locale) {
1441
+ if (!sharedUserId || Number.isNaN(Number(sharedUserId))) {
1442
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.fieldRequired', locale, 'User is required.'));
1443
+ }
1444
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1445
+ if (sharedUserId === userId) {
1446
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.invalidValue', locale, 'You already have access to this dashboard.'));
1447
+ }
1448
+ const targetUser = await this.prismaService.user.findUnique({
1449
+ where: { id: sharedUserId },
1450
+ select: { id: true },
1451
+ });
1452
+ if (!targetUser) {
1453
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('userNotFound', locale, 'User not found.'));
1454
+ }
1455
+ const existingShare = await this.prismaService.dashboard_user.findFirst({
1456
+ where: {
1457
+ dashboard_id: dashboardUser.dashboard_id,
1458
+ user_id: sharedUserId,
1459
+ },
1460
+ select: { id: true },
1461
+ });
1462
+ if (existingShare) {
1463
+ return {
1464
+ success: true,
1465
+ alreadyShared: true,
1466
+ };
1467
+ }
1468
+ await this.prismaService.dashboard_user.create({
1469
+ data: {
1470
+ dashboard_id: dashboardUser.dashboard_id,
1471
+ user_id: sharedUserId,
1472
+ is_home: false,
1473
+ },
1474
+ });
1475
+ return {
1476
+ success: true,
1477
+ };
1478
+ }
1479
+ async revokeDashboardShare(userId, slug, sharedUserId, locale) {
1480
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1481
+ if (sharedUserId === userId) {
1482
+ throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.invalidValue', locale, 'Use the remove dashboard action to leave this tab.'));
1483
+ }
1484
+ await this.prismaService.dashboard_user.deleteMany({
1485
+ where: {
1486
+ dashboard_id: dashboardUser.dashboard_id,
1487
+ user_id: sharedUserId,
1488
+ },
1489
+ });
1490
+ return {
1491
+ success: true,
1492
+ };
1493
+ }
1494
+ async removeUserDashboard(userId, slug, locale) {
1495
+ const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
1496
+ await this.prismaService.dashboard_user.delete({
1497
+ where: { id: dashboardUser.id },
1498
+ });
1499
+ if (dashboardUser.is_home) {
1500
+ const nextDashboard = await this.prismaService.dashboard_user.findFirst({
1501
+ where: { user_id: userId },
1502
+ orderBy: { id: 'asc' },
1503
+ });
1504
+ if (nextDashboard) {
1505
+ await this.prismaService.dashboard_user.update({
1506
+ where: { id: nextDashboard.id },
1507
+ data: { is_home: true },
1508
+ });
1509
+ }
1510
+ }
1511
+ const remainingShares = await this.prismaService.dashboard_user.count({
1512
+ where: {
1513
+ dashboard_id: dashboardUser.dashboard_id,
1514
+ },
1515
+ });
1516
+ if (remainingShares === 0) {
1517
+ await this.prismaService.dashboard.delete({
1518
+ where: { id: dashboardUser.dashboard_id },
1519
+ });
1520
+ }
1521
+ return {
1522
+ success: true,
1523
+ removedSlug: slug,
1524
+ };
1525
+ }
1019
1526
  async getAccountSecurity(userId) {
1020
1527
  const now = new Date();
1021
1528
  const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);