@omnizap-system/omnizap 2.6.1 → 2.6.3

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.
Files changed (172) hide show
  1. package/.env.example +78 -9
  2. package/.github/workflows/ci.yml +3 -3
  3. package/.github/workflows/security-runner-hardening.yml +1 -1
  4. package/.github/workflows/security-zap-full-scan.yml +1 -0
  5. package/app/config/index.js +6 -0
  6. package/app/configParts/adminIdentity.js +36 -7
  7. package/app/configParts/baileysConfig.js +343 -56
  8. package/app/configParts/groupUtils.js +226 -0
  9. package/app/configParts/loggerConfig.js +185 -0
  10. package/app/configParts/messagePersistenceService.js +307 -5
  11. package/app/configParts/sessionConfig.js +242 -0
  12. package/app/connection/baileysCompatibility.test.js +10 -1
  13. package/app/connection/baileysDbAuthState.js +205 -9
  14. package/app/connection/baileysLibsignalPatch.js +210 -0
  15. package/app/connection/groupOwnerWriteStateResolver.js +141 -0
  16. package/app/connection/socketController.js +694 -123
  17. package/app/connection/socketController.multiSession.test.js +128 -0
  18. package/app/controllers/messageController.js +1 -1
  19. package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
  20. package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
  21. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
  22. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +96 -4
  23. package/app/controllers/messageProcessingPipeline.js +90 -9
  24. package/app/controllers/messageProcessingPipeline.test.js +202 -0
  25. package/app/modules/adminModule/AGENT.md +1 -1
  26. package/app/modules/adminModule/commandConfig.json +3318 -1347
  27. package/app/modules/adminModule/groupCommandHandlers.js +856 -14
  28. package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
  29. package/app/modules/adminModule/groupWarningRepository.js +152 -0
  30. package/app/modules/aiModule/AGENT.md +47 -30
  31. package/app/modules/aiModule/aiConfigRuntime.js +1 -0
  32. package/app/modules/aiModule/catCommand.js +132 -25
  33. package/app/modules/aiModule/commandConfig.json +114 -28
  34. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
  35. package/app/modules/gameModule/AGENT.md +1 -1
  36. package/app/modules/gameModule/commandConfig.json +29 -0
  37. package/app/modules/menuModule/AGENT.md +1 -1
  38. package/app/modules/menuModule/commandConfig.json +45 -10
  39. package/app/modules/menuModule/menuCatalogService.js +190 -0
  40. package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
  41. package/app/modules/menuModule/menuDynamicService.js +511 -0
  42. package/app/modules/menuModule/menuDynamicService.test.js +141 -0
  43. package/app/modules/menuModule/menus.js +36 -5
  44. package/app/modules/playModule/AGENT.md +10 -5
  45. package/app/modules/playModule/commandConfig.json +74 -16
  46. package/app/modules/playModule/playCommandConstants.js +13 -7
  47. package/app/modules/playModule/playCommandCore.js +4 -6
  48. package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
  49. package/app/modules/playModule/playConfigRuntime.js +5 -6
  50. package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
  51. package/app/modules/quoteModule/AGENT.md +1 -1
  52. package/app/modules/quoteModule/commandConfig.json +29 -0
  53. package/app/modules/rpgPokemonModule/AGENT.md +1 -1
  54. package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
  55. package/app/modules/statsModule/AGENT.md +1 -1
  56. package/app/modules/statsModule/commandConfig.json +58 -0
  57. package/app/modules/stickerModule/AGENT.md +1 -1
  58. package/app/modules/stickerModule/commandConfig.json +145 -0
  59. package/app/modules/stickerPackModule/AGENT.md +1 -1
  60. package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
  61. package/app/modules/stickerPackModule/commandConfig.json +29 -0
  62. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +1 -1
  63. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
  64. package/app/modules/stickerPackModule/stickerPackService.js +13 -6
  65. package/app/modules/systemMetricsModule/AGENT.md +1 -1
  66. package/app/modules/systemMetricsModule/commandConfig.json +29 -0
  67. package/app/modules/tiktokModule/AGENT.md +1 -1
  68. package/app/modules/tiktokModule/commandConfig.json +29 -0
  69. package/app/modules/userModule/AGENT.md +1 -1
  70. package/app/modules/userModule/commandConfig.json +29 -0
  71. package/app/modules/waifuPicsModule/AGENT.md +57 -27
  72. package/app/modules/waifuPicsModule/commandConfig.json +87 -0
  73. package/app/observability/metrics.js +136 -0
  74. package/app/services/ai/commandConfigEnrichmentService.js +229 -47
  75. package/app/services/ai/geminiService.js +131 -7
  76. package/app/services/ai/geminiService.test.js +59 -2
  77. package/app/services/ai/moduleAiHelpCoreService.js +33 -4
  78. package/app/services/group/groupMetadataService.js +24 -1
  79. package/app/services/infra/dbWriteQueue.js +51 -21
  80. package/app/services/messaging/newsBroadcastService.js +843 -27
  81. package/app/services/multiSession/assignmentBalancerService.js +452 -0
  82. package/app/services/multiSession/groupOwnershipRepository.js +346 -0
  83. package/app/services/multiSession/groupOwnershipService.js +809 -0
  84. package/app/services/multiSession/groupOwnershipService.test.js +317 -0
  85. package/app/services/multiSession/sessionRegistryService.js +239 -0
  86. package/app/store/aiPromptStore.js +36 -19
  87. package/app/store/groupConfigStore.js +41 -5
  88. package/app/store/premiumUserStore.js +21 -7
  89. package/app/utils/antiLink/antiLinkModule.js +391 -25
  90. package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
  91. package/database/index.js +6 -0
  92. package/database/migrations/20260307_d0_hardening_down.sql +1 -1
  93. package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
  94. package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
  95. package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
  96. package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
  97. package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
  98. package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
  99. package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
  100. package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
  101. package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
  102. package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
  103. package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
  104. package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
  105. package/database/schema.sql +102 -1
  106. package/docker-compose.yml +4 -1
  107. package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
  108. package/docs/compliance/privacy-policy-2026-03-07.md +2 -2
  109. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
  110. package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
  111. package/docs/security/omnizap-static-security-headers.conf +25 -0
  112. package/ecosystem.prod.config.cjs +31 -11
  113. package/index.js +52 -18
  114. package/observability/alert-rules.yml +20 -0
  115. package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
  116. package/observability/mysql-setup.sql +4 -4
  117. package/observability/system-admin-observability.md +26 -0
  118. package/package.json +14 -6
  119. package/public/comandos/commands-catalog.json +2253 -78
  120. package/public/css/payments-react.css +478 -0
  121. package/public/js/apps/commandsReactApp.js +267 -87
  122. package/public/js/apps/createPackApp.js +3 -3
  123. package/public/js/apps/homeReactApp.js +2 -2
  124. package/public/js/apps/paymentsCancelReactApp.js +45 -0
  125. package/public/js/apps/paymentsReactApp.js +399 -0
  126. package/public/js/apps/paymentsSuccessReactApp.js +148 -0
  127. package/public/js/apps/stickersApp.js +255 -103
  128. package/public/js/apps/termsReactApp.js +57 -8
  129. package/public/js/apps/userPasswordResetReactApp.js +406 -0
  130. package/public/js/apps/userReactApp.js +96 -47
  131. package/public/js/apps/userSystemAdmReactApp.js +1506 -0
  132. package/public/pages/pagamentos-cancelado.html +21 -0
  133. package/public/pages/pagamentos-sucesso.html +21 -0
  134. package/public/pages/pagamentos.html +30 -0
  135. package/public/pages/politica-de-privacidade.html +1 -1
  136. package/public/pages/stickers.html +5 -5
  137. package/public/pages/termos-de-uso-texto-integral.html +1 -1
  138. package/public/pages/termos-de-uso.html +1 -1
  139. package/public/pages/user-password-reset.html +3 -4
  140. package/public/pages/user-systemadm.html +8 -462
  141. package/public/pages/user.html +1 -1
  142. package/scripts/clear-whatsapp-session.sh +123 -0
  143. package/scripts/core-ai-mode.mjs +163 -0
  144. package/scripts/deploy.sh +13 -0
  145. package/scripts/enrich-command-config-ux-openai.mjs +492 -0
  146. package/scripts/generate-commands-catalog.mjs +155 -0
  147. package/scripts/new-whatsapp-session.sh +564 -0
  148. package/scripts/security-web-surface-check.mjs +218 -0
  149. package/server/controllers/admin/adminPanelHandlers.js +253 -3
  150. package/server/controllers/admin/systemAdminController.js +254 -0
  151. package/server/controllers/payments/paymentsController.js +731 -0
  152. package/server/controllers/sticker/stickerCatalogController.js +9 -23
  153. package/server/controllers/system/contactController.js +9 -17
  154. package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
  155. package/server/controllers/system/systemController.js +228 -1
  156. package/server/controllers/userController.js +6 -0
  157. package/server/email/emailAutomationRuntime.js +36 -1
  158. package/server/email/emailAutomationService.js +42 -1
  159. package/server/email/emailTemplateService.js +140 -33
  160. package/server/http/httpRequestUtils.js +18 -14
  161. package/server/http/httpServer.js +8 -4
  162. package/server/middleware/securityHeaders.js +35 -3
  163. package/server/routes/admin/systemAdminRouter.js +6 -0
  164. package/server/routes/indexRouter.js +50 -6
  165. package/server/routes/observability/grafanaProxyRouter.js +254 -0
  166. package/server/routes/payments/paymentsRouter.js +47 -0
  167. package/server/routes/static/staticPageRouter.js +30 -1
  168. package/server/utils/publicContact.js +31 -0
  169. package/utils/whatsapp/contactEnv.js +39 -0
  170. package/vite.config.mjs +5 -1
  171. package/app/modules/playModule/local/installYtDlp.js +0 -25
  172. package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
