@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.
- package/dist/dashboard/dashboard/dashboard.controller.d.ts +6 -0
- package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard/dashboard.service.d.ts +6 -0
- package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +2 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.js +6 -3
- package/dist/dashboard/dashboard-component/dashboard-component.controller.js.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +7 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.js +76 -33
- package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +65 -0
- 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 +69 -0
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js +526 -19
- package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +2 -0
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +2 -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_role.yaml +2 -8
- package/hedhog/data/route.yaml +72 -0
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +333 -128
- package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +277 -53
- package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +179 -231
- 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 +112 -2
- package/hedhog/frontend/messages/pt.json +111 -1
- package/hedhog/frontend/widgets/account-security.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/active-users-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/activity-timeline.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/email-notifications.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/locale-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/login-history-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/mail-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/mail-sent-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/mail-sent-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/menus-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/oauth-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/permissions-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/permissions-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/profile-card.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/routes-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/session-activity-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/sessions-today-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/stat-access-level.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/stat-actions-today.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/stat-consecutive-days.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/stat-online-time.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/storage-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/theme-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/user-growth-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/user-roles.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/user-sessions.tsx.ejs +1 -1
- package/hedhog/table/dashboard.yaml +6 -0
- package/package.json +5 -5
- package/src/dashboard/dashboard-component/dashboard-component.controller.ts +15 -2
- package/src/dashboard/dashboard-component/dashboard-component.service.ts +107 -43
- package/src/dashboard/dashboard-core/dashboard-core.controller.ts +112 -1
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +674 -19
- package/hedhog/frontend/app/dashboard/components/widgets/core..gitkeep.ejs +0 -11
- package/hedhog/frontend/app/dashboard/components/widgets/core.account-security.tsx.ejs +0 -192
- package/hedhog/frontend/app/dashboard/components/widgets/core.active-users-card.tsx.ejs +0 -58
- package/hedhog/frontend/app/dashboard/components/widgets/core.activity-timeline.tsx.ejs +0 -223
- package/hedhog/frontend/app/dashboard/components/widgets/core.email-notifications.tsx.ejs +0 -226
- package/hedhog/frontend/app/dashboard/components/widgets/core.locale-config.tsx.ejs +0 -168
- package/hedhog/frontend/app/dashboard/components/widgets/core.login-history-chart.tsx.ejs +0 -115
- package/hedhog/frontend/app/dashboard/components/widgets/core.mail-config.tsx.ejs +0 -199
- package/hedhog/frontend/app/dashboard/components/widgets/core.mail-sent-card.tsx.ejs +0 -58
- package/hedhog/frontend/app/dashboard/components/widgets/core.mail-sent-chart.tsx.ejs +0 -149
- package/hedhog/frontend/app/dashboard/components/widgets/core.menus-card.tsx.ejs +0 -58
- package/hedhog/frontend/app/dashboard/components/widgets/core.oauth-config.tsx.ejs +0 -175
- package/hedhog/frontend/app/dashboard/components/widgets/core.permissions-card.tsx.ejs +0 -61
- package/hedhog/frontend/app/dashboard/components/widgets/core.permissions-chart.tsx.ejs +0 -156
- package/hedhog/frontend/app/dashboard/components/widgets/core.profile-card.tsx.ejs +0 -186
- package/hedhog/frontend/app/dashboard/components/widgets/core.routes-card.tsx.ejs +0 -58
- package/hedhog/frontend/app/dashboard/components/widgets/core.session-activity-chart.tsx.ejs +0 -183
- package/hedhog/frontend/app/dashboard/components/widgets/core.sessions-today-card.tsx.ejs +0 -62
- package/hedhog/frontend/app/dashboard/components/widgets/core.stat-access-level.tsx.ejs +0 -57
- package/hedhog/frontend/app/dashboard/components/widgets/core.stat-actions-today.tsx.ejs +0 -57
- package/hedhog/frontend/app/dashboard/components/widgets/core.stat-consecutive-days.tsx.ejs +0 -57
- package/hedhog/frontend/app/dashboard/components/widgets/core.stat-online-time.tsx.ejs +0 -57
- package/hedhog/frontend/app/dashboard/components/widgets/core.storage-config.tsx.ejs +0 -196
- package/hedhog/frontend/app/dashboard/components/widgets/core.theme-config.tsx.ejs +0 -213
- package/hedhog/frontend/app/dashboard/components/widgets/core.user-growth-chart.tsx.ejs +0 -210
- package/hedhog/frontend/app/dashboard/components/widgets/core.user-roles.tsx.ejs +0 -132
- package/hedhog/frontend/app/dashboard/components/widgets/core.user-sessions.tsx.ejs +0 -236
- package/hedhog/frontend/app/dashboard/components/widgets/finance.alerts.tsx.ejs +0 -108
- package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-balance-kpi.tsx.ejs +0 -66
- package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-flow-chart.tsx.ejs +0 -122
- package/hedhog/frontend/app/dashboard/components/widgets/finance.default-kpi.tsx.ejs +0 -63
- package/hedhog/frontend/app/dashboard/components/widgets/finance.payable-30d-kpi.tsx.ejs +0 -73
- package/hedhog/frontend/app/dashboard/components/widgets/finance.receivable-30d-kpi.tsx.ejs +0 -73
- package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-payable.tsx.ejs +0 -123
- package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-receivable.tsx.ejs +0 -118
|
@@ -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 },
|
|
@@ -1061,28 +1208,34 @@ export class DashboardCoreService {
|
|
|
1061
1208
|
throw new ForbiddenException('Access denied to this dashboard');
|
|
1062
1209
|
}
|
|
1063
1210
|
|
|
1064
|
-
|
|
1065
|
-
const itemId = parseInt(item.i.replace('widget-', ''));
|
|
1066
|
-
const dashboardItem = await this.prismaService.dashboard_item.findFirst({
|
|
1067
|
-
where: {
|
|
1068
|
-
id: itemId,
|
|
1069
|
-
dashboard_id: dashboard.id,
|
|
1070
|
-
},
|
|
1071
|
-
});
|
|
1211
|
+
const layoutUpdates = layout.flatMap((item) => {
|
|
1212
|
+
const itemId = Number.parseInt(item.i.replace('widget-', ''), 10);
|
|
1072
1213
|
|
|
1073
|
-
if (
|
|
1074
|
-
|
|
1214
|
+
if (Number.isNaN(itemId)) {
|
|
1215
|
+
this.logger.warn(
|
|
1216
|
+
`Skipping dashboard layout item with invalid id: ${item.i}`,
|
|
1217
|
+
);
|
|
1218
|
+
return [];
|
|
1075
1219
|
}
|
|
1076
1220
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
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);
|
|
1086
1239
|
}
|
|
1087
1240
|
|
|
1088
1241
|
return { success: true };
|
|
@@ -1325,6 +1478,8 @@ export class DashboardCoreService {
|
|
|
1325
1478
|
}
|
|
1326
1479
|
|
|
1327
1480
|
async getUserDashboards(userId: number, locale: string) {
|
|
1481
|
+
await this.getHome(userId, locale);
|
|
1482
|
+
|
|
1328
1483
|
const dashboardUsers = await this.prismaService.dashboard_user.findMany({
|
|
1329
1484
|
where: { user_id: userId },
|
|
1330
1485
|
include: {
|
|
@@ -1340,6 +1495,7 @@ export class DashboardCoreService {
|
|
|
1340
1495
|
},
|
|
1341
1496
|
},
|
|
1342
1497
|
},
|
|
1498
|
+
orderBy: [{ is_home: 'desc' }, { id: 'asc' }],
|
|
1343
1499
|
});
|
|
1344
1500
|
|
|
1345
1501
|
const uniqueByDashboardId = new Map<number, (typeof dashboardUsers)[number]>();
|
|
@@ -1352,10 +1508,509 @@ export class DashboardCoreService {
|
|
|
1352
1508
|
return Array.from(uniqueByDashboardId.values()).map((dashboardUser) => ({
|
|
1353
1509
|
id: dashboardUser.dashboard.id,
|
|
1354
1510
|
slug: dashboardUser.dashboard.slug,
|
|
1511
|
+
name: this.getDashboardDisplayName(dashboardUser.dashboard),
|
|
1512
|
+
icon: dashboardUser.dashboard.icon,
|
|
1513
|
+
is_home: dashboardUser.is_home,
|
|
1355
1514
|
dashboard_locale: dashboardUser.dashboard.dashboard_locale,
|
|
1356
1515
|
}));
|
|
1357
1516
|
}
|
|
1358
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
|
+
|
|
1359
2014
|
async getAccountSecurity(userId: number) {
|
|
1360
2015
|
const now = new Date();
|
|
1361
2016
|
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|