@omnizap-system/omnizap 2.6.0 → 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.
Files changed (261) hide show
  1. package/.env.example +58 -13
  2. package/.github/workflows/ci.yml +5 -5
  3. package/.github/workflows/codeql.yml +1 -1
  4. package/.github/workflows/db-migration-check.yml +2 -2
  5. package/.github/workflows/dependency-review.yml +1 -1
  6. package/.github/workflows/deploy.yml +2 -2
  7. package/.github/workflows/release.yml +2 -2
  8. package/.github/workflows/security-attest-provenance.yml +2 -2
  9. package/.github/workflows/security-gitleaks.yml +13 -4
  10. package/.github/workflows/security-runner-hardening.yml +2 -2
  11. package/.github/workflows/security-scorecard.yml +1 -1
  12. package/.github/workflows/security-zap-baseline.yml +1 -1
  13. package/.github/workflows/security-zap-full-scan.yml +2 -1
  14. package/.github/workflows/security-zizmor.yml +1 -1
  15. package/.github/workflows/wiki-sync.yml +1 -1
  16. package/.gitleaksignore +9 -0
  17. package/CODE_OF_CONDUCT.md +2 -2
  18. package/GEMINI.md +64 -0
  19. package/README.md +52 -82
  20. package/SECURITY.md +1 -1
  21. package/app/config/index.js +2 -0
  22. package/app/configParts/adminIdentity.js +5 -5
  23. package/app/configParts/baileysConfig.js +230 -58
  24. package/app/configParts/groupUtils.js +5 -0
  25. package/app/configParts/messagePersistenceService.js +145 -4
  26. package/app/configParts/sessionConfig.js +157 -0
  27. package/app/connection/baileysCompatibility.test.js +1 -1
  28. package/app/connection/groupOwnerWriteStateResolver.js +109 -0
  29. package/app/connection/socketController.js +660 -158
  30. package/app/connection/socketController.multiSession.test.js +108 -0
  31. package/app/controllers/messageController.js +1 -1
  32. package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
  33. package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
  34. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
  35. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
  36. package/app/controllers/messageProcessingPipeline.js +93 -13
  37. package/app/controllers/messageProcessingPipeline.test.js +200 -0
  38. package/app/modules/adminModule/AGENT.md +1 -1
  39. package/app/modules/adminModule/commandConfig.json +3318 -1347
  40. package/app/modules/adminModule/groupCommandHandlers.js +858 -15
  41. package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
  42. package/app/modules/adminModule/groupWarningRepository.js +152 -0
  43. package/app/modules/aiModule/AGENT.md +47 -30
  44. package/app/modules/aiModule/aiConfigRuntime.js +1 -0
  45. package/app/modules/aiModule/catCommand.js +135 -27
  46. package/app/modules/aiModule/commandConfig.json +114 -28
  47. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
  48. package/app/modules/gameModule/AGENT.md +1 -1
  49. package/app/modules/gameModule/commandConfig.json +29 -0
  50. package/app/modules/menuModule/AGENT.md +1 -1
  51. package/app/modules/menuModule/commandConfig.json +45 -10
  52. package/app/modules/menuModule/menuCatalogService.js +190 -0
  53. package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
  54. package/app/modules/menuModule/menuDynamicService.js +511 -0
  55. package/app/modules/menuModule/menuDynamicService.test.js +141 -0
  56. package/app/modules/menuModule/menus.js +36 -5
  57. package/app/modules/playModule/AGENT.md +10 -5
  58. package/app/modules/playModule/commandConfig.json +140 -12
  59. package/app/modules/playModule/playCommand.js +1 -1417
  60. package/app/modules/playModule/playCommandConstants.js +80 -0
  61. package/app/modules/playModule/playCommandCore.js +361 -0
  62. package/app/modules/playModule/playCommandHandlers.js +41 -0
  63. package/app/modules/playModule/playCommandMediaClient.js +1872 -0
  64. package/app/modules/playModule/playConfigRuntime.js +245 -4
  65. package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
  66. package/app/modules/quoteModule/AGENT.md +1 -1
  67. package/app/modules/quoteModule/commandConfig.json +29 -0
  68. package/app/modules/quoteModule/quoteCommand.js +3 -2
  69. package/app/modules/rpgPokemonModule/AGENT.md +1 -1
  70. package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
  71. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
  72. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
  73. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
  74. package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
  75. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
  76. package/app/modules/statsModule/AGENT.md +1 -1
  77. package/app/modules/statsModule/commandConfig.json +58 -0
  78. package/app/modules/statsModule/rankingCommon.js +5 -4
  79. package/app/modules/stickerModule/AGENT.md +1 -1
  80. package/app/modules/stickerModule/addStickerMetadata.js +4 -3
  81. package/app/modules/stickerModule/commandConfig.json +145 -0
  82. package/app/modules/stickerModule/stickerCommand.js +1 -1
  83. package/app/modules/stickerPackModule/AGENT.md +1 -1
  84. package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
  85. package/app/modules/stickerPackModule/commandConfig.json +29 -0
  86. package/app/modules/stickerPackModule/semanticThemeClusterService.js +7 -6
  87. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
  88. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
  89. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
  90. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
  91. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
  92. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
  93. package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
  94. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
  95. package/app/modules/stickerPackModule/stickerPackService.js +13 -6
  96. package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
  97. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
  98. package/app/modules/systemMetricsModule/AGENT.md +1 -1
  99. package/app/modules/systemMetricsModule/commandConfig.json +29 -0
  100. package/app/modules/systemMetricsModule/pingCommand.js +6 -5
  101. package/app/modules/tiktokModule/AGENT.md +1 -1
  102. package/app/modules/tiktokModule/commandConfig.json +29 -0
  103. package/app/modules/tiktokModule/tiktokCommand.js +2 -1
  104. package/app/modules/userModule/AGENT.md +1 -1
  105. package/app/modules/userModule/commandConfig.json +29 -0
  106. package/app/modules/userModule/userCommand.js +72 -23
  107. package/app/modules/waifuPicsModule/AGENT.md +57 -27
  108. package/app/modules/waifuPicsModule/commandConfig.json +87 -0
  109. package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
  110. package/app/observability/metrics.js +136 -0
  111. package/app/services/ai/commandConfigEnrichmentService.js +229 -47
  112. package/app/services/ai/conversationRouterService.js +4 -3
  113. package/app/services/ai/geminiService.js +132 -7
  114. package/app/services/ai/geminiService.test.js +59 -2
  115. package/app/services/ai/globalModuleAiHelpService.js +3 -2
  116. package/app/services/ai/messageCommandExecutionService.js +2 -1
  117. package/app/services/ai/moduleAiHelpCoreService.js +45 -14
  118. package/app/services/ai/moduleToolExecutorService.js +3 -2
  119. package/app/services/ai/moduleToolRegistryService.js +2 -1
  120. package/app/services/ai/toolCandidateSelectorService.js +6 -5
  121. package/app/services/auth/googleWebLinkService.js +3 -2
  122. package/app/services/auth/whatsappLoginLinkService.js +3 -2
  123. package/app/services/external/pokeApiService.js +4 -3
  124. package/app/services/group/groupMetadataService.js +24 -1
  125. package/app/services/infra/dbWriteQueue.js +57 -26
  126. package/app/services/infra/featureFlagService.js +2 -1
  127. package/app/services/messaging/captchaService.js +3 -2
  128. package/app/services/messaging/newsBroadcastService.js +846 -29
  129. package/app/services/multiSession/assignmentBalancerService.js +457 -0
  130. package/app/services/multiSession/groupOwnershipRepository.js +381 -0
  131. package/app/services/multiSession/groupOwnershipService.js +890 -0
  132. package/app/services/multiSession/groupOwnershipService.test.js +309 -0
  133. package/app/services/multiSession/sessionRegistryService.js +293 -0
  134. package/app/services/sticker/stickerFocusService.js +11 -10
  135. package/app/store/aiPromptStore.js +36 -19
  136. package/app/store/conversationSessionStore.js +7 -6
  137. package/app/store/groupConfigStore.js +41 -5
  138. package/app/store/premiumUserStore.js +21 -7
  139. package/app/utils/antiLink/antiLinkModule.js +352 -16
  140. package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
  141. package/app/workers/aiLearningWorker.js +6 -5
  142. package/app/workers/commandConfigEnrichmentWorker.js +4 -3
  143. package/database/index.js +14 -8
  144. package/database/migrations/20260307_d0_hardening_down.sql +1 -1
  145. package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
  146. package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
  147. package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
  148. package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
  149. package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
  150. package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
  151. package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
  152. package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
  153. package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
  154. package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
  155. package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
  156. package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
  157. package/database/schema.sql +102 -1
  158. package/docker-compose.yml +4 -1
  159. package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
  160. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +1 -1
  161. package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
  162. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
  163. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +1 -1
  164. package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
  165. package/docs/security/omnizap-static-security-headers.conf +25 -0
  166. package/docs/wiki/Home.md +1 -1
  167. package/ecosystem.prod.config.cjs +32 -12
  168. package/index.js +57 -23
  169. package/observability/alert-rules.yml +20 -0
  170. package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
  171. package/observability/mysql-setup.sql +4 -4
  172. package/observability/system-admin-observability.md +26 -0
  173. package/package.json +20 -6
  174. package/public/apple-touch-icon.png +0 -0
  175. package/public/comandos/commands-catalog.json +2853 -3326
  176. package/public/favicon-16x16.png +0 -0
  177. package/public/favicon-32x32.png +0 -0
  178. package/public/favicon.ico +0 -0
  179. package/public/js/apps/apiDocsApp.js +3 -2
  180. package/public/js/apps/commandsReactApp.js +280 -99
  181. package/public/js/apps/createPackApp.js +11 -10
  182. package/public/js/apps/homeReactApp.js +181 -130
  183. package/public/js/apps/loginReactApp.js +1 -1
  184. package/public/js/apps/stickersApp.js +263 -110
  185. package/public/js/apps/termsReactApp.js +73 -24
  186. package/public/js/apps/userApp.js +4 -3
  187. package/public/js/apps/userPasswordResetReactApp.js +406 -0
  188. package/public/js/apps/userReactApp.js +355 -280
  189. package/public/js/apps/userSystemAdmReactApp.js +1506 -0
  190. package/public/pages/api-docs.html +1 -1
  191. package/public/pages/aup.html +2 -2
  192. package/public/pages/dpa.html +3 -3
  193. package/public/pages/licenca.html +4 -4
  194. package/public/pages/login.html +1 -1
  195. package/public/pages/notice-and-takedown.html +2 -2
  196. package/public/pages/politica-de-privacidade.html +6 -6
  197. package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
  198. package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
  199. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
  200. package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
  201. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
  202. package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
  203. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
  204. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
  205. package/public/pages/stickers-admin.html +1 -1
  206. package/public/pages/stickers-create.html +1 -1
  207. package/public/pages/stickers.html +6 -6
  208. package/public/pages/suboperadores.html +2 -2
  209. package/public/pages/termos-de-uso-texto-integral.html +6 -6
  210. package/public/pages/termos-de-uso.html +3 -3
  211. package/public/pages/user-password-reset.html +4 -5
  212. package/public/pages/user-systemadm.html +9 -463
  213. package/public/pages/user.html +2 -2
  214. package/scripts/clear-whatsapp-session.sh +123 -0
  215. package/scripts/core-ai-mode.mjs +163 -0
  216. package/scripts/deploy.sh +11 -1
  217. package/scripts/email-broadcast-terms-update.mjs +2 -1
  218. package/scripts/enrich-command-config-ux-openai.mjs +492 -0
  219. package/scripts/generate-commands-catalog.mjs +166 -2
  220. package/scripts/generate-module-agents.mjs +2 -1
  221. package/scripts/generate-seo-satellite-pages.mjs +5 -4
  222. package/scripts/github-deploy-notify.mjs +2 -1
  223. package/scripts/github-release-notify.mjs +25 -10
  224. package/scripts/new-whatsapp-session.sh +317 -0
  225. package/scripts/release.sh +2 -19
  226. package/scripts/security-smoketest.mjs +6 -5
  227. package/scripts/security-web-surface-check.mjs +218 -0
  228. package/scripts/sticker-catalog-loadtest.mjs +5 -4
  229. package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
  230. package/server/auth/jwt/webJwtService.js +1 -1
  231. package/server/auth/stickerCatalogAuthContext.js +2 -1
  232. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
  233. package/server/auth/userPassword/userPasswordAuthService.js +2 -1
  234. package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
  235. package/server/auth/webAccount/webAccountHandlers.js +9 -10
  236. package/server/controllers/admin/adminPanelHandlers.js +267 -16
  237. package/server/controllers/admin/systemAdminController.js +267 -0
  238. package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
  239. package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
  240. package/server/controllers/sticker/stickerCatalogController.js +23 -36
  241. package/server/controllers/system/contactController.js +9 -17
  242. package/server/controllers/system/githubController.js +3 -2
  243. package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
  244. package/server/controllers/system/systemController.js +254 -1
  245. package/server/controllers/system/systemMetricsController.js +2 -1
  246. package/server/controllers/userController.js +6 -0
  247. package/server/email/emailTemplateService.js +5 -3
  248. package/server/http/httpServer.js +11 -6
  249. package/server/middleware/rateLimit.js +2 -1
  250. package/server/middleware/securityHeaders.js +20 -1
  251. package/server/routes/admin/systemAdminRouter.js +6 -0
  252. package/server/routes/indexRouter.js +30 -6
  253. package/server/routes/observability/grafanaProxyRouter.js +254 -0
  254. package/server/routes/static/staticPageRouter.js +27 -1
  255. package/server/utils/publicContact.js +31 -0
  256. package/utils/time/timeModule.js +135 -0
  257. package/utils/time/timeModule.test.js +65 -0
  258. package/utils/whatsapp/contactEnv.js +39 -0
  259. package/vite.config.mjs +7 -1
  260. package/public/assets/images/brand-icon-192.png +0 -0
  261. package/scripts/sync-readme-snapshot.mjs +0 -133
