@hed-hog/core 0.0.364 → 0.0.365
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/ai.controller.d.ts +4 -4
- package/dist/ai/ai.module.d.ts.map +1 -1
- package/dist/ai/ai.module.js +2 -0
- package/dist/ai/ai.module.js.map +1 -1
- package/dist/ai/ai.service.d.ts +7 -5
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +63 -31
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/ai-instruction/ai-execution.service.d.ts +45 -0
- package/dist/ai-instruction/ai-execution.service.d.ts.map +1 -0
- package/dist/ai-instruction/ai-execution.service.js +203 -0
- package/dist/ai-instruction/ai-execution.service.js.map +1 -0
- package/dist/ai-instruction/ai-instruction.controller.d.ts +25 -0
- package/dist/ai-instruction/ai-instruction.controller.d.ts.map +1 -0
- package/dist/ai-instruction/ai-instruction.controller.js +117 -0
- package/dist/ai-instruction/ai-instruction.controller.js.map +1 -0
- package/dist/ai-instruction/ai-instruction.module.d.ts +3 -0
- package/dist/ai-instruction/ai-instruction.module.d.ts.map +1 -0
- package/dist/ai-instruction/ai-instruction.module.js +27 -0
- package/dist/ai-instruction/ai-instruction.module.js.map +1 -0
- package/dist/ai-instruction/ai-instruction.service.d.ts +38 -0
- package/dist/ai-instruction/ai-instruction.service.d.ts.map +1 -0
- package/dist/ai-instruction/ai-instruction.service.js +289 -0
- package/dist/ai-instruction/ai-instruction.service.js.map +1 -0
- package/dist/ai-instruction/ai-prompt-builder.service.d.ts +53 -0
- package/dist/ai-instruction/ai-prompt-builder.service.d.ts.map +1 -0
- package/dist/ai-instruction/ai-prompt-builder.service.js +132 -0
- package/dist/ai-instruction/ai-prompt-builder.service.js.map +1 -0
- package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts +12 -0
- package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts.map +1 -0
- package/dist/ai-instruction/dto/create-ai-instruction.dto.js +57 -0
- package/dist/ai-instruction/dto/create-ai-instruction.dto.js.map +1 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts +10 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts.map +1 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.js +56 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.js.map +1 -0
- package/dist/ai-instruction/types/ai-instruction.types.d.ts +78 -0
- package/dist/ai-instruction/types/ai-instruction.types.d.ts.map +1 -0
- package/dist/ai-instruction/types/ai-instruction.types.js +3 -0
- package/dist/ai-instruction/types/ai-instruction.types.js.map +1 -0
- package/dist/auth/auth.controller.d.ts +13 -5
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +39 -20
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts +18 -7
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +20 -16
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +3 -0
- package/dist/core.module.js.map +1 -1
- package/dist/file/file.service.d.ts +37 -0
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +94 -4
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/provider/abstract.provider.d.ts +6 -2
- package/dist/file/provider/abstract.provider.d.ts.map +1 -1
- package/dist/file/provider/abstract.provider.js.map +1 -1
- package/dist/file/provider/azure.provider.d.ts +4 -1
- package/dist/file/provider/azure.provider.d.ts.map +1 -1
- package/dist/file/provider/azure.provider.js +7 -2
- package/dist/file/provider/azure.provider.js.map +1 -1
- package/dist/file/provider/gcs.provider.d.ts +4 -1
- package/dist/file/provider/gcs.provider.d.ts.map +1 -1
- package/dist/file/provider/gcs.provider.js +7 -2
- package/dist/file/provider/gcs.provider.js.map +1 -1
- package/dist/file/provider/local.provider.d.ts +2 -2
- package/dist/file/provider/local.provider.d.ts.map +1 -1
- package/dist/file/provider/local.provider.js +7 -2
- package/dist/file/provider/local.provider.js.map +1 -1
- package/dist/file/provider/s3.provider.d.ts +4 -1
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +9 -6
- package/dist/file/provider/s3.provider.js.map +1 -1
- package/dist/file/provider/spaces.provider.d.ts +4 -1
- package/dist/file/provider/spaces.provider.d.ts.map +1 -1
- package/dist/file/provider/spaces.provider.js +9 -5
- package/dist/file/provider/spaces.provider.js.map +1 -1
- package/dist/integration-profile/integration-profile.controller.d.ts +3 -3
- package/dist/integration-profile/integration-profile.service.d.ts +3 -3
- package/dist/mcp-chat/mcp-chat.controller.d.ts +2 -2
- package/dist/mcp-chat/mcp-chat.module.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.module.js +2 -0
- package/dist/mcp-chat/mcp-chat.module.js.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.d.ts +8 -3
- package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +100 -20
- package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
- package/dist/notification/notification.service.d.ts +1 -0
- package/dist/notification/notification.service.d.ts.map +1 -1
- package/dist/notification/notification.service.js +29 -1
- package/dist/notification/notification.service.js.map +1 -1
- package/dist/oauth/oauth.service.d.ts +1 -0
- package/dist/oauth/oauth.service.d.ts.map +1 -1
- package/dist/oauth/oauth.service.js +55 -1
- package/dist/oauth/oauth.service.js.map +1 -1
- package/dist/profile/events/profile-avatar-updated.event.d.ts +7 -0
- package/dist/profile/events/profile-avatar-updated.event.d.ts.map +1 -0
- package/dist/profile/events/profile-avatar-updated.event.js +12 -0
- package/dist/profile/events/profile-avatar-updated.event.js.map +1 -0
- package/dist/profile/events/profile-email-changed.event.d.ts +7 -0
- package/dist/profile/events/profile-email-changed.event.d.ts.map +1 -0
- package/dist/profile/events/profile-email-changed.event.js +12 -0
- package/dist/profile/events/profile-email-changed.event.js.map +1 -0
- package/dist/profile/events/profile-updated.event.d.ts +7 -0
- package/dist/profile/events/profile-updated.event.d.ts.map +1 -0
- package/dist/profile/events/profile-updated.event.js +12 -0
- package/dist/profile/events/profile-updated.event.js.map +1 -0
- package/dist/profile/profile.service.d.ts +3 -1
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +28 -17
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/session/session.controller.d.ts +1 -0
- package/dist/session/session.controller.d.ts.map +1 -1
- package/dist/session/session.service.d.ts +9 -4
- package/dist/session/session.service.d.ts.map +1 -1
- package/dist/session/session.service.js +61 -51
- package/dist/session/session.service.js.map +1 -1
- package/dist/setting/setting.controller.d.ts +5 -5
- package/dist/setting/setting.service.d.ts +5 -5
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +2 -0
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/task/task.service.d.ts +8 -11
- package/dist/task/task.service.d.ts.map +1 -1
- package/dist/task/task.service.js +10 -16
- package/dist/task/task.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +1 -0
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.service.d.ts +3 -0
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-command.registry.d.ts +9 -2
- package/dist/webhook-integration/webhook-command.registry.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-command.registry.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +13 -4
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/hedhog/data/integration_provider.yaml +17 -0
- package/hedhog/data/integration_type.yaml +6 -0
- package/hedhog/data/route.yaml +86 -2
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +109 -0
- package/hedhog/frontend/app/configurations/layout.tsx.ejs +6 -6
- package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +169 -14
- package/hedhog/frontend/messages/en.json +11 -2
- package/hedhog/frontend/messages/pt.json +11 -2
- package/hedhog/table/ai_execution.yaml +79 -0
- package/hedhog/table/ai_execution_step.yaml +65 -0
- package/hedhog/table/ai_instruction.yaml +43 -0
- package/hedhog/table/ai_instruction_version.yaml +30 -0
- package/hedhog/table/ai_model_pricing.yaml +35 -0
- package/hedhog/table/user_session.yaml +2 -0
- package/package.json +5 -4
- package/src/ai/ai.module.ts +2 -0
- package/src/ai/ai.service.ts +64 -31
- package/src/ai-instruction/ai-execution.service.ts +244 -0
- package/src/ai-instruction/ai-instruction.controller.ts +73 -0
- package/src/ai-instruction/ai-instruction.module.ts +14 -0
- package/src/ai-instruction/ai-instruction.service.ts +343 -0
- package/src/ai-instruction/ai-prompt-builder.service.ts +142 -0
- package/src/ai-instruction/dto/create-ai-instruction.dto.ts +36 -0
- package/src/ai-instruction/dto/update-ai-instruction.dto.ts +35 -0
- package/src/ai-instruction/types/ai-instruction.types.ts +90 -0
- package/src/auth/auth.controller.ts +23 -5
- package/src/auth/auth.service.ts +26 -15
- package/src/core.module.ts +3 -0
- package/src/file/file.service.ts +136 -4
- package/src/file/provider/abstract.provider.ts +4 -2
- package/src/file/provider/azure.provider.ts +6 -3
- package/src/file/provider/gcs.provider.ts +5 -3
- package/src/file/provider/local.provider.ts +7 -3
- package/src/file/provider/s3.provider.ts +8 -6
- package/src/file/provider/spaces.provider.ts +8 -7
- package/src/mcp-chat/mcp-chat.module.ts +19 -17
- package/src/mcp-chat/mcp-chat.service.ts +95 -9
- package/src/notification/notification.service.ts +32 -1
- package/src/oauth/oauth.service.ts +74 -1
- package/src/profile/events/profile-avatar-updated.event.ts +8 -0
- package/src/profile/events/profile-email-changed.event.ts +8 -0
- package/src/profile/events/profile-updated.event.ts +8 -0
- package/src/profile/profile.service.ts +25 -16
- package/src/session/session.service.ts +70 -52
- package/src/setting/setting.service.ts +10 -8
- package/src/task/task.service.ts +10 -16
- package/src/webhook-integration/webhook-command.registry.ts +10 -2
- package/src/webhook-integration/webhook-integration.service.ts +31 -4
|
@@ -40,7 +40,7 @@ export class SessionService {
|
|
|
40
40
|
private readonly paginationService: PaginationService,
|
|
41
41
|
) { }
|
|
42
42
|
|
|
43
|
-
async create(locale: string, userId: number, ipAddress: string, userAgent: string) {
|
|
43
|
+
async create(locale: string, userId: number, ipAddress: string, userAgent: string, browserId?: string | null) {
|
|
44
44
|
if (!userId) {
|
|
45
45
|
throw new BadRequestException(getLocaleText('session.userIdRequired', locale, 'User ID is required to create a session.'));
|
|
46
46
|
}
|
|
@@ -60,9 +60,9 @@ export class SessionService {
|
|
|
60
60
|
const token = await this.token.createOpaqueToken();
|
|
61
61
|
const hash = this.security.hashWithPepper(token)
|
|
62
62
|
const maxSessions = Number(settings['max-concurrent-sessions']) || 0;
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
if (maxSessions > 0) {
|
|
65
|
-
await this.enforceSessionLimit(userId, maxSessions);
|
|
65
|
+
await this.enforceSessionLimit(userId, maxSessions, false, browserId);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const session = await this.prisma.user_session.create({
|
|
@@ -71,40 +71,49 @@ export class SessionService {
|
|
|
71
71
|
ip_address: ipAddress,
|
|
72
72
|
user_agent: userAgent,
|
|
73
73
|
expires_at: new Date(Date.now() + ((settings['refresh-token-expiration-minutes'] || 43200) * 60 * 1000)),
|
|
74
|
-
hash
|
|
74
|
+
hash,
|
|
75
|
+
browser_id: browserId ?? null,
|
|
75
76
|
},
|
|
76
77
|
});
|
|
77
78
|
|
|
78
79
|
return { token, session };
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
async refresh(locale: string, refreshToken: string, ipAddress: string, userAgent: string) {
|
|
82
|
+
async refresh(locale: string, refreshToken: string, ipAddress: string, userAgent: string, browserId?: string | null) {
|
|
82
83
|
if (!refreshToken) {
|
|
83
84
|
throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
const hash = this.security.hashWithPepper(refreshToken);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
expires_at: {
|
|
92
|
-
gt: new Date(),
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
select: {
|
|
96
|
-
id: true,
|
|
97
|
-
user_id: true,
|
|
98
|
-
ip_address: true,
|
|
99
|
-
user_agent: true,
|
|
100
|
-
},
|
|
88
|
+
|
|
89
|
+
const rawSession = await this.prisma.user_session.findFirst({
|
|
90
|
+
where: { hash },
|
|
91
|
+
select: { id: true, user_id: true, ip_address: true, user_agent: true, revoked_at: true, expires_at: true, browser_id: true },
|
|
101
92
|
});
|
|
102
93
|
|
|
103
|
-
if (!
|
|
104
|
-
this.logger.warn(`[SessionRefresh] Refresh rejected:
|
|
94
|
+
if (!rawSession) {
|
|
95
|
+
this.logger.warn(`[SessionRefresh] Refresh rejected: hash prefix ${hash.slice(0, 8)} not found in DB`);
|
|
96
|
+
throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (rawSession.revoked_at !== null) {
|
|
100
|
+
this.logger.warn(`[SessionRefresh] Refresh rejected: sessionId=${rawSession.id} userId=${rawSession.user_id} revoked_at=${rawSession.revoked_at.toISOString()} hash prefix ${hash.slice(0, 8)}`);
|
|
105
101
|
throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
|
|
106
102
|
}
|
|
107
103
|
|
|
104
|
+
if (rawSession.expires_at <= new Date()) {
|
|
105
|
+
this.logger.warn(`[SessionRefresh] Refresh rejected: sessionId=${rawSession.id} userId=${rawSession.user_id} expired expires_at=${rawSession.expires_at.toISOString()} hash prefix ${hash.slice(0, 8)}`);
|
|
106
|
+
throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const session = {
|
|
110
|
+
id: rawSession.id,
|
|
111
|
+
user_id: rawSession.user_id,
|
|
112
|
+
ip_address: rawSession.ip_address,
|
|
113
|
+
user_agent: rawSession.user_agent,
|
|
114
|
+
browser_id: rawSession.browser_id,
|
|
115
|
+
};
|
|
116
|
+
|
|
108
117
|
const settings = await this.setting.getSettingValues([
|
|
109
118
|
'refresh-token-expiration-minutes',
|
|
110
119
|
'max-concurrent-sessions',
|
|
@@ -112,7 +121,7 @@ export class SessionService {
|
|
|
112
121
|
|
|
113
122
|
const maxSessions = Number(settings['max-concurrent-sessions']) || 0;
|
|
114
123
|
if (maxSessions > 0) {
|
|
115
|
-
await this.enforceSessionLimit(session.user_id, maxSessions, true);
|
|
124
|
+
await this.enforceSessionLimit(session.user_id, maxSessions, true, browserId);
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
const nextRefreshToken = await this.token.createOpaqueToken();
|
|
@@ -142,6 +151,7 @@ export class SessionService {
|
|
|
142
151
|
user_agent: nextUserAgent,
|
|
143
152
|
expires_at: expiresAt,
|
|
144
153
|
hash: nextHash,
|
|
154
|
+
browser_id: session.browser_id ?? browserId ?? null,
|
|
145
155
|
},
|
|
146
156
|
});
|
|
147
157
|
|
|
@@ -367,51 +377,59 @@ export class SessionService {
|
|
|
367
377
|
|
|
368
378
|
/**
|
|
369
379
|
* Enforce maximum concurrent sessions limit for a user.
|
|
370
|
-
*
|
|
380
|
+
* Sessions from the same browser (same browser_id) count as a single slot.
|
|
381
|
+
* Sessions without a browser_id each count as their own slot (legacy/unknown clients).
|
|
371
382
|
*
|
|
372
383
|
* @param userId - The user ID to check
|
|
373
|
-
* @param maxSessions - Maximum number of concurrent
|
|
384
|
+
* @param maxSessions - Maximum number of concurrent browser slots allowed
|
|
374
385
|
* @param isRotation - When true, the calling flow is replacing an existing session (refresh),
|
|
375
386
|
* not adding a new one, so no extra slot needs to be freed.
|
|
387
|
+
* @param currentBrowserId - The browser_id of the session being created/refreshed.
|
|
376
388
|
*/
|
|
377
|
-
private async enforceSessionLimit(userId: number, maxSessions: number, isRotation = false): Promise<void> {
|
|
389
|
+
private async enforceSessionLimit(userId: number, maxSessions: number, isRotation = false, currentBrowserId?: string | null): Promise<void> {
|
|
378
390
|
const activeSessions = await this.prisma.user_session.findMany({
|
|
379
391
|
where: {
|
|
380
392
|
user_id: userId,
|
|
381
393
|
revoked_at: null,
|
|
382
|
-
expires_at: {
|
|
383
|
-
gt: new Date()
|
|
384
|
-
}
|
|
385
|
-
},
|
|
386
|
-
orderBy: {
|
|
387
|
-
created_at: 'asc'
|
|
394
|
+
expires_at: { gt: new Date() }
|
|
388
395
|
},
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
396
|
+
orderBy: { created_at: 'asc' },
|
|
397
|
+
select: { id: true, browser_id: true }
|
|
392
398
|
});
|
|
393
399
|
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
400
|
+
// Group sessions into browser slots. Sessions sharing a browser_id = 1 slot.
|
|
401
|
+
// Sessions with null browser_id each count as their own slot (legacy clients).
|
|
402
|
+
// Map insertion order is preserved, and since activeSessions is ordered created_at asc,
|
|
403
|
+
// the oldest slot is always first.
|
|
404
|
+
type Slot = { ids: number[] };
|
|
405
|
+
const slotMap = new Map<string, Slot>();
|
|
406
|
+
for (const s of activeSessions) {
|
|
407
|
+
const key = s.browser_id ?? `__null_${s.id}`;
|
|
408
|
+
if (!slotMap.has(key)) slotMap.set(key, { ids: [] });
|
|
409
|
+
slotMap.get(key)!.ids.push(s.id);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const slots = Array.from(slotMap.values());
|
|
413
|
+
const currentSlotCount = slots.length;
|
|
403
414
|
|
|
404
|
-
|
|
415
|
+
// A new browser slot is created only when:
|
|
416
|
+
// - it's not a rotation (rotation keeps the same slot), AND
|
|
417
|
+
// - the current browser doesn't already have an active slot
|
|
418
|
+
const currentBrowserHasSlot = Boolean(currentBrowserId && slotMap.has(currentBrowserId));
|
|
419
|
+
const isNewSlot = !isRotation && !currentBrowserHasSlot;
|
|
405
420
|
|
|
421
|
+
const slotsToRevoke = currentSlotCount - maxSessions + (isNewSlot ? 1 : 0);
|
|
422
|
+
this.logger.debug(`[SessionLimit] userId=${userId} slots=${currentSlotCount} maxSessions=${maxSessions} isRotation=${isRotation} browserId=${currentBrowserId ?? 'null'} newSlot=${isNewSlot} toRevoke=${Math.max(0, slotsToRevoke)}`);
|
|
423
|
+
|
|
424
|
+
if (slotsToRevoke > 0) {
|
|
425
|
+
const sessionIdsToRevoke: number[] = [];
|
|
426
|
+
for (let i = 0; i < slotsToRevoke; i++) {
|
|
427
|
+
sessionIdsToRevoke.push(...slots[i].ids);
|
|
428
|
+
}
|
|
429
|
+
this.logger.warn(`[SessionLimit] Revoking ${slotsToRevoke} oldest browser slot(s) for userId=${userId}: sessions [${sessionIdsToRevoke.join(',')}]`);
|
|
406
430
|
await this.prisma.user_session.updateMany({
|
|
407
|
-
where: {
|
|
408
|
-
|
|
409
|
-
in: sessionIdsToExpire
|
|
410
|
-
}
|
|
411
|
-
},
|
|
412
|
-
data: {
|
|
413
|
-
expires_at: new Date()
|
|
414
|
-
}
|
|
431
|
+
where: { id: { in: sessionIdsToRevoke } },
|
|
432
|
+
data: { revoked_at: new Date() }
|
|
415
433
|
});
|
|
416
434
|
}
|
|
417
435
|
}
|
|
@@ -3,14 +3,14 @@ import { getLocaleText, LocaleService } from '@hed-hog/api-locale';
|
|
|
3
3
|
import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
|
|
4
4
|
import { PrismaService, setting_type_0f055cd5ea_enum } from '@hed-hog/api-prisma';
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
BadRequestException,
|
|
7
|
+
forwardRef,
|
|
8
|
+
Inject,
|
|
9
|
+
Injectable,
|
|
10
|
+
Logger,
|
|
11
|
+
NotFoundException,
|
|
12
|
+
OnModuleDestroy,
|
|
13
|
+
OnModuleInit,
|
|
14
14
|
} from '@nestjs/common';
|
|
15
15
|
import { randomUUID } from 'node:crypto';
|
|
16
16
|
import * as pako from 'pako';
|
|
@@ -422,6 +422,7 @@ export class SettingService implements OnModuleInit, OnModuleDestroy {
|
|
|
422
422
|
async getSystemSettings(): Promise<any> {
|
|
423
423
|
const locales = await this.localeService.getEnables();
|
|
424
424
|
const slugs = [
|
|
425
|
+
'language',
|
|
425
426
|
'system-name',
|
|
426
427
|
'system-slogan',
|
|
427
428
|
'icon-url',
|
|
@@ -476,6 +477,7 @@ export class SettingService implements OnModuleInit, OnModuleDestroy {
|
|
|
476
477
|
'operations.comment-edit-window',
|
|
477
478
|
'lms-video-conversion-enabled',
|
|
478
479
|
'lms-video-frame-capture-interval-seconds',
|
|
480
|
+
'url'
|
|
479
481
|
];
|
|
480
482
|
const setting = await this.getSettingValues(slugs);
|
|
481
483
|
return { locales, setting };
|
package/src/task/task.service.ts
CHANGED
|
@@ -128,20 +128,17 @@ export class TasksService {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
* 2. user_identifier: identificadores que nunca foram verificados e perderam seus challenges
|
|
131
|
+
* Limpa challenges de verificação de identificadores de usuário (email, telefone, etc.)
|
|
132
|
+
* que expiraram e não foram verificados.
|
|
133
|
+
*
|
|
134
|
+
* Remove em cascata:
|
|
135
|
+
* 1. user_identifier_challenge: challenges expirados não verificados
|
|
137
136
|
*
|
|
138
137
|
* Por quê: Quando um usuário tenta adicionar um novo email/telefone, é gerado um challenge
|
|
139
138
|
* para verificação. Se o usuário não verifica dentro do prazo, tanto o challenge quanto
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* outro usuário de cadastrá-lo)
|
|
144
|
-
* - Confusão na interface (mostrar identificadores pendentes indefinidamente)
|
|
139
|
+
* o challenge devem ser removidos para evitar:
|
|
140
|
+
* - Acúmulo de challenges órfãos
|
|
141
|
+
* - Confusão na interface (mostrar verificações pendentes indefinidamente)
|
|
145
142
|
*
|
|
146
143
|
* Limite: 20.000 registros por execução para evitar lock prolongado das tabelas.
|
|
147
144
|
*/
|
|
@@ -166,14 +163,11 @@ export class TasksService {
|
|
|
166
163
|
AND c.verified_at IS NULL
|
|
167
164
|
RETURNING c.user_identifier_id
|
|
168
165
|
)
|
|
169
|
-
|
|
170
|
-
USING deleted_challenges
|
|
171
|
-
WHERE ui.id = deleted_challenges.user_identifier_id
|
|
172
|
-
AND ui.verified_at IS NULL
|
|
166
|
+
SELECT 1
|
|
173
167
|
`;
|
|
174
168
|
|
|
175
169
|
this.logger.verbose(
|
|
176
|
-
`Expired user identifier challenges
|
|
170
|
+
`Expired user identifier challenges cleaned up in ${Date.now() - startAt}ms`,
|
|
177
171
|
);
|
|
178
172
|
}
|
|
179
173
|
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { Injectable } from '@nestjs/common';
|
|
2
2
|
|
|
3
|
+
export interface WebhookCommandContext extends Record<string, unknown> {
|
|
4
|
+
webhookBody?: unknown;
|
|
5
|
+
webhookHeaders?: Record<string, unknown>;
|
|
6
|
+
remoteIp?: string | null;
|
|
7
|
+
publicUuid?: string;
|
|
8
|
+
integrationId?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
export interface WebhookCommand {
|
|
4
12
|
slug: string;
|
|
5
13
|
name: string;
|
|
@@ -7,7 +15,7 @@ export interface WebhookCommand {
|
|
|
7
15
|
inputSchema: Record<string, unknown>;
|
|
8
16
|
handler: (
|
|
9
17
|
params: Record<string, unknown>,
|
|
10
|
-
context?:
|
|
18
|
+
context?: WebhookCommandContext,
|
|
11
19
|
) => Promise<unknown>;
|
|
12
20
|
}
|
|
13
21
|
|
|
@@ -30,7 +38,7 @@ export class WebhookCommandRegistry {
|
|
|
30
38
|
async dispatch(
|
|
31
39
|
slug: string,
|
|
32
40
|
params: Record<string, unknown>,
|
|
33
|
-
context?:
|
|
41
|
+
context?: WebhookCommandContext,
|
|
34
42
|
): Promise<unknown> {
|
|
35
43
|
const command = this.find(slug);
|
|
36
44
|
if (!command) {
|
|
@@ -581,7 +581,12 @@ export class WebhookIntegrationService {
|
|
|
581
581
|
const actionStartedAt = Date.now();
|
|
582
582
|
|
|
583
583
|
try {
|
|
584
|
-
const result = await this.executeAction(action, body
|
|
584
|
+
const result = await this.executeAction(action, body, {
|
|
585
|
+
webhookHeaders: normalizeJsonObject(headers) || {},
|
|
586
|
+
remoteIp,
|
|
587
|
+
publicUuid: uuid,
|
|
588
|
+
integrationId: integration.id,
|
|
589
|
+
});
|
|
585
590
|
const durationMs = Date.now() - actionStartedAt;
|
|
586
591
|
|
|
587
592
|
actionResults.push({
|
|
@@ -695,7 +700,16 @@ export class WebhookIntegrationService {
|
|
|
695
700
|
}
|
|
696
701
|
}
|
|
697
702
|
|
|
698
|
-
private async executeAction(
|
|
703
|
+
private async executeAction(
|
|
704
|
+
action: any,
|
|
705
|
+
body: any,
|
|
706
|
+
context?: {
|
|
707
|
+
webhookHeaders?: Record<string, unknown>;
|
|
708
|
+
remoteIp?: string | null;
|
|
709
|
+
publicUuid?: string;
|
|
710
|
+
integrationId?: number;
|
|
711
|
+
},
|
|
712
|
+
) {
|
|
699
713
|
if (action.type === 'email') {
|
|
700
714
|
return this.executeEmailAction(action, body);
|
|
701
715
|
}
|
|
@@ -713,7 +727,7 @@ export class WebhookIntegrationService {
|
|
|
713
727
|
}
|
|
714
728
|
|
|
715
729
|
if (action.type === 'app_command') {
|
|
716
|
-
return this.executeAppCommandAction(action, body);
|
|
730
|
+
return this.executeAppCommandAction(action, body, context);
|
|
717
731
|
}
|
|
718
732
|
|
|
719
733
|
throw new BadRequestException(`Unsupported action type: ${action.type}`);
|
|
@@ -1013,7 +1027,16 @@ export class WebhookIntegrationService {
|
|
|
1013
1027
|
};
|
|
1014
1028
|
}
|
|
1015
1029
|
|
|
1016
|
-
private async executeAppCommandAction(
|
|
1030
|
+
private async executeAppCommandAction(
|
|
1031
|
+
action: any,
|
|
1032
|
+
body: any,
|
|
1033
|
+
context?: {
|
|
1034
|
+
webhookHeaders?: Record<string, unknown>;
|
|
1035
|
+
remoteIp?: string | null;
|
|
1036
|
+
publicUuid?: string;
|
|
1037
|
+
integrationId?: number;
|
|
1038
|
+
},
|
|
1039
|
+
) {
|
|
1017
1040
|
this.validateActionConfiguration(action);
|
|
1018
1041
|
|
|
1019
1042
|
const slug = String(action.app_command_slug || '').trim();
|
|
@@ -1042,6 +1065,10 @@ export class WebhookIntegrationService {
|
|
|
1042
1065
|
try {
|
|
1043
1066
|
result = await this.commandRegistry.dispatch(slug, renderedParams, {
|
|
1044
1067
|
webhookBody: body,
|
|
1068
|
+
webhookHeaders: context?.webhookHeaders,
|
|
1069
|
+
remoteIp: context?.remoteIp,
|
|
1070
|
+
publicUuid: context?.publicUuid,
|
|
1071
|
+
integrationId: context?.integrationId,
|
|
1045
1072
|
});
|
|
1046
1073
|
} catch (error) {
|
|
1047
1074
|
this.attachErrorRequestPayload(error, requestPayload);
|