@hed-hog/core 0.0.368 → 0.0.371
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/LICENSE.md +21 -0
- package/README.md +2 -0
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +10 -7
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/app-command/app-command.module.d.ts +3 -0
- package/dist/app-command/app-command.module.d.ts.map +1 -0
- package/dist/app-command/app-command.module.js +22 -0
- package/dist/app-command/app-command.module.js.map +1 -0
- package/dist/app-command/app-command.registry.d.ts +22 -0
- package/dist/app-command/app-command.registry.d.ts.map +1 -0
- package/dist/{webhook-integration/webhook-command.registry.js → app-command/app-command.registry.js} +6 -6
- package/dist/app-command/app-command.registry.js.map +1 -0
- package/dist/auth/auth.controller.d.ts +2 -2
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +20 -7
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts +3 -3
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +10 -36
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/auth/dto/forget.dto.d.ts +1 -0
- package/dist/auth/dto/forget.dto.d.ts.map +1 -1
- package/dist/auth/dto/forget.dto.js +5 -0
- package/dist/auth/dto/forget.dto.js.map +1 -1
- package/dist/auth/guards/auth.guard.d.ts.map +1 -1
- package/dist/auth/guards/auth.guard.js +4 -1
- package/dist/auth/guards/auth.guard.js.map +1 -1
- package/dist/challenge/challenge.service.d.ts +8 -7
- package/dist/challenge/challenge.service.d.ts.map +1 -1
- package/dist/challenge/challenge.service.js +27 -19
- package/dist/challenge/challenge.service.js.map +1 -1
- package/dist/file/dto/upload.dto.d.ts +3 -0
- package/dist/file/dto/upload.dto.d.ts.map +1 -1
- package/dist/file/dto/upload.dto.js +22 -0
- package/dist/file/dto/upload.dto.js.map +1 -1
- package/dist/file/file.controller.d.ts +2 -1
- package/dist/file/file.controller.d.ts.map +1 -1
- package/dist/file/file.controller.js +29 -16
- package/dist/file/file.controller.js.map +1 -1
- package/dist/file/file.service.d.ts +51 -1
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +250 -18
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/image-optimization.service.d.ts +17 -4
- package/dist/file/image-optimization.service.d.ts.map +1 -1
- package/dist/file/image-optimization.service.js +101 -46
- package/dist/file/image-optimization.service.js.map +1 -1
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +15 -7
- 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 +18 -8
- package/dist/file/provider/spaces.provider.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/integration/services/event-subscriber.registry.d.ts +5 -0
- package/dist/integration/services/event-subscriber.registry.d.ts.map +1 -1
- package/dist/integration/services/event-subscriber.registry.js +15 -0
- package/dist/integration/services/event-subscriber.registry.js.map +1 -1
- package/dist/integration/services/outbox.processor.d.ts.map +1 -1
- package/dist/integration/services/outbox.processor.js +2 -4
- package/dist/integration/services/outbox.processor.js.map +1 -1
- package/dist/integration/services/outbox.service.d.ts +4 -2
- package/dist/integration/services/outbox.service.d.ts.map +1 -1
- package/dist/integration/services/outbox.service.js +26 -15
- package/dist/integration/services/outbox.service.js.map +1 -1
- package/dist/integration-profile/integration-credential-crypto.service.d.ts +43 -0
- package/dist/integration-profile/integration-credential-crypto.service.d.ts.map +1 -0
- package/dist/integration-profile/integration-credential-crypto.service.js +142 -0
- package/dist/integration-profile/integration-credential-crypto.service.js.map +1 -0
- package/dist/integration-profile/integration-profile.controller.d.ts +6 -7
- 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.module.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.module.js +5 -2
- package/dist/integration-profile/integration-profile.module.js.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.d.ts +16 -0
- package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -0
- package/dist/integration-profile/integration-profile.secrets.js +56 -0
- package/dist/integration-profile/integration-profile.secrets.js.map +1 -0
- package/dist/integration-profile/integration-profile.service.d.ts +125 -8
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.service.js +257 -68
- package/dist/integration-profile/integration-profile.service.js.map +1 -1
- package/dist/integration-profile/integration-profile.utils.d.ts +17 -0
- package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.utils.js +31 -0
- package/dist/integration-profile/integration-profile.utils.js.map +1 -1
- package/dist/language/en.json +178 -0
- package/dist/language/pt.json +178 -0
- package/dist/mail/mail.controller.d.ts.map +1 -1
- package/dist/mail/mail.controller.js +5 -28
- package/dist/mail/mail.controller.js.map +1 -1
- package/dist/mail/mail.module.js +18 -18
- package/dist/mail/mail.module.js.map +1 -1
- package/dist/mail/mail.service.d.ts +13 -2
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +25 -15
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +6 -5
- package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
- package/dist/oauth/providers/facebook.provider.js +5 -5
- package/dist/oauth/providers/facebook.provider.js.map +1 -1
- package/dist/oauth/providers/github.provider.js +5 -5
- package/dist/oauth/providers/github.provider.js.map +1 -1
- package/dist/oauth/providers/google.provider.js +5 -5
- package/dist/oauth/providers/google.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.js +5 -5
- package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft.provider.js +5 -5
- package/dist/oauth/providers/microsoft.provider.js.map +1 -1
- package/dist/pdf/pdf.service.d.ts +4 -0
- package/dist/pdf/pdf.service.d.ts.map +1 -1
- package/dist/pdf/pdf.service.js +16 -9
- package/dist/pdf/pdf.service.js.map +1 -1
- package/dist/profile/profile.controller.d.ts +7 -7
- package/dist/profile/profile.controller.d.ts.map +1 -1
- package/dist/profile/profile.controller.js +28 -21
- package/dist/profile/profile.controller.js.map +1 -1
- package/dist/profile/profile.service.d.ts +7 -7
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +21 -25
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/setting/setting.service.d.ts +1 -0
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +15 -2
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/token/token.service.d.ts.map +1 -1
- package/dist/token/token.service.js +2 -1
- package/dist/token/token.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +9 -0
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +13 -0
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +7 -0
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +19 -0
- package/dist/user/user.service.js.map +1 -1
- package/dist/utils/webauthn-origin.d.ts +11 -0
- package/dist/utils/webauthn-origin.d.ts.map +1 -0
- package/dist/utils/webauthn-origin.js +37 -0
- package/dist/utils/webauthn-origin.js.map +1 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts +13 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.controller.js +29 -0
- package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.module.js +3 -3
- package/dist/webhook-integration/webhook-integration.module.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.d.ts +18 -2
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +110 -6
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/hedhog/data/integration_event_catalog.yaml +118 -118
- package/hedhog/data/integration_provider.yaml +9 -0
- package/hedhog/data/integration_type.yaml +6 -0
- package/hedhog/data/menu.yaml +6 -0
- package/hedhog/data/role.yaml +7 -0
- package/hedhog/data/route.yaml +192 -207
- package/hedhog/data/setting_group.yaml +119 -13
- package/hedhog/frontend/app/access-log/page.tsx.ejs +35 -3
- package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +36 -3
- package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +39 -4
- package/hedhog/frontend/app/account/components/tokens.tsx.ejs +39 -4
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +35 -2
- package/hedhog/frontend/app/configurations/[slug]/components/byte-size-input.tsx.ejs +160 -0
- package/hedhog/frontend/app/configurations/[slug]/components/duration-input.tsx.ejs +157 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +244 -63
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +33 -1
- package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +35 -3
- package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +36 -4
- package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +35 -3
- package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +36 -4
- package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +35 -3
- package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +141 -29
- package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +118 -11
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +37 -4
- package/hedhog/frontend/app/menu/page.tsx.ejs +34 -2
- package/hedhog/frontend/app/preferences/page.tsx.ejs +77 -0
- package/hedhog/frontend/app/roles/menus.tsx.ejs +35 -3
- package/hedhog/frontend/app/roles/page.tsx.ejs +34 -2
- package/hedhog/frontend/app/roles/routes.tsx.ejs +36 -4
- package/hedhog/frontend/app/roles/users.tsx.ejs +36 -4
- package/hedhog/frontend/app/routes/page.tsx.ejs +34 -2
- package/hedhog/frontend/app/setting-list/page.tsx.ejs +34 -2
- package/hedhog/frontend/app/users/page.tsx.ejs +51 -21
- package/hedhog/frontend/messages/en.json +69 -4
- package/hedhog/frontend/messages/pt.json +69 -4
- package/hedhog/table/event_webhook.yaml +55 -55
- package/hedhog/table/event_webhook_delivery_log.yaml +66 -66
- package/hedhog/table/event_webhook_event.yaml +26 -26
- package/hedhog/table/integration_event_catalog.yaml +41 -41
- package/hedhog/table/mcp_conversation.yaml +12 -12
- package/hedhog/table/mcp_conversation_message.yaml +23 -23
- package/hedhog/table/mcp_tool_call_log.yaml +22 -22
- package/hedhog/table/route.yaml +33 -33
- package/hedhog/table/setting.yaml +2 -0
- package/hedhog/table/webhook_call_log.yaml +3 -0
- package/hedhog/table/webhook_integration.yaml +43 -43
- package/package.json +9 -4
- package/src/ai/ai.service.ts +9 -6
- package/src/app-command/app-command.module.ts +9 -0
- package/src/{webhook-integration/webhook-command.registry.ts → app-command/app-command.registry.ts} +9 -9
- package/src/auth/auth.controller.ts +20 -4
- package/src/auth/auth.service.ts +10 -36
- package/src/auth/dto/forget.dto.ts +5 -1
- package/src/auth/guards/auth.guard.ts +4 -1
- package/src/challenge/challenge.service.ts +30 -19
- package/src/file/dto/upload.dto.ts +25 -1
- package/src/file/file.controller.ts +31 -18
- package/src/file/file.service.ts +341 -16
- package/src/file/image-optimization.service.ts +111 -46
- package/src/file/provider/s3.provider.ts +16 -7
- package/src/file/provider/spaces.provider.ts +15 -5
- package/src/index.ts +4 -1
- package/src/integration/services/event-subscriber.registry.ts +15 -0
- package/src/integration/services/outbox.processor.ts +2 -5
- package/src/integration/services/outbox.service.ts +35 -17
- package/src/integration/types/event.types.ts +4 -4
- package/src/integration-profile/integration-credential-crypto.service.ts +146 -0
- package/src/integration-profile/integration-profile.controller.ts +2 -0
- package/src/integration-profile/integration-profile.module.ts +5 -2
- package/src/integration-profile/integration-profile.secrets.ts +51 -0
- package/src/integration-profile/integration-profile.service.ts +335 -54
- package/src/integration-profile/integration-profile.utils.ts +56 -0
- package/src/language/en.json +5 -1
- package/src/language/pt.json +5 -1
- package/src/mail/mail.controller.ts +5 -29
- package/src/mail/mail.module.ts +5 -5
- package/src/mail/mail.service.ts +37 -13
- package/src/mcp/decorators/mcp-tool.decorator.ts +7 -7
- package/src/mcp/guards/mcp-permission.guard.ts +51 -51
- package/src/mcp/interfaces/mcp-tool-definition.interface.ts +6 -6
- package/src/mcp/mcp-server.service.ts +87 -87
- package/src/mcp/types/mcp-context.type.ts +6 -6
- package/src/mcp-chat/dto/create-conversation.dto.ts +7 -7
- package/src/mcp-chat/dto/send-message.dto.ts +7 -7
- package/src/mcp-chat/mcp-chat.service.ts +5 -5
- package/src/notification/events/notification-stale-progress-check.event.ts +5 -5
- package/src/oauth/oauth-callback-coordinator.service.spec.ts +159 -0
- package/src/oauth/providers/facebook.provider.ts +3 -3
- package/src/oauth/providers/github.provider.ts +3 -3
- package/src/oauth/providers/google.provider.ts +3 -3
- package/src/oauth/providers/microsoft-entra-id.provider.ts +3 -3
- package/src/oauth/providers/microsoft.provider.ts +3 -3
- package/src/pdf/pdf.service.ts +19 -6
- package/src/profile/profile.controller.ts +21 -14
- package/src/profile/profile.service.ts +23 -20
- package/src/route/dto/create.dto.ts +32 -32
- package/src/route/dto/update.dto.ts +31 -31
- package/src/security/security.service.spec.ts +222 -0
- package/src/setting/setting.service.ts +15 -2
- package/src/token/token.service.ts +2 -1
- package/src/user/user.controller.ts +26 -16
- package/src/user/user.service.ts +25 -0
- package/src/utils/webauthn-origin.ts +38 -0
- package/src/webhook-integration/integration-event-catalog.controller.ts +50 -50
- package/src/webhook-integration/integration-event-catalog.service.ts +117 -117
- package/src/webhook-integration/webhook-integration.controller.ts +23 -0
- package/src/webhook-integration/webhook-integration.module.ts +3 -3
- package/src/webhook-integration/webhook-integration.service.ts +115 -5
- package/dist/webhook-integration/webhook-command.registry.d.ts +0 -22
- package/dist/webhook-integration/webhook-command.registry.d.ts.map +0 -1
- package/dist/webhook-integration/webhook-command.registry.js.map +0 -1
- package/src/menu/menu.service.spec.ts +0 -247
- package/src/route/route.service.spec.ts +0 -300
- package/src/screen/screen.service.spec.ts +0 -298
- package/src/user/user.service.spec.ts +0 -294
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
ImportProfileItemDTO,
|
|
17
17
|
} from './dto/import-confirm.dto';
|
|
18
18
|
import { UpdateDTO } from './dto/update.dto';
|
|
19
|
+
import { IntegrationCredentialCryptoService } from './integration-credential-crypto.service';
|
|
20
|
+
import { getSensitiveConfigKeys } from './integration-profile.secrets';
|
|
19
21
|
|
|
20
22
|
type IntegrationProfileExportItem = {
|
|
21
23
|
slug: string;
|
|
@@ -58,6 +60,8 @@ export class IntegrationProfileService {
|
|
|
58
60
|
private readonly prismaService: PrismaService,
|
|
59
61
|
@Inject(forwardRef(() => PaginationService))
|
|
60
62
|
private readonly paginationService: PaginationService,
|
|
63
|
+
@Inject(forwardRef(() => IntegrationCredentialCryptoService))
|
|
64
|
+
private readonly credentialCrypto: IntegrationCredentialCryptoService,
|
|
61
65
|
) {}
|
|
62
66
|
|
|
63
67
|
async listTypes() {
|
|
@@ -101,11 +105,13 @@ export class IntegrationProfileService {
|
|
|
101
105
|
typeId?: number,
|
|
102
106
|
typeSlug?: string,
|
|
103
107
|
providerSlug?: string,
|
|
108
|
+
slug?: string,
|
|
104
109
|
) {
|
|
105
110
|
const fields = ['slug', 'name'];
|
|
106
111
|
const OR = this.prismaService.createInsensitiveSearch(fields, paginationParams);
|
|
107
112
|
const normalizedTypeSlug = String(typeSlug ?? '').trim();
|
|
108
113
|
const normalizedProviderSlug = String(providerSlug ?? '').trim();
|
|
114
|
+
const normalizedSlug = String(slug ?? '').trim();
|
|
109
115
|
|
|
110
116
|
const result = await this.paginationService.paginate(
|
|
111
117
|
this.prismaService.integration_profile,
|
|
@@ -120,6 +126,7 @@ export class IntegrationProfileService {
|
|
|
120
126
|
...(normalizedProviderSlug
|
|
121
127
|
? { integration_provider: { slug: normalizedProviderSlug } }
|
|
122
128
|
: {}),
|
|
129
|
+
...(normalizedSlug ? { slug: normalizedSlug } : {}),
|
|
123
130
|
},
|
|
124
131
|
include: {
|
|
125
132
|
integration_type: {
|
|
@@ -166,6 +173,11 @@ export class IntegrationProfileService {
|
|
|
166
173
|
|
|
167
174
|
result.data = items.map((p: any) => ({
|
|
168
175
|
...p,
|
|
176
|
+
// Não devolve segredos na listagem (middleware já decifrou; mascaramos).
|
|
177
|
+
config: this.credentialCrypto.maskConfig(
|
|
178
|
+
p.integration_provider?.slug ?? '',
|
|
179
|
+
p.config as Record<string, unknown>,
|
|
180
|
+
),
|
|
169
181
|
uses_count: countMap.get(p.id) ?? 0,
|
|
170
182
|
}));
|
|
171
183
|
}
|
|
@@ -173,6 +185,98 @@ export class IntegrationProfileService {
|
|
|
173
185
|
return result;
|
|
174
186
|
}
|
|
175
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Resolve o config do perfil de integração ATIVO para um par tipo/provider — uso
|
|
190
|
+
* server-side por serviços que precisam das credenciais (ex.: InfraScaler do
|
|
191
|
+
* autoscaling). Opcionalmente fixa um slug específico (vindo de uma setting). É o ponto
|
|
192
|
+
* único de acesso às credenciais armazenadas no Perfil de Integração.
|
|
193
|
+
*/
|
|
194
|
+
async getActiveProfileConfig(
|
|
195
|
+
typeSlug: string,
|
|
196
|
+
providerSlug: string,
|
|
197
|
+
options?: { profileSlug?: string; profileId?: number },
|
|
198
|
+
): Promise<{ id: number; slug: string; config: Record<string, unknown> }> {
|
|
199
|
+
// O middleware do PrismaService decifra config nesta leitura (uso interno).
|
|
200
|
+
const profile = await this.prismaService.integration_profile.findFirst({
|
|
201
|
+
where: {
|
|
202
|
+
is_active: true,
|
|
203
|
+
integration_type: { slug: typeSlug },
|
|
204
|
+
integration_provider: { slug: providerSlug },
|
|
205
|
+
...(options?.profileId ? { id: options.profileId } : {}),
|
|
206
|
+
...(options?.profileSlug ? { slug: options.profileSlug } : {}),
|
|
207
|
+
},
|
|
208
|
+
orderBy: [{ updated_at: 'desc' }, { id: 'desc' }],
|
|
209
|
+
select: { id: true, slug: true, config: true },
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
if (!profile) {
|
|
213
|
+
const ref = options?.profileId
|
|
214
|
+
? ` / id ${options.profileId}`
|
|
215
|
+
: options?.profileSlug
|
|
216
|
+
? ` / slug "${options.profileSlug}"`
|
|
217
|
+
: '';
|
|
218
|
+
throw new NotFoundException(
|
|
219
|
+
`No active integration profile found for type "${typeSlug}" / provider "${providerSlug}"${ref}.`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
id: profile.id,
|
|
225
|
+
slug: profile.slug,
|
|
226
|
+
config: (profile.config as Record<string, unknown>) ?? {},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Resolve um perfil ATIVO do tipo `infrastructure` (DigitalOcean, Kubernetes, …) por
|
|
232
|
+
* SLUG (vindo de uma setting) ou por ID (seleção direta na UI) — ou o mais recente se
|
|
233
|
+
* nada for dado. Retorna o slug do provider (para dispatch) e o config já decifrado
|
|
234
|
+
* pelo middleware. Uso interno do autoscaling.
|
|
235
|
+
*/
|
|
236
|
+
async getActiveInfrastructureProfile(
|
|
237
|
+
options?: { profileSlug?: string; profileId?: number },
|
|
238
|
+
): Promise<{
|
|
239
|
+
id: number;
|
|
240
|
+
slug: string;
|
|
241
|
+
providerSlug: string;
|
|
242
|
+
config: Record<string, unknown>;
|
|
243
|
+
}> {
|
|
244
|
+
const normalizedSlug = String(options?.profileSlug ?? '').trim();
|
|
245
|
+
const profile = await this.prismaService.integration_profile.findFirst({
|
|
246
|
+
where: {
|
|
247
|
+
is_active: true,
|
|
248
|
+
integration_type: { slug: 'infrastructure' },
|
|
249
|
+
...(options?.profileId ? { id: options.profileId } : {}),
|
|
250
|
+
...(normalizedSlug ? { slug: normalizedSlug } : {}),
|
|
251
|
+
},
|
|
252
|
+
orderBy: [{ updated_at: 'desc' }, { id: 'desc' }],
|
|
253
|
+
select: {
|
|
254
|
+
id: true,
|
|
255
|
+
slug: true,
|
|
256
|
+
config: true,
|
|
257
|
+
integration_provider: { select: { slug: true } },
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (!profile) {
|
|
262
|
+
const ref = options?.profileId
|
|
263
|
+
? ` for id ${options.profileId}`
|
|
264
|
+
: normalizedSlug
|
|
265
|
+
? ` for slug "${normalizedSlug}"`
|
|
266
|
+
: '';
|
|
267
|
+
throw new NotFoundException(
|
|
268
|
+
`No active infrastructure integration profile found${ref}.`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
id: profile.id,
|
|
274
|
+
slug: profile.slug,
|
|
275
|
+
providerSlug: profile.integration_provider?.slug ?? '',
|
|
276
|
+
config: (profile.config as Record<string, unknown>) ?? {},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
176
280
|
async get(id: number) {
|
|
177
281
|
const profile = await this.prismaService.integration_profile.findUnique({
|
|
178
282
|
where: { id },
|
|
@@ -203,25 +307,71 @@ export class IntegrationProfileService {
|
|
|
203
307
|
);
|
|
204
308
|
}
|
|
205
309
|
|
|
206
|
-
|
|
310
|
+
// Não devolve segredos ao cliente (middleware já decifrou; mascaramos para a UI).
|
|
311
|
+
return {
|
|
312
|
+
...profile,
|
|
313
|
+
config: this.credentialCrypto.maskConfig(
|
|
314
|
+
profile.integration_provider?.slug ?? '',
|
|
315
|
+
profile.config as Record<string, unknown>,
|
|
316
|
+
),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Igual a `get(id)`, porém resolvendo o perfil pelo SLUG (valor estável guardado em
|
|
322
|
+
* settings). Reusa `get()` para manter o mesmo include e o mascaramento de segredos.
|
|
323
|
+
*/
|
|
324
|
+
async getBySlug(slug: string) {
|
|
325
|
+
const normalized = String(slug ?? '').trim();
|
|
326
|
+
if (!normalized) {
|
|
327
|
+
throw new NotFoundException('Integration profile slug is empty.');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const found = await this.prismaService.integration_profile.findUnique({
|
|
331
|
+
where: { slug: normalized },
|
|
332
|
+
select: { id: true },
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (!found) {
|
|
336
|
+
throw new NotFoundException(
|
|
337
|
+
`Integration profile with slug "${normalized}" not found.`,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return this.get(found.id);
|
|
207
342
|
}
|
|
208
343
|
|
|
209
344
|
async create(data: CreateDTO) {
|
|
210
345
|
await this.ensureSlugIsAvailable(data.slug);
|
|
211
346
|
await this.validateTypeAndProvider(data.type_id, data.provider_id);
|
|
212
347
|
|
|
348
|
+
const providerSlug = await this.getProviderSlug(data.provider_id);
|
|
349
|
+
const config = this.credentialCrypto.encryptConfig(
|
|
350
|
+
providerSlug,
|
|
351
|
+
(data.config as Record<string, unknown>) ?? null,
|
|
352
|
+
);
|
|
353
|
+
|
|
213
354
|
return this.prismaService.integration_profile.create({
|
|
214
355
|
data: {
|
|
215
356
|
slug: data.slug.trim(),
|
|
216
357
|
name: data.name.trim(),
|
|
217
358
|
type_id: data.type_id,
|
|
218
359
|
provider_id: data.provider_id,
|
|
219
|
-
config: (
|
|
360
|
+
config: (config as object) ?? null,
|
|
220
361
|
is_active: data.is_active ?? true,
|
|
221
362
|
},
|
|
222
363
|
});
|
|
223
364
|
}
|
|
224
365
|
|
|
366
|
+
/** Resolve o slug do provider a partir do id (para saber as chaves sensíveis). */
|
|
367
|
+
private async getProviderSlug(providerId: number): Promise<string> {
|
|
368
|
+
const provider = await this.prismaService.integration_provider.findUnique({
|
|
369
|
+
where: { id: providerId },
|
|
370
|
+
select: { slug: true },
|
|
371
|
+
});
|
|
372
|
+
return provider?.slug ?? '';
|
|
373
|
+
}
|
|
374
|
+
|
|
225
375
|
async update({ id, data }: { id: number; data: UpdateDTO }) {
|
|
226
376
|
const existing = await this.prismaService.integration_profile.findUnique({
|
|
227
377
|
where: { id },
|
|
@@ -241,6 +391,20 @@ export class IntegrationProfileService {
|
|
|
241
391
|
const provider_id = data.provider_id ?? existing.provider_id;
|
|
242
392
|
await this.validateTypeAndProvider(type_id, provider_id);
|
|
243
393
|
|
|
394
|
+
const providerSlug = await this.getProviderSlug(provider_id);
|
|
395
|
+
// `existing.config` já vem DECIFRADO pelo middleware. Em update parcial, mantemos os
|
|
396
|
+
// segredos enviados em branco ("deixe em branco para manter") e recifra-mos tudo.
|
|
397
|
+
const storedDecrypted = (existing.config as Record<string, unknown>) ?? null;
|
|
398
|
+
const finalPlain =
|
|
399
|
+
data.config !== undefined
|
|
400
|
+
? this.credentialCrypto.mergeKeepingStoredSecrets(
|
|
401
|
+
providerSlug,
|
|
402
|
+
(data.config as Record<string, unknown>) ?? null,
|
|
403
|
+
storedDecrypted,
|
|
404
|
+
)
|
|
405
|
+
: storedDecrypted;
|
|
406
|
+
const config = this.credentialCrypto.encryptConfig(providerSlug, finalPlain);
|
|
407
|
+
|
|
244
408
|
return this.prismaService.integration_profile.update({
|
|
245
409
|
where: { id },
|
|
246
410
|
data: {
|
|
@@ -248,10 +412,7 @@ export class IntegrationProfileService {
|
|
|
248
412
|
name: data.name?.trim() ?? existing.name,
|
|
249
413
|
type_id,
|
|
250
414
|
provider_id,
|
|
251
|
-
config:
|
|
252
|
-
data.config !== undefined
|
|
253
|
-
? ((data.config as object) ?? null)
|
|
254
|
-
: existing.config,
|
|
415
|
+
config: (config as object) ?? null,
|
|
255
416
|
is_active: data.is_active ?? existing.is_active,
|
|
256
417
|
},
|
|
257
418
|
});
|
|
@@ -296,10 +457,25 @@ export class IntegrationProfileService {
|
|
|
296
457
|
);
|
|
297
458
|
}
|
|
298
459
|
|
|
299
|
-
|
|
460
|
+
// "Deixe em branco para manter": ao testar na edição, herda os segredos já
|
|
461
|
+
// armazenados (decifrados pelo middleware) para os campos vazios/mascarados.
|
|
462
|
+
let effectiveConfig = (data.config as Record<string, unknown>) ?? null;
|
|
463
|
+
const stored = await this.prismaService.integration_profile.findUnique({
|
|
464
|
+
where: { slug: data.slug.trim() },
|
|
465
|
+
select: { config: true },
|
|
466
|
+
});
|
|
467
|
+
if (stored?.config) {
|
|
468
|
+
effectiveConfig = this.credentialCrypto.mergeKeepingStoredSecrets(
|
|
469
|
+
provider.slug,
|
|
470
|
+
effectiveConfig,
|
|
471
|
+
stored.config as Record<string, unknown>,
|
|
472
|
+
) as Record<string, unknown>;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
buildMailConfigFromIntegration(provider.slug, effectiveConfig);
|
|
300
476
|
|
|
301
477
|
const destination = String(
|
|
302
|
-
(
|
|
478
|
+
(effectiveConfig as Record<string, unknown> | null | undefined)?.from_email ??
|
|
303
479
|
'',
|
|
304
480
|
).trim();
|
|
305
481
|
|
|
@@ -320,9 +496,36 @@ export class IntegrationProfileService {
|
|
|
320
496
|
ids?: number[];
|
|
321
497
|
includeSecrets?: boolean;
|
|
322
498
|
}): Promise<Buffer> {
|
|
499
|
+
const items = await this.exportProfileItemsByIds(ids, includeSecrets);
|
|
500
|
+
|
|
501
|
+
const envelope: IntegrationProfileImportEnvelope = {
|
|
502
|
+
type: 'integration-profile',
|
|
503
|
+
version: 1,
|
|
504
|
+
generated_at: new Date().toISOString(),
|
|
505
|
+
include_secrets: includeSecrets,
|
|
506
|
+
// O envelope de integration-profile não carrega o id de origem.
|
|
507
|
+
data: items.map(({ id: _id, ...rest }) => rest),
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const compressed = pako.gzip(JSON.stringify(envelope));
|
|
511
|
+
return Buffer.from(compressed);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Returns export-ready profile items (slug/name/type_slug/provider_slug/config/
|
|
516
|
+
* is_active) plus the source-environment `id`. Used by callers that must reference
|
|
517
|
+
* profiles by id — e.g. the agent workflow export, which remaps node
|
|
518
|
+
* `integrationProfileId` through id → slug → (destination) id. `config` is decrypted
|
|
519
|
+
* defensively and sanitized per `includeSecrets`.
|
|
520
|
+
*/
|
|
521
|
+
async exportProfileItemsByIds(
|
|
522
|
+
ids: number[] | undefined,
|
|
523
|
+
includeSecrets: boolean,
|
|
524
|
+
): Promise<(IntegrationProfileExportItem & { id: number })[]> {
|
|
323
525
|
const rows = await this.prismaService.integration_profile.findMany({
|
|
324
526
|
where: ids?.length ? { id: { in: ids } } : undefined,
|
|
325
527
|
select: {
|
|
528
|
+
id: true,
|
|
326
529
|
slug: true,
|
|
327
530
|
name: true,
|
|
328
531
|
config: true,
|
|
@@ -337,29 +540,72 @@ export class IntegrationProfileService {
|
|
|
337
540
|
orderBy: [{ slug: 'asc' }],
|
|
338
541
|
});
|
|
339
542
|
|
|
340
|
-
|
|
543
|
+
return rows.map((row) => ({
|
|
544
|
+
id: row.id,
|
|
341
545
|
slug: row.slug,
|
|
342
546
|
name: row.name,
|
|
343
547
|
type_slug: row.integration_type.slug,
|
|
344
548
|
provider_slug: row.integration_provider.slug,
|
|
345
549
|
config: this.sanitizeConfigForExport(
|
|
346
550
|
row.integration_provider.slug,
|
|
347
|
-
|
|
551
|
+
// Decrypt defensivo: garante que o arquivo nunca leve um envelope preso à chave
|
|
552
|
+
// da origem (o middleware já decifra, isto é redundância de segurança).
|
|
553
|
+
this.credentialCrypto.decryptConfig(
|
|
554
|
+
row.config as Record<string, unknown> | null,
|
|
555
|
+
) as Record<string, unknown> | null,
|
|
348
556
|
includeSecrets,
|
|
349
557
|
),
|
|
350
558
|
is_active: row.is_active,
|
|
351
559
|
}));
|
|
560
|
+
}
|
|
352
561
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
562
|
+
/**
|
|
563
|
+
* Validates that each item's type_slug/provider_slug exist and that the provider
|
|
564
|
+
* belongs to the type. Returns a map of item slug → list of errors (empty = valid).
|
|
565
|
+
* Reused by external importers (e.g. the agent workflow import) to validate the
|
|
566
|
+
* credential profiles bundled inside a workflow file.
|
|
567
|
+
*/
|
|
568
|
+
async validateImportItems(
|
|
569
|
+
items: { slug: string; type_slug: string; provider_slug: string }[],
|
|
570
|
+
): Promise<Map<string, string[]>> {
|
|
571
|
+
const typeMap = new Map(
|
|
572
|
+
(
|
|
573
|
+
await this.prismaService.integration_type.findMany({
|
|
574
|
+
select: { id: true, slug: true },
|
|
575
|
+
})
|
|
576
|
+
).map((row) => [row.slug.toLowerCase(), row]),
|
|
577
|
+
);
|
|
360
578
|
|
|
361
|
-
const
|
|
362
|
-
|
|
579
|
+
const providerMap = new Map(
|
|
580
|
+
(
|
|
581
|
+
await this.prismaService.integration_provider.findMany({
|
|
582
|
+
select: { id: true, slug: true, type_id: true },
|
|
583
|
+
})
|
|
584
|
+
).map((row) => [row.slug.toLowerCase(), row]),
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
const out = new Map<string, string[]>();
|
|
588
|
+
for (const item of items) {
|
|
589
|
+
const errors: string[] = [];
|
|
590
|
+
const typeSlug = String(item.type_slug ?? '').trim().toLowerCase();
|
|
591
|
+
const providerSlug = String(item.provider_slug ?? '').trim().toLowerCase();
|
|
592
|
+
|
|
593
|
+
const type = typeMap.get(typeSlug);
|
|
594
|
+
const provider = providerMap.get(providerSlug);
|
|
595
|
+
if (!typeSlug) errors.push('Missing type_slug.');
|
|
596
|
+
else if (!type) errors.push(`Unknown type_slug "${typeSlug}".`);
|
|
597
|
+
if (!providerSlug) errors.push('Missing provider_slug.');
|
|
598
|
+
else if (!provider) errors.push(`Unknown provider_slug "${providerSlug}".`);
|
|
599
|
+
if (type && provider && provider.type_id !== type.id) {
|
|
600
|
+
errors.push(
|
|
601
|
+
`Provider "${providerSlug}" does not belong to type "${typeSlug}".`,
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
out.set(item.slug, errors);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return out;
|
|
363
609
|
}
|
|
364
610
|
|
|
365
611
|
async validateImportFile(file: MulterFile) {
|
|
@@ -509,6 +755,59 @@ export class IntegrationProfileService {
|
|
|
509
755
|
throw new BadRequestException('No profiles provided for import.');
|
|
510
756
|
}
|
|
511
757
|
|
|
758
|
+
const { counts } = await this.ensureProfilesBySlug(
|
|
759
|
+
profiles,
|
|
760
|
+
conflictResolutions,
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
return {
|
|
764
|
+
success: true,
|
|
765
|
+
total_received: profiles.length,
|
|
766
|
+
created: counts.created,
|
|
767
|
+
replaced: counts.replaced,
|
|
768
|
+
renamed: counts.renamed,
|
|
769
|
+
ignored: counts.ignored,
|
|
770
|
+
imported: counts.created + counts.replaced + counts.renamed,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Creates/updates integration profiles from import items, applying per-slug
|
|
776
|
+
* conflict resolutions, and returns a map from each item's (original) slug to the
|
|
777
|
+
* resulting profile id + applied action — plus aggregate counts. Re-encrypts config
|
|
778
|
+
* with the destination system key. Used by `confirmImport` (counts) and by the agent
|
|
779
|
+
* workflow import (which needs the resulting ids to remap node integrationProfileId).
|
|
780
|
+
*
|
|
781
|
+
* For `ignore` on a conflicting slug, the existing profile is left untouched but its
|
|
782
|
+
* id is still returned so callers can relink references to the local profile.
|
|
783
|
+
*/
|
|
784
|
+
async ensureProfilesBySlug(
|
|
785
|
+
profiles: ImportProfileItemDTO[],
|
|
786
|
+
conflictResolutions?: ConflictResolutionDTO[],
|
|
787
|
+
): Promise<{
|
|
788
|
+
results: Map<
|
|
789
|
+
string,
|
|
790
|
+
{
|
|
791
|
+
id: number;
|
|
792
|
+
slug: string;
|
|
793
|
+
action: 'created' | 'replaced' | 'renamed' | 'ignored';
|
|
794
|
+
}
|
|
795
|
+
>;
|
|
796
|
+
counts: { created: number; replaced: number; renamed: number; ignored: number };
|
|
797
|
+
}> {
|
|
798
|
+
const results = new Map<
|
|
799
|
+
string,
|
|
800
|
+
{
|
|
801
|
+
id: number;
|
|
802
|
+
slug: string;
|
|
803
|
+
action: 'created' | 'replaced' | 'renamed' | 'ignored';
|
|
804
|
+
}
|
|
805
|
+
>();
|
|
806
|
+
|
|
807
|
+
if (!profiles?.length) {
|
|
808
|
+
return { results, counts: { created: 0, replaced: 0, renamed: 0, ignored: 0 } };
|
|
809
|
+
}
|
|
810
|
+
|
|
512
811
|
const resolutionMap = new Map<string, ConflictResolutionDTO['action']>(
|
|
513
812
|
(conflictResolutions ?? []).map((resolution) => [
|
|
514
813
|
resolution.slug,
|
|
@@ -596,18 +895,27 @@ export class IntegrationProfileService {
|
|
|
596
895
|
name,
|
|
597
896
|
type_id: type.id,
|
|
598
897
|
provider_id: provider.id,
|
|
599
|
-
|
|
898
|
+
// Import traz texto puro (do .hedhog); cifra com a chave do sistema de DESTINO.
|
|
899
|
+
config: (this.credentialCrypto.encryptConfig(
|
|
900
|
+
providerSlug,
|
|
901
|
+
(item.config as Record<string, unknown>) ?? null,
|
|
902
|
+
) as object | null) ?? null,
|
|
600
903
|
is_active: item.is_active ?? true,
|
|
601
904
|
};
|
|
602
905
|
|
|
603
906
|
if (!existing) {
|
|
604
|
-
await this.prismaService.integration_profile.create({
|
|
907
|
+
const createdRow = await this.prismaService.integration_profile.create({
|
|
908
|
+
data,
|
|
909
|
+
select: { id: true },
|
|
910
|
+
});
|
|
911
|
+
results.set(slug, { id: createdRow.id, slug, action: 'created' });
|
|
605
912
|
created += 1;
|
|
606
913
|
continue;
|
|
607
914
|
}
|
|
608
915
|
|
|
609
916
|
const action = resolutionMap.get(slug) ?? 'ignore';
|
|
610
917
|
if (action === 'ignore') {
|
|
918
|
+
results.set(slug, { id: existing.id, slug, action: 'ignored' });
|
|
611
919
|
ignored += 1;
|
|
612
920
|
continue;
|
|
613
921
|
}
|
|
@@ -623,29 +931,24 @@ export class IntegrationProfileService {
|
|
|
623
931
|
is_active: data.is_active,
|
|
624
932
|
},
|
|
625
933
|
});
|
|
934
|
+
results.set(slug, { id: existing.id, slug, action: 'replaced' });
|
|
626
935
|
replaced += 1;
|
|
627
936
|
continue;
|
|
628
937
|
}
|
|
629
938
|
|
|
630
939
|
const newSlug = await this.generateUniqueSlug(slug);
|
|
631
|
-
await this.prismaService.integration_profile.create({
|
|
940
|
+
const renamedRow = await this.prismaService.integration_profile.create({
|
|
632
941
|
data: {
|
|
633
942
|
...data,
|
|
634
943
|
slug: newSlug,
|
|
635
944
|
},
|
|
945
|
+
select: { id: true },
|
|
636
946
|
});
|
|
947
|
+
results.set(slug, { id: renamedRow.id, slug: newSlug, action: 'renamed' });
|
|
637
948
|
renamed += 1;
|
|
638
949
|
}
|
|
639
950
|
|
|
640
|
-
return {
|
|
641
|
-
success: true,
|
|
642
|
-
total_received: profiles.length,
|
|
643
|
-
created,
|
|
644
|
-
replaced,
|
|
645
|
-
renamed,
|
|
646
|
-
ignored,
|
|
647
|
-
imported: created + replaced + renamed,
|
|
648
|
-
};
|
|
951
|
+
return { results, counts: { created, replaced, renamed, ignored } };
|
|
649
952
|
}
|
|
650
953
|
|
|
651
954
|
private async ensureSlugIsAvailable(slug: string) {
|
|
@@ -731,7 +1034,7 @@ export class IntegrationProfileService {
|
|
|
731
1034
|
): Record<string, unknown> | null {
|
|
732
1035
|
if (!config || includeSecrets) return config;
|
|
733
1036
|
|
|
734
|
-
const hiddenKeys =
|
|
1037
|
+
const hiddenKeys = getSensitiveConfigKeys(providerSlug);
|
|
735
1038
|
if (hiddenKeys.length === 0) return config;
|
|
736
1039
|
|
|
737
1040
|
const sanitized = { ...config };
|
|
@@ -742,28 +1045,6 @@ export class IntegrationProfileService {
|
|
|
742
1045
|
return sanitized;
|
|
743
1046
|
}
|
|
744
1047
|
|
|
745
|
-
private getSensitiveConfigKeys(providerSlug: string): string[] {
|
|
746
|
-
const map: Record<string, string[]> = {
|
|
747
|
-
smtp: ['password'],
|
|
748
|
-
gmail: ['client_secret', 'refresh_token'],
|
|
749
|
-
ses: ['secret_access_key'],
|
|
750
|
-
'evolution-api': ['token'],
|
|
751
|
-
'whatsapp-official': ['access_token'],
|
|
752
|
-
s3: ['secret_access_key'],
|
|
753
|
-
gcs: ['key_file_json'],
|
|
754
|
-
'azure-blob': ['key'],
|
|
755
|
-
's3-compatible': ['secret_access_key'],
|
|
756
|
-
openai: ['api_key'],
|
|
757
|
-
gemini: ['api_key'],
|
|
758
|
-
claude: ['api_key'],
|
|
759
|
-
'google-oauth': ['client_secret'],
|
|
760
|
-
'github-oauth': ['client_secret'],
|
|
761
|
-
'microsoft-oauth': ['client_secret'],
|
|
762
|
-
};
|
|
763
|
-
|
|
764
|
-
return map[providerSlug.toLowerCase()] ?? [];
|
|
765
|
-
}
|
|
766
|
-
|
|
767
1048
|
private async generateUniqueSlug(baseSlug: string): Promise<string> {
|
|
768
1049
|
let candidate = `${baseSlug}-copy`;
|
|
769
1050
|
let suffix = 1;
|
|
@@ -150,6 +150,62 @@ export function buildOAuthConfigFromIntegration(
|
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
// ─── Infrastructure utilities ──────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
export type DigitalOceanConfig = {
|
|
156
|
+
apiToken: string;
|
|
157
|
+
clusterId: string;
|
|
158
|
+
region: string;
|
|
159
|
+
videoNodePoolId: string;
|
|
160
|
+
videoNodePoolName: string;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/** Maps an integration_profile config JSON to the DigitalOcean API credentials/targets. */
|
|
164
|
+
export function buildDigitalOceanConfigFromIntegration(
|
|
165
|
+
providerSlug: string,
|
|
166
|
+
config: unknown,
|
|
167
|
+
): DigitalOceanConfig {
|
|
168
|
+
if (providerSlug.toLowerCase() !== 'digitalocean') {
|
|
169
|
+
throw new BadRequestException(
|
|
170
|
+
`Unsupported infrastructure provider: ${providerSlug}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const cfg = (config as Cfg) ?? {};
|
|
174
|
+
return {
|
|
175
|
+
apiToken: String(cfg.api_token ?? ''),
|
|
176
|
+
clusterId: String(cfg.cluster_id ?? ''),
|
|
177
|
+
region: String(cfg.region ?? ''),
|
|
178
|
+
videoNodePoolId: String(cfg.video_node_pool_id ?? ''),
|
|
179
|
+
videoNodePoolName: String(cfg.video_node_pool_name ?? ''),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export type KubernetesConfig = {
|
|
184
|
+
apiServer: string;
|
|
185
|
+
token: string;
|
|
186
|
+
caCert: string;
|
|
187
|
+
namespace: string;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/** Maps an integration_profile config JSON to read-only Kubernetes API access (opcional). */
|
|
191
|
+
export function buildKubernetesConfigFromIntegration(
|
|
192
|
+
providerSlug: string,
|
|
193
|
+
config: unknown,
|
|
194
|
+
): KubernetesConfig {
|
|
195
|
+
if (providerSlug.toLowerCase() !== 'kubernetes') {
|
|
196
|
+
throw new BadRequestException(
|
|
197
|
+
`Unsupported infrastructure provider: ${providerSlug}`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
const cfg = (config as Cfg) ?? {};
|
|
201
|
+
return {
|
|
202
|
+
apiServer: String(cfg.api_server ?? ''),
|
|
203
|
+
token: String(cfg.token ?? ''),
|
|
204
|
+
caCert: String(cfg.ca_cert ?? ''),
|
|
205
|
+
namespace: String(cfg.namespace ?? 'hcode'),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
153
209
|
// ─── AI utilities ─────────────────────────────────────────────────────────────
|
|
154
210
|
|
|
155
211
|
/** Maps an integration_profile config JSON to the API key and optional org needed by AI providers. */
|
package/src/language/en.json
CHANGED
|
@@ -25,7 +25,11 @@
|
|
|
25
25
|
"emailExists": "Email already in use."
|
|
26
26
|
},
|
|
27
27
|
"forgot": {
|
|
28
|
-
"rateLimit": "Too many requests. Please try again later."
|
|
28
|
+
"rateLimit": "Too many requests. Please try again later.",
|
|
29
|
+
"userNotFound": "User not found."
|
|
30
|
+
},
|
|
31
|
+
"challenge": {
|
|
32
|
+
"invalidOrExpired": "The provided code is invalid or has expired."
|
|
29
33
|
},
|
|
30
34
|
"reset": {
|
|
31
35
|
"password": {
|
package/src/language/pt.json
CHANGED
|
@@ -25,7 +25,11 @@
|
|
|
25
25
|
"emailExists": "E-mail já está em uso."
|
|
26
26
|
},
|
|
27
27
|
"forgot": {
|
|
28
|
-
"rateLimit": "Muitas solicitações. Por favor, tente novamente mais tarde."
|
|
28
|
+
"rateLimit": "Muitas solicitações. Por favor, tente novamente mais tarde.",
|
|
29
|
+
"userNotFound": "Usuário não encontrado."
|
|
30
|
+
},
|
|
31
|
+
"challenge": {
|
|
32
|
+
"invalidOrExpired": "O código informado é inválido ou expirou."
|
|
29
33
|
},
|
|
30
34
|
"reset": {
|
|
31
35
|
"password": {
|