@@ -1,6 +1,8 @@
1
+ import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
1
2
  import { randomUUID, randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
2
- import { URLSearchParams } from 'node:url';
3
+ import { URL, URLSearchParams } from 'node:url';
3
4
 
5
+ import { setAdminOverviewSnapshot } from '../../../app/observability/metrics.js';
4
6
  import { parseAdminModeratorUpsertPayload, parseAdminSessionPasswordPayload } from '../../auth/validation/authSchemas.js';
5
7
 
6
8
  export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger, sendJson, readJsonBody, parseCookies, getCookieValuesFromRequest, appendSetCookie, buildCookieString, sanitizeText, normalizeGoogleSubject, normalizeEmail, normalizeJid, toIsoOrNull, toWhatsAppPhoneDigits, mapGoogleSessionResponseData, resolveGoogleWebSessionFromRequest, revokeGoogleWebSessionsByIdentity, getMarketplaceGlobalStatsCached, getSystemSummaryCached, getFeatureFlagsSnapshot, refreshFeatureFlags, listAdminBans, createAdminBanRecord, revokeAdminBanRecord, normalizeVisitPath, stickerWebPath, findStickerPackByPackKey, stickerPackService, buildManagedPackResponseData, sendManagedMutationStatus, sendManagedPackMutationStatus, deleteManagedPackWithCleanup, mapStickerPackWebManageError, cleanupOrphanStickerAssets, invalidateStickerCatalogDerivedCaches }) => {
@@ -15,6 +17,229 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
15
17
  const ADMIN_PANEL_SESSION_TTL_MS = Math.max(10 * 60 * 1000, Number(process.env.ADM_PANEL_SESSION_TTL_MS) || 12 * 60 * 60 * 1000);
16
18
  const ADMIN_MODERATOR_PASSWORD_MIN_LENGTH = Math.max(6, Number(process.env.ADM_MODERATOR_PASSWORD_MIN_LENGTH) || 8);
17
19
  const ADMIN_PANEL_SESSION_COOKIE_NAME = 'omnizap_admin_panel_session';
20
+ const SYSTEM_ADMIN_GRAFANA_TIME_FROM = sanitizeText(process.env.SYSTEM_ADMIN_GRAFANA_TIME_FROM || 'now-6h', 40, { allowEmpty: true }) || 'now-6h';
21
+ const SYSTEM_ADMIN_GRAFANA_TIME_TO = sanitizeText(process.env.SYSTEM_ADMIN_GRAFANA_TIME_TO || 'now', 40, { allowEmpty: true }) || 'now';
22
+ const SYSTEM_ADMIN_GRAFANA_REFRESH = sanitizeText(process.env.SYSTEM_ADMIN_GRAFANA_REFRESH || '10s', 20, { allowEmpty: true }) || '10s';
23
+ const SYSTEM_ADMIN_GRAFANA_STATUS_TIMEOUT_MS = Math.max(800, Number(process.env.SYSTEM_ADMIN_GRAFANA_STATUS_TIMEOUT_MS) || 2500);
24
+ const SYSTEM_ADMIN_GRAFANA_STATUS_CACHE_TTL_MS = Math.max(5000, Number(process.env.SYSTEM_ADMIN_GRAFANA_STATUS_CACHE_TTL_MS) || 15000);
25
+
26
+ const normalizeGrafanaBaseUrl = (value) => {
27
+ const raw = String(value || '').trim();
28
+ if (!raw) return '';
29
+ try {
30
+ const parsed = new URL(raw);
31
+ if (!['http:', 'https:'].includes(parsed.protocol)) return '';
32
+ const normalizedPathname = String(parsed.pathname || '/').replace(/\/+$/, '');
33
+ parsed.search = '';
34
+ parsed.hash = '';
35
+ return `${parsed.origin}${normalizedPathname === '/' ? '' : normalizedPathname}`;
36
+ } catch {
37
+ return '';
38
+ }
39
+ };
40
+
41
+ const normalizeGrafanaUid = (value) =>
42
+ String(value || '')
43
+ .trim()
44
+ .replace(/[^a-zA-Z0-9_-]/g, '')
45
+ .slice(0, 120);
46
+
47
+ const slugifyGrafanaDashboard = (value) => {
48
+ const raw = String(value || '')
49
+ .trim()
50
+ .toLowerCase();
51
+ const slug = raw
52
+ .replace(/[^a-z0-9]+/g, '-')
53
+ .replace(/^-+|-+$/g, '')
54
+ .slice(0, 90);
55
+ return slug || 'dashboard';
56
+ };
57
+
58
+ const parseGrafanaDashboardDefinitions = () => {
59
+ const raw = String(process.env.SYSTEM_ADMIN_GRAFANA_DASHBOARDS || '')
60
+ .split(',')
61
+ .map((item) => String(item || '').trim())
62
+ .filter(Boolean);
63
+
64
+ const parsed = raw
65
+ .map((entry) => {
66
+ const [uidPart, ...titleParts] = entry.split('|');
67
+ const uid = normalizeGrafanaUid(uidPart);
68
+ const title = sanitizeText(titleParts.join('|') || '', 90, { allowEmpty: true }) || '';
69
+ return uid ? { uid, title } : null;
70
+ })
71
+ .filter(Boolean);
72
+
73
+ if (parsed.length) return parsed;
74
+
75
+ return [
76
+ { uid: 'omnizap-system-admin', title: 'System Admin' },
77
+ { uid: 'omnizap-overview', title: 'Overview' },
78
+ { uid: 'omnizap-mysql', title: 'MySQL' },
79
+ ];
80
+ };
81
+
82
+ const buildAdminGrafanaObservabilityLinks = () => {
83
+ const baseUrl = normalizeGrafanaBaseUrl(process.env.SYSTEM_ADMIN_GRAFANA_URL || process.env.GRAFANA_PUBLIC_URL || '');
84
+ if (!baseUrl) {
85
+ return {
86
+ enabled: false,
87
+ base_url: null,
88
+ dashboards: [],
89
+ };
90
+ }
91
+
92
+ const base = new URL(`${baseUrl}/`);
93
+ const definitions = parseGrafanaDashboardDefinitions();
94
+
95
+ const dashboards = definitions
96
+ .map((entry, index) => {
97
+ const uid = normalizeGrafanaUid(entry?.uid || '');
98
+ if (!uid) return null;
99
+ const title = sanitizeText(entry?.title || '', 90, { allowEmpty: true }) || `Dashboard ${index + 1}`;
100
+ const slug = slugifyGrafanaDashboard(title || uid);
101
+ const viewUrl = new URL(`d/${encodeURIComponent(uid)}/${encodeURIComponent(slug)}`, base);
102
+ viewUrl.searchParams.set('orgId', '1');
103
+ viewUrl.searchParams.set('from', SYSTEM_ADMIN_GRAFANA_TIME_FROM);
104
+ viewUrl.searchParams.set('to', SYSTEM_ADMIN_GRAFANA_TIME_TO);
105
+ viewUrl.searchParams.set('refresh', SYSTEM_ADMIN_GRAFANA_REFRESH);
106
+
107
+ const embedUrl = new URL(String(viewUrl));
108
+ embedUrl.searchParams.set('kiosk', 'tv');
109
+
110
+ return {
111
+ uid,
112
+ title,
113
+ view_url: viewUrl.toString(),
114
+ embed_url: embedUrl.toString(),
115
+ };
116
+ })
117
+ .filter(Boolean);
118
+
119
+ return {
120
+ enabled: dashboards.length > 0,
121
+ base_url: baseUrl,
122
+ dashboards,
123
+ };
124
+ };
125
+ const grafanaObservabilityLinks = buildAdminGrafanaObservabilityLinks();
126
+ let grafanaRuntimeSnapshotCache = {
127
+ expires_at_ms: 0,
128
+ payload: null,
129
+ };
130
+
131
+ const resolveGrafanaHealthEndpointUrl = () => {
132
+ const candidates = [process.env.GRAFANA_PROXY_TARGET_URL, process.env.GRAFANA_INTERNAL_URL, process.env.SYSTEM_ADMIN_GRAFANA_URL, process.env.GRAFANA_PUBLIC_URL];
133
+ for (const candidate of candidates) {
134
+ const baseUrl = normalizeGrafanaBaseUrl(candidate);
135
+ if (!baseUrl) continue;
136
+ try {
137
+ return new URL('api/health', `${baseUrl}/`).toString();
138
+ } catch {
139
+ // noop
140
+ }
141
+ }
142
+ return '';
143
+ };
144
+
145
+ const getGrafanaRuntimeSnapshot = async () => {
146
+ const nowMs = __timeNowMs();
147
+ if (grafanaRuntimeSnapshotCache.payload && nowMs < grafanaRuntimeSnapshotCache.expires_at_ms) {
148
+ return grafanaRuntimeSnapshotCache.payload;
149
+ }
150
+
151
+ const dashboardsTotal = Array.isArray(grafanaObservabilityLinks?.dashboards) ? grafanaObservabilityLinks.dashboards.length : 0;
152
+ const baseSnapshot = {
153
+ enabled: Boolean(grafanaObservabilityLinks?.enabled),
154
+ available: false,
155
+ status: 'unavailable',
156
+ response_ms: null,
157
+ checked_at: __timeNowIso(),
158
+ http_status: null,
159
+ version: null,
160
+ database: null,
161
+ commit: null,
162
+ dashboards_total: dashboardsTotal,
163
+ error: null,
164
+ };
165
+
166
+ const healthEndpointUrl = resolveGrafanaHealthEndpointUrl();
167
+ if (!healthEndpointUrl) {
168
+ grafanaRuntimeSnapshotCache = {
169
+ expires_at_ms: nowMs + SYSTEM_ADMIN_GRAFANA_STATUS_CACHE_TTL_MS,
170
+ payload: baseSnapshot,
171
+ };
172
+ return baseSnapshot;
173
+ }
174
+
175
+ const controller = typeof AbortController === 'function' ? new AbortController() : null;
176
+ const timeoutId =
177
+ controller && Number.isFinite(SYSTEM_ADMIN_GRAFANA_STATUS_TIMEOUT_MS)
178
+ ? setTimeout(() => {
179
+ controller.abort();
180
+ }, SYSTEM_ADMIN_GRAFANA_STATUS_TIMEOUT_MS)
181
+ : null;
182
+ const startedAtMs = __timeNowMs();
183
+
184
+ try {
185
+ const response = await globalThis.fetch(healthEndpointUrl, {
186
+ method: 'GET',
187
+ headers: {
188
+ Accept: 'application/json',
189
+ },
190
+ signal: controller?.signal,
191
+ });
192
+ const responseMs = Math.max(0, __timeNowMs() - startedAtMs);
193
+ let payload = null;
194
+ try {
195
+ payload = await response.json();
196
+ } catch {
197
+ payload = null;
198
+ }
199
+
200
+ const version = sanitizeText(payload?.version || '', 40, { allowEmpty: true }) || null;
201
+ const commit = sanitizeText(payload?.commit || '', 80, { allowEmpty: true }) || null;
202
+ const database = sanitizeText(payload?.database || '', 30, { allowEmpty: true }) || null;
203
+ const normalizedDatabase = String(database || '')
204
+ .trim()
205
+ .toLowerCase();
206
+ const status = !response.ok ? 'offline' : normalizedDatabase === 'ok' ? 'online' : normalizedDatabase ? 'degraded' : 'online';
207
+
208
+ const snapshot = {
209
+ ...baseSnapshot,
210
+ available: response.ok,
211
+ status,
212
+ response_ms: responseMs,
213
+ checked_at: __timeNowIso(),
214
+ http_status: Number(response.status || 0) || null,
215
+ version,
216
+ database,
217
+ commit,
218
+ };
219
+ grafanaRuntimeSnapshotCache = {
220
+ expires_at_ms: __timeNowMs() + SYSTEM_ADMIN_GRAFANA_STATUS_CACHE_TTL_MS,
221
+ payload: snapshot,
222
+ };
223
+ return snapshot;
224
+ } catch (error) {
225
+ const responseMs = Math.max(0, __timeNowMs() - startedAtMs);
226
+ const isAbort = error?.name === 'AbortError';
227
+ const snapshot = {
228
+ ...baseSnapshot,
229
+ status: 'offline',
230
+ response_ms: responseMs,
231
+ checked_at: __timeNowIso(),
232
+ error: sanitizeText(isAbort ? 'timeout' : error?.message || 'fetch_failed', 80, { allowEmpty: true }) || 'fetch_failed',
233
+ };
234
+ grafanaRuntimeSnapshotCache = {
235
+ expires_at_ms: __timeNowMs() + SYSTEM_ADMIN_GRAFANA_STATUS_CACHE_TTL_MS,
236
+ payload: snapshot,
237
+ };
238
+ return snapshot;
239
+ } finally {
240
+ if (timeoutId) clearTimeout(timeoutId);
241
+ }
242
+ };
18
243
 
19
244
  const adminPanelSessionMap = new Map();
20
245
  let adminPanelSessionPruneAt = 0;
@@ -298,7 +523,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
298
523
  };
