@omnizap-system/omnizap 2.6.1 → 2.6.2
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/.env.example +54 -9
- package/.github/workflows/ci.yml +3 -3
- package/.github/workflows/security-runner-hardening.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +1 -0
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +226 -55
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +143 -3
- package/app/configParts/sessionConfig.js +157 -0
- package/app/connection/baileysCompatibility.test.js +1 -1
- package/app/connection/groupOwnerWriteStateResolver.js +109 -0
- package/app/connection/socketController.js +625 -124
- package/app/connection/socketController.multiSession.test.js +108 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
- package/app/controllers/messageProcessingPipeline.js +88 -9
- package/app/controllers/messageProcessingPipeline.test.js +200 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +856 -14
- package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +132 -25
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +74 -16
- package/app/modules/playModule/playCommandConstants.js +13 -7
- package/app/modules/playModule/playCommandCore.js +4 -6
- package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
- package/app/modules/playModule/playConfigRuntime.js +5 -6
- package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +1 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/geminiService.js +131 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/moduleAiHelpCoreService.js +33 -4
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +51 -21
- package/app/services/messaging/newsBroadcastService.js +843 -27
- package/app/services/multiSession/assignmentBalancerService.js +457 -0
- package/app/services/multiSession/groupOwnershipRepository.js +381 -0
- package/app/services/multiSession/groupOwnershipService.js +890 -0
- package/app/services/multiSession/groupOwnershipService.test.js +309 -0
- package/app/services/multiSession/sessionRegistryService.js +293 -0
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +352 -16
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/database/index.js +6 -0
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +2 -2
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/ecosystem.prod.config.cjs +31 -11
- package/index.js +52 -18
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +12 -5
- package/public/comandos/commands-catalog.json +2253 -78
- package/public/js/apps/commandsReactApp.js +267 -87
- package/public/js/apps/createPackApp.js +3 -3
- package/public/js/apps/stickersApp.js +255 -103
- package/public/js/apps/termsReactApp.js +57 -8
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +96 -47
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/politica-de-privacidade.html +1 -1
- package/public/pages/stickers.html +5 -5
- package/public/pages/termos-de-uso-texto-integral.html +1 -1
- package/public/pages/termos-de-uso.html +1 -1
- package/public/pages/user-password-reset.html +3 -4
- package/public/pages/user-systemadm.html +8 -462
- package/public/pages/user.html +1 -1
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +10 -0
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +155 -0
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/security-web-surface-check.mjs +218 -0
- package/server/controllers/admin/adminPanelHandlers.js +253 -3
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/sticker/stickerCatalogController.js +9 -23
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +3 -2
- package/server/http/httpServer.js +8 -4
- package/server/middleware/securityHeaders.js +20 -1
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +30 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/static/staticPageRouter.js +27 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +2 -1
- package/app/modules/playModule/local/installYtDlp.js +0 -25
- package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import logger from '#logger';
|
|
2
2
|
import { getActiveSocket, getAdminPhone, getAdminRawValue, getJidUser, resolveAdminJid, resolveBotJid, extractUserIdInfo, resolveUserId } from '../../../app/config/index.js';
|
|
3
|
+
import { isLikelyWhatsAppPhone, normalizePhoneDigits, resolveAdminPhoneFromEnv, resolveBotPhoneFromEnv, resolveSupportPhoneFromEnv } from '../../../utils/whatsapp/contactEnv.js';
|
|
3
4
|
|
|
4
5
|
const PACK_COMMAND_PREFIX = String(process.env.COMMAND_PREFIX || '/').trim() || '/';
|
|
5
6
|
|
|
6
|
-
const normalizePhoneDigits = (value) => String(value || '').replace(/\D+/g, '');
|
|
7
|
-
|
|
8
7
|
const isPlausibleWhatsAppPhone = (value) => {
|
|
9
8
|
const digits = normalizePhoneDigits(value);
|
|
10
|
-
return digits
|
|
9
|
+
return isLikelyWhatsAppPhone(digits) ? digits : '';
|
|
11
10
|
};
|
|
12
11
|
|
|
13
12
|
const resolveActiveSocketBotJid = (activeSocket) => {
|
|
@@ -26,18 +25,12 @@ export const resolveCatalogBotPhone = () => {
|
|
|
26
25
|
const jidUser = botJid ? getJidUser(botJid) : null;
|
|
27
26
|
const fromSocket = normalizePhoneDigits(jidUser || '');
|
|
28
27
|
|
|
29
|
-
if (fromSocket
|
|
28
|
+
if (isLikelyWhatsAppPhone(fromSocket)) {
|
|
30
29
|
return fromSocket;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
for (const candidate of envCandidates) {
|
|
36
|
-
const digits = normalizePhoneDigits(candidate || '');
|
|
37
|
-
if (digits && digits.length >= 10 && digits.length <= 15) {
|
|
38
|
-
return digits;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
32
|
+
const fromEnv = resolveBotPhoneFromEnv({ fallback: '' });
|
|
33
|
+
if (fromEnv) return fromEnv;
|
|
41
34
|
|
|
42
35
|
logger.warn('Nao foi possivel resolver o numero do bot para contato.', {
|
|
43
36
|
action: 'resolve_bot_phone_failed',
|
|
@@ -75,12 +68,11 @@ const resolveSupportAdminPhone = async () => {
|
|
|
75
68
|
const adminPhone = isPlausibleWhatsAppPhone(getAdminPhone() || '');
|
|
76
69
|
if (adminPhone) return adminPhone;
|
|
77
70
|
|
|
78
|
-
const
|
|
71
|
+
const configuredAdminPhone = resolveAdminPhoneFromEnv({ fallback: '' });
|
|
72
|
+
if (configuredAdminPhone) return configuredAdminPhone;
|
|
79
73
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (digits) return digits;
|
|
83
|
-
}
|
|
74
|
+
const configuredSupportPhone = resolveSupportPhoneFromEnv({ fallback: '' });
|
|
75
|
+
if (configuredSupportPhone) return configuredSupportPhone;
|
|
84
76
|
|
|
85
77
|
return '';
|
|
86
78
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
2
|
import { withTimeout } from '../../http/httpRequestUtils.js';
|
|
3
|
+
import { resolveBotPhoneFromEnv } from '../../../utils/whatsapp/contactEnv.js';
|
|
3
4
|
|
|
4
5
|
export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger, getSystemMetrics, getActiveSocket, resolveSocketReadyState, resolveActiveSocketBotJid, resolveCatalogBotPhone, fetchPrometheusSummary, metricsEndpoint, systemSummaryCache, systemSummaryCacheSeconds, readmeSummaryCache, readmeSummaryCacheSeconds, readmeMessageTypeSampleLimit, readmeCommandPrefix, buildMenuCaption, buildStickerMenu, buildMediaMenu, buildQuoteMenu, buildAnimeMenu, buildAiMenu, buildStatsMenu, buildAdminMenu, profilePictureUrlFromActiveSocket, normalizeJid, getJidUser, globalRankCache, globalRankRefreshSeconds, marketplaceGlobalStatsCache, marketplaceGlobalStatsCacheSeconds }) => {
|
|
5
6
|
let globalRankRefreshTimer = null;
|
|
@@ -153,6 +154,12 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
153
154
|
systemSummaryCache.expiresAt = __timeNowMs() + systemSummaryCacheSeconds * 1000;
|
|
154
155
|
return payload;
|
|
155
156
|
})
|
|
157
|
+
.catch((error) => {
|
|
158
|
+
if (hasValue && systemSummaryCache.value) {
|
|
159
|
+
return systemSummaryCache.value;
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
})
|
|
156
163
|
.finally(() => {
|
|
157
164
|
systemSummaryCache.pending = null;
|
|
158
165
|
});
|
|
@@ -329,6 +336,12 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
329
336
|
readmeSummaryCache.expiresAt = __timeNowMs() + readmeSummaryCacheSeconds * 1000;
|
|
330
337
|
return payload;
|
|
331
338
|
})
|
|
339
|
+
.catch((error) => {
|
|
340
|
+
if (hasValue && readmeSummaryCache.value) {
|
|
341
|
+
return readmeSummaryCache.value;
|
|
342
|
+
}
|
|
343
|
+
throw error;
|
|
344
|
+
})
|
|
332
345
|
.finally(() => {
|
|
333
346
|
readmeSummaryCache.pending = null;
|
|
334
347
|
});
|
|
@@ -346,12 +359,8 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
346
359
|
const botPhoneFromCatalog = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '');
|
|
347
360
|
if (botPhoneFromCatalog) candidates.add(botPhoneFromCatalog);
|
|
348
361
|
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
for (const candidate of envCandidates) {
|
|
352
|
-
const digits = String(candidate || '').replace(/\D+/g, '');
|
|
353
|
-
if (digits) candidates.add(digits);
|
|
354
|
-
}
|
|
362
|
+
const configuredBotPhone = String(resolveBotPhoneFromEnv({ fallback: '' }) || '').replace(/\D+/g, '');
|
|
363
|
+
if (configuredBotPhone) candidates.add(configuredBotPhone);
|
|
355
364
|
|
|
356
365
|
return Array.from(candidates).filter((value) => value.length >= 8);
|
|
357
366
|
};
|
|
@@ -542,6 +551,12 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
542
551
|
globalRankCache.expiresAt = __timeNowMs() + globalRankRefreshSeconds * 1000;
|
|
543
552
|
return data;
|
|
544
553
|
})
|
|
554
|
+
.catch((error) => {
|
|
555
|
+
if (hasValue && globalRankCache.value) {
|
|
556
|
+
return globalRankCache.value;
|
|
557
|
+
}
|
|
558
|
+
throw error;
|
|
559
|
+
})
|
|
545
560
|
.finally(() => {
|
|
546
561
|
globalRankCache.pending = null;
|
|
547
562
|
});
|
|
@@ -737,6 +752,12 @@ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger
|
|
|
737
752
|
marketplaceGlobalStatsCache.expiresAt = __timeNowMs() + marketplaceGlobalStatsCacheSeconds * 1000;
|
|
738
753
|
return data;
|
|
739
754
|
})
|
|
755
|
+
.catch((error) => {
|
|
756
|
+
if (hasValue && marketplaceGlobalStatsCache.value) {
|
|
757
|
+
return marketplaceGlobalStatsCache.value;
|
|
758
|
+
}
|
|
759
|
+
throw error;
|
|
760
|
+
})
|
|
740
761
|
.finally(() => {
|
|
741
762
|
marketplaceGlobalStatsCache.pending = null;
|
|
742
763
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logger from '#logger';
|
|
2
2
|
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
3
|
-
import { getActiveSocket, getJidUser, normalizeJid, profilePictureUrlFromActiveSocket } from '../../../app/config/index.js';
|
|
3
|
+
import { getActiveSocket, getActiveSocketsBySession, getJidUser, getMultiSessionRuntimeConfig, isSocketOpen, normalizeJid, profilePictureUrlFromActiveSocket } from '../../../app/config/index.js';
|
|
4
4
|
import { getSystemMetrics } from '../../../app/utils/systemMetrics/systemMetricsModule.js';
|
|
5
5
|
import { createStickerCatalogSystemContext } from './stickerCatalogSystemContext.js';
|
|
6
6
|
import { createStickerCatalogNonCatalogHandlers } from '../sticker/nonCatalogHandlers.js';
|
|
@@ -10,6 +10,9 @@ import { fetchPrometheusSummary } from './systemMetricsController.js';
|
|
|
10
10
|
import { buildBotContactInfo, buildSupportInfo, resolveCatalogBotPhone } from './contactController.js';
|
|
11
11
|
import { buildAdminMenu, buildAiMenu, buildAnimeMenu, buildMediaMenu, buildMenuCaption, buildQuoteMenu, buildStatsMenu, buildStickerMenu } from '../../../app/modules/menuModule/common.js';
|
|
12
12
|
import { trackWebVisitMetric } from './visitController.js';
|
|
13
|
+
import groupOwnershipService from '../../../app/services/multiSession/groupOwnershipService.js';
|
|
14
|
+
import sessionRegistryService from '../../../app/services/multiSession/sessionRegistryService.js';
|
|
15
|
+
import { runGroupAssignmentBalancerCycle } from '../../../app/services/multiSession/assignmentBalancerService.js';
|
|
13
16
|
|
|
14
17
|
const SYSTEM_SUMMARY_CACHE_SECONDS = Number(process.env.SYSTEM_SUMMARY_CACHE_SECONDS || 20);
|
|
15
18
|
const README_SUMMARY_CACHE_SECONDS = Number(process.env.README_SUMMARY_CACHE_SECONDS || 1800);
|
|
@@ -132,4 +135,254 @@ export const systemHandlers = createStickerCatalogNonCatalogHandlers({
|
|
|
132
135
|
isAuthenticatedGoogleSession: (sess) => Boolean(sess?.sub && (sess?.ownerJid || sess?.ownerPhone || sess?.email)),
|
|
133
136
|
});
|
|
134
137
|
|
|
138
|
+
const clampLimit = (value, fallback = 200, min = 1, max = 5_000) => {
|
|
139
|
+
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
140
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
141
|
+
return Math.max(min, Math.min(max, parsed));
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const normalizeOptional = (value, maxLength = 255) => {
|
|
145
|
+
const normalized = String(value || '')
|
|
146
|
+
.trim()
|
|
147
|
+
.slice(0, maxLength);
|
|
148
|
+
return normalized || null;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const normalizeBoolean = (value, fallback = false) => {
|
|
152
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
153
|
+
const normalized = String(value).trim().toLowerCase();
|
|
154
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
|
|
155
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
|
|
156
|
+
return fallback;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const toIso = (value) => {
|
|
160
|
+
if (!value) return null;
|
|
161
|
+
const parsed = new Date(value);
|
|
162
|
+
if (Number.isNaN(parsed.getTime())) return null;
|
|
163
|
+
return parsed.toISOString();
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const resolveSocketRuntimeState = (socket) => {
|
|
167
|
+
const open = isSocketOpen(socket);
|
|
168
|
+
const readyState = resolveSocketReadyState(socket);
|
|
169
|
+
const botJid = resolveActiveSocketBotJid(socket);
|
|
170
|
+
return {
|
|
171
|
+
socket_open: open,
|
|
172
|
+
socket_ready_state: readyState,
|
|
173
|
+
socket_bot_jid: botJid || null,
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const listSystemAdminSessions = async ({ status = null, limit = 200 } = {}) => {
|
|
178
|
+
const runtimeConfig = getMultiSessionRuntimeConfig();
|
|
179
|
+
const safeStatus = normalizeOptional(status, 24);
|
|
180
|
+
const safeLimit = clampLimit(limit, 200, 1, 5_000);
|
|
181
|
+
const registryRows = await sessionRegistryService.listSessions({
|
|
182
|
+
status: safeStatus,
|
|
183
|
+
limit: safeLimit,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const socketsBySession = getActiveSocketsBySession();
|
|
187
|
+
const knownSessionIds = new Set();
|
|
188
|
+
for (const sessionId of runtimeConfig.sessionIds || []) knownSessionIds.add(sessionId);
|
|
189
|
+
for (const row of registryRows || []) {
|
|
190
|
+
if (row?.sessionId) knownSessionIds.add(row.sessionId);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const selectedSessionIds = Array.from(knownSessionIds)
|
|
194
|
+
.filter(Boolean)
|
|
195
|
+
.slice(0, safeLimit);
|
|
196
|
+
const registryBySession = new Map((registryRows || []).map((row) => [row.sessionId, row]));
|
|
197
|
+
|
|
198
|
+
const sessions = selectedSessionIds.map((sessionId) => {
|
|
199
|
+
const row = registryBySession.get(sessionId) || null;
|
|
200
|
+
const socket = socketsBySession.get(sessionId) || null;
|
|
201
|
+
const socketRuntime = resolveSocketRuntimeState(socket);
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
session_id: sessionId,
|
|
205
|
+
is_primary: sessionId === runtimeConfig.primarySessionId,
|
|
206
|
+
configured: (runtimeConfig.sessionIds || []).includes(sessionId),
|
|
207
|
+
configured_weight: Number(runtimeConfig.sessionWeights?.[sessionId] || 1),
|
|
208
|
+
status: row?.status || (socketRuntime.socket_open ? 'online' : 'offline'),
|
|
209
|
+
bot_jid: row?.botJid || socketRuntime.socket_bot_jid || null,
|
|
210
|
+
capacity_weight: Number(row?.capacityWeight || runtimeConfig.sessionWeights?.[sessionId] || 1),
|
|
211
|
+
current_score: Number(row?.currentScore || 0),
|
|
212
|
+
last_heartbeat_at: toIso(row?.lastHeartbeatAt),
|
|
213
|
+
last_connected_at: toIso(row?.lastConnectedAt),
|
|
214
|
+
last_disconnected_at: toIso(row?.lastDisconnectedAt),
|
|
215
|
+
updated_at: toIso(row?.updatedAt),
|
|
216
|
+
metadata: row?.metadata || null,
|
|
217
|
+
...socketRuntime,
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
generated_at: new Date().toISOString(),
|
|
223
|
+
primary_session_id: runtimeConfig.primarySessionId,
|
|
224
|
+
configured_session_ids: runtimeConfig.sessionIds || [],
|
|
225
|
+
sessions,
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const listSystemAdminAssignments = async (
|
|
230
|
+
{
|
|
231
|
+
groupJid = null,
|
|
232
|
+
ownerSessionId = null,
|
|
233
|
+
includeExpired = false,
|
|
234
|
+
limit = 200,
|
|
235
|
+
} = {},
|
|
236
|
+
) => {
|
|
237
|
+
const safeGroupJid = normalizeOptional(groupJid, 255);
|
|
238
|
+
const safeOwnerSessionId = normalizeOptional(ownerSessionId, 64);
|
|
239
|
+
const safeIncludeExpired = normalizeBoolean(includeExpired, false);
|
|
240
|
+
const safeLimit = clampLimit(limit, 200, 1, 5_000);
|
|
241
|
+
|
|
242
|
+
const assignments = await groupOwnershipService.listAssignments({
|
|
243
|
+
groupJid: safeGroupJid,
|
|
244
|
+
ownerSessionId: safeOwnerSessionId,
|
|
245
|
+
includeExpired: safeIncludeExpired,
|
|
246
|
+
limit: safeLimit,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
generated_at: new Date().toISOString(),
|
|
251
|
+
filters: {
|
|
252
|
+
group_jid: safeGroupJid,
|
|
253
|
+
owner_session_id: safeOwnerSessionId,
|
|
254
|
+
include_expired: safeIncludeExpired,
|
|
255
|
+
limit: safeLimit,
|
|
256
|
+
},
|
|
257
|
+
assignments: (assignments || []).map((assignment) => ({
|
|
258
|
+
group_jid: assignment?.groupJid || null,
|
|
259
|
+
owner_session_id: assignment?.ownerSessionId || null,
|
|
260
|
+
lease_expires_at: toIso(assignment?.leaseExpiresAt),
|
|
261
|
+
cooldown_until: toIso(assignment?.cooldownUntil),
|
|
262
|
+
assignment_version: Number(assignment?.assignmentVersion || 1),
|
|
263
|
+
pinned: assignment?.pinned === true,
|
|
264
|
+
active: assignment?.active !== false,
|
|
265
|
+
last_reason: assignment?.lastReason || null,
|
|
266
|
+
created_at: toIso(assignment?.createdAt),
|
|
267
|
+
updated_at: toIso(assignment?.updatedAt),
|
|
268
|
+
})),
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export const setSystemAdminGroupPin = async (
|
|
273
|
+
{
|
|
274
|
+
groupJid,
|
|
275
|
+
pinned,
|
|
276
|
+
sessionId = null,
|
|
277
|
+
reason = null,
|
|
278
|
+
changedBy = 'admin_api',
|
|
279
|
+
metadata = null,
|
|
280
|
+
} = {},
|
|
281
|
+
) => {
|
|
282
|
+
const outcome = await groupOwnershipService.setPinned({
|
|
283
|
+
groupJid,
|
|
284
|
+
pinned,
|
|
285
|
+
sessionId,
|
|
286
|
+
reason: reason || (pinned ? 'admin_pin_group' : 'admin_unpin_group'),
|
|
287
|
+
changedBy,
|
|
288
|
+
metadata,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
updated: Boolean(outcome?.updated),
|
|
293
|
+
reason: outcome?.reason || null,
|
|
294
|
+
assignment_version: Number(outcome?.assignmentVersion || 0) || null,
|
|
295
|
+
previous_owner_session_id: outcome?.previousOwnerSessionId || null,
|
|
296
|
+
owner: outcome?.owner
|
|
297
|
+
? {
|
|
298
|
+
group_jid: outcome.owner.groupJid,
|
|
299
|
+
owner_session_id: outcome.owner.ownerSessionId,
|
|
300
|
+
lease_expires_at: toIso(outcome.owner.leaseExpiresAt),
|
|
301
|
+
cooldown_until: toIso(outcome.owner.cooldownUntil),
|
|
302
|
+
assignment_version: Number(outcome.owner.assignmentVersion || 1),
|
|
303
|
+
pinned: outcome.owner.pinned === true,
|
|
304
|
+
last_reason: outcome.owner.lastReason || null,
|
|
305
|
+
}
|
|
306
|
+
: null,
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export const forceSystemAdminGroupFailover = async (
|
|
311
|
+
{
|
|
312
|
+
groupJid,
|
|
313
|
+
targetSessionId,
|
|
314
|
+
reason = 'admin_force_failover',
|
|
315
|
+
changedBy = 'admin_api',
|
|
316
|
+
metadata = null,
|
|
317
|
+
} = {},
|
|
318
|
+
) => {
|
|
319
|
+
const outcome = await groupOwnershipService.forceAssign({
|
|
320
|
+
groupJid,
|
|
321
|
+
sessionId: targetSessionId,
|
|
322
|
+
reason,
|
|
323
|
+
changedBy,
|
|
324
|
+
metadata,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
reassigned: Boolean(outcome?.reassigned),
|
|
329
|
+
reason: outcome?.reason || null,
|
|
330
|
+
assignment_version: Number(outcome?.assignmentVersion || 0) || null,
|
|
331
|
+
previous_owner_session_id: outcome?.previousOwnerSessionId || null,
|
|
332
|
+
owner: outcome?.owner
|
|
333
|
+
? {
|
|
334
|
+
group_jid: outcome.owner.groupJid,
|
|
335
|
+
owner_session_id: outcome.owner.ownerSessionId,
|
|
336
|
+
lease_expires_at: toIso(outcome.owner.leaseExpiresAt),
|
|
337
|
+
assignment_version: Number(outcome.owner.assignmentVersion || 1),
|
|
338
|
+
pinned: outcome.owner.pinned === true,
|
|
339
|
+
last_reason: outcome.owner.lastReason || null,
|
|
340
|
+
}
|
|
341
|
+
: null,
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
export const triggerSystemAdminManualRebalance = async () => {
|
|
346
|
+
const cycle = await runGroupAssignmentBalancerCycle();
|
|
347
|
+
return {
|
|
348
|
+
generated_at: new Date().toISOString(),
|
|
349
|
+
cycle,
|
|
350
|
+
};
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
export const listSystemAdminAssignmentHistory = async ({ groupJid = null, limit = 100 } = {}) => {
|
|
354
|
+
const safeLimit = clampLimit(limit, 100, 1, 5_000);
|
|
355
|
+
const safeGroupJid = normalizeOptional(groupJid, 255);
|
|
356
|
+
const params = [];
|
|
357
|
+
const where = [];
|
|
358
|
+
if (safeGroupJid) {
|
|
359
|
+
where.push('group_jid = ?');
|
|
360
|
+
params.push(safeGroupJid);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const rows = await executeQuery(
|
|
364
|
+
`SELECT id, group_jid, previous_session_id, new_session_id, change_reason, changed_by, assignment_version, metadata, created_at
|
|
365
|
+
FROM ${TABLES.GROUP_ASSIGNMENT_HISTORY}
|
|
366
|
+
${where.length > 0 ? `WHERE ${where.join(' AND ')}` : ''}
|
|
367
|
+
ORDER BY id DESC
|
|
368
|
+
LIMIT ${safeLimit}`,
|
|
369
|
+
params,
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
generated_at: new Date().toISOString(),
|
|
374
|
+
history: (Array.isArray(rows) ? rows : []).map((row) => ({
|
|
375
|
+
id: Number(row?.id || 0),
|
|
376
|
+
group_jid: row?.group_jid || null,
|
|
377
|
+
previous_session_id: row?.previous_session_id || null,
|
|
378
|
+
new_session_id: row?.new_session_id || null,
|
|
379
|
+
change_reason: row?.change_reason || null,
|
|
380
|
+
changed_by: row?.changed_by || null,
|
|
381
|
+
assignment_version: Number(row?.assignment_version || 0) || null,
|
|
382
|
+
metadata: row?.metadata || null,
|
|
383
|
+
created_at: toIso(row?.created_at),
|
|
384
|
+
})),
|
|
385
|
+
};
|
|
386
|
+
};
|
|
387
|
+
|
|
135
388
|
export { scheduleGlobalRankingPreload };
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import logger from '#logger';
|
|
5
5
|
import { DEFAULT_LEGACY_STICKER_API_BASE_PATH, DEFAULT_USER_API_BASE_PATH, isUserApiPath, normalizeBasePath, resolveLegacyUserApiPath } from '../routes/user/userApiPaths.js';
|
|
6
|
+
import { buildWhatsappUrl, resolvePublicWhatsappNumber } from '../utils/publicContact.js';
|
|
6
7
|
|
|
7
8
|
const LEGACY_STICKER_API_BASE_PATH = normalizeBasePath(process.env.STICKER_API_BASE_PATH, DEFAULT_LEGACY_STICKER_API_BASE_PATH);
|
|
8
9
|
const USER_API_BASE_PATH = normalizeBasePath(process.env.USER_API_BASE_PATH || process.env.AUTH_API_BASE_PATH, DEFAULT_USER_API_BASE_PATH);
|
|
@@ -11,6 +12,8 @@ const USER_PROFILE_WEB_PATH = normalizeBasePath(process.env.USER_PROFILE_WEB_PAT
|
|
|
11
12
|
const USER_PASSWORD_RESET_WEB_PATH = normalizeBasePath(process.env.USER_PASSWORD_RESET_WEB_PATH, '/user/password-reset');
|
|
12
13
|
const USER_DASHBOARD_TEMPLATE_PATH = path.join(process.cwd(), 'public', 'pages', 'user.html');
|
|
13
14
|
const USER_PASSWORD_RESET_TEMPLATE_PATH = path.join(process.cwd(), 'public', 'pages', 'user-password-reset.html');
|
|
15
|
+
const PUBLIC_WHATSAPP_NUMBER = resolvePublicWhatsappNumber();
|
|
16
|
+
const PUBLIC_WHATSAPP_URL = buildWhatsappUrl(PUBLIC_WHATSAPP_NUMBER);
|
|
14
17
|
|
|
15
18
|
const hasPathPrefix = (pathname, prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`);
|
|
16
19
|
const escapeHtmlAttribute = (value) =>
|
|
@@ -52,7 +55,10 @@ const renderUserDashboardHtml = async ({ passwordReset = false } = {}) => {
|
|
|
52
55
|
const dataAttributes = {
|
|
53
56
|
'data-api-base-path': USER_API_BASE_PATH,
|
|
54
57
|
'data-login-path': STICKER_LOGIN_WEB_PATH,
|
|
58
|
+
'data-panel-path': USER_PROFILE_WEB_PATH,
|
|
55
59
|
'data-password-reset-web-path': USER_PASSWORD_RESET_WEB_PATH,
|
|
60
|
+
'data-support-whatsapp-number': PUBLIC_WHATSAPP_NUMBER,
|
|
61
|
+
'data-support-whatsapp-url': PUBLIC_WHATSAPP_URL,
|
|
56
62
|
};
|
|
57
63
|
|
|
58
64
|
let html = template;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
|
+
import { resolveAdminPhoneFromEnv, resolveBotPhoneFromEnv, resolveSupportPhoneFromEnv } from '../../utils/whatsapp/contactEnv.js';
|
|
2
3
|
const DEFAULT_SITE_ORIGIN = 'https://omnizap.shop';
|
|
3
4
|
const DEFAULT_BRAND_NAME = 'OmniZap';
|
|
4
5
|
|
|
@@ -77,7 +78,7 @@ const resolveBrandConfig = (payload = {}) => {
|
|
|
77
78
|
const supportFallback = `${siteOrigin}/termos-de-uso/`;
|
|
78
79
|
const replyToAddress = normalizeEmailAddress(payload?.replyTo || process.env.SMTP_REPLY_TO || process.env.EMAIL_REPLY_TO || process.env.MAIL_REPLY_TO || '');
|
|
79
80
|
const fromAddress = normalizeEmailAddress(process.env.SMTP_FROM || process.env.EMAIL_FROM || process.env.MAIL_FROM || process.env.SMTP_USER || process.env.EMAIL_USER || process.env.MAIL_USER || '');
|
|
80
|
-
const supportPhoneCandidate = normalizePhoneDigits(payload?.supportPhone ||
|
|
81
|
+
const supportPhoneCandidate = normalizePhoneDigits(payload?.supportPhone || resolveSupportPhoneFromEnv({ fallback: resolveAdminPhoneFromEnv({ fallback: '' }) }) || '', 20);
|
|
81
82
|
const supportPhoneDigits = isLikelyPhoneDigits(supportPhoneCandidate) ? supportPhoneCandidate : '';
|
|
82
83
|
const supportPhonePn = formatPhonePn(supportPhoneDigits);
|
|
83
84
|
const supportWhatsappUrl = supportPhoneDigits ? `https://wa.me/${supportPhoneDigits}` : '';
|
|
@@ -216,7 +217,7 @@ const resolveNavigationLinks = (payload = {}) => {
|
|
|
216
217
|
};
|
|
217
218
|
|
|
218
219
|
const resolveWelcomeBotWhatsApp = (payload = {}) => {
|
|
219
|
-
const botPhoneCandidate = normalizePhoneDigits(payload?.botPhone || payload?.botNumber || process.env.EMAIL_WELCOME_BOT_PHONE ||
|
|
220
|
+
const botPhoneCandidate = normalizePhoneDigits(payload?.botPhone || payload?.botNumber || process.env.EMAIL_WELCOME_BOT_PHONE || resolveBotPhoneFromEnv({ fallback: resolveSupportPhoneFromEnv({ fallback: '' }) }) || '', 20);
|
|
220
221
|
const botPhoneDigits = isLikelyPhoneDigits(botPhoneCandidate) ? botPhoneCandidate : '';
|
|
221
222
|
const botPhonePn = formatPhonePn(botPhoneDigits);
|
|
222
223
|
const botWhatsAppUrl = botPhoneDigits ? `https://wa.me/${botPhoneDigits}` : '';
|
|
@@ -6,6 +6,7 @@ import { getMetricsServerConfig, isMetricsEnabled, recordHttpRequest, resolveRou
|
|
|
6
6
|
import { applyCachePolicy } from '../middleware/cachePolicy.js';
|
|
7
7
|
import { applySensitiveRouteRateLimit } from '../middleware/endpointRateLimit.js';
|
|
8
8
|
import { applySecurityHeaders } from '../middleware/securityHeaders.js';
|
|
9
|
+
import { shouldHandleGrafanaProxyPath } from '../routes/observability/grafanaProxyRouter.js';
|
|
9
10
|
import { getIndexRouteConfigs, routeRequest } from '../routes/indexRouter.js';
|
|
10
11
|
import { parseRequestUrl, normalizeRequestId } from './requestContext.js';
|
|
11
12
|
|
|
@@ -65,6 +66,7 @@ export const startHttpServer = () => {
|
|
|
65
66
|
userConfig: routeConfigs?.userConfig || null,
|
|
66
67
|
systemAdminConfig: routeConfigs?.systemAdminConfig || null,
|
|
67
68
|
});
|
|
69
|
+
const isGrafanaProxyRequest = shouldHandleGrafanaProxyPath(pathname, routeConfigs?.grafanaProxyConfig || null);
|
|
68
70
|
|
|
69
71
|
res.once('finish', () => {
|
|
70
72
|
recordHttpRequest({
|
|
@@ -76,10 +78,12 @@ export const startHttpServer = () => {
|
|
|
76
78
|
});
|
|
77
79
|
|
|
78
80
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
if (!isGrafanaProxyRequest) {
|
|
82
|
+
applySecurityHeaders(req, res);
|
|
83
|
+
applyCachePolicy(req, res, { pathname });
|
|
84
|
+
const allowedByRateLimit = await applySensitiveRouteRateLimit(req, res, { pathname });
|
|
85
|
+
if (!allowedByRateLimit) return;
|
|
86
|
+
}
|
|
83
87
|
|
|
84
88
|
await routeRequest(req, res, {
|
|
85
89
|
pathname,
|
|
@@ -10,10 +10,29 @@ const parseEnvBool = (value, fallback = false) => {
|
|
|
10
10
|
return fallback;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
const parseEnvList = (value) =>
|
|
14
|
+
String(value || '')
|
|
15
|
+
.split(',')
|
|
16
|
+
.map((item) => String(item || '').trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
|
|
19
|
+
const toHttpOrigin = (value) => {
|
|
20
|
+
const raw = String(value || '').trim();
|
|
21
|
+
if (!raw) return '';
|
|
22
|
+
try {
|
|
23
|
+
const parsed = new URL(raw);
|
|
24
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) return '';
|
|
25
|
+
return parsed.origin;
|
|
26
|
+
} catch {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
13
31
|
const HELMET_CSP_ENFORCE = parseEnvBool(process.env.HELMET_CONTENT_SECURITY_POLICY_ENABLED, true);
|
|
14
32
|
const BACKEND_BUILD_ID = String(process.env.OMNIZAP_BUILD_ID || '')
|
|
15
33
|
.trim()
|
|
16
34
|
.slice(0, 80);
|
|
35
|
+
const FRAME_SRC_EXTRA = Array.from(new Set([...parseEnvList(process.env.HELMET_CSP_FRAME_SRC_EXTRA), process.env.SYSTEM_ADMIN_GRAFANA_URL, process.env.GRAFANA_PUBLIC_URL].map((item) => toHttpOrigin(item)).filter(Boolean)));
|
|
17
36
|
|
|
18
37
|
const HELMET_CSP_DIRECTIVES = {
|
|
19
38
|
defaultSrc: ["'self'"],
|
|
@@ -26,7 +45,7 @@ const HELMET_CSP_DIRECTIVES = {
|
|
|
26
45
|
imgSrc: ["'self'", 'data:', 'blob:', 'https:'],
|
|
27
46
|
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com', 'https://cdnjs.cloudflare.com'],
|
|
28
47
|
connectSrc: ["'self'", 'https://accounts.google.com', 'https://oauth2.googleapis.com', 'https://api.github.com'],
|
|
29
|
-
frameSrc: ["'self'", 'https://accounts.google.com'],
|
|
48
|
+
frameSrc: ["'self'", 'https://accounts.google.com', ...FRAME_SRC_EXTRA],
|
|
30
49
|
workerSrc: ["'self'", 'blob:'],
|
|
31
50
|
manifestSrc: ["'self'"],
|
|
32
51
|
};
|
|
@@ -24,8 +24,10 @@ const DEFAULT_USER_SYSTEM_ADMIN_WEB_PATH = '/user/systemadm';
|
|
|
24
24
|
const DEFAULT_LEGACY_STICKER_ADMIN_WEB_PATH = '/stickers/admin';
|
|
25
25
|
const DEFAULT_SYSTEM_ADMIN_API_BASE_PATH = '/api/admin';
|
|
26
26
|
const DEFAULT_SYSTEM_ADMIN_API_SESSION_PATH = '/api/admin/session';
|
|
27
|
+
const DEFAULT_SYSTEM_ADMIN_API_MULTI_SESSION_PATH = '/api/admin/multi-session';
|
|
27
28
|
const DEFAULT_LEGACY_STICKER_ADMIN_API_BASE_PATH = '/api/sticker-packs/admin';
|
|
28
29
|
const DEFAULT_LEGACY_STICKER_ADMIN_API_SESSION_PATH = '/api/sticker-packs/admin/session';
|
|
30
|
+
const DEFAULT_LEGACY_STICKER_ADMIN_API_MULTI_SESSION_PATH = '/api/sticker-packs/admin/multi-session';
|
|
29
31
|
|
|
30
32
|
export const getSystemAdminRouterConfig = async () => {
|
|
31
33
|
const controller = await loadSystemAdminController();
|
|
@@ -35,8 +37,10 @@ export const getSystemAdminRouterConfig = async () => {
|
|
|
35
37
|
legacyWebPath: normalizeBasePath(legacyConfig.legacyWebPath, DEFAULT_LEGACY_STICKER_ADMIN_WEB_PATH),
|
|
36
38
|
apiAdminBasePath: normalizeBasePath(legacyConfig.apiAdminBasePath, DEFAULT_SYSTEM_ADMIN_API_BASE_PATH),
|
|
37
39
|
apiAdminSessionPath: normalizeBasePath(legacyConfig.apiAdminSessionPath, DEFAULT_SYSTEM_ADMIN_API_SESSION_PATH),
|
|
40
|
+
apiAdminMultiSessionPath: normalizeBasePath(legacyConfig.apiAdminMultiSessionPath, DEFAULT_SYSTEM_ADMIN_API_MULTI_SESSION_PATH),
|
|
38
41
|
legacyApiAdminBasePath: normalizeBasePath(legacyConfig.legacyApiAdminBasePath, DEFAULT_LEGACY_STICKER_ADMIN_API_BASE_PATH),
|
|
39
42
|
legacyApiAdminSessionPath: normalizeBasePath(legacyConfig.legacyApiAdminSessionPath, DEFAULT_LEGACY_STICKER_ADMIN_API_SESSION_PATH),
|
|
43
|
+
legacyApiAdminMultiSessionPath: normalizeBasePath(legacyConfig.legacyApiAdminMultiSessionPath, DEFAULT_LEGACY_STICKER_ADMIN_API_MULTI_SESSION_PATH),
|
|
40
44
|
};
|
|
41
45
|
};
|
|
42
46
|
|
|
@@ -46,8 +50,10 @@ export const shouldHandleSystemAdminPath = (pathname, systemAdminConfig = null)
|
|
|
46
50
|
legacyWebPath: DEFAULT_LEGACY_STICKER_ADMIN_WEB_PATH,
|
|
47
51
|
apiAdminBasePath: DEFAULT_SYSTEM_ADMIN_API_BASE_PATH,
|
|
48
52
|
apiAdminSessionPath: DEFAULT_SYSTEM_ADMIN_API_SESSION_PATH,
|
|
53
|
+
apiAdminMultiSessionPath: DEFAULT_SYSTEM_ADMIN_API_MULTI_SESSION_PATH,
|
|
49
54
|
legacyApiAdminBasePath: DEFAULT_LEGACY_STICKER_ADMIN_API_BASE_PATH,
|
|
50
55
|
legacyApiAdminSessionPath: DEFAULT_LEGACY_STICKER_ADMIN_API_SESSION_PATH,
|
|
56
|
+
legacyApiAdminMultiSessionPath: DEFAULT_LEGACY_STICKER_ADMIN_API_MULTI_SESSION_PATH,
|
|
51
57
|
};
|
|
52
58
|
|
|
53
59
|
if (startsWithPath(pathname, resolvedConfig.webPath)) return true;
|
|
@@ -9,6 +9,7 @@ import { getStickerSiteRouterConfig, maybeHandleStickerSiteRequest, shouldHandle
|
|
|
9
9
|
import { getStickerDataRouterConfig, maybeHandleStickerDataRequest, shouldHandleStickerDataPath } from './sticker/stickerDataRouter.js';
|
|
10
10
|
import { getStickerApiRouterConfig, maybeHandleStickerApiRequest, shouldHandleStickerApiPath } from './sticker/stickerApiRouter.js';
|
|
11
11
|
import { maybeHandleStaticPageRequest, shouldHandleStaticPagePath } from './static/staticPageRouter.js';
|
|
12
|
+
import { getGrafanaProxyRouterConfig, maybeHandleGrafanaProxyRequest, shouldHandleGrafanaProxyPath } from './observability/grafanaProxyRouter.js';
|
|
12
13
|
|
|
13
14
|
const startsWithPath = (pathname, prefix) => {
|
|
14
15
|
if (!pathname || !prefix) return false;
|
|
@@ -112,12 +113,27 @@ const loadStickerApiConfigSafe = async () => {
|
|
|
112
113
|
}
|
|
113
114
|
};
|
|
114
115
|
|
|
116
|
+
const loadGrafanaProxyConfigSafe = async () => {
|
|
117
|
+
try {
|
|
118
|
+
return getGrafanaProxyRouterConfig();
|
|
119
|
+
} catch {
|
|
120
|
+
return {
|
|
121
|
+
enabled: false,
|
|
122
|
+
basePath: '/api/grafana',
|
|
123
|
+
legacyBasePath: '/grafana',
|
|
124
|
+
timeoutMs: 15000,
|
|
125
|
+
target: null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
115
130
|
export const getIndexRouteConfigs = async () => {
|
|
116
131
|
if (!indexRouteConfigsPromise) {
|
|
117
|
-
indexRouteConfigsPromise = Promise.all([loadUserConfigSafe(), loadSystemAdminConfigSafe(), loadEmailAutomationConfigSafe(), loadStickerSiteConfigSafe(), loadStickerDataConfigSafe(), loadStickerApiConfigSafe()]).then(([userConfig, systemAdminConfig, emailAutomationConfig, stickerSiteConfig, stickerDataConfig, stickerApiConfig]) => ({
|
|
132
|
+
indexRouteConfigsPromise = Promise.all([loadUserConfigSafe(), loadSystemAdminConfigSafe(), loadEmailAutomationConfigSafe(), loadStickerSiteConfigSafe(), loadStickerDataConfigSafe(), loadStickerApiConfigSafe(), loadGrafanaProxyConfigSafe()]).then(([userConfig, systemAdminConfig, emailAutomationConfig, stickerSiteConfig, stickerDataConfig, stickerApiConfig, grafanaProxyConfig]) => ({
|
|
118
133
|
userConfig,
|
|
119
134
|
systemAdminConfig,
|
|
120
135
|
emailAutomationConfig,
|
|
136
|
+
grafanaProxyConfig,
|
|
121
137
|
stickerConfig: {
|
|
122
138
|
...stickerSiteConfig,
|
|
123
139
|
...stickerDataConfig,
|
|
@@ -140,6 +156,7 @@ export const routeRequest = async (req, res, { pathname, url, metricsPath = '/me
|
|
|
140
156
|
const userConfig = resolvedConfigs?.userConfig || null;
|
|
141
157
|
const systemAdminConfig = resolvedConfigs?.systemAdminConfig || null;
|
|
142
158
|
const emailAutomationConfig = resolvedConfigs?.emailAutomationConfig || null;
|
|
159
|
+
const grafanaProxyConfig = resolvedConfigs?.grafanaProxyConfig || null;
|
|
143
160
|
const stickerConfig = resolvedConfigs?.stickerConfig || null;
|
|
144
161
|
|
|
145
162
|
// 1) Metrics
|
|
@@ -163,7 +180,14 @@ export const routeRequest = async (req, res, { pathname, url, metricsPath = '/me
|
|
|
163
180
|
return sendNotFound(req, res);
|
|
164
181
|
}
|
|
165
182
|
|
|
166
|
-
// 4)
|
|
183
|
+
// 4) Grafana proxy (/api/grafana e alias /grafana)
|
|
184
|
+
if (shouldHandleGrafanaProxyPath(pathname, grafanaProxyConfig)) {
|
|
185
|
+
const handled = await maybeHandleGrafanaProxyRequest(req, res, { pathname, url, config: grafanaProxyConfig });
|
|
186
|
+
if (handled) return true;
|
|
187
|
+
return sendNotFound(req, res);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 5) User
|
|
167
191
|
const systemAdminCandidate = shouldHandleSystemAdminStep(pathname, systemAdminConfig);
|
|
168
192
|
if (shouldHandleUserStep(pathname, userConfig)) {
|
|
169
193
|
const handled = await maybeHandleUserRequest(req, res, { pathname, url });
|
|
@@ -173,14 +197,14 @@ export const routeRequest = async (req, res, { pathname, url, metricsPath = '/me
|
|
|
173
197
|
if (!systemAdminCandidate) return sendNotFound(req, res);
|
|
174
198
|
}
|
|
175
199
|
|
|
176
|
-
//
|
|
200
|
+
// 6) System admin + legacy /stickers/admin
|
|
177
201
|
if (systemAdminCandidate) {
|
|
178
202
|
const handled = await maybeHandleSystemAdminRequest(req, res, { pathname, url });
|
|
179
203
|
if (handled) return true;
|
|
180
204
|
return sendNotFound(req, res);
|
|
181
205
|
}
|
|
182
206
|
|
|
183
|
-
//
|
|
207
|
+
// 7) Sticker catalog apenas nos prefixes permitidos
|
|
184
208
|
if (shouldHandleStickerSitePath(pathname, stickerConfig)) {
|
|
185
209
|
const handled = await maybeHandleStickerSiteRequest(req, res, { pathname, url });
|
|
186
210
|
if (handled) return true;
|
|
@@ -212,14 +236,14 @@ export const routeRequest = async (req, res, { pathname, url, metricsPath = '/me
|
|
|
212
236
|
return sendNotFound(req, res);
|
|
213
237
|
}
|
|
214
238
|
|
|
215
|
-
//
|
|
239
|
+
// 8) Paginas estaticas (templates em public/pages)
|
|
216
240
|
if (shouldHandleStaticPagePath(pathname)) {
|
|
217
241
|
const handled = await maybeHandleStaticPageRequest(req, res, { pathname });
|
|
218
242
|
if (handled) return true;
|
|
219
243
|
return sendNotFound(req, res);
|
|
220
244
|
}
|
|
221
245
|
|
|
222
|
-
//
|
|
246
|
+
// 9) 404 global
|
|
223
247
|
return sendNotFound(req, res);
|
|
224
248
|
};
|
|
225
249
|
|