@hed-hog/core 0.0.298 → 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.
- package/dist/dashboard/dashboard/dashboard.controller.d.ts +9 -0
- package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard/dashboard.service.d.ts +9 -0
- package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +14 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.js +28 -3
- package/dist/dashboard/dashboard-component/dashboard-component.controller.js.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +22 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.js +185 -35
- package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
- package/dist/dashboard/dashboard-component/dto/create.dto.d.ts +1 -0
- package/dist/dashboard/dashboard-component/dto/create.dto.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dto/create.dto.js +5 -0
- package/dist/dashboard/dashboard-component/dto/create.dto.js.map +1 -1
- package/dist/dashboard/dashboard-component/dto/update.dto.d.ts +1 -0
- package/dist/dashboard/dashboard-component/dto/update.dto.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dto/update.dto.js +5 -0
- package/dist/dashboard/dashboard-component/dto/update.dto.js.map +1 -1
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +1 -0
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +1 -0
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +72 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js +111 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +76 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js +614 -23
- package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +3 -0
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +3 -0
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +2 -0
- package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +2 -0
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts.map +1 -1
- package/hedhog/data/dashboard.yaml +12 -6
- package/hedhog/data/dashboard_component_role.yaml +66 -0
- package/hedhog/data/dashboard_item.yaml +1 -1
- package/hedhog/data/dashboard_role.yaml +2 -8
- package/hedhog/data/route.yaml +84 -0
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +457 -135
- package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +3 -0
- package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +365 -28
- package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +376 -247
- package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +64 -18
- package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +1389 -0
- package/hedhog/frontend/app/dashboard/dashboard.css.ejs +37 -0
- package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +6 -6
- package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +8 -8
- package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +3 -3
- package/hedhog/frontend/app/dashboard/page.tsx.ejs +3 -25
- package/hedhog/frontend/messages/en.json +115 -2
- package/hedhog/frontend/messages/pt.json +114 -1
- package/hedhog/frontend/public/dashboard-previews/.gitkeep +12 -0
- package/hedhog/frontend/public/dashboard-previews/account-security.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/active-users-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/activity-timeline.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/cash-balance-kpi.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/cash-flow-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/default-kpi.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/email-notifications.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/financial-alerts.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/login-history-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/mail-sent-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/mail-sent-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/menus-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/payable-30d-kpi.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/permissions-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/permissions-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/profile-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/receivable-30d-kpi.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/routes-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/session-activity-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/sessions-today-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/stat-access-level.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/stat-actions-today.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/stat-consecutive-days.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/stat-online-time.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/upcoming-payable.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/upcoming-receivable.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/user-growth-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/user-roles.png +0 -0
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/account-security.tsx.ejs +34 -30
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/active-users-card.tsx.ejs +2 -2
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/activity-timeline.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/email-notifications.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/locale-config.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/login-history-chart.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/mail-config.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/mail-sent-card.tsx.ejs +2 -2
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/mail-sent-chart.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/menus-card.tsx.ejs +2 -2
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/oauth-config.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/permissions-card.tsx.ejs +2 -2
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/permissions-chart.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/profile-card.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/routes-card.tsx.ejs +2 -2
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/session-activity-chart.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/sessions-today-card.tsx.ejs +2 -2
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/stat-access-level.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/stat-actions-today.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/stat-consecutive-days.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/stat-online-time.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/storage-config.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/theme-config.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-growth-chart.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-roles.tsx.ejs +1 -1
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-sessions.tsx.ejs +2 -2
- package/hedhog/table/dashboard.yaml +6 -0
- package/hedhog/table/dashboard_component.yaml +7 -0
- package/package.json +5 -5
- package/src/dashboard/dashboard-component/dashboard-component.controller.ts +51 -14
- package/src/dashboard/dashboard-component/dashboard-component.service.ts +254 -43
- package/src/dashboard/dashboard-component/dto/create.dto.ts +4 -0
- package/src/dashboard/dashboard-component/dto/update.dto.ts +4 -0
- package/src/dashboard/dashboard-core/dashboard-core.controller.ts +112 -1
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +782 -24
|
@@ -642,6 +642,153 @@ export class DashboardCoreService {
|
|
|
642
642
|
return value.map((provider) => String(provider).toLowerCase());
|
|
643
643
|
}
|
|
644
644
|
|
|
645
|
+
private slugifyDashboardName(value: string): string {
|
|
646
|
+
const normalized = value
|
|
647
|
+
.normalize('NFD')
|
|
648
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
649
|
+
.toLowerCase()
|
|
650
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
651
|
+
.replace(/^-+|-+$/g, '');
|
|
652
|
+
|
|
653
|
+
return normalized || `dashboard-${Date.now()}`;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
private getDashboardDisplayName(
|
|
657
|
+
dashboard: {
|
|
658
|
+
slug: string;
|
|
659
|
+
dashboard_locale?: Array<{ name?: string | null }>;
|
|
660
|
+
},
|
|
661
|
+
): string {
|
|
662
|
+
return dashboard.dashboard_locale?.[0]?.name || dashboard.slug;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
private async buildUniqueDashboardSlug(baseSlug: string): Promise<string> {
|
|
666
|
+
let slug = baseSlug;
|
|
667
|
+
let suffix = 2;
|
|
668
|
+
|
|
669
|
+
while (
|
|
670
|
+
await this.prismaService.dashboard.findFirst({
|
|
671
|
+
where: { slug },
|
|
672
|
+
select: { id: true },
|
|
673
|
+
})
|
|
674
|
+
) {
|
|
675
|
+
slug = `${baseSlug}-${suffix}`;
|
|
676
|
+
suffix += 1;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return slug;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
private async getDashboardUserOrThrow(
|
|
683
|
+
userId: number,
|
|
684
|
+
slug: string,
|
|
685
|
+
locale: string,
|
|
686
|
+
) {
|
|
687
|
+
const dashboardUser = await this.prismaService.dashboard_user.findFirst({
|
|
688
|
+
where: {
|
|
689
|
+
user_id: userId,
|
|
690
|
+
dashboard: { slug },
|
|
691
|
+
},
|
|
692
|
+
include: {
|
|
693
|
+
dashboard: {
|
|
694
|
+
include: {
|
|
695
|
+
dashboard_locale: {
|
|
696
|
+
where: {
|
|
697
|
+
locale: {
|
|
698
|
+
code: locale,
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
if (!dashboardUser) {
|
|
708
|
+
throw new ForbiddenException(
|
|
709
|
+
getLocaleText('dashboardNotFound', locale, 'Dashboard not found.'),
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return dashboardUser;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
private async getUserRoleIds(userId: number) {
|
|
717
|
+
const roleUsers = await this.prismaService.role_user.findMany({
|
|
718
|
+
where: { user_id: userId },
|
|
719
|
+
select: { role_id: true },
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
return roleUsers.map((roleUser) => roleUser.role_id);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
private async getAccessibleTemplateOrThrow(
|
|
726
|
+
userId: number,
|
|
727
|
+
templateSlug: string,
|
|
728
|
+
locale: string,
|
|
729
|
+
) {
|
|
730
|
+
const userRoleIds = await this.getUserRoleIds(userId);
|
|
731
|
+
const templateAccessFilter =
|
|
732
|
+
userRoleIds.length > 0
|
|
733
|
+
? {
|
|
734
|
+
OR: [
|
|
735
|
+
{
|
|
736
|
+
dashboard_role: {
|
|
737
|
+
some: {
|
|
738
|
+
role_id: {
|
|
739
|
+
in: userRoleIds,
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
dashboard_role: {
|
|
746
|
+
none: {},
|
|
747
|
+
},
|
|
748
|
+
},
|
|
749
|
+
],
|
|
750
|
+
}
|
|
751
|
+
: {
|
|
752
|
+
dashboard_role: {
|
|
753
|
+
none: {},
|
|
754
|
+
},
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
const template = await this.prismaService.dashboard.findFirst({
|
|
758
|
+
where: {
|
|
759
|
+
slug: templateSlug,
|
|
760
|
+
is_template: true,
|
|
761
|
+
...templateAccessFilter,
|
|
762
|
+
},
|
|
763
|
+
include: {
|
|
764
|
+
dashboard_locale: {
|
|
765
|
+
where: {
|
|
766
|
+
locale: {
|
|
767
|
+
code: locale,
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
},
|
|
771
|
+
dashboard_item: {
|
|
772
|
+
select: {
|
|
773
|
+
component_id: true,
|
|
774
|
+
width: true,
|
|
775
|
+
height: true,
|
|
776
|
+
x_axis: true,
|
|
777
|
+
y_axis: true,
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
if (!template) {
|
|
784
|
+
throw new ForbiddenException(
|
|
785
|
+
getLocaleText('dashboardNotFound', locale, 'Dashboard template not found.'),
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return template;
|
|
790
|
+
}
|
|
791
|
+
|
|
645
792
|
async getHome(userId: number, locale: string) {
|
|
646
793
|
const user = await this.prismaService.user.findUnique({
|
|
647
794
|
where: { id: userId },
|
|
@@ -1014,6 +1161,7 @@ export class DashboardCoreService {
|
|
|
1014
1161
|
i: `widget-${item.id}`,
|
|
1015
1162
|
component_id: item.component_id,
|
|
1016
1163
|
slug: component.slug,
|
|
1164
|
+
library_slug: component.library_slug,
|
|
1017
1165
|
name: locale?.name || component.slug,
|
|
1018
1166
|
description: locale?.description || '',
|
|
1019
1167
|
x: item.x_axis,
|
|
@@ -1060,28 +1208,34 @@ export class DashboardCoreService {
|
|
|
1060
1208
|
throw new ForbiddenException('Access denied to this dashboard');
|
|
1061
1209
|
}
|
|
1062
1210
|
|
|
1063
|
-
|
|
1064
|
-
const itemId = parseInt(item.i.replace('widget-', ''));
|
|
1065
|
-
const dashboardItem = await this.prismaService.dashboard_item.findFirst({
|
|
1066
|
-
where: {
|
|
1067
|
-
id: itemId,
|
|
1068
|
-
dashboard_id: dashboard.id,
|
|
1069
|
-
},
|
|
1070
|
-
});
|
|
1211
|
+
const layoutUpdates = layout.flatMap((item) => {
|
|
1212
|
+
const itemId = Number.parseInt(item.i.replace('widget-', ''), 10);
|
|
1071
1213
|
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1214
|
+
if (Number.isNaN(itemId)) {
|
|
1215
|
+
this.logger.warn(
|
|
1216
|
+
`Skipping dashboard layout item with invalid id: ${item.i}`,
|
|
1217
|
+
);
|
|
1218
|
+
return [];
|
|
1074
1219
|
}
|
|
1075
1220
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1221
|
+
return [
|
|
1222
|
+
this.prismaService.dashboard_item.updateMany({
|
|
1223
|
+
where: {
|
|
1224
|
+
id: itemId,
|
|
1225
|
+
dashboard_id: dashboard.id,
|
|
1226
|
+
},
|
|
1227
|
+
data: {
|
|
1228
|
+
x_axis: item.x,
|
|
1229
|
+
y_axis: item.y,
|
|
1230
|
+
width: item.w,
|
|
1231
|
+
height: item.h,
|
|
1232
|
+
},
|
|
1233
|
+
}),
|
|
1234
|
+
];
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
if (layoutUpdates.length > 0) {
|
|
1238
|
+
await this.prismaService.$transaction(layoutUpdates);
|
|
1085
1239
|
}
|
|
1086
1240
|
|
|
1087
1241
|
return { success: true };
|
|
@@ -1113,8 +1267,50 @@ export class DashboardCoreService {
|
|
|
1113
1267
|
throw new ForbiddenException('Access denied to this dashboard');
|
|
1114
1268
|
}
|
|
1115
1269
|
|
|
1270
|
+
const userRoles = await this.prismaService.role_user.findMany({
|
|
1271
|
+
where: { user_id: userId },
|
|
1272
|
+
select: { role_id: true },
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
const userRoleIds = userRoles.map((item) => item.role_id);
|
|
1276
|
+
|
|
1277
|
+
if (userRoleIds.length === 0) {
|
|
1278
|
+
throw new ForbiddenException('Access denied to this component');
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
const slugParts = componentSlug.split('.').filter(Boolean);
|
|
1282
|
+
const requestedSlug =
|
|
1283
|
+
slugParts.length > 0 ? slugParts[slugParts.length - 1]! : componentSlug;
|
|
1284
|
+
const requestedLibrarySlug =
|
|
1285
|
+
slugParts.length > 1 ? slugParts[0] : undefined;
|
|
1286
|
+
|
|
1116
1287
|
const component = await this.prismaService.dashboard_component.findFirst({
|
|
1117
|
-
where: {
|
|
1288
|
+
where: {
|
|
1289
|
+
AND: [
|
|
1290
|
+
requestedLibrarySlug
|
|
1291
|
+
? {
|
|
1292
|
+
OR: [
|
|
1293
|
+
{ slug: componentSlug },
|
|
1294
|
+
{
|
|
1295
|
+
slug: requestedSlug,
|
|
1296
|
+
library_slug: requestedLibrarySlug,
|
|
1297
|
+
},
|
|
1298
|
+
],
|
|
1299
|
+
}
|
|
1300
|
+
: {
|
|
1301
|
+
OR: [{ slug: componentSlug }, { slug: requestedSlug }],
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
dashboard_component_role: {
|
|
1305
|
+
some: {
|
|
1306
|
+
role_id: {
|
|
1307
|
+
in: userRoleIds,
|
|
1308
|
+
},
|
|
1309
|
+
},
|
|
1310
|
+
},
|
|
1311
|
+
},
|
|
1312
|
+
],
|
|
1313
|
+
},
|
|
1118
1314
|
include: {
|
|
1119
1315
|
dashboard_component_locale: {
|
|
1120
1316
|
where: {
|
|
@@ -1127,7 +1323,7 @@ export class DashboardCoreService {
|
|
|
1127
1323
|
});
|
|
1128
1324
|
|
|
1129
1325
|
if (!component) {
|
|
1130
|
-
throw new
|
|
1326
|
+
throw new ForbiddenException('Access denied to this component');
|
|
1131
1327
|
}
|
|
1132
1328
|
|
|
1133
1329
|
let dashboardItem = await this.prismaService.dashboard_item.findFirst({
|
|
@@ -1138,6 +1334,21 @@ export class DashboardCoreService {
|
|
|
1138
1334
|
});
|
|
1139
1335
|
|
|
1140
1336
|
if (!dashboardItem) {
|
|
1337
|
+
const dashboardItems = await this.prismaService.dashboard_item.findMany({
|
|
1338
|
+
where: {
|
|
1339
|
+
dashboard_id: dashboard.id,
|
|
1340
|
+
},
|
|
1341
|
+
select: {
|
|
1342
|
+
y_axis: true,
|
|
1343
|
+
height: true,
|
|
1344
|
+
},
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
const nextAvailableY = dashboardItems.reduce(
|
|
1348
|
+
(maxY, item) => Math.max(maxY, item.y_axis + item.height),
|
|
1349
|
+
0,
|
|
1350
|
+
);
|
|
1351
|
+
|
|
1141
1352
|
dashboardItem = await this.prismaService.dashboard_item.create({
|
|
1142
1353
|
data: {
|
|
1143
1354
|
dashboard_id: dashboard.id,
|
|
@@ -1145,7 +1356,7 @@ export class DashboardCoreService {
|
|
|
1145
1356
|
width: component.width,
|
|
1146
1357
|
height: component.height,
|
|
1147
1358
|
x_axis: 0,
|
|
1148
|
-
y_axis:
|
|
1359
|
+
y_axis: nextAvailableY,
|
|
1149
1360
|
},
|
|
1150
1361
|
});
|
|
1151
1362
|
}
|
|
@@ -1156,6 +1367,7 @@ export class DashboardCoreService {
|
|
|
1156
1367
|
i: `widget-${dashboardItem.id}`,
|
|
1157
1368
|
component_id: component.id,
|
|
1158
1369
|
slug: component.slug,
|
|
1370
|
+
library_slug: component.library_slug,
|
|
1159
1371
|
name: locale?.name || component.slug,
|
|
1160
1372
|
description: locale?.description || '',
|
|
1161
1373
|
x: dashboardItem.x_axis,
|
|
@@ -1175,8 +1387,52 @@ export class DashboardCoreService {
|
|
|
1175
1387
|
slug: string,
|
|
1176
1388
|
widgetId: string,
|
|
1177
1389
|
) {
|
|
1178
|
-
|
|
1179
|
-
|
|
1390
|
+
const dashboard = await this.prismaService.dashboard.findFirst({
|
|
1391
|
+
where: { slug },
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1394
|
+
if (!dashboard) {
|
|
1395
|
+
throw new Error(`Dashboard with slug '${slug}' not found`);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
const canAccess = await this.prismaService.dashboard_user.findFirst({
|
|
1399
|
+
where: {
|
|
1400
|
+
dashboard_id: dashboard.id,
|
|
1401
|
+
user_id: userId,
|
|
1402
|
+
},
|
|
1403
|
+
select: { id: true },
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
if (!canAccess) {
|
|
1407
|
+
throw new ForbiddenException('Access denied to this dashboard');
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
const parsedWidgetId = Number(widgetId.replace(/^widget-/, ''));
|
|
1411
|
+
|
|
1412
|
+
if (!Number.isInteger(parsedWidgetId) || parsedWidgetId <= 0) {
|
|
1413
|
+
throw new BadRequestException('Invalid widget id');
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
const dashboardItem = await this.prismaService.dashboard_item.findFirst({
|
|
1417
|
+
where: {
|
|
1418
|
+
id: parsedWidgetId,
|
|
1419
|
+
dashboard_id: dashboard.id,
|
|
1420
|
+
},
|
|
1421
|
+
select: { id: true },
|
|
1422
|
+
});
|
|
1423
|
+
|
|
1424
|
+
if (!dashboardItem) {
|
|
1425
|
+
throw new BadRequestException('Widget not found in this dashboard');
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
await this.prismaService.dashboard_item.delete({
|
|
1429
|
+
where: { id: dashboardItem.id },
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
return {
|
|
1433
|
+
success: true,
|
|
1434
|
+
removedWidgetId: `widget-${dashboardItem.id}`,
|
|
1435
|
+
};
|
|
1180
1436
|
}
|
|
1181
1437
|
|
|
1182
1438
|
async checkDashboardAccess(userId: number, slug: string, locale: string) {
|
|
@@ -1222,6 +1478,8 @@ export class DashboardCoreService {
|
|
|
1222
1478
|
}
|
|
1223
1479
|
|
|
1224
1480
|
async getUserDashboards(userId: number, locale: string) {
|
|
1481
|
+
await this.getHome(userId, locale);
|
|
1482
|
+
|
|
1225
1483
|
const dashboardUsers = await this.prismaService.dashboard_user.findMany({
|
|
1226
1484
|
where: { user_id: userId },
|
|
1227
1485
|
include: {
|
|
@@ -1237,6 +1495,7 @@ export class DashboardCoreService {
|
|
|
1237
1495
|
},
|
|
1238
1496
|
},
|
|
1239
1497
|
},
|
|
1498
|
+
orderBy: [{ is_home: 'desc' }, { id: 'asc' }],
|
|
1240
1499
|
});
|
|
1241
1500
|
|
|
1242
1501
|
const uniqueByDashboardId = new Map<number, (typeof dashboardUsers)[number]>();
|
|
@@ -1249,10 +1508,509 @@ export class DashboardCoreService {
|
|
|
1249
1508
|
return Array.from(uniqueByDashboardId.values()).map((dashboardUser) => ({
|
|
1250
1509
|
id: dashboardUser.dashboard.id,
|
|
1251
1510
|
slug: dashboardUser.dashboard.slug,
|
|
1511
|
+
name: this.getDashboardDisplayName(dashboardUser.dashboard),
|
|
1512
|
+
icon: dashboardUser.dashboard.icon,
|
|
1513
|
+
is_home: dashboardUser.is_home,
|
|
1252
1514
|
dashboard_locale: dashboardUser.dashboard.dashboard_locale,
|
|
1253
1515
|
}));
|
|
1254
1516
|
}
|
|
1255
1517
|
|
|
1518
|
+
async getAvailableTemplates(userId: number, locale: string) {
|
|
1519
|
+
const userRoleIds = await this.getUserRoleIds(userId);
|
|
1520
|
+
const templateAccessFilter =
|
|
1521
|
+
userRoleIds.length > 0
|
|
1522
|
+
? {
|
|
1523
|
+
OR: [
|
|
1524
|
+
{
|
|
1525
|
+
dashboard_role: {
|
|
1526
|
+
some: {
|
|
1527
|
+
role_id: {
|
|
1528
|
+
in: userRoleIds,
|
|
1529
|
+
},
|
|
1530
|
+
},
|
|
1531
|
+
},
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
dashboard_role: {
|
|
1535
|
+
none: {},
|
|
1536
|
+
},
|
|
1537
|
+
},
|
|
1538
|
+
],
|
|
1539
|
+
}
|
|
1540
|
+
: {
|
|
1541
|
+
dashboard_role: {
|
|
1542
|
+
none: {},
|
|
1543
|
+
},
|
|
1544
|
+
};
|
|
1545
|
+
|
|
1546
|
+
const templates = await this.prismaService.dashboard.findMany({
|
|
1547
|
+
where: {
|
|
1548
|
+
is_template: true,
|
|
1549
|
+
...templateAccessFilter,
|
|
1550
|
+
},
|
|
1551
|
+
include: {
|
|
1552
|
+
dashboard_locale: {
|
|
1553
|
+
where: {
|
|
1554
|
+
locale: {
|
|
1555
|
+
code: locale,
|
|
1556
|
+
},
|
|
1557
|
+
},
|
|
1558
|
+
},
|
|
1559
|
+
dashboard_item: {
|
|
1560
|
+
select: { id: true },
|
|
1561
|
+
},
|
|
1562
|
+
},
|
|
1563
|
+
orderBy: [{ id: 'asc' }],
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
return templates.map((template) => ({
|
|
1567
|
+
id: template.id,
|
|
1568
|
+
slug: template.slug,
|
|
1569
|
+
name: this.getDashboardDisplayName(template),
|
|
1570
|
+
icon: template.icon,
|
|
1571
|
+
itemCount: template.dashboard_item.length,
|
|
1572
|
+
}));
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
async createUserDashboard(
|
|
1576
|
+
userId: number,
|
|
1577
|
+
data: { name?: string; slug?: string; icon?: string | null; templateSlug?: string },
|
|
1578
|
+
locale: string,
|
|
1579
|
+
) {
|
|
1580
|
+
const templateSlug = this.toNullableString(data?.templateSlug);
|
|
1581
|
+
const template = templateSlug
|
|
1582
|
+
? await this.getAccessibleTemplateOrThrow(userId, templateSlug, locale)
|
|
1583
|
+
: null;
|
|
1584
|
+
|
|
1585
|
+
const templateName = template ? this.getDashboardDisplayName(template) : null;
|
|
1586
|
+
const name = this.toNullableString(data?.name) || templateName;
|
|
1587
|
+
|
|
1588
|
+
if (!name) {
|
|
1589
|
+
throw new BadRequestException(
|
|
1590
|
+
getLocaleText('validation.fieldRequired', locale, 'Dashboard name is required.'),
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
const requestedSlug = this.toNullableString(data?.slug);
|
|
1595
|
+
const baseSlug = this.slugifyDashboardName(requestedSlug || name);
|
|
1596
|
+
const slug = await this.buildUniqueDashboardSlug(baseSlug);
|
|
1597
|
+
const icon =
|
|
1598
|
+
data?.icon === undefined
|
|
1599
|
+
? template?.icon ?? null
|
|
1600
|
+
: this.toNullableString(data?.icon) ?? template?.icon ?? null;
|
|
1601
|
+
|
|
1602
|
+
const [localeRecord, existingCount] = await Promise.all([
|
|
1603
|
+
this.prismaService.locale.findFirst({
|
|
1604
|
+
where: { code: locale },
|
|
1605
|
+
select: { id: true },
|
|
1606
|
+
}),
|
|
1607
|
+
this.prismaService.dashboard_user.count({
|
|
1608
|
+
where: { user_id: userId },
|
|
1609
|
+
}),
|
|
1610
|
+
]);
|
|
1611
|
+
|
|
1612
|
+
const dashboard = await this.prismaService.dashboard.create({
|
|
1613
|
+
data: {
|
|
1614
|
+
slug,
|
|
1615
|
+
icon,
|
|
1616
|
+
},
|
|
1617
|
+
});
|
|
1618
|
+
|
|
1619
|
+
if (localeRecord) {
|
|
1620
|
+
await this.prismaService.dashboard_locale.create({
|
|
1621
|
+
data: {
|
|
1622
|
+
dashboard_id: dashboard.id,
|
|
1623
|
+
locale_id: localeRecord.id,
|
|
1624
|
+
name,
|
|
1625
|
+
},
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
await this.prismaService.dashboard_user.create({
|
|
1630
|
+
data: {
|
|
1631
|
+
dashboard_id: dashboard.id,
|
|
1632
|
+
user_id: userId,
|
|
1633
|
+
is_home: existingCount === 0,
|
|
1634
|
+
},
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
if (template?.dashboard_item?.length) {
|
|
1638
|
+
await this.prismaService.dashboard_item.createMany({
|
|
1639
|
+
data: template.dashboard_item.map((item) => ({
|
|
1640
|
+
dashboard_id: dashboard.id,
|
|
1641
|
+
component_id: item.component_id,
|
|
1642
|
+
width: item.width,
|
|
1643
|
+
height: item.height,
|
|
1644
|
+
x_axis: item.x_axis,
|
|
1645
|
+
y_axis: item.y_axis,
|
|
1646
|
+
})),
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
return {
|
|
1651
|
+
id: dashboard.id,
|
|
1652
|
+
slug,
|
|
1653
|
+
name,
|
|
1654
|
+
icon,
|
|
1655
|
+
is_home: existingCount === 0,
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
async renameUserDashboard(
|
|
1660
|
+
userId: number,
|
|
1661
|
+
slug: string,
|
|
1662
|
+
data: { name?: string; icon?: string | null },
|
|
1663
|
+
locale: string,
|
|
1664
|
+
) {
|
|
1665
|
+
const dashboardUser = await this.getDashboardUserOrThrow(
|
|
1666
|
+
userId,
|
|
1667
|
+
slug,
|
|
1668
|
+
locale,
|
|
1669
|
+
);
|
|
1670
|
+
const name = this.toNullableString(data?.name);
|
|
1671
|
+
const normalizedIcon =
|
|
1672
|
+
data?.icon === undefined ? undefined : this.toNullableString(data?.icon);
|
|
1673
|
+
|
|
1674
|
+
if (!name && data?.icon === undefined) {
|
|
1675
|
+
throw new BadRequestException(
|
|
1676
|
+
getLocaleText(
|
|
1677
|
+
'validation.fieldRequired',
|
|
1678
|
+
locale,
|
|
1679
|
+
'Dashboard name or icon is required.',
|
|
1680
|
+
),
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
if (data?.icon !== undefined) {
|
|
1685
|
+
await this.prismaService.dashboard.update({
|
|
1686
|
+
where: { id: dashboardUser.dashboard_id },
|
|
1687
|
+
data: {
|
|
1688
|
+
icon: normalizedIcon ?? null,
|
|
1689
|
+
},
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
if (name) {
|
|
1694
|
+
const localeRecord = await this.prismaService.locale.findFirst({
|
|
1695
|
+
where: { code: locale },
|
|
1696
|
+
select: { id: true },
|
|
1697
|
+
});
|
|
1698
|
+
|
|
1699
|
+
if (!localeRecord) {
|
|
1700
|
+
throw new BadRequestException(
|
|
1701
|
+
getLocaleText('localeNotFound', locale, 'Locale not found.'),
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
const existingLocale = await this.prismaService.dashboard_locale.findFirst({
|
|
1706
|
+
where: {
|
|
1707
|
+
dashboard_id: dashboardUser.dashboard_id,
|
|
1708
|
+
locale_id: localeRecord.id,
|
|
1709
|
+
},
|
|
1710
|
+
select: { id: true },
|
|
1711
|
+
});
|
|
1712
|
+
|
|
1713
|
+
if (existingLocale) {
|
|
1714
|
+
await this.prismaService.dashboard_locale.update({
|
|
1715
|
+
where: { id: existingLocale.id },
|
|
1716
|
+
data: { name },
|
|
1717
|
+
});
|
|
1718
|
+
} else {
|
|
1719
|
+
await this.prismaService.dashboard_locale.create({
|
|
1720
|
+
data: {
|
|
1721
|
+
dashboard_id: dashboardUser.dashboard_id,
|
|
1722
|
+
locale_id: localeRecord.id,
|
|
1723
|
+
name,
|
|
1724
|
+
},
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
const updatedDashboard = await this.prismaService.dashboard.findUnique({
|
|
1730
|
+
where: { id: dashboardUser.dashboard_id },
|
|
1731
|
+
include: {
|
|
1732
|
+
dashboard_locale: {
|
|
1733
|
+
where: {
|
|
1734
|
+
locale: {
|
|
1735
|
+
code: locale,
|
|
1736
|
+
},
|
|
1737
|
+
},
|
|
1738
|
+
},
|
|
1739
|
+
},
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1742
|
+
return {
|
|
1743
|
+
success: true,
|
|
1744
|
+
slug,
|
|
1745
|
+
name:
|
|
1746
|
+
name ||
|
|
1747
|
+
(updatedDashboard
|
|
1748
|
+
? this.getDashboardDisplayName(updatedDashboard)
|
|
1749
|
+
: slug),
|
|
1750
|
+
icon: updatedDashboard?.icon ?? normalizedIcon ?? null,
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
async setHomeDashboard(userId: number, slug: string, locale: string) {
|
|
1755
|
+
const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
|
|
1756
|
+
|
|
1757
|
+
await this.prismaService.dashboard_user.updateMany({
|
|
1758
|
+
where: { user_id: userId },
|
|
1759
|
+
data: { is_home: false },
|
|
1760
|
+
});
|
|
1761
|
+
|
|
1762
|
+
await this.prismaService.dashboard_user.update({
|
|
1763
|
+
where: { id: dashboardUser.id },
|
|
1764
|
+
data: { is_home: true },
|
|
1765
|
+
});
|
|
1766
|
+
|
|
1767
|
+
return {
|
|
1768
|
+
success: true,
|
|
1769
|
+
slug,
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
async getDashboardShares(userId: number, slug: string, locale: string) {
|
|
1774
|
+
const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
|
|
1775
|
+
|
|
1776
|
+
const sharedUsers = await this.prismaService.dashboard_user.findMany({
|
|
1777
|
+
where: {
|
|
1778
|
+
dashboard_id: dashboardUser.dashboard_id,
|
|
1779
|
+
},
|
|
1780
|
+
include: {
|
|
1781
|
+
user: {
|
|
1782
|
+
select: {
|
|
1783
|
+
id: true,
|
|
1784
|
+
name: true,
|
|
1785
|
+
user_identifier: {
|
|
1786
|
+
where: {
|
|
1787
|
+
type: 'email',
|
|
1788
|
+
},
|
|
1789
|
+
select: {
|
|
1790
|
+
value: true,
|
|
1791
|
+
},
|
|
1792
|
+
take: 1,
|
|
1793
|
+
},
|
|
1794
|
+
},
|
|
1795
|
+
},
|
|
1796
|
+
},
|
|
1797
|
+
orderBy: {
|
|
1798
|
+
id: 'asc',
|
|
1799
|
+
},
|
|
1800
|
+
});
|
|
1801
|
+
|
|
1802
|
+
return sharedUsers.map((sharedDashboardUser) => ({
|
|
1803
|
+
id: sharedDashboardUser.user.id,
|
|
1804
|
+
name: sharedDashboardUser.user.name,
|
|
1805
|
+
email: sharedDashboardUser.user.user_identifier[0]?.value ?? null,
|
|
1806
|
+
isCurrentUser: sharedDashboardUser.user.id === userId,
|
|
1807
|
+
isHome: sharedDashboardUser.is_home,
|
|
1808
|
+
}));
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
async getShareableUsers(
|
|
1812
|
+
userId: number,
|
|
1813
|
+
slug: string,
|
|
1814
|
+
search: string | undefined,
|
|
1815
|
+
locale: string,
|
|
1816
|
+
) {
|
|
1817
|
+
const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
|
|
1818
|
+
const normalizedSearch = this.toNullableString(search);
|
|
1819
|
+
|
|
1820
|
+
const existingUsers = await this.prismaService.dashboard_user.findMany({
|
|
1821
|
+
where: {
|
|
1822
|
+
dashboard_id: dashboardUser.dashboard_id,
|
|
1823
|
+
},
|
|
1824
|
+
select: {
|
|
1825
|
+
user_id: true,
|
|
1826
|
+
},
|
|
1827
|
+
});
|
|
1828
|
+
|
|
1829
|
+
const users = await this.prismaService.user.findMany({
|
|
1830
|
+
where: {
|
|
1831
|
+
id: {
|
|
1832
|
+
notIn: existingUsers.map((item) => item.user_id),
|
|
1833
|
+
},
|
|
1834
|
+
...(normalizedSearch
|
|
1835
|
+
? {
|
|
1836
|
+
OR: [
|
|
1837
|
+
{
|
|
1838
|
+
name: {
|
|
1839
|
+
contains: normalizedSearch,
|
|
1840
|
+
mode: 'insensitive',
|
|
1841
|
+
},
|
|
1842
|
+
},
|
|
1843
|
+
{
|
|
1844
|
+
user_identifier: {
|
|
1845
|
+
some: {
|
|
1846
|
+
type: 'email',
|
|
1847
|
+
value: {
|
|
1848
|
+
contains: normalizedSearch,
|
|
1849
|
+
mode: 'insensitive',
|
|
1850
|
+
},
|
|
1851
|
+
},
|
|
1852
|
+
},
|
|
1853
|
+
},
|
|
1854
|
+
],
|
|
1855
|
+
}
|
|
1856
|
+
: {}),
|
|
1857
|
+
},
|
|
1858
|
+
select: {
|
|
1859
|
+
id: true,
|
|
1860
|
+
name: true,
|
|
1861
|
+
user_identifier: {
|
|
1862
|
+
where: {
|
|
1863
|
+
type: 'email',
|
|
1864
|
+
},
|
|
1865
|
+
select: {
|
|
1866
|
+
value: true,
|
|
1867
|
+
},
|
|
1868
|
+
take: 1,
|
|
1869
|
+
},
|
|
1870
|
+
},
|
|
1871
|
+
take: 20,
|
|
1872
|
+
orderBy: {
|
|
1873
|
+
id: 'desc',
|
|
1874
|
+
},
|
|
1875
|
+
});
|
|
1876
|
+
|
|
1877
|
+
return users.map((candidateUser) => ({
|
|
1878
|
+
id: candidateUser.id,
|
|
1879
|
+
name: candidateUser.name,
|
|
1880
|
+
email: candidateUser.user_identifier[0]?.value ?? null,
|
|
1881
|
+
}));
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
async shareDashboard(
|
|
1885
|
+
userId: number,
|
|
1886
|
+
slug: string,
|
|
1887
|
+
sharedUserId: number | undefined,
|
|
1888
|
+
locale: string,
|
|
1889
|
+
) {
|
|
1890
|
+
if (!sharedUserId || Number.isNaN(Number(sharedUserId))) {
|
|
1891
|
+
throw new BadRequestException(
|
|
1892
|
+
getLocaleText('validation.fieldRequired', locale, 'User is required.'),
|
|
1893
|
+
);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
|
|
1897
|
+
|
|
1898
|
+
if (sharedUserId === userId) {
|
|
1899
|
+
throw new BadRequestException(
|
|
1900
|
+
getLocaleText(
|
|
1901
|
+
'validation.invalidValue',
|
|
1902
|
+
locale,
|
|
1903
|
+
'You already have access to this dashboard.',
|
|
1904
|
+
),
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
const targetUser = await this.prismaService.user.findUnique({
|
|
1909
|
+
where: { id: sharedUserId },
|
|
1910
|
+
select: { id: true },
|
|
1911
|
+
});
|
|
1912
|
+
|
|
1913
|
+
if (!targetUser) {
|
|
1914
|
+
throw new BadRequestException(getLocaleText('userNotFound', locale, 'User not found.'));
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
const existingShare = await this.prismaService.dashboard_user.findFirst({
|
|
1918
|
+
where: {
|
|
1919
|
+
dashboard_id: dashboardUser.dashboard_id,
|
|
1920
|
+
user_id: sharedUserId,
|
|
1921
|
+
},
|
|
1922
|
+
select: { id: true },
|
|
1923
|
+
});
|
|
1924
|
+
|
|
1925
|
+
if (existingShare) {
|
|
1926
|
+
return {
|
|
1927
|
+
success: true,
|
|
1928
|
+
alreadyShared: true,
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
await this.prismaService.dashboard_user.create({
|
|
1933
|
+
data: {
|
|
1934
|
+
dashboard_id: dashboardUser.dashboard_id,
|
|
1935
|
+
user_id: sharedUserId,
|
|
1936
|
+
is_home: false,
|
|
1937
|
+
},
|
|
1938
|
+
});
|
|
1939
|
+
|
|
1940
|
+
return {
|
|
1941
|
+
success: true,
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
async revokeDashboardShare(
|
|
1946
|
+
userId: number,
|
|
1947
|
+
slug: string,
|
|
1948
|
+
sharedUserId: number,
|
|
1949
|
+
locale: string,
|
|
1950
|
+
) {
|
|
1951
|
+
const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
|
|
1952
|
+
|
|
1953
|
+
if (sharedUserId === userId) {
|
|
1954
|
+
throw new BadRequestException(
|
|
1955
|
+
getLocaleText(
|
|
1956
|
+
'validation.invalidValue',
|
|
1957
|
+
locale,
|
|
1958
|
+
'Use the remove dashboard action to leave this tab.',
|
|
1959
|
+
),
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
await this.prismaService.dashboard_user.deleteMany({
|
|
1964
|
+
where: {
|
|
1965
|
+
dashboard_id: dashboardUser.dashboard_id,
|
|
1966
|
+
user_id: sharedUserId,
|
|
1967
|
+
},
|
|
1968
|
+
});
|
|
1969
|
+
|
|
1970
|
+
return {
|
|
1971
|
+
success: true,
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
async removeUserDashboard(userId: number, slug: string, locale: string) {
|
|
1976
|
+
const dashboardUser = await this.getDashboardUserOrThrow(userId, slug, locale);
|
|
1977
|
+
|
|
1978
|
+
await this.prismaService.dashboard_user.delete({
|
|
1979
|
+
where: { id: dashboardUser.id },
|
|
1980
|
+
});
|
|
1981
|
+
|
|
1982
|
+
if (dashboardUser.is_home) {
|
|
1983
|
+
const nextDashboard = await this.prismaService.dashboard_user.findFirst({
|
|
1984
|
+
where: { user_id: userId },
|
|
1985
|
+
orderBy: { id: 'asc' },
|
|
1986
|
+
});
|
|
1987
|
+
|
|
1988
|
+
if (nextDashboard) {
|
|
1989
|
+
await this.prismaService.dashboard_user.update({
|
|
1990
|
+
where: { id: nextDashboard.id },
|
|
1991
|
+
data: { is_home: true },
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
const remainingShares = await this.prismaService.dashboard_user.count({
|
|
1997
|
+
where: {
|
|
1998
|
+
dashboard_id: dashboardUser.dashboard_id,
|
|
1999
|
+
},
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
if (remainingShares === 0) {
|
|
2003
|
+
await this.prismaService.dashboard.delete({
|
|
2004
|
+
where: { id: dashboardUser.dashboard_id },
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
return {
|
|
2009
|
+
success: true,
|
|
2010
|
+
removedSlug: slug,
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
|
|
1256
2014
|
async getAccountSecurity(userId: number) {
|
|
1257
2015
|
const now = new Date();
|
|
1258
2016
|
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|