299
524
 
300
525
  const pruneExpiredAdminPanelSessions = () => {
301
- const now = Date.now();
526
+ const now = __timeNowMs();
302
527
  if (now - adminPanelSessionPruneAt < 30_000) return;
303
528
  adminPanelSessionPruneAt = now;
304
529
  for (const [token, session] of adminPanelSessionMap.entries()) {
@@ -334,7 +559,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
334
559
 
335
560
  const createAdminPanelSession = (googleSession, { role = 'owner' } = {}) => {
336
561
  pruneExpiredAdminPanelSessions();
337
- const now = Date.now();
562
+ const now = __timeNowMs();
338
563
  const normalizedRole = normalizeAdminPanelRole(role, 'owner');
339
564
  const token = randomUUID();
340
565
  const session = {
@@ -359,7 +584,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
359
584
  if (!token) return null;
360
585
  const session = adminPanelSessionMap.get(token);
361
586
  if (!session) return null;
362
- if (Number(session.expiresAt || 0) <= Date.now()) {
587
+ if (Number(session.expiresAt || 0) <= __timeNowMs()) {
363
588
  adminPanelSessionMap.delete(token);
364
589
  return null;
365
590
  }
@@ -616,7 +841,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
616
841
  rollout_percent: Math.max(0, Math.min(100, Number(row?.rollout_percent ?? normalizedRollout))),
617
842
  description: sanitizeText(row?.description || '', 255, { allowEmpty: true }) || null,
618
843
  updated_by: sanitizeText(row?.updated_by || '', 120, { allowEmpty: true }) || null,
619
- updated_at: toIsoOrNull(row?.updated_at) || new Date().toISOString(),
844
+ updated_at: toIsoOrNull(row?.updated_at) || __timeNowIso(),
620
845
  };
621
846
  };
622
847
 
@@ -759,7 +984,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
759
984
 
760
985
  const buildAdminAlertSnapshot = ({ dashboardQuick = null, systemHealth = null, systemSummary = null, systemMeta = null } = {}) => {
761
986
  const alerts = [];
762
- const updatedAt = toIsoOrNull(systemSummary?.updated_at) || new Date().toISOString();
987
+ const updatedAt = toIsoOrNull(systemSummary?.updated_at) || __timeNowIso();
763
988
  const pushAlert = (severity, code, title, message) => {
764
989
  alerts.push({
765
990
  id: `${code}:${severity}`,
@@ -956,7 +1181,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
956
1181
  };
957
1182
 
958
1183
  const buildAdminOverviewPayload = async ({ adminSession = null } = {}) => {
959
- const [marketplaceStats, activeSessions, knownUsers, bans, packsCountRows, stickersCountRows, recentPacks, visitSummary, systemSummaryPayload, messageFlowDaily, moderationQueue, auditLog, featureFlags] = await Promise.all([
1184
+ const [marketplaceStats, activeSessions, knownUsers, bans, packsCountRows, stickersCountRows, recentPacks, visitSummary, systemSummaryPayload, messageFlowDaily, moderationQueue, auditLog, featureFlags, grafanaRuntimeSnapshot] = await Promise.all([
960
1185
  getMarketplaceGlobalStatsCached().catch(() => null),
961
1186
  listAdminActiveGoogleWebSessions({ limit: 80 }),
962
1187
  listAdminKnownGoogleUsers({ limit: 120 }),
@@ -975,6 +1200,19 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
975
1200
  buildModerationQueueSnapshot({ limit: 80 }).catch(() => []),
976
1201
  listAdminAuditLog({ limit: 120 }).catch(() => []),
977
1202
  listAdminFeatureFlagsDetailed({ limit: 300 }).catch(() => []),
1203
+ getGrafanaRuntimeSnapshot().catch(() => ({
1204
+ enabled: Boolean(grafanaObservabilityLinks?.enabled),
1205
+ available: false,
1206
+ status: 'offline',
1207
+ response_ms: null,
1208
+ checked_at: __timeNowIso(),
1209
+ http_status: null,
1210
+ version: null,
1211
+ database: null,
1212
+ commit: null,
1213
+ dashboards_total: Array.isArray(grafanaObservabilityLinks?.dashboards) ? grafanaObservabilityLinks.dashboards.length : 0,
1214
+ error: 'runtime_snapshot_failed',
1215
+ })),
978
1216
  ]);
979
1217
 
980
1218
  const systemSummary = systemSummaryPayload?.data || null;
@@ -996,7 +1234,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
996
1234
  systemMeta,
997
1235
  });
998
1236
 
999
- return {
1237
+ const overviewPayload = {
1000
1238
  admin_session: mapAdminPanelSessionResponseData(adminSession),
1001
1239
  marketplace_stats: marketplaceStats,
1002
1240
  counters: {
@@ -1044,9 +1282,22 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
1044
1282
  visit_metrics: visitSummary,
1045
1283
  system_summary: systemSummary,
1046
1284
  system_meta: systemMeta,
1285
+ observability_links: {
1286
+ grafana: grafanaObservabilityLinks,
1287
+ },
1288
+ observability_runtime: {
1289
+ grafana: grafanaRuntimeSnapshot,
1290
+ },
1047
1291
  message_flow_daily: messageFlowDaily,
1048
- updated_at: new Date().toISOString(),
1292
+ updated_at: __timeNowIso(),
1049
1293
  };
1294
+
1295
+ setAdminOverviewSnapshot({
1296
+ overview: overviewPayload,
1297
+ source: 'admin_overview_payload',
1298
+ });
1299
+
1300
+ return overviewPayload;
1050
1301
  };
1051
1302
 
1052
1303
  const findAdminPackContextByKey = async (rawPackKey) => {
@@ -1364,7 +1615,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
1364
1615
  action,
1365
1616
  success: true,
1366
1617
  message: 'Caches internos invalidados com sucesso.',
1367
- updated_at: new Date().toISOString(),
1618
+ updated_at: __timeNowIso(),
1368
1619
  },
1369
1620
  });
1370
1621
  return;
@@ -1408,7 +1659,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
1408
1659
  success: true,
1409
1660
  released_processing_items: released,
1410
1661
  message: released > 0 ? 'Itens em processamento foram recolocados em pending.' : 'Nenhum item travado encontrado nas filas.',
1411
- updated_at: new Date().toISOString(),
1662
+ updated_at: __timeNowIso(),
1412
1663
  },
1413
1664
  });
1414
1665
  return;
@@ -1418,7 +1669,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
1418
1669
  const payloadJson = JSON.stringify({
1419
1670
  source: 'admin_panel',
1420
1671
  requested_by: normalizeEmail(adminSession?.email) || normalizeGoogleSubject(adminSession?.googleSub) || 'admin',
1421
- requested_at: new Date().toISOString(),
1672
+ requested_at: __timeNowIso(),
1422
1673
  });
1423
1674
  await executeQuery(
1424
1675
  `INSERT INTO ${TABLES.STICKER_WORKER_TASK_QUEUE}
@@ -1440,7 +1691,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
1440
1691
  success: true,
1441
1692
  enqueued_tasks: 2,
1442
1693
  message: 'Ciclos de classificação e curadoria foram agendados.',
1443
- updated_at: new Date().toISOString(),
1694
+ updated_at: __timeNowIso(),
1444
1695
  },
1445
1696
  });
1446
1697
  return;
@@ -1632,7 +1883,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
1632
1883
  data: {
1633
1884
  type,
1634
1885
  format: 'json',
1635
- exported_at: new Date().toISOString(),
1886
+ exported_at: __timeNowIso(),
1636
1887
  payload: exportData,
1637
1888
  },
1638
1889
  });
@@ -1705,7 +1956,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
1705
1956
  }
1706
1957
 
1707
1958
  const csv = buildCsv(rows, headers);
1708
- const stamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-');
1959
+ const stamp = __timeNowIso().slice(0, 19).replace(/[:T]/g, '-');
1709
1960
  res.statusCode = 200;
1710
1961
  res.setHeader('Content-Type', 'text/csv; charset=utf-8');
1711
1962
  res.setHeader('Content-Disposition', `attachment; filename="admin-${type}-${stamp}.csv"`);
@@ -1862,7 +2113,7 @@ export const createStickerCatalogAdminHandlers = ({ executeQuery, tables, logger
1862
2113
  deleted: !result?.missing,
1863
2114
  pack_key: result?.deletedPack?.pack_key || context.packKey,
1864
2115
  id: result?.deletedPack?.id || context.fullPack?.id || null,
1865
- deleted_at: toIsoOrNull(result?.deletedPack?.deleted_at || new Date()),
2116
+ deleted_at: toIsoOrNull(result?.deletedPack?.deleted_at || __timeNow()),
1866
2117
  removed_sticker_count: Number(result?.removedCount || 0),
1867
2118
  });
1868
2119
  };