@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
|
@@ -8,9 +8,13 @@ import {
|
|
|
8
8
|
Inject,
|
|
9
9
|
Injectable,
|
|
10
10
|
Logger,
|
|
11
|
-
NotFoundException
|
|
11
|
+
NotFoundException,
|
|
12
|
+
OnModuleDestroy,
|
|
13
|
+
OnModuleInit,
|
|
12
14
|
} from '@nestjs/common';
|
|
15
|
+
import { randomUUID } from 'node:crypto';
|
|
13
16
|
import * as pako from 'pako';
|
|
17
|
+
import { createClient, type RedisClientType } from 'redis';
|
|
14
18
|
import { IntegrationDeveloperApiService } from '../integration/services/integration-developer-api.service';
|
|
15
19
|
import { CreateDTO } from './dto/create.dto';
|
|
16
20
|
import { DeleteDTO } from './dto/delete.dto';
|
|
@@ -27,11 +31,34 @@ type SettingChangeEventInput = {
|
|
|
27
31
|
source: string;
|
|
28
32
|
};
|
|
29
33
|
|
|
34
|
+
type SettingsVersionMessage = {
|
|
35
|
+
originInstanceId: string;
|
|
36
|
+
version: number;
|
|
37
|
+
slugs: string[];
|
|
38
|
+
};
|
|
39
|
+
|
|
30
40
|
@Injectable()
|
|
31
|
-
export class SettingService {
|
|
41
|
+
export class SettingService implements OnModuleInit, OnModuleDestroy {
|
|
32
42
|
|
|
33
43
|
private readonly logger = new Logger(SettingService.name);
|
|
34
44
|
private cachedSettings: Record<string, any> = {};
|
|
45
|
+
private readonly instanceId = randomUUID();
|
|
46
|
+
private readonly redisChannel = 'core:settings:version-changed';
|
|
47
|
+
private readonly redisUrl = String(process.env.REDIS_URL || '').trim();
|
|
48
|
+
private readonly settingsVersionCheckIntervalMs = Math.max(
|
|
49
|
+
1000,
|
|
50
|
+
Number(process.env.SETTINGS_VERSION_CHECK_INTERVAL_MS || 5000),
|
|
51
|
+
);
|
|
52
|
+
private localSettingsVersion = 0;
|
|
53
|
+
private lastSettingsVersionCheckAt = 0;
|
|
54
|
+
private redisPublisher: RedisClientType | null = null;
|
|
55
|
+
private redisSubscriber: RedisClientType | null = null;
|
|
56
|
+
private readonly redisErrorLogCooldownMs = Math.max(
|
|
57
|
+
1000,
|
|
58
|
+
Number(process.env.REDIS_SETTINGS_ERROR_LOG_COOLDOWN_MS || 30000),
|
|
59
|
+
);
|
|
60
|
+
private lastRedisSubscriberErrorLogAt = 0;
|
|
61
|
+
private lastRedisPublisherErrorLogAt = 0;
|
|
35
62
|
|
|
36
63
|
constructor(
|
|
37
64
|
@Inject(forwardRef(() => PrismaService))
|
|
@@ -44,6 +71,231 @@ export class SettingService {
|
|
|
44
71
|
private readonly integrationApi: IntegrationDeveloperApiService,
|
|
45
72
|
) { }
|
|
46
73
|
|
|
74
|
+
async onModuleInit() {
|
|
75
|
+
await this.syncSettingsVersionFromDatabase();
|
|
76
|
+
await this.initializeRedisSync();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async onModuleDestroy() {
|
|
80
|
+
await Promise.allSettled([
|
|
81
|
+
this.safeCloseRedisClient(this.redisSubscriber),
|
|
82
|
+
this.safeCloseRedisClient(this.redisPublisher),
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private async safeCloseRedisClient(client: RedisClientType | null) {
|
|
87
|
+
if (!client || !client.isOpen) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
await client.quit();
|
|
93
|
+
} catch {
|
|
94
|
+
try {
|
|
95
|
+
client.disconnect();
|
|
96
|
+
} catch {
|
|
97
|
+
// ignore shutdown errors
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private logRedisClientError(kind: 'subscriber' | 'publisher', error: unknown) {
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
const lastLogAt =
|
|
105
|
+
kind === 'subscriber'
|
|
106
|
+
? this.lastRedisSubscriberErrorLogAt
|
|
107
|
+
: this.lastRedisPublisherErrorLogAt;
|
|
108
|
+
|
|
109
|
+
if (now - lastLogAt < this.redisErrorLogCooldownMs) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (kind === 'subscriber') {
|
|
114
|
+
this.lastRedisSubscriberErrorLogAt = now;
|
|
115
|
+
} else {
|
|
116
|
+
this.lastRedisPublisherErrorLogAt = now;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
120
|
+
this.logger.warn(`Redis settings ${kind} error: ${message}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private async initializeRedisSync() {
|
|
124
|
+
if (!this.redisUrl) {
|
|
125
|
+
this.logger.log(
|
|
126
|
+
'Redis settings sync disabled because REDIS_URL is not configured.',
|
|
127
|
+
);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
this.redisSubscriber = createClient({
|
|
133
|
+
url: this.redisUrl,
|
|
134
|
+
socket: {
|
|
135
|
+
reconnectStrategy: (retries) => Math.min(retries * 250, 5000),
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
this.redisPublisher = createClient({
|
|
139
|
+
url: this.redisUrl,
|
|
140
|
+
socket: {
|
|
141
|
+
reconnectStrategy: (retries) => Math.min(retries * 250, 5000),
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this.redisSubscriber.on('error', (error) => {
|
|
146
|
+
this.logRedisClientError('subscriber', error);
|
|
147
|
+
});
|
|
148
|
+
this.redisPublisher.on('error', (error) => {
|
|
149
|
+
this.logRedisClientError('publisher', error);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await this.redisSubscriber.connect();
|
|
153
|
+
await this.redisPublisher.connect();
|
|
154
|
+
await this.redisSubscriber.subscribe(this.redisChannel, async (message) => {
|
|
155
|
+
await this.handleRedisSettingsVersionMessage(message);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
this.logger.log(
|
|
159
|
+
`Redis settings sync enabled on channel ${this.redisChannel}`,
|
|
160
|
+
);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
this.logger.warn(
|
|
163
|
+
`Failed to initialize Redis settings sync: ${error instanceof Error ? error.message : String(error)}`,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
await Promise.allSettled([
|
|
167
|
+
this.safeCloseRedisClient(this.redisSubscriber),
|
|
168
|
+
this.safeCloseRedisClient(this.redisPublisher),
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
this.redisSubscriber = null;
|
|
172
|
+
this.redisPublisher = null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private async handleRedisSettingsVersionMessage(message: string) {
|
|
177
|
+
let payload: SettingsVersionMessage;
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
payload = JSON.parse(message) as SettingsVersionMessage;
|
|
181
|
+
} catch {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!payload || payload.originInstanceId === this.instanceId) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!Number.isFinite(payload.version) || payload.version <= this.localSettingsVersion) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.localSettingsVersion = payload.version;
|
|
194
|
+
this.lastSettingsVersionCheckAt = Date.now();
|
|
195
|
+
this.clearCache();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async ensureSettingsVersionState() {
|
|
199
|
+
await this.prismaService.$executeRawUnsafe(`
|
|
200
|
+
INSERT INTO "setting_runtime_state" ("scope", "version", "updated_at")
|
|
201
|
+
VALUES ('global', 0, NOW())
|
|
202
|
+
ON CONFLICT ("scope") DO NOTHING
|
|
203
|
+
`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private parseVersionValue(value: unknown): number {
|
|
207
|
+
if (typeof value === 'number') {
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (typeof value === 'bigint') {
|
|
212
|
+
return Number(value);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (typeof value === 'string') {
|
|
216
|
+
return Number(value);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private async getSettingsVersionFromDatabase(): Promise<number> {
|
|
223
|
+
await this.ensureSettingsVersionState();
|
|
224
|
+
|
|
225
|
+
const rows = await this.prismaService.$queryRawUnsafe<Array<{ version: unknown }>>(`
|
|
226
|
+
SELECT "version"
|
|
227
|
+
FROM "setting_runtime_state"
|
|
228
|
+
WHERE "scope" = 'global'
|
|
229
|
+
LIMIT 1
|
|
230
|
+
`);
|
|
231
|
+
|
|
232
|
+
return this.parseVersionValue(rows[0]?.version);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private async syncSettingsVersionFromDatabase() {
|
|
236
|
+
this.localSettingsVersion = await this.getSettingsVersionFromDatabase();
|
|
237
|
+
this.lastSettingsVersionCheckAt = Date.now();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private async bumpSettingsVersion(): Promise<number> {
|
|
241
|
+
await this.ensureSettingsVersionState();
|
|
242
|
+
|
|
243
|
+
const rows = await this.prismaService.$queryRawUnsafe<Array<{ version: unknown }>>(`
|
|
244
|
+
UPDATE "setting_runtime_state"
|
|
245
|
+
SET "version" = "version" + 1,
|
|
246
|
+
"updated_at" = NOW()
|
|
247
|
+
WHERE "scope" = 'global'
|
|
248
|
+
RETURNING "version"
|
|
249
|
+
`);
|
|
250
|
+
|
|
251
|
+
const nextVersion = this.parseVersionValue(rows[0]?.version);
|
|
252
|
+
this.localSettingsVersion = nextVersion;
|
|
253
|
+
this.lastSettingsVersionCheckAt = Date.now();
|
|
254
|
+
return nextVersion;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private async ensureCacheVersionFresh() {
|
|
258
|
+
const now = Date.now();
|
|
259
|
+
if (now - this.lastSettingsVersionCheckAt < this.settingsVersionCheckIntervalMs) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const currentVersion = await this.getSettingsVersionFromDatabase();
|
|
264
|
+
this.lastSettingsVersionCheckAt = now;
|
|
265
|
+
|
|
266
|
+
if (currentVersion > this.localSettingsVersion) {
|
|
267
|
+
this.localSettingsVersion = currentVersion;
|
|
268
|
+
this.clearCache();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private async publishSettingsVersionChange(slugs: string[]) {
|
|
273
|
+
const filteredSlugs = Array.from(new Set(slugs.filter(Boolean)));
|
|
274
|
+
if (filteredSlugs.length === 0) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const nextVersion = await this.bumpSettingsVersion();
|
|
279
|
+
|
|
280
|
+
if (!this.redisPublisher?.isOpen) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const payload: SettingsVersionMessage = {
|
|
285
|
+
originInstanceId: this.instanceId,
|
|
286
|
+
version: nextVersion,
|
|
287
|
+
slugs: filteredSlugs,
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
await this.redisPublisher.publish(this.redisChannel, JSON.stringify(payload));
|
|
292
|
+
} catch (error) {
|
|
293
|
+
this.logger.warn(
|
|
294
|
+
`Failed to publish settings version change: ${error instanceof Error ? error.message : String(error)}`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
47
299
|
async exportSettings(includeSecrets = false): Promise<Buffer> {
|
|
48
300
|
const settings = await this.prismaService.setting.findMany({
|
|
49
301
|
where: {
|
|
@@ -158,6 +410,7 @@ export class SettingService {
|
|
|
158
410
|
if (transaction.length > 0) {
|
|
159
411
|
await this.prismaService.$transaction(transaction);
|
|
160
412
|
await this.emitSettingChangedEvents(changedSettings);
|
|
413
|
+
await this.publishSettingsVersionChange(changedSettings.map((item) => item.slug));
|
|
161
414
|
}
|
|
162
415
|
|
|
163
416
|
return {
|
|
@@ -211,7 +464,14 @@ export class SettingService {
|
|
|
211
464
|
'time-format',
|
|
212
465
|
'timezone',
|
|
213
466
|
'menu-width',
|
|
467
|
+
'maintenance-mode-enabled',
|
|
214
468
|
'providers',
|
|
469
|
+
'oauth-facebook-enabled',
|
|
470
|
+
'oauth-github-enabled',
|
|
471
|
+
'oauth-google-enabled',
|
|
472
|
+
'oauth-microsoft-enabled',
|
|
473
|
+
'oauth-microsoft-entra-id-enabled',
|
|
474
|
+
'admin-login-page-oauth-providers',
|
|
215
475
|
'disable-authentication-with-email-and-password',
|
|
216
476
|
'contact-allow-company-registration',
|
|
217
477
|
'operations.comment-edit-window',
|
|
@@ -311,6 +571,7 @@ export class SettingService {
|
|
|
311
571
|
|
|
312
572
|
await this.prismaService.$transaction(transaction);
|
|
313
573
|
await this.emitSettingChangedEvents(changedSettings);
|
|
574
|
+
await this.publishSettingsVersionChange(changedSettings.map((item) => item.slug));
|
|
314
575
|
return { success: true };
|
|
315
576
|
}
|
|
316
577
|
|
|
@@ -655,6 +916,8 @@ export class SettingService {
|
|
|
655
916
|
},
|
|
656
917
|
]);
|
|
657
918
|
|
|
919
|
+
await this.publishSettingsVersionChange([createdSetting.slug]);
|
|
920
|
+
|
|
658
921
|
return createdSetting;
|
|
659
922
|
}
|
|
660
923
|
|
|
@@ -726,6 +989,8 @@ export class SettingService {
|
|
|
726
989
|
},
|
|
727
990
|
]);
|
|
728
991
|
|
|
992
|
+
await this.publishSettingsVersionChange([updatedSetting.slug]);
|
|
993
|
+
|
|
729
994
|
// Garantir que o value sobrescreve o valor original
|
|
730
995
|
const result = { ...updatedSetting };
|
|
731
996
|
result.value = this.getValueFormattedByType(updatedSetting.type, updatedSetting.value);
|
|
@@ -770,6 +1035,8 @@ export class SettingService {
|
|
|
770
1035
|
},
|
|
771
1036
|
]);
|
|
772
1037
|
|
|
1038
|
+
await this.publishSettingsVersionChange([updatedSetting.slug]);
|
|
1039
|
+
|
|
773
1040
|
const result = { ...updatedSetting };
|
|
774
1041
|
result.value = this.getValueFormattedByType(updatedSetting.type, updatedSetting.value);
|
|
775
1042
|
return result;
|
|
@@ -855,8 +1122,18 @@ export class SettingService {
|
|
|
855
1122
|
switch (type) {
|
|
856
1123
|
case 'boolean':
|
|
857
1124
|
return value === true || value === 'true' ? 'true' : 'false';
|
|
858
|
-
case 'number':
|
|
859
|
-
|
|
1125
|
+
case 'number': {
|
|
1126
|
+
if (value === undefined || value === null || String(value).trim() === '') {
|
|
1127
|
+
return '';
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
const numericValue = Number(value);
|
|
1131
|
+
if (!Number.isFinite(numericValue)) {
|
|
1132
|
+
throw new BadRequestException(`Invalid numeric value for setting: ${value}`);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
return numericValue.toString();
|
|
1136
|
+
}
|
|
860
1137
|
case 'array':
|
|
861
1138
|
case 'json':
|
|
862
1139
|
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
@@ -869,8 +1146,18 @@ export class SettingService {
|
|
|
869
1146
|
switch (type) {
|
|
870
1147
|
case 'boolean':
|
|
871
1148
|
return value === true || value === 'true' ? 'true' : 'false';
|
|
872
|
-
case 'number':
|
|
873
|
-
|
|
1149
|
+
case 'number': {
|
|
1150
|
+
if (value === undefined || value === null || String(value).trim() === '') {
|
|
1151
|
+
return '';
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const numericValue = Number(value);
|
|
1155
|
+
if (!Number.isFinite(numericValue)) {
|
|
1156
|
+
throw new BadRequestException(`Invalid numeric value for setting: ${value}`);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
return numericValue.toString();
|
|
1160
|
+
}
|
|
874
1161
|
case 'array':
|
|
875
1162
|
case 'json':
|
|
876
1163
|
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
@@ -895,6 +1182,7 @@ export class SettingService {
|
|
|
895
1182
|
},
|
|
896
1183
|
select: {
|
|
897
1184
|
id: true,
|
|
1185
|
+
slug: true,
|
|
898
1186
|
},
|
|
899
1187
|
});
|
|
900
1188
|
|
|
@@ -904,13 +1192,19 @@ export class SettingService {
|
|
|
904
1192
|
);
|
|
905
1193
|
}
|
|
906
1194
|
|
|
907
|
-
|
|
1195
|
+
const deleted = await this.prismaService.setting.deleteMany({
|
|
908
1196
|
where: {
|
|
909
1197
|
id: {
|
|
910
1198
|
in: ids,
|
|
911
1199
|
},
|
|
912
1200
|
},
|
|
913
1201
|
});
|
|
1202
|
+
|
|
1203
|
+
if (deleted.count > 0) {
|
|
1204
|
+
await this.publishSettingsVersionChange(existingSettings.map((item) => item.slug));
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
return deleted;
|
|
914
1208
|
}
|
|
915
1209
|
|
|
916
1210
|
async setSettingUserValue(locale:string, user_id: number, slug: string, value: string) {
|
|
@@ -970,6 +1264,10 @@ export class SettingService {
|
|
|
970
1264
|
): Promise<Record<string, any>> {
|
|
971
1265
|
|
|
972
1266
|
const slugs = Array.isArray(slug) ? slug : [slug];
|
|
1267
|
+
if (userId === undefined) {
|
|
1268
|
+
await this.ensureCacheVersionFresh();
|
|
1269
|
+
}
|
|
1270
|
+
|
|
973
1271
|
if (
|
|
974
1272
|
userId === undefined &&
|
|
975
1273
|
this.cachedSettings &&
|
|
@@ -1142,6 +1440,8 @@ export class SettingService {
|
|
|
1142
1440
|
},
|
|
1143
1441
|
]);
|
|
1144
1442
|
|
|
1443
|
+
await this.publishSettingsVersionChange([currentSetting.slug]);
|
|
1444
|
+
|
|
1145
1445
|
return result;
|
|
1146
1446
|
}
|
|
1147
1447
|
}
|
|
@@ -12,12 +12,14 @@ import {
|
|
|
12
12
|
ParseIntPipe,
|
|
13
13
|
Patch,
|
|
14
14
|
Post,
|
|
15
|
+
Req,
|
|
15
16
|
Res,
|
|
16
17
|
UploadedFile,
|
|
17
18
|
UseInterceptors,
|
|
18
19
|
forwardRef,
|
|
19
20
|
} from '@nestjs/common';
|
|
20
21
|
import { FileInterceptor } from '@nestjs/platform-express';
|
|
22
|
+
import { Request } from 'express';
|
|
21
23
|
import { DeleteDTO } from '../dto/delete.dto';
|
|
22
24
|
import { CreateWithEmailAndPasswordDTO } from './dto/create-with-email-and-password.dto';
|
|
23
25
|
import { ResetPasswordDTO } from './dto/reset-password.dto';
|
|
@@ -47,9 +49,10 @@ export class UserController {
|
|
|
47
49
|
async openPublicAvatar(
|
|
48
50
|
@Param('fileId', ParseIntPipe) fileId: number,
|
|
49
51
|
@Locale() locale: string,
|
|
52
|
+
@Req() req: Request,
|
|
50
53
|
@Res() res,
|
|
51
54
|
) {
|
|
52
|
-
return this.userService.openPublicAvatar(locale, fileId, res);
|
|
55
|
+
return this.userService.openPublicAvatar(locale, fileId, req.headers['if-none-match'] as string | undefined, res);
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
@Post()
|
package/src/user/user.service.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
1
2
|
import { getLocaleText } from '@hed-hog/api-locale';
|
|
2
3
|
import { PaginationService } from '@hed-hog/api-pagination';
|
|
3
4
|
import { PrismaService } from '@hed-hog/api-prisma';
|
|
@@ -13,6 +14,7 @@ import { DeleteDTO } from '../dto/delete.dto';
|
|
|
13
14
|
import { FileService } from '../file/file.service';
|
|
14
15
|
import { IntegrationDeveloperApiService } from '../integration/services/integration-developer-api.service';
|
|
15
16
|
import { SecurityService } from '../security/security.service';
|
|
17
|
+
import { SettingService } from '../setting/setting.service';
|
|
16
18
|
import { USER_AVATAR_UPLOAD_DESTINATION } from './constants/user.constants';
|
|
17
19
|
import { CreateWithEmailAndPasswordDTO } from './dto/create-with-email-and-password.dto';
|
|
18
20
|
import { ResetPasswordDTO } from './dto/reset-password.dto';
|
|
@@ -27,6 +29,7 @@ const CREDENTIAL_TYPE = {
|
|
|
27
29
|
PASSWORD: 'password',
|
|
28
30
|
} as const;
|
|
29
31
|
|
|
32
|
+
const USER_AVATAR_CACHE_MAX_AGE_SETTING = 'user-avatar-cache-max-age';
|
|
30
33
|
const DEFAULT_ROLE_SLUG = 'user';
|
|
31
34
|
const DEFAULT_LOCALE = 'en';
|
|
32
35
|
const DAYS_IN_MS = 24 * 60 * 60 * 1000;
|
|
@@ -68,6 +71,8 @@ export class UserService {
|
|
|
68
71
|
private readonly file: FileService,
|
|
69
72
|
@Inject(forwardRef(() => IntegrationDeveloperApiService))
|
|
70
73
|
private readonly integrationApi: IntegrationDeveloperApiService,
|
|
74
|
+
@Inject(forwardRef(() => SettingService))
|
|
75
|
+
private readonly settingService: SettingService,
|
|
71
76
|
) { }
|
|
72
77
|
|
|
73
78
|
// ==========================================
|
|
@@ -319,7 +324,7 @@ export class UserService {
|
|
|
319
324
|
};
|
|
320
325
|
}
|
|
321
326
|
|
|
322
|
-
async openPublicAvatar(locale: string, fileId: number, res: any) {
|
|
327
|
+
async openPublicAvatar(locale: string, fileId: number, ifNoneMatch: string | undefined, res: any) {
|
|
323
328
|
const userWithAvatar = await this.prismaService.user.findFirst({
|
|
324
329
|
where: {
|
|
325
330
|
photo_id: fileId,
|
|
@@ -337,10 +342,23 @@ export class UserService {
|
|
|
337
342
|
|
|
338
343
|
const { file, buffer } = await this.file.getBuffer(fileId);
|
|
339
344
|
|
|
345
|
+
const etag = `"${createHash('md5').update(buffer).digest('hex')}"`;
|
|
346
|
+
|
|
347
|
+
if (ifNoneMatch === etag) {
|
|
348
|
+
res.status(304).send();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const settings = await this.settingService.getSettingValues(
|
|
353
|
+
USER_AVATAR_CACHE_MAX_AGE_SETTING,
|
|
354
|
+
);
|
|
355
|
+
const maxAge = Number(settings[USER_AVATAR_CACHE_MAX_AGE_SETTING] ?? 3600);
|
|
356
|
+
|
|
340
357
|
res.set({
|
|
341
358
|
'Content-Type': file.file_mimetype.name,
|
|
342
359
|
'Content-Length': buffer.length,
|
|
343
|
-
'Cache-Control':
|
|
360
|
+
'Cache-Control': `public, max-age=${maxAge}`,
|
|
361
|
+
'ETag': etag,
|
|
344
362
|
});
|
|
345
363
|
|
|
346
364
|
res.send(buffer);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Transform } from 'class-transformer';
|
|
2
2
|
import { PartialType } from '@nestjs/mapped-types';
|
|
3
|
-
import { IsIn, IsInt, IsOptional, IsString } from 'class-validator';
|
|
3
|
+
import { IsIn, IsInt, IsObject, IsOptional, IsString } from 'class-validator';
|
|
4
4
|
|
|
5
5
|
const toNullableInteger = ({ value }: { value: unknown }) => {
|
|
6
6
|
if (value === undefined || value === null || value === '') {
|
|
@@ -19,8 +19,19 @@ const toOptionalTrimmedString = ({ value }: { value: unknown }) => {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export class CreateWebhookActionDTO {
|
|
22
|
-
@IsIn([
|
|
23
|
-
|
|
22
|
+
@IsIn([
|
|
23
|
+
'email',
|
|
24
|
+
'whatsapp_evolution',
|
|
25
|
+
'http_request',
|
|
26
|
+
'internal_api',
|
|
27
|
+
'app_command',
|
|
28
|
+
])
|
|
29
|
+
type:
|
|
30
|
+
| 'email'
|
|
31
|
+
| 'whatsapp_evolution'
|
|
32
|
+
| 'http_request'
|
|
33
|
+
| 'internal_api'
|
|
34
|
+
| 'app_command';
|
|
24
35
|
|
|
25
36
|
@IsOptional()
|
|
26
37
|
@Transform(toOptionalTrimmedString)
|
|
@@ -39,7 +50,7 @@ export class CreateWebhookActionDTO {
|
|
|
39
50
|
@IsOptional()
|
|
40
51
|
@Transform(toNullableInteger)
|
|
41
52
|
@IsInt()
|
|
42
|
-
|
|
53
|
+
integration_profile_id?: number;
|
|
43
54
|
|
|
44
55
|
@IsOptional()
|
|
45
56
|
@Transform(toNullableInteger)
|
|
@@ -89,6 +100,76 @@ export class CreateWebhookActionDTO {
|
|
|
89
100
|
@Transform(toOptionalTrimmedString)
|
|
90
101
|
@IsString()
|
|
91
102
|
whatsapp_token?: string;
|
|
103
|
+
|
|
104
|
+
@IsOptional()
|
|
105
|
+
@Transform(toOptionalTrimmedString)
|
|
106
|
+
@IsString()
|
|
107
|
+
http_url?: string;
|
|
108
|
+
|
|
109
|
+
@IsOptional()
|
|
110
|
+
@Transform(toOptionalTrimmedString)
|
|
111
|
+
@IsString()
|
|
112
|
+
http_method?: string;
|
|
113
|
+
|
|
114
|
+
@IsOptional()
|
|
115
|
+
@IsObject()
|
|
116
|
+
http_headers?: Record<string, string>;
|
|
117
|
+
|
|
118
|
+
@IsOptional()
|
|
119
|
+
@IsObject()
|
|
120
|
+
http_query?: Record<string, string>;
|
|
121
|
+
|
|
122
|
+
@IsOptional()
|
|
123
|
+
@Transform(toOptionalTrimmedString)
|
|
124
|
+
@IsString()
|
|
125
|
+
http_body?: string;
|
|
126
|
+
|
|
127
|
+
@IsOptional()
|
|
128
|
+
@Transform(toNullableInteger)
|
|
129
|
+
@IsInt()
|
|
130
|
+
http_timeout_ms?: number;
|
|
131
|
+
|
|
132
|
+
@IsOptional()
|
|
133
|
+
@Transform(toNullableInteger)
|
|
134
|
+
@IsInt()
|
|
135
|
+
http_retry_count?: number;
|
|
136
|
+
|
|
137
|
+
@IsOptional()
|
|
138
|
+
@Transform(toOptionalTrimmedString)
|
|
139
|
+
@IsString()
|
|
140
|
+
internal_api_path?: string;
|
|
141
|
+
|
|
142
|
+
@IsOptional()
|
|
143
|
+
@Transform(toOptionalTrimmedString)
|
|
144
|
+
@IsString()
|
|
145
|
+
internal_api_method?: string;
|
|
146
|
+
|
|
147
|
+
@IsOptional()
|
|
148
|
+
@IsObject()
|
|
149
|
+
internal_api_query?: Record<string, unknown>;
|
|
150
|
+
|
|
151
|
+
@IsOptional()
|
|
152
|
+
@IsObject()
|
|
153
|
+
internal_api_body?: Record<string, unknown>;
|
|
154
|
+
|
|
155
|
+
@IsOptional()
|
|
156
|
+
@Transform(toOptionalTrimmedString)
|
|
157
|
+
@IsString()
|
|
158
|
+
internal_api_token?: string;
|
|
159
|
+
|
|
160
|
+
@IsOptional()
|
|
161
|
+
@Transform(toNullableInteger)
|
|
162
|
+
@IsInt()
|
|
163
|
+
internal_api_user_id?: number;
|
|
164
|
+
|
|
165
|
+
@IsOptional()
|
|
166
|
+
@Transform(toOptionalTrimmedString)
|
|
167
|
+
@IsString()
|
|
168
|
+
app_command_slug?: string;
|
|
169
|
+
|
|
170
|
+
@IsOptional()
|
|
171
|
+
@IsObject()
|
|
172
|
+
app_command_params?: Record<string, unknown>;
|
|
92
173
|
}
|
|
93
174
|
|
|
94
175
|
export class UpdateWebhookActionDTO extends PartialType(
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
export interface WebhookCommand {
|
|
4
|
+
slug: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
inputSchema: Record<string, unknown>;
|
|
8
|
+
handler: (
|
|
9
|
+
params: Record<string, unknown>,
|
|
10
|
+
context?: Record<string, unknown>,
|
|
11
|
+
) => Promise<unknown>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class WebhookCommandRegistry {
|
|
16
|
+
private readonly commands = new Map<string, WebhookCommand>();
|
|
17
|
+
|
|
18
|
+
register(command: WebhookCommand): void {
|
|
19
|
+
this.commands.set(command.slug, command);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
find(slug: string): WebhookCommand | undefined {
|
|
23
|
+
return this.commands.get(slug);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
list(): WebhookCommand[] {
|
|
27
|
+
return Array.from(this.commands.values());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async dispatch(
|
|
31
|
+
slug: string,
|
|
32
|
+
params: Record<string, unknown>,
|
|
33
|
+
context?: Record<string, unknown>,
|
|
34
|
+
): Promise<unknown> {
|
|
35
|
+
const command = this.find(slug);
|
|
36
|
+
if (!command) {
|
|
37
|
+
throw new Error(`Unknown app_command slug: ${slug}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return command.handler(params, context);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -31,6 +31,26 @@ export class WebhookIntegrationController {
|
|
|
31
31
|
return this.service.list(paginationParams);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
@Get('user-tokens')
|
|
35
|
+
listUserTokens(@Query('userId', ParseIntPipe) userId: number) {
|
|
36
|
+
return this.service.listUserApiTokens(userId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Post('user-tokens')
|
|
40
|
+
createUserToken(@Body() data: { userId: number; name: string }) {
|
|
41
|
+
return this.service.createUserApiToken(data.userId, data.name);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Get('internal-routes')
|
|
45
|
+
listInternalRoutes() {
|
|
46
|
+
return this.service.listInternalRoutes();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Get('app-commands')
|
|
50
|
+
listAppCommands() {
|
|
51
|
+
return this.service.listAppCommands();
|
|
52
|
+
}
|
|
53
|
+
|
|
34
54
|
@Get(':id')
|
|
35
55
|
get(@Param('id', ParseIntPipe) id: number) {
|
|
36
56
|
return this.service.get(id);
|