@@ -1,8 +1,10 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { timingSafeEqual } from 'node:crypto';
3
4
  import { URL } from 'node:url';
4
5
 
5
6
  import logger from '#logger';
7
+ import { forceSystemAdminGroupFailover, listSystemAdminAssignmentHistory, listSystemAdminAssignments, listSystemAdminSessions, setSystemAdminGroupPin, triggerSystemAdminManualRebalance } from '../system/systemController.js';
6
8
 
7
9
  const parseEnvBool = (value, fallback) => {
8
10
  if (value === undefined || value === null || value === '') return fallback;
@@ -25,6 +27,8 @@ const SYSTEM_ADMIN_API_BASE_PATH = normalizeBasePath(process.env.SYSTEM_ADMIN_AP
25
27
  const SYSTEM_ADMIN_API_SESSION_PATH = `${SYSTEM_ADMIN_API_BASE_PATH}/session`;
26
28
  const LEGACY_SYSTEM_ADMIN_API_BASE_PATH = `${LEGACY_STICKER_API_BASE_PATH}/admin`;
27
29
  const LEGACY_SYSTEM_ADMIN_API_SESSION_PATH = `${LEGACY_SYSTEM_ADMIN_API_BASE_PATH}/session`;
30
+ const SYSTEM_ADMIN_MULTI_SESSION_API_PATH = `${SYSTEM_ADMIN_API_BASE_PATH}/multi-session`;
31
+ const LEGACY_SYSTEM_ADMIN_MULTI_SESSION_API_PATH = `${LEGACY_SYSTEM_ADMIN_API_BASE_PATH}/multi-session`;
28
32
  const STICKER_LOGIN_WEB_PATH = normalizeBasePath(process.env.STICKER_LOGIN_WEB_PATH, '/login');
29
33
  const STICKER_WEB_PATH = normalizeBasePath(process.env.STICKER_WEB_PATH, '/stickers');
30
34
  const STICKER_ADMIN_WEB_PATH = `${STICKER_WEB_PATH}/admin`;
@@ -37,6 +41,7 @@ const SITE_ORIGIN = String(process.env.SITE_ORIGIN || 'https://omnizap.shop')
37
41
 
38
42
  const USER_SYSTEMADM_TEMPLATE_PATH = path.join(process.cwd(), 'public', 'pages', 'user-systemadm.html');
39
43
  const LEGACY_STICKER_ADMIN_TEMPLATE_PATH = path.join(process.cwd(), 'public', 'pages', 'stickers-admin.html');
44
+ const SYSTEM_ADMIN_OPS_TOKEN = String(process.env.SYSTEM_ADMIN_OPS_TOKEN || process.env.USER_INTERNAL_API_TOKEN || process.env.ADMIN_TOKEN || process.env.ADMIN_API_TOKEN || '').trim();
40
45
 
41
46
  let stickerCatalogControllerPromise = null;
42
47
  const loadStickerCatalogController = async () => {
@@ -108,6 +113,104 @@ const mapAdminApiPathToLegacy = (pathname) => {
108
113
  return null;
109
114
  };
110
115
 
116
+ const mapMultiSessionApiPath = (pathname) => {
117
+ if (hasPathPrefix(pathname, SYSTEM_ADMIN_MULTI_SESSION_API_PATH)) {
118
+ const suffix = pathname.slice(SYSTEM_ADMIN_MULTI_SESSION_API_PATH.length);
119
+ return suffix || '/';
120
+ }
121
+ if (hasPathPrefix(pathname, LEGACY_SYSTEM_ADMIN_MULTI_SESSION_API_PATH)) {
122
+ const suffix = pathname.slice(LEGACY_SYSTEM_ADMIN_MULTI_SESSION_API_PATH.length);
123
+ return suffix || '/';
124
+ }
125
+ return null;
126
+ };
127
+
128
+ const constantTimeStringEqual = (left, right) => {
129
+ const leftBuffer = Buffer.from(String(left || ''), 'utf8');
130
+ const rightBuffer = Buffer.from(String(right || ''), 'utf8');
131
+ if (!leftBuffer.length || leftBuffer.length !== rightBuffer.length) return false;
132
+ try {
133
+ return timingSafeEqual(leftBuffer, rightBuffer);
134
+ } catch {
135
+ return false;
136
+ }
137
+ };
138
+
139
+ const extractBearerToken = (req) => {
140
+ const authHeader = String(req?.headers?.authorization || '').trim();
141
+ if (!authHeader.toLowerCase().startsWith('bearer ')) return '';
142
+ return authHeader.slice(7).trim();
143
+ };
144
+
145
+ const resolveOpsTokenFromRequest = (req) => String(req?.headers?.['x-system-admin-token'] || req?.headers?.['x-internal-api-token'] || req?.headers?.['x-admin-token'] || '').trim() || extractBearerToken(req);
146
+
147
+ const hasValidOpsToken = (req) => {
148
+ if (!SYSTEM_ADMIN_OPS_TOKEN) return true;
149
+ const requestToken = resolveOpsTokenFromRequest(req);
150
+ if (!requestToken) return false;
151
+ return constantTimeStringEqual(requestToken, SYSTEM_ADMIN_OPS_TOKEN);
152
+ };
153
+
154
+ const readJsonBody = async (req, { maxBytes = 64 * 1024 } = {}) =>
155
+ new Promise((resolve, reject) => {
156
+ const chunks = [];
157
+ let total = 0;
158
+
159
+ req.on('data', (chunk) => {
160
+ total += chunk.length;
161
+ if (total > maxBytes) {
162
+ const error = new Error('Payload excedeu limite permitido.');
163
+ error.statusCode = 413;
164
+ reject(error);
165
+ req.destroy();
166
+ return;
167
+ }
168
+ chunks.push(chunk);
169
+ });
170
+
171
+ req.on('end', () => {
172
+ const raw = Buffer.concat(chunks).toString('utf8').trim();
173
+ if (!raw) {
174
+ resolve({});
175
+ return;
176
+ }
177
+
178
+ try {
179
+ resolve(JSON.parse(raw));
180
+ } catch {
181
+ const error = new Error('JSON invalido.');
182
+ error.statusCode = 400;
183
+ reject(error);
184
+ }
185
+ });
186
+
187
+ req.on('error', (error) => reject(error));
188
+ });
189
+
190
+ const parseBool = (value, fallback = false) => {
191
+ if (value === undefined || value === null || value === '') return fallback;
192
+ const normalized = String(value).trim().toLowerCase();
193
+ if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
194
+ if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
195
+ return fallback;
196
+ };
197
+
198
+ const parsePositiveInt = (value, fallback = 200, min = 1, max = 5000) => {
199
+ const parsed = Number.parseInt(String(value ?? ''), 10);
200
+ if (!Number.isFinite(parsed)) return fallback;
201
+ return Math.max(min, Math.min(max, parsed));
202
+ };
203
+
204
+ const decodePathSegment = (value) => {
205
+ const raw = String(value || '').trim();
206
+ if (!raw) return '';
207
+ try {
208
+ return decodeURIComponent(raw);
209
+ } catch {
210
+ return raw;
211
+ }
212
+ };
213
+
111
214
  const renderUserSystemAdminHtml = async () => {
112
215
  const template = await fs.readFile(USER_SYSTEMADM_TEMPLATE_PATH, 'utf8');
113
216
  const dataAttributes = {
@@ -124,13 +227,145 @@ const renderUserSystemAdminHtml = async () => {
124
227
  return html;
125
228
  };
126
229
 
230
+ const requireSystemAdminOpsAccess = (req, res) => {
231
+ if (hasValidOpsToken(req)) return true;
232
+ sendJson(req, res, 401, { error: 'Nao autorizado para operacoes de system admin.' });
233
+ return false;
234
+ };
235
+
236
+ const normalizeMultiSessionSubPath = (value) => {
237
+ const raw = String(value || '/').trim();
238
+ if (!raw || raw === '/') return '/';
239
+ return `/${raw.replace(/^\/+/g, '').replace(/\/+$/g, '')}`;
240
+ };
241
+
242
+ const handleMultiSessionOpsRequest = async (req, res, { pathname, url }) => {
243
+ if (!requireSystemAdminOpsAccess(req, res)) return true;
244
+
245
+ const subPath = normalizeMultiSessionSubPath(pathname);
246
+ const requestUrl = (() => {
247
+ try {
248
+ return new URL(String(url?.href || req.url || '/'), SITE_ORIGIN);
249
+ } catch {
250
+ return new URL(SITE_ORIGIN);
251
+ }
252
+ })();
253
+
254
+ if (subPath === '/sessions') {
255
+ if (!['GET', 'HEAD'].includes(req.method || '')) {
256
+ sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
257
+ return true;
258
+ }
259
+ const payload = await listSystemAdminSessions({
260
+ status: requestUrl.searchParams.get('status'),
261
+ limit: parsePositiveInt(requestUrl.searchParams.get('limit'), 200, 1, 5000),
262
+ });
263
+ sendJson(req, res, 200, payload);
264
+ return true;
265
+ }
266
+
267
+ if (subPath === '/assignments') {
268
+ if (!['GET', 'HEAD'].includes(req.method || '')) {
269
+ sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
270
+ return true;
271
+ }
272
+ const payload = await listSystemAdminAssignments({
273
+ groupJid: requestUrl.searchParams.get('group_jid'),
274
+ ownerSessionId: requestUrl.searchParams.get('owner_session_id'),
275
+ includeExpired: parseBool(requestUrl.searchParams.get('include_expired'), false),
276
+ limit: parsePositiveInt(requestUrl.searchParams.get('limit'), 200, 1, 5000),
277
+ });
278
+ sendJson(req, res, 200, payload);
279
+ return true;
280
+ }
281
+
282
+ if (subPath === '/history') {
283
+ if (!['GET', 'HEAD'].includes(req.method || '')) {
284
+ sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
285
+ return true;
286
+ }
287
+ const payload = await listSystemAdminAssignmentHistory({
288
+ groupJid: requestUrl.searchParams.get('group_jid'),
289
+ limit: parsePositiveInt(requestUrl.searchParams.get('limit'), 100, 1, 5000),
290
+ });
291
+ sendJson(req, res, 200, payload);
292
+ return true;
293
+ }
294
+
295
+ if (subPath === '/rebalance') {
296
+ if (!['POST'].includes(req.method || '')) {
297
+ sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
298
+ return true;
299
+ }
300
+ const payload = await triggerSystemAdminManualRebalance();
301
+ sendJson(req, res, 200, payload);
302
+ return true;
303
+ }
304
+
305
+ const groupActionMatch = subPath.match(/^\/groups\/([^/]+)\/(pin|unpin|failover)$/i);
306
+ if (groupActionMatch) {
307
+ if (!['POST'].includes(req.method || '')) {
308
+ sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
309
+ return true;
310
+ }
311
+
312
+ const groupJid = decodePathSegment(groupActionMatch[1]);
313
+ const action = String(groupActionMatch[2] || '').toLowerCase();
314
+ const body = await readJsonBody(req).catch((error) => {
315
+ const statusCode = Number(error?.statusCode || 400);
316
+ sendJson(req, res, statusCode, { error: error?.message || 'Falha ao interpretar payload JSON.' });
317
+ return null;
318
+ });
319
+ if (body === null) return true;
320
+
321
+ if (action === 'pin' || action === 'unpin') {
322
+ const pinned = action === 'pin';
323
+ const payload = await setSystemAdminGroupPin({
324
+ groupJid,
325
+ pinned,
326
+ sessionId: body?.session_id || body?.sessionId || null,
327
+ reason: body?.reason || null,
328
+ changedBy: 'system_admin_api',
329
+ metadata: body?.metadata || null,
330
+ });
331
+ sendJson(req, res, 200, payload);
332
+ return true;
333
+ }
334
+
335
+ if (action === 'failover') {
336
+ const targetSessionId = String(body?.target_session_id || body?.targetSessionId || requestUrl.searchParams.get('target_session_id') || '')
337
+ .trim()
338
+ .slice(0, 64);
339
+ if (!targetSessionId) {
340
+ sendJson(req, res, 400, { error: 'target_session_id e obrigatorio.' });
341
+ return true;
342
+ }
343
+
344
+ const payload = await forceSystemAdminGroupFailover({
345
+ groupJid,
346
+ targetSessionId,
347
+ reason: body?.reason || 'admin_force_failover',
348
+ changedBy: 'system_admin_api',
349
+ metadata: body?.metadata || null,
350
+ });
351
+ sendJson(req, res, 200, payload);
352
+ return true;
353
+ }
354
+ }
355
+
356
+ sendJson(req, res, 404, { error: 'Endpoint de operacao multi-session nao encontrado.' });
357
+ return true;
358
+ };
359
+
127
360
  export const getSystemAdminRouteConfig = () => ({
128
361
  webPath: USER_SYSTEMADM_WEB_PATH,
129
362
  legacyWebPath: STICKER_ADMIN_WEB_PATH,
130
363
  apiAdminBasePath: SYSTEM_ADMIN_API_BASE_PATH,
131
364
  apiAdminSessionPath: SYSTEM_ADMIN_API_SESSION_PATH,
365
+ apiAdminMultiSessionPath: SYSTEM_ADMIN_MULTI_SESSION_API_PATH,
132
366
  legacyApiAdminBasePath: LEGACY_SYSTEM_ADMIN_API_BASE_PATH,
133
367
  legacyApiAdminSessionPath: LEGACY_SYSTEM_ADMIN_API_SESSION_PATH,
368
+ legacyApiAdminMultiSessionPath: LEGACY_SYSTEM_ADMIN_MULTI_SESSION_API_PATH,
134
369
  });
135
370
 
136
371
  export const maybeHandleSystemAdminRequest = async (req, res, { pathname, url }) => {
@@ -186,6 +421,25 @@ export const maybeHandleSystemAdminRequest = async (req, res, { pathname, url })
186
421
  }
187
422
 
188
423
  if (hasPathPrefix(pathname, SYSTEM_ADMIN_API_BASE_PATH) || hasPathPrefix(pathname, LEGACY_SYSTEM_ADMIN_API_BASE_PATH)) {
424
+ const multiSessionPath = mapMultiSessionApiPath(pathname);
425
+ if (multiSessionPath !== null) {
426
+ try {
427
+ return await handleMultiSessionOpsRequest(req, res, {
428
+ pathname: multiSessionPath,
429
+ url,
430
+ });
431
+ } catch (error) {
432
+ logger.error('Falha ao processar endpoint operacional multi-sessao.', {
433
+ action: 'system_admin_multi_session_endpoint_failed',
434
+ method: req.method,
435
+ path: pathname,
436
+ error: error?.message,
437
+ });
438
+ sendJson(req, res, 500, { error: 'Falha interna ao processar operacao multi-sessao.' });
439
+ return true;
440
+ }
441
+ }
442
+
189
443
  const legacyPathname = mapAdminApiPathToLegacy(pathname);
190
444
  if (!legacyPathname) return false;
191
445