@hed-hog/core 0.0.262 → 0.0.270
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/auth/auth.controller.d.ts +1 -1
- package/dist/auth/auth.service.d.ts +1 -1
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +15 -15
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +15 -15
- package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +7 -7
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +7 -7
- package/dist/dashboard/dashboard-user/dashboard-user.controller.d.ts +3 -3
- package/dist/dashboard/dashboard-user/dashboard-user.service.d.ts +3 -3
- package/dist/file/file.controller.d.ts +2 -2
- package/dist/file/file.service.d.ts +4 -4
- package/dist/install/install.module.d.ts.map +1 -1
- package/dist/install/install.module.js +2 -0
- package/dist/install/install.module.js.map +1 -1
- package/dist/install/install.service.d.ts +3 -2
- package/dist/install/install.service.d.ts.map +1 -1
- package/dist/install/install.service.js +33 -56
- package/dist/install/install.service.js.map +1 -1
- package/dist/menu/menu.controller.d.ts +3 -3
- package/dist/menu/menu.service.d.ts +3 -3
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +2 -1
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +2 -2
- package/dist/user/user.service.d.ts +3 -3
- package/hedhog/data/dashboard_component.yaml +8 -8
- package/hedhog/data/dashboard_item.yaml +18 -18
- package/hedhog/frontend/app/configurations/[slug]/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/configurations/layout.tsx.ejs +2 -2
- package/hedhog/frontend/app/configurations/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +10 -8
- package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +54 -26
- package/hedhog/frontend/app/dashboard/components/widget-wrapper.tsx.ejs +6 -6
- package/hedhog/frontend/app/dashboard/dashboard.css.ejs +92 -184
- package/hedhog/frontend/app/preferences/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/roles/page.tsx.ejs +59 -57
- package/hedhog/frontend/app/users/page.tsx.ejs +1 -1
- package/package.json +14 -7
- package/src/install/install.module.ts +2 -0
- package/src/install/install.service.ts +42 -65
- package/src/setting/setting.service.ts +5 -4
|
@@ -1,157 +1,62 @@
|
|
|
1
|
-
.react-grid-layout {
|
|
2
|
-
position: relative;
|
|
3
|
-
transition: height 200ms ease;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
.react-grid-item {
|
|
7
|
-
transition: all 200ms ease;
|
|
8
|
-
transition-property: left, top, width, height;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.react-grid-item img {
|
|
12
|
-
pointer-events: none;
|
|
13
|
-
user-select: none;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.react-grid-item > .react-resizable-handle {
|
|
17
|
-
position: absolute;
|
|
18
|
-
width:
|
|
19
|
-
height:
|
|
20
|
-
z-index: 10;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
.react-grid-item
|
|
39
|
-
opacity: 1;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
right: 0;
|
|
61
|
-
cursor: se-resize;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-se::after {
|
|
65
|
-
right: 2px;
|
|
66
|
-
bottom: 2px;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-nw {
|
|
70
|
-
top: 0;
|
|
71
|
-
left: 0;
|
|
72
|
-
cursor: nw-resize;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-nw::after {
|
|
76
|
-
left: 2px;
|
|
77
|
-
top: 2px;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-ne {
|
|
81
|
-
top: 0;
|
|
82
|
-
right: 0;
|
|
83
|
-
cursor: ne-resize;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-ne::after {
|
|
87
|
-
right: 2px;
|
|
88
|
-
top: 2px;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-w {
|
|
92
|
-
left: 0;
|
|
93
|
-
top: 50%;
|
|
94
|
-
transform: translateY(-50%);
|
|
95
|
-
cursor: ew-resize;
|
|
96
|
-
width: 20px;
|
|
97
|
-
height: 60px;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-w::after {
|
|
101
|
-
left: 2px;
|
|
102
|
-
top: 50%;
|
|
103
|
-
transform: translateY(-50%);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-e {
|
|
107
|
-
right: 0;
|
|
108
|
-
top: 50%;
|
|
109
|
-
transform: translateY(-50%);
|
|
110
|
-
cursor: ew-resize;
|
|
111
|
-
width: 20px;
|
|
112
|
-
height: 60px;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-e::after {
|
|
116
|
-
right: 2px;
|
|
117
|
-
top: 50%;
|
|
118
|
-
transform: translateY(-50%);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-n {
|
|
122
|
-
top: 0;
|
|
123
|
-
left: 50%;
|
|
124
|
-
transform: translateX(-50%);
|
|
125
|
-
cursor: ns-resize;
|
|
126
|
-
width: 60px;
|
|
127
|
-
height: 20px;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-n::after {
|
|
131
|
-
top: 2px;
|
|
132
|
-
left: 50%;
|
|
133
|
-
transform: translateX(-50%);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-s {
|
|
137
|
-
bottom: 0;
|
|
138
|
-
left: 50%;
|
|
139
|
-
transform: translateX(-50%);
|
|
140
|
-
cursor: ns-resize;
|
|
141
|
-
width: 60px;
|
|
142
|
-
height: 20px;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.react-grid-item > .react-resizable-handle.react-resizable-handle-s::after {
|
|
146
|
-
bottom: 2px;
|
|
147
|
-
left: 50%;
|
|
148
|
-
transform: translateX(-50%);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/* Drag handle styles */
|
|
152
|
-
.drag-handle {
|
|
153
|
-
cursor: grab !important;
|
|
154
|
-
user-select: none !important;
|
|
1
|
+
.dashboard-grid.react-grid-layout {
|
|
2
|
+
position: relative;
|
|
3
|
+
transition: height 200ms ease;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.dashboard-grid .react-grid-item {
|
|
7
|
+
transition: all 200ms ease;
|
|
8
|
+
transition-property: left, top, width, height;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.dashboard-grid .react-grid-item img {
|
|
12
|
+
pointer-events: none;
|
|
13
|
+
user-select: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.dashboard-grid .react-grid-item > .react-resizable-handle {
|
|
17
|
+
position: absolute;
|
|
18
|
+
width: 18px;
|
|
19
|
+
height: 18px;
|
|
20
|
+
z-index: 10;
|
|
21
|
+
opacity: 0;
|
|
22
|
+
transition: opacity 0.15s ease;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dashboard-grid .react-grid-item > .react-resizable-handle::after {
|
|
26
|
+
content: '';
|
|
27
|
+
position: absolute;
|
|
28
|
+
right: 3px;
|
|
29
|
+
bottom: 3px;
|
|
30
|
+
width: 6px;
|
|
31
|
+
height: 6px;
|
|
32
|
+
border-right: 2px solid hsl(var(--muted-foreground));
|
|
33
|
+
border-bottom: 2px solid hsl(var(--muted-foreground));
|
|
34
|
+
opacity: 0.45;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.dashboard-grid .react-grid-item:hover > .react-resizable-handle.react-resizable-handle-se,
|
|
38
|
+
.dashboard-grid .react-grid-item.resizing > .react-resizable-handle.react-resizable-handle-se {
|
|
39
|
+
opacity: 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.dashboard-grid .react-resizable-hide > .react-resizable-handle {
|
|
43
|
+
display: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.dashboard-grid .react-grid-item > .react-resizable-handle:not(.react-resizable-handle-se) {
|
|
47
|
+
display: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.dashboard-grid .react-grid-item > .react-resizable-handle.react-resizable-handle-se {
|
|
51
|
+
bottom: 0;
|
|
52
|
+
right: 0;
|
|
53
|
+
cursor: se-resize;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Drag handle styles */
|
|
57
|
+
.drag-handle {
|
|
58
|
+
cursor: grab !important;
|
|
59
|
+
user-select: none !important;
|
|
155
60
|
}
|
|
156
61
|
|
|
157
62
|
.drag-handle:active {
|
|
@@ -159,38 +64,41 @@
|
|
|
159
64
|
}
|
|
160
65
|
|
|
161
66
|
/* Prevent text selection during drag */
|
|
162
|
-
.react-grid-item.react-draggable-dragging {
|
|
163
|
-
transition: none;
|
|
164
|
-
z-index: 100;
|
|
165
|
-
will-change: transform;
|
|
166
|
-
opacity: 0.9;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.react-grid-item.resizing {
|
|
170
|
-
transition: none;
|
|
171
|
-
z-index: 100;
|
|
172
|
-
will-change: transform;
|
|
173
|
-
opacity: 0.9;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
.
|
|
180
|
-
|
|
181
|
-
opacity: 0.5;
|
|
182
|
-
transition-duration: 100ms;
|
|
67
|
+
.dashboard-grid .react-grid-item.react-draggable-dragging {
|
|
68
|
+
transition: none;
|
|
69
|
+
z-index: 100;
|
|
70
|
+
will-change: transform;
|
|
71
|
+
opacity: 0.9;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.dashboard-grid .react-grid-item.resizing {
|
|
75
|
+
transition: none;
|
|
76
|
+
z-index: 100;
|
|
77
|
+
will-change: transform;
|
|
78
|
+
opacity: 0.9;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Placeholder styling */
|
|
82
|
+
.dashboard-grid .react-grid-placeholder {
|
|
83
|
+
background: hsl(var(--primary) / 0.2);
|
|
84
|
+
opacity: 0.5;
|
|
85
|
+
transition-duration: 100ms;
|
|
183
86
|
z-index: 2;
|
|
184
87
|
border-radius: 0.5rem;
|
|
185
88
|
border: 2px dashed hsl(var(--primary));
|
|
186
89
|
}
|
|
187
90
|
|
|
188
|
-
.react-grid-item:hover {
|
|
189
|
-
outline: 1px solid hsl(var(--primary) / 0.
|
|
190
|
-
outline-offset: -1px;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
91
|
+
.dashboard-grid .react-grid-item:hover {
|
|
92
|
+
outline: 1px solid hsl(var(--primary) / 0.12);
|
|
93
|
+
outline-offset: -1px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.dashboard-widget > [data-slot='card'] {
|
|
97
|
+
gap: 0.75rem;
|
|
98
|
+
padding-top: 0.75rem;
|
|
99
|
+
padding-bottom: 0.75rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.dashboard-widget > [data-slot='card'] > [data-slot='card-header'] {
|
|
103
|
+
padding-top: 0;
|
|
104
|
+
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
SelectTrigger,
|
|
12
12
|
SelectValue,
|
|
13
13
|
} from '@/components/ui/select';
|
|
14
|
-
import { PaginatedResult } from '
|
|
14
|
+
import { PaginatedResult } from '@/types/pagination-result';
|
|
15
15
|
import { Setting, SettingList } from '@hed-hog/api-types';
|
|
16
16
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
17
17
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
@@ -223,7 +223,7 @@ export default function RolePage() {
|
|
|
223
223
|
}
|
|
224
224
|
};
|
|
225
225
|
|
|
226
|
-
const handleEdit = async (role: Role) => {
|
|
226
|
+
const handleEdit = async (role: Role & { role_id: number }) => {
|
|
227
227
|
setEditFormError(null);
|
|
228
228
|
|
|
229
229
|
try {
|
|
@@ -426,65 +426,67 @@ export default function RolePage() {
|
|
|
426
426
|
<p className="text-sm text-muted-foreground">{t('noRolesFound')}</p>
|
|
427
427
|
) : (
|
|
428
428
|
<div className="grid gap-4 grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
|
|
429
|
-
{rolesResponse?.data
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
<
|
|
437
|
-
<div className="
|
|
438
|
-
<
|
|
439
|
-
|
|
440
|
-
<div className="flex-1">
|
|
441
|
-
<CardTitle className="text-sm font-semibold">
|
|
442
|
-
{role.name}
|
|
443
|
-
</CardTitle>
|
|
444
|
-
<CardDescription className="text-xs text-muted-foreground">
|
|
445
|
-
{role.slug}
|
|
446
|
-
</CardDescription>
|
|
447
|
-
</div>
|
|
448
|
-
</div>
|
|
449
|
-
<Button
|
|
450
|
-
variant="outline"
|
|
451
|
-
size="sm"
|
|
452
|
-
onClick={() => handleEdit(role)}
|
|
453
|
-
>
|
|
454
|
-
{t('buttonEditRole')}
|
|
455
|
-
</Button>
|
|
456
|
-
</CardHeader>
|
|
457
|
-
{role.description && (
|
|
458
|
-
<CardContent className="p-0">
|
|
459
|
-
<p className="text-xs text-muted-foreground line-clamp-2">
|
|
460
|
-
{role.description}
|
|
461
|
-
</p>
|
|
462
|
-
<div className="text-xs line-clamp-2 flex gap-2 py-2">
|
|
463
|
-
<div>
|
|
464
|
-
{(role as any).user_count}{' '}
|
|
465
|
-
{(role as any).user_count === 1
|
|
466
|
-
? t('user')
|
|
467
|
-
: t('users')}
|
|
468
|
-
</div>
|
|
469
|
-
<div>•</div>
|
|
470
|
-
<div>
|
|
471
|
-
{(role as any).menu_count}{' '}
|
|
472
|
-
{(role as any).menu_count === 1
|
|
473
|
-
? t('menu')
|
|
474
|
-
: t('menus')}
|
|
429
|
+
{(rolesResponse?.data as (Role & { role_id: number })[])?.map(
|
|
430
|
+
(role: Role & { role_id: number }) => (
|
|
431
|
+
<Card
|
|
432
|
+
key={String(role.role_id)}
|
|
433
|
+
onDoubleClick={() => handleEdit(role)}
|
|
434
|
+
className="cursor-pointer rounded-md flex flex-col justify-between gap-2 border border-border/60 bg-card p-4 shadow-sm transition hover:border-primary"
|
|
435
|
+
>
|
|
436
|
+
<CardHeader className="flex items-start justify-between gap-4 p-0">
|
|
437
|
+
<div className="flex items-center gap-3 flex-1">
|
|
438
|
+
<div className="h-12 w-12 shrink-0 rounded-full bg-primary/10 flex items-center justify-center">
|
|
439
|
+
<ShieldCheck className="h-6 w-6 text-primary" />
|
|
475
440
|
</div>
|
|
476
|
-
<div
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
441
|
+
<div className="flex-1">
|
|
442
|
+
<CardTitle className="text-sm font-semibold">
|
|
443
|
+
{role.name}
|
|
444
|
+
</CardTitle>
|
|
445
|
+
<CardDescription className="text-xs text-muted-foreground">
|
|
446
|
+
{role.slug}
|
|
447
|
+
</CardDescription>
|
|
482
448
|
</div>
|
|
483
449
|
</div>
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
450
|
+
<Button
|
|
451
|
+
variant="outline"
|
|
452
|
+
size="sm"
|
|
453
|
+
onClick={() => handleEdit(role)}
|
|
454
|
+
>
|
|
455
|
+
{t('buttonEditRole')}
|
|
456
|
+
</Button>
|
|
457
|
+
</CardHeader>
|
|
458
|
+
{role.description && (
|
|
459
|
+
<CardContent className="p-0">
|
|
460
|
+
<p className="text-xs text-muted-foreground line-clamp-2">
|
|
461
|
+
{role.description}
|
|
462
|
+
</p>
|
|
463
|
+
<div className="text-xs line-clamp-2 flex gap-2 py-2">
|
|
464
|
+
<div>
|
|
465
|
+
{(role as any).user_count}{' '}
|
|
466
|
+
{(role as any).user_count === 1
|
|
467
|
+
? t('user')
|
|
468
|
+
: t('users')}
|
|
469
|
+
</div>
|
|
470
|
+
<div>•</div>
|
|
471
|
+
<div>
|
|
472
|
+
{(role as any).menu_count}{' '}
|
|
473
|
+
{(role as any).menu_count === 1
|
|
474
|
+
? t('menu')
|
|
475
|
+
: t('menus')}
|
|
476
|
+
</div>
|
|
477
|
+
<div>•</div>
|
|
478
|
+
<div>
|
|
479
|
+
{(role as any).route_count}{' '}
|
|
480
|
+
{(role as any).route_count === 1
|
|
481
|
+
? t('route')
|
|
482
|
+
: t('routes')}
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
</CardContent>
|
|
486
|
+
)}
|
|
487
|
+
</Card>
|
|
488
|
+
)
|
|
489
|
+
)}
|
|
488
490
|
</div>
|
|
489
491
|
)}
|
|
490
492
|
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
import { formatDateTime } from '@/lib/format-date';
|
|
42
42
|
import { getPhotoUrl } from '@/lib/get-photo-url';
|
|
43
43
|
import { getUserEmail } from '@/lib/get-user-email';
|
|
44
|
-
import { PaginatedResult } from '
|
|
44
|
+
import { PaginatedResult } from '@/types/pagination-result';
|
|
45
45
|
import { User, UserMfa } from '@hed-hog/api-types';
|
|
46
46
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
47
47
|
import { zodResolver } from '@hookform/resolvers/zod';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hed-hog/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.270",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
"sharp": "^0.34.2",
|
|
31
31
|
"speakeasy": "^2.0.0",
|
|
32
32
|
"uuid": "^11.1.0",
|
|
33
|
-
"@hed-hog/api-pagination": "0.0.
|
|
34
|
-
"@hed-hog/api-
|
|
35
|
-
"@hed-hog/api-
|
|
36
|
-
"@hed-hog/api-
|
|
37
|
-
"@hed-hog/api
|
|
38
|
-
"@hed-hog/api": "0.0.
|
|
33
|
+
"@hed-hog/api-pagination": "0.0.6",
|
|
34
|
+
"@hed-hog/api-prisma": "0.0.5",
|
|
35
|
+
"@hed-hog/api-locale": "0.0.13",
|
|
36
|
+
"@hed-hog/api-mail": "0.0.8",
|
|
37
|
+
"@hed-hog/api": "0.0.4",
|
|
38
|
+
"@hed-hog/api-types": "0.0.1"
|
|
39
39
|
},
|
|
40
40
|
"exports": {
|
|
41
41
|
".": {
|
|
@@ -44,11 +44,18 @@
|
|
|
44
44
|
"require": "./dist/index.js",
|
|
45
45
|
"default": "./dist/index.js"
|
|
46
46
|
},
|
|
47
|
+
"./*": {
|
|
48
|
+
"types": "./dist/*.d.ts",
|
|
49
|
+
"import": "./dist/*.js",
|
|
50
|
+
"require": "./dist/*.js",
|
|
51
|
+
"default": "./dist/*.js"
|
|
52
|
+
},
|
|
47
53
|
"./package.json": "./package.json"
|
|
48
54
|
},
|
|
49
55
|
"typesVersions": {
|
|
50
56
|
"*": {
|
|
51
57
|
"*": [
|
|
58
|
+
"./dist/*.d.ts",
|
|
52
59
|
"./dist/index.d.ts"
|
|
53
60
|
]
|
|
54
61
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PrismaModule } from '@hed-hog/api-prisma';
|
|
2
2
|
import { forwardRef, Module } from '@nestjs/common';
|
|
3
|
+
import { ConfigModule } from '@nestjs/config';
|
|
3
4
|
import { AuthModule } from '../auth/auth.module';
|
|
4
5
|
import { SecurityModule } from '../security/security.module';
|
|
5
6
|
import { SettingModule } from '../setting/setting.module';
|
|
@@ -12,6 +13,7 @@ import { InstallService } from './install.service';
|
|
|
12
13
|
forwardRef(() => SettingModule),
|
|
13
14
|
forwardRef(() => SecurityModule),
|
|
14
15
|
forwardRef(() => AuthModule),
|
|
16
|
+
forwardRef(() => ConfigModule),
|
|
15
17
|
],
|
|
16
18
|
controllers: [InstallController],
|
|
17
19
|
providers: [InstallService],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PrismaService } from '@hed-hog/api-prisma';
|
|
2
2
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
|
3
|
+
import { ConfigService } from '@nestjs/config';
|
|
3
4
|
import { existsSync, readFileSync } from 'fs';
|
|
4
5
|
import { writeFile } from 'fs/promises';
|
|
5
6
|
import { resolve } from 'path';
|
|
@@ -13,80 +14,50 @@ export class InstallService {
|
|
|
13
14
|
|
|
14
15
|
constructor(
|
|
15
16
|
private readonly security: SecurityService,
|
|
16
|
-
private readonly prisma: PrismaService
|
|
17
|
+
private readonly prisma: PrismaService,
|
|
18
|
+
private readonly configService: ConfigService,
|
|
17
19
|
) { }
|
|
18
20
|
|
|
19
21
|
private async forceReset() {
|
|
20
22
|
|
|
21
23
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
22
24
|
|
|
23
|
-
//
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// In development, touching main.ts triggers restart; in production only dist exists.
|
|
26
|
+
const candidatePaths = [
|
|
27
|
+
resolve(process.cwd(), 'src', 'main.ts'),
|
|
28
|
+
resolve(process.cwd(), 'dist', 'apps', 'api', 'src', 'main.js'),
|
|
29
|
+
resolve(process.cwd(), 'dist', 'src', 'main.js'),
|
|
30
|
+
];
|
|
31
|
+
const mainFilePath = candidatePaths.find((filePath) => existsSync(filePath));
|
|
32
|
+
if (!mainFilePath) {
|
|
33
|
+
this.logger.warn('Skip force reset: no main entry file found to touch.');
|
|
34
|
+
return;
|
|
27
35
|
}
|
|
36
|
+
|
|
28
37
|
let mainContent: string;
|
|
29
38
|
try {
|
|
30
39
|
mainContent = readFileSync(mainFilePath, 'utf-8');
|
|
31
40
|
} catch (err: any) {
|
|
32
|
-
throw new BadRequestException(
|
|
41
|
+
throw new BadRequestException(
|
|
42
|
+
`Failed to read main entry file: ${err.message}`,
|
|
43
|
+
);
|
|
33
44
|
}
|
|
34
45
|
try {
|
|
35
|
-
this.logger.verbose(
|
|
46
|
+
this.logger.verbose(
|
|
47
|
+
`Forcing application restart by touching ${mainFilePath}...`,
|
|
48
|
+
);
|
|
36
49
|
await writeFile(mainFilePath, mainContent, 'utf-8');
|
|
37
50
|
} catch (err: any) {
|
|
38
|
-
throw new BadRequestException(
|
|
51
|
+
throw new BadRequestException(
|
|
52
|
+
`Failed to write main entry file: ${err.message}`,
|
|
53
|
+
);
|
|
39
54
|
}
|
|
40
55
|
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
private async checkInstallation() {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let data = {
|
|
48
|
-
installed: false
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
if (existsSync(hedhogFilePath)) {
|
|
52
|
-
try {
|
|
53
|
-
const fileContent = readFileSync(hedhogFilePath, 'utf-8');
|
|
54
|
-
data = JSON.parse(fileContent);
|
|
55
|
-
} catch (err: any) {
|
|
56
|
-
this.logger.warn(`Failed to read hedhog.json: ${err.message}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return data.installed === true;
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private async markAsInstalled() {
|
|
65
|
-
|
|
66
|
-
const hedhogFilePath = resolve(process.cwd(), '..', '..', 'hedhog.json');
|
|
67
|
-
|
|
68
|
-
let content: any = {}
|
|
69
|
-
|
|
70
|
-
if (existsSync(hedhogFilePath)) {
|
|
71
|
-
try {
|
|
72
|
-
const fileContent = readFileSync(hedhogFilePath, 'utf-8');
|
|
73
|
-
content = JSON.parse(fileContent);
|
|
74
|
-
} catch (err: any) {
|
|
75
|
-
this.logger.warn(`Failed to read hedhog.json: ${err.message}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
content.installed = true;
|
|
80
|
-
content.installedAt = new Date().toISOString();
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
this.logger.verbose('Marking application as installed in hedghog.json...');
|
|
84
|
-
await writeFile(hedhogFilePath, JSON.stringify(content, null, 2), 'utf-8');
|
|
85
|
-
} catch (err: any) {
|
|
86
|
-
throw new BadRequestException(`Failed to write hedhog.json: ${err.message}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return true;
|
|
59
|
+
const usersCount = await this.prisma.user.count();
|
|
60
|
+
return usersCount > 0;
|
|
90
61
|
}
|
|
91
62
|
|
|
92
63
|
private async updateEnvSecrets(pepper: string) {
|
|
@@ -170,8 +141,6 @@ export class InstallService {
|
|
|
170
141
|
throw new BadRequestException('Application is already installed.');
|
|
171
142
|
}
|
|
172
143
|
|
|
173
|
-
const pepper = this.base64Encode(this.security.randomOpaque(16));
|
|
174
|
-
|
|
175
144
|
await this.prisma.$transaction(async (prisma) => {
|
|
176
145
|
|
|
177
146
|
this.logger.log('Starting installation process...');
|
|
@@ -220,6 +189,13 @@ export class InstallService {
|
|
|
220
189
|
|
|
221
190
|
this.logger.log(`Creating admin user: ${userName} <${email}>`);
|
|
222
191
|
|
|
192
|
+
const pepper = this.configService.get<string>('PEPPER');
|
|
193
|
+
if (!pepper) {
|
|
194
|
+
throw new BadRequestException(
|
|
195
|
+
'PEPPER is not configured. Set PEPPER before installation.',
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
223
199
|
const passwordHash = await this.security.hashArgon2(password, pepper);
|
|
224
200
|
|
|
225
201
|
const user = await prisma.user.create({
|
|
@@ -260,9 +236,15 @@ export class InstallService {
|
|
|
260
236
|
|
|
261
237
|
this.logger.log(`Roles assigned to user ID: ${user.id}`);
|
|
262
238
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
239
|
+
const isDevelopment = process.env.NODE_ENV !== 'production';
|
|
240
|
+
if (isDevelopment) {
|
|
241
|
+
await this.updateEnvSecrets(pepper);
|
|
242
|
+
await this.forceReset();
|
|
243
|
+
} else {
|
|
244
|
+
this.logger.log(
|
|
245
|
+
'Skipping env secret update and force reset outside development.',
|
|
246
|
+
);
|
|
247
|
+
}
|
|
266
248
|
|
|
267
249
|
this.logger.log('Installation process completed successfully.');
|
|
268
250
|
|
|
@@ -273,12 +255,7 @@ export class InstallService {
|
|
|
273
255
|
}
|
|
274
256
|
|
|
275
257
|
async check() {
|
|
276
|
-
|
|
277
|
-
return { success: true };
|
|
278
|
-
} else {
|
|
279
|
-
throw new BadRequestException('Application is not installed.');
|
|
280
|
-
}
|
|
281
|
-
|
|
258
|
+
return { success: true };
|
|
282
259
|
}
|
|
283
260
|
|
|
284
261
|
async generateMailMigration({
|