@hed-hog/core 0.0.356 → 0.0.359
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/guards/auth.guard.d.ts +6 -1
- package/dist/auth/guards/auth.guard.d.ts.map +1 -1
- package/dist/auth/guards/auth.guard.js +33 -2
- package/dist/auth/guards/auth.guard.js.map +1 -1
- package/dist/file/file.controller.d.ts +4 -2
- package/dist/file/file.controller.d.ts.map +1 -1
- package/dist/file/file.controller.js +44 -38
- package/dist/file/file.controller.js.map +1 -1
- package/dist/file/file.service.d.ts +2 -2
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +25 -42
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/provider/azure.provider.d.ts.map +1 -1
- package/dist/file/provider/azure.provider.js +10 -15
- package/dist/file/provider/azure.provider.js.map +1 -1
- package/dist/file/provider/gcs.provider.d.ts.map +1 -1
- package/dist/file/provider/gcs.provider.js +8 -9
- package/dist/file/provider/gcs.provider.js.map +1 -1
- package/dist/file/provider/local.provider.d.ts.map +1 -1
- package/dist/file/provider/local.provider.js +11 -10
- package/dist/file/provider/local.provider.js.map +1 -1
- package/dist/file/provider/provider.factory.d.ts +1 -1
- package/dist/file/provider/provider.factory.d.ts.map +1 -1
- package/dist/file/provider/provider.factory.js +16 -6
- package/dist/file/provider/provider.factory.js.map +1 -1
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +4 -20
- package/dist/file/provider/s3.provider.js.map +1 -1
- package/dist/file/provider/spaces.provider.d.ts.map +1 -1
- package/dist/file/provider/spaces.provider.js +4 -19
- package/dist/file/provider/spaces.provider.js.map +1 -1
- package/dist/integration-profile/integration-profile.controller.d.ts +1 -1
- package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.controller.js +4 -3
- package/dist/integration-profile/integration-profile.controller.js.map +1 -1
- package/dist/integration-profile/integration-profile.service.d.ts +1 -1
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.service.js +7 -4
- package/dist/integration-profile/integration-profile.service.js.map +1 -1
- package/dist/integration-profile/integration-profile.utils.d.ts +6 -0
- package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.utils.js +9 -0
- package/dist/integration-profile/integration-profile.utils.js.map +1 -1
- package/dist/mail/mail.module.d.ts.map +1 -1
- package/dist/mail/mail.module.js +9 -4
- package/dist/mail/mail.module.js.map +1 -1
- package/dist/mail/mail.service.d.ts +2 -1
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +11 -7
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mcp/guards/mcp-auth.guard.js +1 -1
- package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
- package/dist/mcp/mcp-api-key.controller.d.ts +2 -0
- package/dist/mcp/mcp-api-key.controller.d.ts.map +1 -1
- package/dist/mcp/mcp-api-key.controller.js +1 -1
- package/dist/mcp/mcp-api-key.controller.js.map +1 -1
- package/dist/mcp/mcp-api-key.service.d.ts +2 -1
- package/dist/mcp/mcp-api-key.service.d.ts.map +1 -1
- package/dist/mcp/mcp-api-key.service.js +6 -2
- package/dist/mcp/mcp-api-key.service.js.map +1 -1
- package/dist/mcp/mcp.module.d.ts.map +1 -1
- package/dist/mcp/mcp.module.js +1 -3
- package/dist/mcp/mcp.module.js.map +1 -1
- package/dist/oauth/interfaces/OAuthProvider.d.ts +1 -1
- package/dist/oauth/interfaces/OAuthProvider.d.ts.map +1 -1
- package/dist/oauth/oauth.controller.d.ts +4 -1
- package/dist/oauth/oauth.controller.d.ts.map +1 -1
- package/dist/oauth/oauth.controller.js +31 -5
- package/dist/oauth/oauth.controller.js.map +1 -1
- package/dist/oauth/oauth.service.d.ts +12 -1
- package/dist/oauth/oauth.service.d.ts.map +1 -1
- package/dist/oauth/oauth.service.js +125 -23
- package/dist/oauth/oauth.service.js.map +1 -1
- package/dist/oauth/providers/abstract.provider.d.ts +1 -1
- package/dist/oauth/providers/abstract.provider.d.ts.map +1 -1
- package/dist/oauth/providers/facebook.provider.d.ts +5 -2
- package/dist/oauth/providers/facebook.provider.d.ts.map +1 -1
- package/dist/oauth/providers/facebook.provider.js +34 -19
- package/dist/oauth/providers/facebook.provider.js.map +1 -1
- package/dist/oauth/providers/github.provider.d.ts +6 -2
- package/dist/oauth/providers/github.provider.d.ts.map +1 -1
- package/dist/oauth/providers/github.provider.js +43 -20
- package/dist/oauth/providers/github.provider.js.map +1 -1
- package/dist/oauth/providers/google.provider.d.ts +5 -2
- package/dist/oauth/providers/google.provider.d.ts.map +1 -1
- package/dist/oauth/providers/google.provider.js +34 -19
- package/dist/oauth/providers/google.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +5 -2
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.js +43 -40
- package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft.provider.d.ts +5 -2
- package/dist/oauth/providers/microsoft.provider.d.ts.map +1 -1
- package/dist/oauth/providers/microsoft.provider.js +34 -19
- package/dist/oauth/providers/microsoft.provider.js.map +1 -1
- package/dist/profile/profile.controller.js +1 -1
- package/dist/profile/profile.controller.js.map +1 -1
- package/dist/security/security.service.d.ts +2 -0
- package/dist/security/security.service.d.ts.map +1 -1
- package/dist/security/security.service.js +21 -0
- package/dist/security/security.service.js.map +1 -1
- package/dist/setting/setting.service.d.ts +26 -1
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +240 -5
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +2 -1
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +5 -4
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +4 -2
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +19 -4
- package/dist/user/user.service.js.map +1 -1
- package/dist/webhook-integration/dto/webhook-action.dto.d.ts +17 -2
- package/dist/webhook-integration/dto/webhook-action.dto.d.ts.map +1 -1
- package/dist/webhook-integration/dto/webhook-action.dto.js +93 -2
- package/dist/webhook-integration/dto/webhook-action.dto.js.map +1 -1
- package/dist/webhook-integration/webhook-command.registry.d.ts +15 -0
- package/dist/webhook-integration/webhook-command.registry.d.ts.map +1 -0
- package/dist/webhook-integration/webhook-command.registry.js +36 -0
- package/dist/webhook-integration/webhook-command.registry.js.map +1 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts +19 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.controller.js +38 -0
- package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.module.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.module.js +5 -0
- package/dist/webhook-integration/webhook-integration.module.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.d.ts +25 -1
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +332 -23
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/hedhog/data/integration_provider.yaml +16 -0
- package/hedhog/data/menu.yaml +0 -16
- package/hedhog/data/route.yaml +32 -383
- package/hedhog/data/setting_group.yaml +179 -174
- package/hedhog/data/setting_subgroup.yaml +16 -0
- package/hedhog/frontend/app/account/components/tokens.tsx.ejs +421 -0
- package/hedhog/frontend/app/account/layout.tsx.ejs +6 -3
- package/hedhog/frontend/app/account/mcp-tokens/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/account/tokens/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +414 -76
- package/hedhog/frontend/app/configurations/layout.tsx.ejs +1 -1
- package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +3 -1
- package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +186 -18
- package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +597 -21
- package/hedhog/frontend/app/mail/profiles/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/setting-list/page.tsx.ejs +229 -395
- package/hedhog/frontend/messages/en.json +67 -3
- package/hedhog/frontend/messages/pt.json +67 -3
- package/hedhog/table/file.yaml +1 -1
- package/hedhog/table/mcp_api_key.yaml +4 -0
- package/hedhog/table/webhook_action.yaml +57 -1
- package/package.json +5 -4
- package/src/auth/guards/auth.guard.ts +32 -0
- package/src/file/file.controller.ts +51 -42
- package/src/file/file.service.ts +31 -52
- package/src/file/provider/azure.provider.ts +14 -28
- package/src/file/provider/gcs.provider.ts +9 -9
- package/src/file/provider/local.provider.ts +10 -13
- package/src/file/provider/provider.factory.ts +19 -7
- package/src/file/provider/s3.provider.ts +4 -20
- package/src/file/provider/spaces.provider.ts +4 -19
- package/src/integration-profile/integration-profile.controller.ts +2 -0
- package/src/integration-profile/integration-profile.service.ts +7 -2
- package/src/integration-profile/integration-profile.utils.ts +17 -0
- package/src/mail/mail.module.ts +14 -4
- package/src/mail/mail.service.ts +12 -7
- package/src/mcp/guards/mcp-auth.guard.ts +1 -1
- package/src/mcp/mcp-api-key.controller.ts +2 -2
- package/src/mcp/mcp-api-key.service.ts +6 -2
- package/src/mcp/mcp.module.ts +1 -3
- package/src/oauth/interfaces/OAuthProvider.ts +1 -1
- package/src/oauth/oauth.controller.ts +30 -2
- package/src/oauth/oauth.service.ts +194 -35
- package/src/oauth/providers/abstract.provider.ts +1 -1
- package/src/oauth/providers/facebook.provider.ts +36 -20
- package/src/oauth/providers/github.provider.ts +46 -23
- package/src/oauth/providers/google.provider.ts +36 -20
- package/src/oauth/providers/microsoft-entra-id.provider.ts +42 -46
- package/src/oauth/providers/microsoft.provider.ts +36 -21
- package/src/profile/profile.controller.ts +2 -2
- package/src/security/security.service.ts +34 -1
- package/src/setting/setting.service.ts +307 -7
- package/src/user/user.controller.ts +4 -1
- package/src/user/user.service.ts +20 -2
- package/src/webhook-integration/dto/webhook-action.dto.ts +85 -4
- package/src/webhook-integration/webhook-command.registry.ts +42 -0
- package/src/webhook-integration/webhook-integration.controller.ts +20 -0
- package/src/webhook-integration/webhook-integration.module.ts +50 -45
- package/src/webhook-integration/webhook-integration.service.ts +374 -11
- package/dist/mail-profile/dto/create.dto.d.ts +0 -23
- package/dist/mail-profile/dto/create.dto.d.ts.map +0 -1
- package/dist/mail-profile/dto/create.dto.js +0 -139
- package/dist/mail-profile/dto/create.dto.js.map +0 -1
- package/dist/mail-profile/dto/update.dto.d.ts +0 -6
- package/dist/mail-profile/dto/update.dto.d.ts.map +0 -1
- package/dist/mail-profile/dto/update.dto.js +0 -9
- package/dist/mail-profile/dto/update.dto.js.map +0 -1
- package/dist/mail-profile/mail-profile.controller.d.ts +0 -94
- package/dist/mail-profile/mail-profile.controller.d.ts.map +0 -1
- package/dist/mail-profile/mail-profile.controller.js +0 -95
- package/dist/mail-profile/mail-profile.controller.js.map +0 -1
- package/dist/mail-profile/mail-profile.module.d.ts +0 -3
- package/dist/mail-profile/mail-profile.module.d.ts.map +0 -1
- package/dist/mail-profile/mail-profile.module.js +0 -31
- package/dist/mail-profile/mail-profile.module.js.map +0 -1
- package/dist/mail-profile/mail-profile.service.d.ts +0 -109
- package/dist/mail-profile/mail-profile.service.d.ts.map +0 -1
- package/dist/mail-profile/mail-profile.service.js +0 -278
- package/dist/mail-profile/mail-profile.service.js.map +0 -1
- package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +0 -20
- package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +0 -1
- package/dist/mcp/tools/core-mail-profile.mcp-tools.js +0 -96
- package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +0 -1
- package/dist/whatsapp-profile/dto/create.dto.d.ts +0 -11
- package/dist/whatsapp-profile/dto/create.dto.d.ts.map +0 -1
- package/dist/whatsapp-profile/dto/create.dto.js +0 -55
- package/dist/whatsapp-profile/dto/create.dto.js.map +0 -1
- package/dist/whatsapp-profile/dto/update.dto.d.ts +0 -6
- package/dist/whatsapp-profile/dto/update.dto.d.ts.map +0 -1
- package/dist/whatsapp-profile/dto/update.dto.js +0 -9
- package/dist/whatsapp-profile/dto/update.dto.js.map +0 -1
- package/dist/whatsapp-profile/whatsapp-profile.controller.d.ts +0 -54
- package/dist/whatsapp-profile/whatsapp-profile.controller.d.ts.map +0 -1
- package/dist/whatsapp-profile/whatsapp-profile.controller.js +0 -85
- package/dist/whatsapp-profile/whatsapp-profile.controller.js.map +0 -1
- package/dist/whatsapp-profile/whatsapp-profile.module.d.ts +0 -3
- package/dist/whatsapp-profile/whatsapp-profile.module.d.ts.map +0 -1
- package/dist/whatsapp-profile/whatsapp-profile.module.js +0 -26
- package/dist/whatsapp-profile/whatsapp-profile.module.js.map +0 -1
- package/dist/whatsapp-profile/whatsapp-profile.service.d.ts +0 -63
- package/dist/whatsapp-profile/whatsapp-profile.service.d.ts.map +0 -1
- package/dist/whatsapp-profile/whatsapp-profile.service.js +0 -138
- package/dist/whatsapp-profile/whatsapp-profile.service.js.map +0 -1
- package/hedhog/data/mail_profile.yaml +0 -18
- package/hedhog/data/whatsapp_profile.yaml +0 -6
- package/hedhog/table/mail_profile.yaml +0 -75
- package/hedhog/table/whatsapp_profile.yaml +0 -26
- package/src/mail-profile/dto/create.dto.ts +0 -145
- package/src/mail-profile/dto/update.dto.ts +0 -4
- package/src/mail-profile/mail-profile.controller.ts +0 -56
- package/src/mail-profile/mail-profile.module.ts +0 -18
- package/src/mail-profile/mail-profile.service.ts +0 -308
- package/src/mcp/tools/core-mail-profile.mcp-tools.ts +0 -69
- package/src/whatsapp-profile/dto/create.dto.ts +0 -36
- package/src/whatsapp-profile/dto/update.dto.ts +0 -4
- package/src/whatsapp-profile/whatsapp-profile.controller.ts +0 -51
- package/src/whatsapp-profile/whatsapp-profile.module.ts +0 -13
- package/src/whatsapp-profile/whatsapp-profile.service.ts +0 -160
package/src/file/file.service.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
forwardRef,
|
|
7
7
|
Inject,
|
|
8
8
|
Injectable,
|
|
9
|
+
Logger,
|
|
9
10
|
NotFoundException,
|
|
10
11
|
OnModuleInit
|
|
11
12
|
} from '@nestjs/common';
|
|
@@ -14,21 +15,30 @@ import { createWriteStream, promises as fs } from 'fs';
|
|
|
14
15
|
import { basename, dirname } from 'path';
|
|
15
16
|
import { pipeline } from 'stream/promises';
|
|
16
17
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
buildStorageConfigFromIntegration,
|
|
19
|
+
resolveFileProviderSlug,
|
|
20
|
+
resolveStorageEnumProvider,
|
|
20
21
|
} from '../integration-profile/integration-profile.utils';
|
|
21
22
|
import { SettingService } from '../setting/setting.service';
|
|
22
23
|
import { DeleteDTO } from './dto/delete.dto';
|
|
23
24
|
import { AbstractProvider } from './provider/abstract.provider';
|
|
24
|
-
import { EnumProvider } from './provider/provider.enum';
|
|
25
25
|
import { ProviderFactory } from './provider/provider.factory';
|
|
26
26
|
|
|
27
27
|
@Injectable()
|
|
28
28
|
export class FileService implements OnModuleInit {
|
|
29
|
-
private
|
|
29
|
+
private readonly logger = new Logger(FileService.name);
|
|
30
|
+
private providerId: number | undefined;
|
|
30
31
|
private mimetypes: Record<string, number> = {};
|
|
31
32
|
|
|
33
|
+
private resolveProviderPath(file: { location: string; path: string }): string {
|
|
34
|
+
// Supports legacy basename-only values while prioritizing full provider keys.
|
|
35
|
+
if (typeof file.path === 'string' && file.path.includes('/')) {
|
|
36
|
+
return file.path;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return `${file.location}/${file.path}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
32
42
|
private hasMojibake(value: string): boolean {
|
|
33
43
|
return /Ã.|Â.|â[\u0080-\u00BF]/.test(value);
|
|
34
44
|
}
|
|
@@ -60,17 +70,21 @@ export class FileService implements OnModuleInit {
|
|
|
60
70
|
async onModuleInit() {
|
|
61
71
|
try {
|
|
62
72
|
await this.getProvider();
|
|
63
|
-
} catch {
|
|
73
|
+
} catch (error) {
|
|
64
74
|
// No storage profile configured yet — server starts normally, uploads will
|
|
65
75
|
// fail until a profile is selected in Settings → File Storage.
|
|
76
|
+
this.logger.warn(
|
|
77
|
+
`Storage provider not ready on startup: ${error instanceof Error ? error.message : String(error)}`,
|
|
78
|
+
);
|
|
66
79
|
}
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
async getProvider(): Promise<AbstractProvider> {
|
|
70
83
|
const settings = await this.settingService.getSettingValues(['file-storage-profile-id']);
|
|
71
|
-
const
|
|
84
|
+
const rawProfileId = settings['file-storage-profile-id'];
|
|
85
|
+
const profileId = Number(rawProfileId);
|
|
72
86
|
|
|
73
|
-
if (!profileId) {
|
|
87
|
+
if (!Number.isFinite(profileId) || profileId <= 0) {
|
|
74
88
|
throw new BadRequestException('No file storage profile configured. Set a storage profile in the File Storage settings.');
|
|
75
89
|
}
|
|
76
90
|
|
|
@@ -107,7 +121,7 @@ export class FileService implements OnModuleInit {
|
|
|
107
121
|
}
|
|
108
122
|
|
|
109
123
|
this.providerId = providerData.id;
|
|
110
|
-
return ProviderFactory.create(enumProvider, config, this.jwtService);
|
|
124
|
+
return await ProviderFactory.create(enumProvider, config, this.jwtService);
|
|
111
125
|
}
|
|
112
126
|
|
|
113
127
|
async getMimeType(mimetype: string) {
|
|
@@ -202,38 +216,6 @@ export class FileService implements OnModuleInit {
|
|
|
202
216
|
return file;
|
|
203
217
|
}
|
|
204
218
|
|
|
205
|
-
async getStorage(): Promise<string> {
|
|
206
|
-
const settings = await this.settingService.getSettingValues(['file-storage-profile-id']);
|
|
207
|
-
const profileId = Number(settings['file-storage-profile-id']);
|
|
208
|
-
|
|
209
|
-
if (!profileId) {
|
|
210
|
-
return EnumProvider.LOCAL;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const profile = await this.prismaService.integration_profile.findUnique({
|
|
214
|
-
where: { id: profileId },
|
|
215
|
-
include: { integration_provider: { select: { slug: true } } },
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
if (!profile) {
|
|
219
|
-
return EnumProvider.LOCAL;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return resolveStorageEnumProvider(profile.integration_provider.slug);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
private getProviderPath(storage: string, filepath: string) {
|
|
226
|
-
if (storage === EnumProvider.LOCAL) {
|
|
227
|
-
return filepath;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
return new URL(filepath).pathname;
|
|
232
|
-
} catch {
|
|
233
|
-
return filepath;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
219
|
private mapStorageNotFoundError(error: any, fileId: number) {
|
|
238
220
|
const missingInStorage =
|
|
239
221
|
error?.name === 'NoSuchKey' ||
|
|
@@ -265,9 +247,8 @@ export class FileService implements OnModuleInit {
|
|
|
265
247
|
throw new NotFoundException(`File not found: ${fileId}`);
|
|
266
248
|
}
|
|
267
249
|
|
|
268
|
-
const storage = await this.getStorage();
|
|
269
250
|
const provider = await this.getProvider();
|
|
270
|
-
const providerPath = this.
|
|
251
|
+
const providerPath = this.resolveProviderPath(file);
|
|
271
252
|
|
|
272
253
|
let metadata: any;
|
|
273
254
|
try {
|
|
@@ -348,11 +329,8 @@ export class FileService implements OnModuleInit {
|
|
|
348
329
|
throw new NotFoundException(`File not found: ${fileId}`);
|
|
349
330
|
}
|
|
350
331
|
|
|
351
|
-
const storage = await this.getStorage();
|
|
352
|
-
|
|
353
332
|
const provider = await this.getProvider();
|
|
354
|
-
|
|
355
|
-
const providerPath = this.getProviderPath(storage, file.path);
|
|
333
|
+
const providerPath = this.resolveProviderPath(file);
|
|
356
334
|
|
|
357
335
|
let providerBuffer: Buffer;
|
|
358
336
|
try {
|
|
@@ -526,6 +504,7 @@ export class FileService implements OnModuleInit {
|
|
|
526
504
|
},
|
|
527
505
|
select: {
|
|
528
506
|
id: true,
|
|
507
|
+
location: true,
|
|
529
508
|
path: true,
|
|
530
509
|
},
|
|
531
510
|
});
|
|
@@ -535,7 +514,7 @@ export class FileService implements OnModuleInit {
|
|
|
535
514
|
}
|
|
536
515
|
|
|
537
516
|
for (const file of files) {
|
|
538
|
-
await (await this.getProvider()).delete(file
|
|
517
|
+
await (await this.getProvider()).delete(this.resolveProviderPath(file));
|
|
539
518
|
|
|
540
519
|
await this.prismaService.file.deleteMany({
|
|
541
520
|
where: {
|
|
@@ -629,8 +608,9 @@ export class FileService implements OnModuleInit {
|
|
|
629
608
|
}
|
|
630
609
|
|
|
631
610
|
const provider = await this.getProvider();
|
|
632
|
-
const
|
|
633
|
-
const
|
|
611
|
+
const providerPath = this.resolveProviderPath(file);
|
|
612
|
+
const metadata = await provider.metaData(providerPath);
|
|
613
|
+
const stream = await provider.readStream(providerPath);
|
|
634
614
|
const size = this.getFileSizeFromMetadata(metadata);
|
|
635
615
|
|
|
636
616
|
return {
|
|
@@ -658,9 +638,8 @@ export class FileService implements OnModuleInit {
|
|
|
658
638
|
);
|
|
659
639
|
}
|
|
660
640
|
|
|
661
|
-
const storage = await this.getStorage();
|
|
662
641
|
const provider = await this.getProvider();
|
|
663
|
-
const providerPath = this.
|
|
642
|
+
const providerPath = this.resolveProviderPath(file);
|
|
664
643
|
const metadata = await provider.metaData(providerPath);
|
|
665
644
|
const stream = await provider.readStream(providerPath);
|
|
666
645
|
const size = this.getFileSizeFromMetadata(metadata);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
BlobServiceClient,
|
|
3
|
+
ContainerSASPermissions,
|
|
4
|
+
generateBlobSASQueryParameters,
|
|
5
|
+
StorageSharedKeyCredential,
|
|
6
6
|
} from '@azure/storage-blob';
|
|
7
7
|
import { BadRequestException } from '@nestjs/common';
|
|
8
8
|
import { basename } from 'path';
|
|
@@ -11,7 +11,6 @@ import { AbstractProvider } from './abstract.provider';
|
|
|
11
11
|
export class AzureProvider extends AbstractProvider {
|
|
12
12
|
constructor(private setting: Record<string, string>) {
|
|
13
13
|
super();
|
|
14
|
-
this.initValidation();
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
async initValidation() {
|
|
@@ -68,13 +67,13 @@ export class AzureProvider extends AbstractProvider {
|
|
|
68
67
|
const containerClient = blobServiceClient.getContainerClient(
|
|
69
68
|
this.setting['storage-abs-container'],
|
|
70
69
|
);
|
|
71
|
-
const
|
|
72
|
-
const blobClient = containerClient.getBlockBlobClient(
|
|
70
|
+
const storedFilename = this.getFilename(filename);
|
|
71
|
+
const blobClient = containerClient.getBlockBlobClient([destination, storedFilename].join('/'));
|
|
73
72
|
|
|
74
73
|
await blobClient.upload(fileContent, fileContent.length, {
|
|
75
74
|
blobHTTPHeaders: { blobContentType: mimetype },
|
|
76
75
|
});
|
|
77
|
-
return
|
|
76
|
+
return storedFilename;
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
async upload(destination: string, file: MulterFile): Promise<any> {
|
|
@@ -82,15 +81,13 @@ export class AzureProvider extends AbstractProvider {
|
|
|
82
81
|
const containerClient = blobServiceClient.getContainerClient(
|
|
83
82
|
this.setting['storage-abs-container'],
|
|
84
83
|
);
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
);
|
|
88
|
-
const blobClient = containerClient.getBlockBlobClient(filepath);
|
|
84
|
+
const storedFilename = this.getFilename(file.originalname);
|
|
85
|
+
const blobClient = containerClient.getBlockBlobClient([destination, storedFilename].join('/'));
|
|
89
86
|
|
|
90
87
|
await blobClient.uploadData(file.buffer, {
|
|
91
88
|
blobHTTPHeaders: { blobContentType: file.mimetype },
|
|
92
89
|
});
|
|
93
|
-
return
|
|
90
|
+
return storedFilename;
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
async uploadFromPath(
|
|
@@ -102,10 +99,8 @@ export class AzureProvider extends AbstractProvider {
|
|
|
102
99
|
const containerClient = blobServiceClient.getContainerClient(
|
|
103
100
|
this.setting['storage-abs-container'],
|
|
104
101
|
);
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
);
|
|
108
|
-
const blobClient = containerClient.getBlockBlobClient(filepath);
|
|
102
|
+
const storedFilename = this.getFilename(options?.filename ?? basename(filePath));
|
|
103
|
+
const blobClient = containerClient.getBlockBlobClient([destination, storedFilename].join('/'));
|
|
109
104
|
|
|
110
105
|
await blobClient.uploadFile(filePath, {
|
|
111
106
|
blobHTTPHeaders: options?.mimetype
|
|
@@ -113,24 +108,15 @@ export class AzureProvider extends AbstractProvider {
|
|
|
113
108
|
: undefined,
|
|
114
109
|
});
|
|
115
110
|
|
|
116
|
-
return
|
|
111
|
+
return storedFilename;
|
|
117
112
|
}
|
|
118
113
|
|
|
119
114
|
async delete(filepath: string): Promise<any> {
|
|
120
|
-
const url = new URL(filepath);
|
|
121
|
-
|
|
122
|
-
if (!url.pathname) {
|
|
123
|
-
throw new Error(`Invalid filepath "${filepath}" for S3`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
115
|
const blobServiceClient = await this.getClient();
|
|
127
116
|
const containerClient = blobServiceClient.getContainerClient(
|
|
128
117
|
this.setting['storage-abs-container'],
|
|
129
118
|
);
|
|
130
|
-
const blobClient = containerClient.getBlockBlobClient(
|
|
131
|
-
url.pathname.split('/').slice(1).join('/'),
|
|
132
|
-
);
|
|
133
|
-
|
|
119
|
+
const blobClient = containerClient.getBlockBlobClient(filepath);
|
|
134
120
|
return await blobClient.delete();
|
|
135
121
|
}
|
|
136
122
|
|
|
@@ -8,7 +8,6 @@ export class GCSProvider extends AbstractProvider {
|
|
|
8
8
|
|
|
9
9
|
constructor(private setting: Record<string, string>) {
|
|
10
10
|
super();
|
|
11
|
-
this.initValidation();
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
async initValidation() {
|
|
@@ -53,20 +52,21 @@ export class GCSProvider extends AbstractProvider {
|
|
|
53
52
|
): Promise<any> {
|
|
54
53
|
const storage = await this.getClient();
|
|
55
54
|
const bucket = storage.bucket(this.setting['storage-gcs-bucket']);
|
|
56
|
-
const
|
|
55
|
+
const storedFilename = this.getFilename(filename);
|
|
56
|
+
const file = bucket.file([destination, storedFilename].join('/'));
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
58
|
+
await file.save(fileContent, { contentType: mimetype });
|
|
59
|
+
return storedFilename;
|
|
61
60
|
}
|
|
61
|
+
|
|
62
62
|
async upload(destination: string, file: MulterFile): Promise<any> {
|
|
63
63
|
const storage = await this.getClient();
|
|
64
64
|
const bucket = storage.bucket(this.setting['storage-gcs-bucket']);
|
|
65
|
-
const
|
|
65
|
+
const storedFilename = this.getFilename(file.originalname);
|
|
66
|
+
const fileObject = bucket.file([destination, storedFilename].join('/'));
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
68
|
+
await fileObject.save(file.buffer, { contentType: file.mimetype });
|
|
69
|
+
return storedFilename;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
async uploadFromPath(
|
|
@@ -13,7 +13,6 @@ export class LocalProvider extends AbstractProvider {
|
|
|
13
13
|
private jwt: JwtService
|
|
14
14
|
) {
|
|
15
15
|
super();
|
|
16
|
-
this.initValidation();
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
async initValidation() {
|
|
@@ -40,7 +39,8 @@ export class LocalProvider extends AbstractProvider {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
private resolveFilepath(filepath: string): string {
|
|
43
|
-
|
|
42
|
+
if (path.isAbsolute(filepath)) return filepath;
|
|
43
|
+
return join(this.setting['storage-local-path'], filepath);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
async uploadFromString(
|
|
@@ -67,11 +67,12 @@ export class LocalProvider extends AbstractProvider {
|
|
|
67
67
|
);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const
|
|
70
|
+
const storedFilename = this.getFilename(filename);
|
|
71
|
+
const filePath = join(storagePath, storedFilename);
|
|
71
72
|
|
|
72
73
|
await writeFile(filePath, fileContent);
|
|
73
74
|
|
|
74
|
-
return
|
|
75
|
+
return storedFilename;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
async upload(destination: string, file: MulterFile): Promise<any> {
|
|
@@ -92,11 +93,9 @@ export class LocalProvider extends AbstractProvider {
|
|
|
92
93
|
`The storage path does not exist: ${storagePath}`,
|
|
93
94
|
);
|
|
94
95
|
}
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return filePath;
|
|
96
|
+
const storedFilename = this.getFilename(file.originalname);
|
|
97
|
+
await writeFile(join(storagePath, storedFilename), file.buffer);
|
|
98
|
+
return storedFilename;
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
async uploadFromPath(
|
|
@@ -121,10 +120,8 @@ export class LocalProvider extends AbstractProvider {
|
|
|
121
120
|
}
|
|
122
121
|
|
|
123
122
|
const targetFilename = this.getFilename(options?.filename ?? basename(sourcePath));
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return targetPath;
|
|
123
|
+
await copyFile(sourcePath, join(storagePath, targetFilename));
|
|
124
|
+
return targetFilename;
|
|
128
125
|
}
|
|
129
126
|
|
|
130
127
|
async delete(filepath: string): Promise<any> {
|
|
@@ -8,22 +8,34 @@ import { S3Provider } from './s3.provider';
|
|
|
8
8
|
import { SpacesProvider } from './spaces.provider';
|
|
9
9
|
|
|
10
10
|
export class ProviderFactory {
|
|
11
|
-
static create(
|
|
11
|
+
static async create(
|
|
12
12
|
providerType: EnumProvider,
|
|
13
13
|
setting: Record<string, string>,
|
|
14
14
|
jwtService: JwtService,
|
|
15
|
-
): AbstractProvider {
|
|
15
|
+
): Promise<AbstractProvider> {
|
|
16
|
+
let provider: AbstractProvider;
|
|
17
|
+
|
|
16
18
|
switch (providerType) {
|
|
17
19
|
case EnumProvider.S3:
|
|
18
|
-
|
|
20
|
+
provider = new S3Provider(setting);
|
|
21
|
+
break;
|
|
19
22
|
case EnumProvider.LOCAL:
|
|
20
|
-
|
|
23
|
+
provider = new LocalProvider(setting, jwtService);
|
|
24
|
+
break;
|
|
21
25
|
case EnumProvider.AZURE:
|
|
22
|
-
|
|
26
|
+
provider = new AzureProvider(setting);
|
|
27
|
+
break;
|
|
23
28
|
case EnumProvider.GCS:
|
|
24
|
-
|
|
29
|
+
provider = new GCSProvider(setting);
|
|
30
|
+
break;
|
|
25
31
|
case EnumProvider.SPACES:
|
|
26
|
-
|
|
32
|
+
provider = new SpacesProvider(setting);
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
throw new Error(`Unsupported provider type: ${providerType}`);
|
|
27
36
|
}
|
|
37
|
+
|
|
38
|
+
await provider.initValidation();
|
|
39
|
+
return provider;
|
|
28
40
|
}
|
|
29
41
|
}
|
|
@@ -20,28 +20,13 @@ export class S3Provider extends AbstractProvider {
|
|
|
20
20
|
|
|
21
21
|
constructor(private setting: Record<string, string>) {
|
|
22
22
|
super();
|
|
23
|
-
this.initValidation();
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
private resolveS3Key(filepath: string): string {
|
|
27
26
|
if (!filepath) {
|
|
28
27
|
throw new Error('Invalid filepath for S3');
|
|
29
28
|
}
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
const url = new URL(filepath);
|
|
33
|
-
const keyFromUrl = url.pathname.split('/').filter(Boolean).join('/');
|
|
34
|
-
if (!keyFromUrl) {
|
|
35
|
-
throw new Error(`Invalid filepath "${filepath}" for S3`);
|
|
36
|
-
}
|
|
37
|
-
return keyFromUrl;
|
|
38
|
-
} catch {
|
|
39
|
-
const keyFromPath = filepath.split('/').filter(Boolean).join('/');
|
|
40
|
-
if (!keyFromPath) {
|
|
41
|
-
throw new Error(`Invalid filepath "${filepath}" for S3`);
|
|
42
|
-
}
|
|
43
|
-
return keyFromPath;
|
|
44
|
-
}
|
|
29
|
+
return filepath;
|
|
45
30
|
}
|
|
46
31
|
|
|
47
32
|
async buffer(filepath: string): Promise<Buffer> {
|
|
@@ -151,8 +136,7 @@ export class S3Provider extends AbstractProvider {
|
|
|
151
136
|
Body: fileContent,
|
|
152
137
|
});
|
|
153
138
|
await s3.send(command);
|
|
154
|
-
|
|
155
|
-
return `https://${this.setting['storage-s3-bucket']}.s3.${this.setting['storage-s3-region']}.amazonaws.com/${key}`;
|
|
139
|
+
return storedFilename;
|
|
156
140
|
}
|
|
157
141
|
|
|
158
142
|
async upload(destination: string, file: MulterFile): Promise<any> {
|
|
@@ -165,7 +149,7 @@ export class S3Provider extends AbstractProvider {
|
|
|
165
149
|
Body: file.buffer,
|
|
166
150
|
});
|
|
167
151
|
await s3.send(command);
|
|
168
|
-
return
|
|
152
|
+
return storedFilename;
|
|
169
153
|
}
|
|
170
154
|
|
|
171
155
|
async uploadFromPath(
|
|
@@ -185,7 +169,7 @@ export class S3Provider extends AbstractProvider {
|
|
|
185
169
|
});
|
|
186
170
|
|
|
187
171
|
await s3.send(command);
|
|
188
|
-
return
|
|
172
|
+
return storedFilename;
|
|
189
173
|
}
|
|
190
174
|
|
|
191
175
|
async delete(filepath: string): Promise<any> {
|
|
@@ -20,7 +20,6 @@ export class SpacesProvider extends AbstractProvider {
|
|
|
20
20
|
|
|
21
21
|
constructor(private setting: Record<string, string>) {
|
|
22
22
|
super();
|
|
23
|
-
this.initValidation();
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
private get endpoint() {
|
|
@@ -39,21 +38,7 @@ export class SpacesProvider extends AbstractProvider {
|
|
|
39
38
|
if (!filepath) {
|
|
40
39
|
throw new Error('Invalid filepath for DigitalOcean Spaces');
|
|
41
40
|
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
const url = new URL(filepath);
|
|
45
|
-
const key = url.pathname.split('/').filter(Boolean).join('/');
|
|
46
|
-
if (!key) {
|
|
47
|
-
throw new Error(`Invalid filepath "${filepath}" for DigitalOcean Spaces`);
|
|
48
|
-
}
|
|
49
|
-
return key;
|
|
50
|
-
} catch {
|
|
51
|
-
const key = filepath.split('/').filter(Boolean).join('/');
|
|
52
|
-
if (!key) {
|
|
53
|
-
throw new Error(`Invalid filepath "${filepath}" for DigitalOcean Spaces`);
|
|
54
|
-
}
|
|
55
|
-
return key;
|
|
56
|
-
}
|
|
41
|
+
return filepath;
|
|
57
42
|
}
|
|
58
43
|
|
|
59
44
|
async initValidation() {
|
|
@@ -169,7 +154,7 @@ export class SpacesProvider extends AbstractProvider {
|
|
|
169
154
|
ACL: 'public-read',
|
|
170
155
|
}),
|
|
171
156
|
);
|
|
172
|
-
return
|
|
157
|
+
return storedFilename;
|
|
173
158
|
}
|
|
174
159
|
|
|
175
160
|
async uploadFromString(
|
|
@@ -188,7 +173,7 @@ export class SpacesProvider extends AbstractProvider {
|
|
|
188
173
|
ACL: 'public-read',
|
|
189
174
|
}),
|
|
190
175
|
);
|
|
191
|
-
return
|
|
176
|
+
return storedFilename;
|
|
192
177
|
}
|
|
193
178
|
|
|
194
179
|
async uploadFromPath(
|
|
@@ -209,7 +194,7 @@ export class SpacesProvider extends AbstractProvider {
|
|
|
209
194
|
ACL: 'public-read',
|
|
210
195
|
}),
|
|
211
196
|
);
|
|
212
|
-
return
|
|
197
|
+
return storedFilename;
|
|
213
198
|
}
|
|
214
199
|
|
|
215
200
|
async delete(filepath: string): Promise<any> {
|
|
@@ -54,11 +54,13 @@ export class IntegrationProfileController {
|
|
|
54
54
|
@Pagination() paginationParams,
|
|
55
55
|
@Query('typeId') typeId?: string,
|
|
56
56
|
@Query('typeSlug') typeSlug?: string,
|
|
57
|
+
@Query('providerSlug') providerSlug?: string,
|
|
57
58
|
) {
|
|
58
59
|
return this.integrationProfileService.list(
|
|
59
60
|
paginationParams,
|
|
60
61
|
typeId ? parseInt(typeId, 10) : undefined,
|
|
61
62
|
typeSlug,
|
|
63
|
+
providerSlug,
|
|
62
64
|
);
|
|
63
65
|
}
|
|
64
66
|
|
|
@@ -100,10 +100,12 @@ export class IntegrationProfileService {
|
|
|
100
100
|
paginationParams: PaginationDTO,
|
|
101
101
|
typeId?: number,
|
|
102
102
|
typeSlug?: string,
|
|
103
|
+
providerSlug?: string,
|
|
103
104
|
) {
|
|
104
105
|
const fields = ['slug', 'name'];
|
|
105
106
|
const OR = this.prismaService.createInsensitiveSearch(fields, paginationParams);
|
|
106
107
|
const normalizedTypeSlug = String(typeSlug ?? '').trim();
|
|
108
|
+
const normalizedProviderSlug = String(providerSlug ?? '').trim();
|
|
107
109
|
|
|
108
110
|
const result = await this.paginationService.paginate(
|
|
109
111
|
this.prismaService.integration_profile,
|
|
@@ -115,6 +117,9 @@ export class IntegrationProfileService {
|
|
|
115
117
|
...(normalizedTypeSlug
|
|
116
118
|
? { integration_type: { slug: normalizedTypeSlug } }
|
|
117
119
|
: {}),
|
|
120
|
+
...(normalizedProviderSlug
|
|
121
|
+
? { integration_provider: { slug: normalizedProviderSlug } }
|
|
122
|
+
: {}),
|
|
118
123
|
},
|
|
119
124
|
select: {
|
|
120
125
|
id: true,
|
|
@@ -157,8 +162,8 @@ export class IntegrationProfileService {
|
|
|
157
162
|
SELECT
|
|
158
163
|
ip.id,
|
|
159
164
|
(
|
|
160
|
-
COALESCE((SELECT COUNT(*)::int FROM campaign WHERE
|
|
161
|
-
COALESCE((SELECT COUNT(*)::int FROM webhook_action WHERE
|
|
165
|
+
COALESCE((SELECT COUNT(*)::int FROM campaign WHERE integration_profile_id = ip.id), 0) +
|
|
166
|
+
COALESCE((SELECT COUNT(*)::int FROM webhook_action WHERE integration_profile_id = ip.id), 0)
|
|
162
167
|
) AS uses_count
|
|
163
168
|
FROM integration_profile ip
|
|
164
169
|
WHERE ip.id = ANY(${ids}::int[])
|
|
@@ -135,6 +135,23 @@ export function resolveFileProviderSlug(providerSlug: string): string {
|
|
|
135
135
|
|
|
136
136
|
// ─── AI utilities ─────────────────────────────────────────────────────────────
|
|
137
137
|
|
|
138
|
+
// ─── OAuth utilities ──────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
/** Maps an integration_profile config JSON to OAuth client credentials. */
|
|
141
|
+
export function buildOAuthConfigFromIntegration(
|
|
142
|
+
_providerSlug: string,
|
|
143
|
+
config: unknown,
|
|
144
|
+
): { clientId: string; clientSecret: string; tenantId?: string } {
|
|
145
|
+
const cfg = (config as Cfg) ?? {};
|
|
146
|
+
return {
|
|
147
|
+
clientId: String(cfg.client_id ?? ''),
|
|
148
|
+
clientSecret: String(cfg.client_secret ?? ''),
|
|
149
|
+
...(cfg.tenant_id ? { tenantId: String(cfg.tenant_id) } : {}),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─── AI utilities ─────────────────────────────────────────────────────────────
|
|
154
|
+
|
|
138
155
|
/** Maps an integration_profile config JSON to the API key and optional org needed by AI providers. */
|
|
139
156
|
export function buildAiConfigFromIntegration(
|
|
140
157
|
providerSlug: string,
|
package/src/mail/mail.module.ts
CHANGED
|
@@ -15,12 +15,22 @@ import { MailService } from './mail.service';
|
|
|
15
15
|
inject: [PrismaService],
|
|
16
16
|
useFactory: async (prisma: PrismaService) => {
|
|
17
17
|
try {
|
|
18
|
-
const
|
|
19
|
-
where: {
|
|
20
|
-
|
|
18
|
+
const profileSettings = await prisma.setting.findMany({
|
|
19
|
+
where: {
|
|
20
|
+
slug: {
|
|
21
|
+
in: ['mail-integration-profile-id'],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
select: { slug: true, value: true },
|
|
21
25
|
});
|
|
22
26
|
|
|
23
|
-
const
|
|
27
|
+
const profileSettingsMap = new Map(
|
|
28
|
+
profileSettings.map((item) => [item.slug, item.value]),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const profileId = Number(
|
|
32
|
+
profileSettingsMap.get('mail-integration-profile-id'),
|
|
33
|
+
);
|
|
24
34
|
|
|
25
35
|
if (!profileId || profileId <= 0) {
|
|
26
36
|
return { global: true, type: 'SMTP', host: 'localhost', port: 1025, secure: false, username: '', password: '' } as MailModuleOptions;
|