@hed-hog/core 0.0.300 → 0.0.302
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/ai/ai.service.d.ts +13 -2
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +104 -2
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +26 -9
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js +11 -5
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +34 -10
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js +196 -69
- package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/integration/services/integration-link.service.d.ts +5 -1
- package/dist/integration/services/integration-link.service.d.ts.map +1 -1
- package/dist/integration/services/integration-link.service.js +141 -53
- package/dist/integration/services/integration-link.service.js.map +1 -1
- package/dist/mail/mail.service.d.ts +9 -2
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +56 -4
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/setting/setting.service.d.ts +6 -1
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +188 -15
- package/dist/setting/setting.service.js.map +1 -1
- package/hedhog/data/setting_group.yaml +28 -0
- package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +305 -75
- package/hedhog/frontend/messages/en.json +15 -3
- package/hedhog/frontend/messages/pt.json +15 -3
- package/package.json +5 -5
- package/src/ai/ai.service.ts +129 -1
- package/src/dashboard/dashboard-core/dashboard-core.controller.ts +9 -2
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +276 -75
- package/src/index.ts +7 -6
- package/src/integration/services/integration-link.service.ts +190 -55
- package/src/mail/mail.service.ts +67 -3
- package/src/setting/setting.service.ts +222 -15
|
@@ -14,13 +14,81 @@ export interface CreateIntegrationLinkDto {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export interface IntegrationLinkPersistenceClient {
|
|
17
|
-
integrationLink
|
|
17
|
+
integrationLink?: PrismaService['integrationLink'];
|
|
18
|
+
integration_link?: PrismaService['integration_link'];
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
type IntegrationLinkRecord = {
|
|
22
|
+
id: number | string;
|
|
23
|
+
source_module?: string;
|
|
24
|
+
source_entity_type?: string;
|
|
25
|
+
source_entity_id?: string;
|
|
26
|
+
target_module?: string;
|
|
27
|
+
target_entity_type?: string;
|
|
28
|
+
target_entity_id?: string;
|
|
29
|
+
link_type?: LinkType | string;
|
|
30
|
+
created_at?: Date;
|
|
31
|
+
updated_at?: Date;
|
|
32
|
+
sourceModule?: string;
|
|
33
|
+
sourceEntityType?: string;
|
|
34
|
+
sourceEntityId?: string;
|
|
35
|
+
targetModule?: string;
|
|
36
|
+
targetEntityType?: string;
|
|
37
|
+
targetEntityId?: string;
|
|
38
|
+
linkType?: LinkType | string;
|
|
39
|
+
metadata?: Record<string, any> | null;
|
|
40
|
+
createdAt?: Date;
|
|
41
|
+
updatedAt?: Date;
|
|
42
|
+
};
|
|
43
|
+
|
|
20
44
|
@Injectable()
|
|
21
45
|
export class IntegrationLinkService {
|
|
22
46
|
constructor(private readonly prisma: PrismaService) {}
|
|
23
47
|
|
|
48
|
+
private resolvePersistence(
|
|
49
|
+
persistenceClient?: IntegrationLinkPersistenceClient,
|
|
50
|
+
) {
|
|
51
|
+
const client = (persistenceClient ?? this.prisma) as PrismaService &
|
|
52
|
+
IntegrationLinkPersistenceClient;
|
|
53
|
+
const delegate = client.integration_link ?? client.integrationLink;
|
|
54
|
+
const useSnakeCase = !!client.integration_link;
|
|
55
|
+
|
|
56
|
+
if (!delegate) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
'Integration link delegate is not available on the Prisma client.',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { delegate, useSnakeCase };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private toDomainLink(record: IntegrationLinkRecord): IntegrationLink {
|
|
66
|
+
return {
|
|
67
|
+
id: String(record.id),
|
|
68
|
+
sourceModule: record.source_module ?? record.sourceModule ?? '',
|
|
69
|
+
sourceEntityType:
|
|
70
|
+
record.source_entity_type ?? record.sourceEntityType ?? '',
|
|
71
|
+
sourceEntityId: record.source_entity_id ?? record.sourceEntityId ?? '',
|
|
72
|
+
targetModule: record.target_module ?? record.targetModule ?? '',
|
|
73
|
+
targetEntityType:
|
|
74
|
+
record.target_entity_type ?? record.targetEntityType ?? '',
|
|
75
|
+
targetEntityId: record.target_entity_id ?? record.targetEntityId ?? '',
|
|
76
|
+
linkType: (record.link_type ?? record.linkType ?? LinkType.REFERENCE) as LinkType,
|
|
77
|
+
metadata: record.metadata ?? null,
|
|
78
|
+
createdAt: record.created_at ?? record.createdAt ?? new Date(),
|
|
79
|
+
updatedAt: record.updated_at ?? record.updatedAt ?? new Date(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private toDatabaseId(linkId: string | number) {
|
|
84
|
+
if (typeof linkId === 'number') {
|
|
85
|
+
return linkId;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const numericId = Number(linkId);
|
|
89
|
+
return Number.isNaN(numericId) ? linkId : numericId;
|
|
90
|
+
}
|
|
91
|
+
|
|
24
92
|
/**
|
|
25
93
|
* Create a link between entities from different modules
|
|
26
94
|
*/
|
|
@@ -28,20 +96,33 @@ export class IntegrationLinkService {
|
|
|
28
96
|
dto: CreateIntegrationLinkDto,
|
|
29
97
|
persistenceClient?: IntegrationLinkPersistenceClient,
|
|
30
98
|
): Promise<IntegrationLink> {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
data:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
99
|
+
const { delegate, useSnakeCase } = this.resolvePersistence(persistenceClient);
|
|
100
|
+
|
|
101
|
+
const record = await delegate.create({
|
|
102
|
+
data: useSnakeCase
|
|
103
|
+
? {
|
|
104
|
+
source_module: dto.sourceModule,
|
|
105
|
+
source_entity_type: dto.sourceEntityType,
|
|
106
|
+
source_entity_id: dto.sourceEntityId,
|
|
107
|
+
target_module: dto.targetModule,
|
|
108
|
+
target_entity_type: dto.targetEntityType,
|
|
109
|
+
target_entity_id: dto.targetEntityId,
|
|
110
|
+
link_type: dto.linkType,
|
|
111
|
+
metadata: dto.metadata || null,
|
|
112
|
+
}
|
|
113
|
+
: {
|
|
114
|
+
sourceModule: dto.sourceModule,
|
|
115
|
+
sourceEntityType: dto.sourceEntityType,
|
|
116
|
+
sourceEntityId: dto.sourceEntityId,
|
|
117
|
+
targetModule: dto.targetModule,
|
|
118
|
+
targetEntityType: dto.targetEntityType,
|
|
119
|
+
targetEntityId: dto.targetEntityId,
|
|
120
|
+
linkType: dto.linkType,
|
|
121
|
+
metadata: dto.metadata || null,
|
|
122
|
+
},
|
|
44
123
|
});
|
|
124
|
+
|
|
125
|
+
return this.toDomainLink(record as IntegrationLinkRecord);
|
|
45
126
|
}
|
|
46
127
|
|
|
47
128
|
/**
|
|
@@ -52,13 +133,24 @@ export class IntegrationLinkService {
|
|
|
52
133
|
sourceEntityType: string,
|
|
53
134
|
sourceEntityId: string,
|
|
54
135
|
): Promise<IntegrationLink[]> {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
136
|
+
const { delegate, useSnakeCase } = this.resolvePersistence();
|
|
137
|
+
const records = await delegate.findMany({
|
|
138
|
+
where: useSnakeCase
|
|
139
|
+
? {
|
|
140
|
+
source_module: sourceModule,
|
|
141
|
+
source_entity_type: sourceEntityType,
|
|
142
|
+
source_entity_id: sourceEntityId,
|
|
143
|
+
}
|
|
144
|
+
: {
|
|
145
|
+
sourceModule,
|
|
146
|
+
sourceEntityType,
|
|
147
|
+
sourceEntityId,
|
|
148
|
+
},
|
|
61
149
|
});
|
|
150
|
+
|
|
151
|
+
return records.map((record: IntegrationLinkRecord) =>
|
|
152
|
+
this.toDomainLink(record),
|
|
153
|
+
);
|
|
62
154
|
}
|
|
63
155
|
|
|
64
156
|
/**
|
|
@@ -69,40 +161,62 @@ export class IntegrationLinkService {
|
|
|
69
161
|
targetEntityType: string,
|
|
70
162
|
targetEntityId: string,
|
|
71
163
|
): Promise<IntegrationLink[]> {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
164
|
+
const { delegate, useSnakeCase } = this.resolvePersistence();
|
|
165
|
+
const records = await delegate.findMany({
|
|
166
|
+
where: useSnakeCase
|
|
167
|
+
? {
|
|
168
|
+
target_module: targetModule,
|
|
169
|
+
target_entity_type: targetEntityType,
|
|
170
|
+
target_entity_id: targetEntityId,
|
|
171
|
+
}
|
|
172
|
+
: {
|
|
173
|
+
targetModule,
|
|
174
|
+
targetEntityType,
|
|
175
|
+
targetEntityId,
|
|
176
|
+
},
|
|
78
177
|
});
|
|
178
|
+
|
|
179
|
+
return records.map((record: IntegrationLinkRecord) =>
|
|
180
|
+
this.toDomainLink(record),
|
|
181
|
+
);
|
|
79
182
|
}
|
|
80
183
|
|
|
81
184
|
/**
|
|
82
185
|
* Find links of a specific type
|
|
83
186
|
*/
|
|
84
187
|
async findByLinkType(linkType: LinkType): Promise<IntegrationLink[]> {
|
|
85
|
-
|
|
86
|
-
|
|
188
|
+
const { delegate, useSnakeCase } = this.resolvePersistence();
|
|
189
|
+
const records = await delegate.findMany({
|
|
190
|
+
where: useSnakeCase ? { link_type: linkType } : { linkType },
|
|
87
191
|
});
|
|
192
|
+
|
|
193
|
+
return records.map((record: IntegrationLinkRecord) =>
|
|
194
|
+
this.toDomainLink(record),
|
|
195
|
+
);
|
|
88
196
|
}
|
|
89
197
|
|
|
90
198
|
/**
|
|
91
199
|
* Delete a link
|
|
92
200
|
*/
|
|
93
201
|
async deleteLink(linkId: string): Promise<IntegrationLink> {
|
|
94
|
-
|
|
95
|
-
|
|
202
|
+
const { delegate } = this.resolvePersistence();
|
|
203
|
+
const record = await delegate.delete({
|
|
204
|
+
where: { id: this.toDatabaseId(linkId) },
|
|
96
205
|
});
|
|
206
|
+
|
|
207
|
+
return this.toDomainLink(record as IntegrationLinkRecord);
|
|
97
208
|
}
|
|
98
209
|
|
|
99
210
|
/**
|
|
100
211
|
* Get link by ID
|
|
101
212
|
*/
|
|
102
213
|
async getById(linkId: string): Promise<IntegrationLink | null> {
|
|
103
|
-
|
|
104
|
-
|
|
214
|
+
const { delegate } = this.resolvePersistence();
|
|
215
|
+
const record = await delegate.findUnique({
|
|
216
|
+
where: { id: this.toDatabaseId(linkId) },
|
|
105
217
|
});
|
|
218
|
+
|
|
219
|
+
return record ? this.toDomainLink(record as IntegrationLinkRecord) : null;
|
|
106
220
|
}
|
|
107
221
|
|
|
108
222
|
/**
|
|
@@ -116,39 +230,60 @@ export class IntegrationLinkService {
|
|
|
116
230
|
entity2Type: string,
|
|
117
231
|
entity2Id: string,
|
|
118
232
|
): Promise<IntegrationLink | null> {
|
|
119
|
-
|
|
120
|
-
|
|
233
|
+
const { delegate, useSnakeCase } = this.resolvePersistence();
|
|
234
|
+
|
|
235
|
+
const record = await delegate.findFirst({
|
|
121
236
|
where: {
|
|
122
|
-
OR:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
237
|
+
OR: useSnakeCase
|
|
238
|
+
? [
|
|
239
|
+
{
|
|
240
|
+
source_module: module1,
|
|
241
|
+
source_entity_type: entity1Type,
|
|
242
|
+
source_entity_id: entity1Id,
|
|
243
|
+
target_module: module2,
|
|
244
|
+
target_entity_type: entity2Type,
|
|
245
|
+
target_entity_id: entity2Id,
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
source_module: module2,
|
|
249
|
+
source_entity_type: entity2Type,
|
|
250
|
+
source_entity_id: entity2Id,
|
|
251
|
+
target_module: module1,
|
|
252
|
+
target_entity_type: entity1Type,
|
|
253
|
+
target_entity_id: entity1Id,
|
|
254
|
+
},
|
|
255
|
+
]
|
|
256
|
+
: [
|
|
257
|
+
{
|
|
258
|
+
sourceModule: module1,
|
|
259
|
+
sourceEntityType: entity1Type,
|
|
260
|
+
sourceEntityId: entity1Id,
|
|
261
|
+
targetModule: module2,
|
|
262
|
+
targetEntityType: entity2Type,
|
|
263
|
+
targetEntityId: entity2Id,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
sourceModule: module2,
|
|
267
|
+
sourceEntityType: entity2Type,
|
|
268
|
+
sourceEntityId: entity2Id,
|
|
269
|
+
targetModule: module1,
|
|
270
|
+
targetEntityType: entity1Type,
|
|
271
|
+
targetEntityId: entity1Id,
|
|
272
|
+
},
|
|
273
|
+
],
|
|
140
274
|
},
|
|
141
275
|
});
|
|
142
276
|
|
|
143
|
-
return
|
|
277
|
+
return record ? this.toDomainLink(record as IntegrationLinkRecord) : null;
|
|
144
278
|
}
|
|
145
279
|
|
|
146
280
|
/**
|
|
147
281
|
* Count links from a module
|
|
148
282
|
*/
|
|
149
283
|
async countFromModule(sourceModule: string): Promise<number> {
|
|
150
|
-
|
|
151
|
-
|
|
284
|
+
const { delegate, useSnakeCase } = this.resolvePersistence();
|
|
285
|
+
return delegate.count({
|
|
286
|
+
where: useSnakeCase ? { source_module: sourceModule } : { sourceModule },
|
|
152
287
|
});
|
|
153
288
|
}
|
|
154
289
|
}
|
package/src/mail/mail.service.ts
CHANGED
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
Injectable,
|
|
11
11
|
Logger,
|
|
12
12
|
NotFoundException,
|
|
13
|
+
OnModuleInit,
|
|
13
14
|
} from '@nestjs/common';
|
|
14
15
|
import * as Handlebars from 'handlebars';
|
|
16
|
+
import { IntegrationDeveloperApiService } from '../integration/services/integration-developer-api.service';
|
|
15
17
|
import { SettingService } from '../setting/setting.service';
|
|
16
18
|
import { CreateDTO } from './dto/create.dto';
|
|
17
19
|
import { ImportDTO } from './dto/import.dto';
|
|
@@ -20,7 +22,7 @@ import { TestMailDTO } from './dto/test-mail.dto';
|
|
|
20
22
|
import { UpdateDTO } from './dto/update.dto';
|
|
21
23
|
|
|
22
24
|
@Injectable()
|
|
23
|
-
export class MailService {
|
|
25
|
+
export class MailService implements OnModuleInit {
|
|
24
26
|
private readonly logger = new Logger(MailService.name);
|
|
25
27
|
private readonly modelName = 'mail';
|
|
26
28
|
|
|
@@ -33,8 +35,70 @@ export class MailService {
|
|
|
33
35
|
private readonly localeService: LocaleService,
|
|
34
36
|
@Inject(forwardRef(() => SettingService))
|
|
35
37
|
private readonly setting: SettingService,
|
|
38
|
+
@Inject(forwardRef(() => IntegrationDeveloperApiService))
|
|
39
|
+
private readonly integrationApi: IntegrationDeveloperApiService,
|
|
36
40
|
) { }
|
|
37
41
|
|
|
42
|
+
onModuleInit(): void {
|
|
43
|
+
this.integrationApi.subscribe({
|
|
44
|
+
eventName: 'core.setting.changed',
|
|
45
|
+
consumerName: 'core.mail-config-validator',
|
|
46
|
+
priority: 0,
|
|
47
|
+
handler: async (event) => {
|
|
48
|
+
const slug = String(event.payload?.slug || '').trim();
|
|
49
|
+
|
|
50
|
+
if (!this.isMailConfigSlug(slug)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await this.syncConfiguredMailFlag();
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
void this.syncConfiguredMailFlag();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private isMailConfigSlug(slug: string) {
|
|
62
|
+
return slug.startsWith('mail-');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private isSettingEnabled(value: unknown) {
|
|
66
|
+
return value === true || value === 'true';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private async syncConfiguredMailFlag() {
|
|
70
|
+
try {
|
|
71
|
+
const settings = await this.setting.getSettingValues([
|
|
72
|
+
'mail-provider',
|
|
73
|
+
'mail-from',
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
const provider = String(settings['mail-provider'] || '').trim();
|
|
77
|
+
const from = String(settings['mail-from'] || '').trim();
|
|
78
|
+
|
|
79
|
+
if (!provider || !from) {
|
|
80
|
+
await this.setting.setValue('configured-mail', 'false');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await this.reloadConfig();
|
|
85
|
+
|
|
86
|
+
await this.mailMainService.send({
|
|
87
|
+
to: from,
|
|
88
|
+
from,
|
|
89
|
+
subject: '[HedHog] Mail configuration validation',
|
|
90
|
+
body: `This is an automatic validation email for the configured provider "${provider}".`,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await this.setting.setValue('configured-mail', 'true');
|
|
94
|
+
} catch (error) {
|
|
95
|
+
this.logger.warn(
|
|
96
|
+
`Mail configuration validation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
97
|
+
);
|
|
98
|
+
await this.setting.setValue('configured-mail', 'false');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
38
102
|
/**
|
|
39
103
|
* Reload mail configuration from database and update MailMainService
|
|
40
104
|
*/
|
|
@@ -333,7 +397,7 @@ export class MailService {
|
|
|
333
397
|
'mail-from'
|
|
334
398
|
]);
|
|
335
399
|
|
|
336
|
-
if (!settings['configured-mail']) {
|
|
400
|
+
if (!this.isSettingEnabled(settings['configured-mail'])) {
|
|
337
401
|
this.logger.warn('Mail service is not configured. Aborting sending email.');
|
|
338
402
|
return;
|
|
339
403
|
}
|
|
@@ -422,7 +486,7 @@ export class MailService {
|
|
|
422
486
|
'mail-from'
|
|
423
487
|
]);
|
|
424
488
|
|
|
425
|
-
if (!settings['configured-mail']) {
|
|
489
|
+
if (!this.isSettingEnabled(settings['configured-mail'])) {
|
|
426
490
|
throw new BadRequestException('Mail service is not configured.');
|
|
427
491
|
}
|
|
428
492
|
|