@omnizap-system/omnizap 2.5.12

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 (425) hide show
  1. package/.clusterfuzzlite/Dockerfile +10 -0
  2. package/.env.example +907 -0
  3. package/.github/codeql/codeql-config.yml +10 -0
  4. package/.github/dependabot.yml +35 -0
  5. package/.github/workflows/ci.yml +73 -0
  6. package/.github/workflows/codeql.yml +106 -0
  7. package/.github/workflows/db-migration-check.yml +98 -0
  8. package/.github/workflows/dependency-review.yml +22 -0
  9. package/.github/workflows/deploy.yml +95 -0
  10. package/.github/workflows/release.yml +106 -0
  11. package/.github/workflows/security-attest-provenance.yml +51 -0
  12. package/.github/workflows/security-gitleaks.yml +34 -0
  13. package/.github/workflows/security-runner-hardening.yml +31 -0
  14. package/.github/workflows/security-scorecard.yml +44 -0
  15. package/.github/workflows/security-zap-baseline.yml +44 -0
  16. package/.github/workflows/security-zap-full-scan.yml +43 -0
  17. package/.github/workflows/security-zizmor.yml +36 -0
  18. package/.github/workflows/wiki-sync.yml +44 -0
  19. package/.gitleaks.toml +15 -0
  20. package/.prettierrc +34 -0
  21. package/CODE_OF_CONDUCT.md +114 -0
  22. package/LICENSE +56 -0
  23. package/README.md +110 -0
  24. package/SECURITY.md +110 -0
  25. package/app/config/index.js +4 -0
  26. package/app/configParts/adminIdentity.js +92 -0
  27. package/app/configParts/baileysConfig.js +1818 -0
  28. package/app/configParts/groupUtils.js +692 -0
  29. package/app/configParts/loggerConfig.js +394 -0
  30. package/app/configParts/messagePersistenceService.js +305 -0
  31. package/app/connection/baileysCompatibility.test.js +40 -0
  32. package/app/connection/baileysDbAuthState.js +344 -0
  33. package/app/connection/socketController.js +2243 -0
  34. package/app/controllers/messageController.js +7 -0
  35. package/app/controllers/messagePipeline/commandMiddleware.js +146 -0
  36. package/app/controllers/messagePipeline/conversationMiddleware.js +183 -0
  37. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +522 -0
  38. package/app/controllers/messagePipeline/postProcessingMiddleware.js +41 -0
  39. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +166 -0
  40. package/app/controllers/messageProcessingPipeline.js +699 -0
  41. package/app/modules/adminModule/AGENT.md +4056 -0
  42. package/app/modules/adminModule/adminAiHelpService.js +56 -0
  43. package/app/modules/adminModule/adminConfigRuntime.js +177 -0
  44. package/app/modules/adminModule/commandConfig.json +7122 -0
  45. package/app/modules/adminModule/groupCommandHandlers.js +1823 -0
  46. package/app/modules/adminModule/groupCommandHandlers.test.js +350 -0
  47. package/app/modules/adminModule/groupEventHandlers.js +399 -0
  48. package/app/modules/aiModule/AGENT.md +547 -0
  49. package/app/modules/aiModule/aiAiHelpService.js +14 -0
  50. package/app/modules/aiModule/aiConfigRuntime.js +135 -0
  51. package/app/modules/aiModule/catCommand.js +967 -0
  52. package/app/modules/aiModule/commandConfig.json +981 -0
  53. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +83 -0
  54. package/app/modules/gameModule/AGENT.md +196 -0
  55. package/app/modules/gameModule/commandConfig.json +366 -0
  56. package/app/modules/gameModule/diceCommand.js +42 -0
  57. package/app/modules/gameModule/gameAiHelpService.js +14 -0
  58. package/app/modules/gameModule/gameConfigRuntime.js +68 -0
  59. package/app/modules/menuModule/AGENT.md +205 -0
  60. package/app/modules/menuModule/commandConfig.json +366 -0
  61. package/app/modules/menuModule/common.js +316 -0
  62. package/app/modules/menuModule/menuAiHelpService.js +14 -0
  63. package/app/modules/menuModule/menuConfigRuntime.js +68 -0
  64. package/app/modules/menuModule/menus.js +66 -0
  65. package/app/modules/playModule/AGENT.md +321 -0
  66. package/app/modules/playModule/commandConfig.json +584 -0
  67. package/app/modules/playModule/playAiHelpService.js +14 -0
  68. package/app/modules/playModule/playCommand.js +1417 -0
  69. package/app/modules/playModule/playConfigRuntime.js +68 -0
  70. package/app/modules/quoteModule/AGENT.md +199 -0
  71. package/app/modules/quoteModule/commandConfig.json +366 -0
  72. package/app/modules/quoteModule/quoteAiHelpService.js +14 -0
  73. package/app/modules/quoteModule/quoteCommand.js +842 -0
  74. package/app/modules/quoteModule/quoteConfigRuntime.js +68 -0
  75. package/app/modules/rpgPokemonModule/AGENT.md +229 -0
  76. package/app/modules/rpgPokemonModule/commandConfig.json +386 -0
  77. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +795 -0
  78. package/app/modules/rpgPokemonModule/rpgBattleService.js +2110 -0
  79. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +770 -0
  80. package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
  81. package/app/modules/rpgPokemonModule/rpgPokemonAiHelpService.js +14 -0
  82. package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +174 -0
  83. package/app/modules/rpgPokemonModule/rpgPokemonConfigRuntime.js +68 -0
  84. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
  85. package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
  86. package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
  87. package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
  88. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1847 -0
  89. package/app/modules/rpgPokemonModule/rpgPokemonService.js +6839 -0
  90. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
  91. package/app/modules/statsModule/AGENT.md +320 -0
  92. package/app/modules/statsModule/commandConfig.json +540 -0
  93. package/app/modules/statsModule/globalRankingCommand.js +64 -0
  94. package/app/modules/statsModule/rankingCommand.js +41 -0
  95. package/app/modules/statsModule/rankingCommon.js +1305 -0
  96. package/app/modules/statsModule/statsAiHelpService.js +14 -0
  97. package/app/modules/statsModule/statsConfigRuntime.js +68 -0
  98. package/app/modules/stickerModule/AGENT.md +692 -0
  99. package/app/modules/stickerModule/addStickerMetadata.js +239 -0
  100. package/app/modules/stickerModule/commandConfig.json +1216 -0
  101. package/app/modules/stickerModule/convertToWebp.js +367 -0
  102. package/app/modules/stickerModule/stickerAiHelpService.js +14 -0
  103. package/app/modules/stickerModule/stickerCommand.js +446 -0
  104. package/app/modules/stickerModule/stickerConfigRuntime.js +68 -0
  105. package/app/modules/stickerModule/stickerConvertCommand.js +159 -0
  106. package/app/modules/stickerModule/stickerTextCommand.js +653 -0
  107. package/app/modules/stickerPackModule/AGENT.md +215 -0
  108. package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
  109. package/app/modules/stickerPackModule/autoPackCollectorService.js +357 -0
  110. package/app/modules/stickerPackModule/commandConfig.json +387 -0
  111. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +227 -0
  112. package/app/modules/stickerPackModule/domainEvents.js +52 -0
  113. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +429 -0
  114. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +75 -0
  115. package/app/modules/stickerPackModule/semanticThemeClusterService.js +544 -0
  116. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +400 -0
  117. package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
  118. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +175 -0
  119. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +3702 -0
  120. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +559 -0
  121. package/app/modules/stickerPackModule/stickerClassificationService.js +557 -0
  122. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +249 -0
  123. package/app/modules/stickerPackModule/stickerDomainEventBus.js +65 -0
  124. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +208 -0
  125. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +99 -0
  126. package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
  127. package/app/modules/stickerPackModule/stickerPackAiHelpService.js +14 -0
  128. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1148 -0
  129. package/app/modules/stickerPackModule/stickerPackConfigRuntime.js +68 -0
  130. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +152 -0
  131. package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
  132. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +101 -0
  133. package/app/modules/stickerPackModule/stickerPackItemRepository.js +432 -0
  134. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +313 -0
  135. package/app/modules/stickerPackModule/stickerPackMessageService.js +268 -0
  136. package/app/modules/stickerPackModule/stickerPackRepository.js +450 -0
  137. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +179 -0
  138. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +271 -0
  139. package/app/modules/stickerPackModule/stickerPackService.js +733 -0
  140. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +32 -0
  141. package/app/modules/stickerPackModule/stickerPackUtils.js +107 -0
  142. package/app/modules/stickerPackModule/stickerStorageService.js +559 -0
  143. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +242 -0
  144. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +242 -0
  145. package/app/modules/systemMetricsModule/AGENT.md +193 -0
  146. package/app/modules/systemMetricsModule/commandConfig.json +344 -0
  147. package/app/modules/systemMetricsModule/pingCommand.js +399 -0
  148. package/app/modules/systemMetricsModule/systemMetricsAiHelpService.js +14 -0
  149. package/app/modules/systemMetricsModule/systemMetricsConfigRuntime.js +68 -0
  150. package/app/modules/tiktokModule/AGENT.md +196 -0
  151. package/app/modules/tiktokModule/commandConfig.json +366 -0
  152. package/app/modules/tiktokModule/tiktokAiHelpService.js +14 -0
  153. package/app/modules/tiktokModule/tiktokCommand.js +716 -0
  154. package/app/modules/tiktokModule/tiktokConfigRuntime.js +68 -0
  155. package/app/modules/userModule/AGENT.md +200 -0
  156. package/app/modules/userModule/commandConfig.json +386 -0
  157. package/app/modules/userModule/userAiHelpService.js +14 -0
  158. package/app/modules/userModule/userCommand.js +1155 -0
  159. package/app/modules/userModule/userConfigRuntime.js +68 -0
  160. package/app/modules/waifuPicsModule/AGENT.md +431 -0
  161. package/app/modules/waifuPicsModule/commandConfig.json +780 -0
  162. package/app/modules/waifuPicsModule/waifuPicsAiHelpService.js +14 -0
  163. package/app/modules/waifuPicsModule/waifuPicsCommand.js +586 -0
  164. package/app/modules/waifuPicsModule/waifuPicsConfigRuntime.js +68 -0
  165. package/app/observability/metrics.js +766 -0
  166. package/app/services/ai/aiHelpResponseCacheRepository.js +280 -0
  167. package/app/services/ai/aiLearningRepository.js +400 -0
  168. package/app/services/ai/commandConfigEnrichmentRepository.js +769 -0
  169. package/app/services/ai/commandConfigEnrichmentService.js +452 -0
  170. package/app/services/ai/commandConfigValidationService.js +443 -0
  171. package/app/services/ai/commandToolBuilderService.js +192 -0
  172. package/app/services/ai/conversationRouterService.js +516 -0
  173. package/app/services/ai/geminiService.js +115 -0
  174. package/app/services/ai/geminiService.test.js +87 -0
  175. package/app/services/ai/globalModuleAiHelpService.js +1412 -0
  176. package/app/services/ai/globalToolCallingService.js +203 -0
  177. package/app/services/ai/messageCommandExecutionService.js +391 -0
  178. package/app/services/ai/moduleAiHelpCoreService.js +1099 -0
  179. package/app/services/ai/moduleAiHelpWrapperFactory.js +65 -0
  180. package/app/services/ai/moduleCommandConfigRuntimeService.js +113 -0
  181. package/app/services/ai/moduleToolExecutorService.js +464 -0
  182. package/app/services/ai/moduleToolRegistryService.js +178 -0
  183. package/app/services/ai/toolCandidateSelectorService.js +781 -0
  184. package/app/services/auth/googleWebLinkService.js +80 -0
  185. package/app/services/auth/whatsappLoginLinkService.js +230 -0
  186. package/app/services/external/pokeApiService.js +398 -0
  187. package/app/services/group/groupMetadataService.js +311 -0
  188. package/app/services/infra/dbWriteQueue.js +874 -0
  189. package/app/services/infra/featureFlagService.js +131 -0
  190. package/app/services/infra/queueUtils.js +55 -0
  191. package/app/services/messaging/captchaService.js +491 -0
  192. package/app/services/messaging/messagePersistenceService.js +1 -0
  193. package/app/services/messaging/newsBroadcastService.js +347 -0
  194. package/app/services/sticker/stickerFocusService.js +347 -0
  195. package/app/services/sticker/stickerFocusService.test.js +43 -0
  196. package/app/store/aiPromptStore.js +38 -0
  197. package/app/store/conversationSessionStore.js +131 -0
  198. package/app/store/groupConfigStore.js +58 -0
  199. package/app/store/premiumUserStore.js +54 -0
  200. package/app/utils/antiLink/antiLinkModule.js +700 -0
  201. package/app/utils/http/getImageBufferModule.js +18 -0
  202. package/app/utils/json/jsonSanitizer.js +113 -0
  203. package/app/utils/json/jsonSanitizer.test.js +40 -0
  204. package/app/utils/systemMetrics/systemMetricsModule.js +88 -0
  205. package/app/workers/aiLearningWorker.js +605 -0
  206. package/app/workers/commandConfigEnrichmentWorker.js +242 -0
  207. package/database/index.js +2075 -0
  208. package/database/init.js +151 -0
  209. package/database/migrations/.gitkeep +0 -0
  210. package/database/migrations/20260307_d0_hardening_down.sql +64 -0
  211. package/database/migrations/20260307_d0_hardening_up.sql +79 -0
  212. package/database/migrations/20260307_d1_terms_acceptance_down.sql +11 -0
  213. package/database/migrations/20260307_d1_terms_acceptance_up.sql +37 -0
  214. package/database/migrations/20260307_d2_auth_hardening_down.sql +75 -0
  215. package/database/migrations/20260307_d2_auth_hardening_up.sql +100 -0
  216. package/database/migrations/20260314_d7_canonical_sender_down.sql +53 -0
  217. package/database/migrations/20260314_d7_canonical_sender_up.sql +114 -0
  218. package/database/migrations/20260406_d30_security_analytics_down.sql +95 -0
  219. package/database/migrations/20260406_d30_security_analytics_up.sql +292 -0
  220. package/database/migrations/20260407_d31_web_google_session_token_hardening_down.sql +2 -0
  221. package/database/migrations/20260407_d31_web_google_session_token_hardening_up.sql +17 -0
  222. package/database/migrations/20260408_d32_ai_help_response_cache_down.sql +1 -0
  223. package/database/migrations/20260408_d32_ai_help_response_cache_up.sql +22 -0
  224. package/database/migrations/20260409_d33_ai_learning_tables_down.sql +4 -0
  225. package/database/migrations/20260409_d33_ai_learning_tables_up.sql +52 -0
  226. package/database/migrations/20260410_d34_command_config_enrichment_down.sql +3 -0
  227. package/database/migrations/20260410_d34_command_config_enrichment_up.sql +48 -0
  228. package/database/schema.sql +1186 -0
  229. package/docker-compose.yml +104 -0
  230. package/docs/audits/stickerCatalogController-out-of-scope.md +103 -0
  231. package/docs/audits/stickerCatalogController-symbols.md +58 -0
  232. package/docs/compliance/acceptable-use-policy-2026-03-07.md +35 -0
  233. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +80 -0
  234. package/docs/compliance/monthly-compliance-checklist-2026-03-07.md +88 -0
  235. package/docs/compliance/notice-and-takedown-policy-2026-03-07.md +34 -0
  236. package/docs/compliance/privacy-policy-2026-03-07.md +75 -0
  237. package/docs/compliance/subprocessors-inventory-2026-03-07.md +16 -0
  238. package/docs/database/production-db-evolution-runbook-2026q1.md +365 -0
  239. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +86 -0
  240. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +77 -0
  241. package/docs/security/network-hardening-runbook-2026-03-07.md +137 -0
  242. package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +238 -0
  243. package/docs/seo/satellite-page-template.md +116 -0
  244. package/docs/seo/satellite-pages-phase1.json +364 -0
  245. package/docs/wiki/Home.md +120 -0
  246. package/docs/wiki/pair-extraordinaire-2026-03-08.md +3 -0
  247. package/docs/wiki/recent-changes-2026-03-08.md +47 -0
  248. package/ecosystem.prod.config.cjs +135 -0
  249. package/eslint.config.js +89 -0
  250. package/index.js +488 -0
  251. package/ml/clip_classifier/Dockerfile +18 -0
  252. package/ml/clip_classifier/README.md +118 -0
  253. package/ml/clip_classifier/adaptive_scoring.py +40 -0
  254. package/ml/clip_classifier/classifier.py +654 -0
  255. package/ml/clip_classifier/embedding_store.py +481 -0
  256. package/ml/clip_classifier/env_loader.py +15 -0
  257. package/ml/clip_classifier/llm_label_expander.py +144 -0
  258. package/ml/clip_classifier/main.py +213 -0
  259. package/ml/clip_classifier/requirements.txt +10 -0
  260. package/ml/clip_classifier/similarity_engine.py +74 -0
  261. package/new-logo.png +0 -0
  262. package/observability/alert-rules.yml +60 -0
  263. package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
  264. package/observability/grafana/dashboards/omnizap-overview.json +170 -0
  265. package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
  266. package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
  267. package/observability/loki-config.yml +38 -0
  268. package/observability/mysql-setup.sql +46 -0
  269. package/observability/prometheus.yml +35 -0
  270. package/observability/promtail-config.yml +84 -0
  271. package/observability/sticker-catalog-slo.md +83 -0
  272. package/observability/sticker-scale-hardening-rollout.md +128 -0
  273. package/package.json +144 -0
  274. package/public/apple-touch-icon.png +0 -0
  275. package/public/assets/css/commands-react.input.css +71 -0
  276. package/public/assets/css/create-pack-react.input.css +31 -0
  277. package/public/assets/css/home-react.input.css +106 -0
  278. package/public/assets/css/login-react.input.css +58 -0
  279. package/public/assets/css/stickers-react.input.css +18 -0
  280. package/public/assets/css/terms-react.input.css +115 -0
  281. package/public/assets/css/user-react.input.css +57 -0
  282. package/public/assets/images/brand-icon-192.png +0 -0
  283. package/public/assets/images/brand-logo-128.webp +0 -0
  284. package/public/assets/images/hero-banner-1280.jpg +0 -0
  285. package/public/comandos/commands-catalog.json +4517 -0
  286. package/public/css/api-docs.css +161 -0
  287. package/public/css/stickers-admin.css +1288 -0
  288. package/public/css/styles.css +679 -0
  289. package/public/css/systemadm/admin.css +474 -0
  290. package/public/css/systemadm/base.css +73 -0
  291. package/public/css/systemadm/components.css +662 -0
  292. package/public/css/systemadm/layout.css +229 -0
  293. package/public/css/systemadm/tokens.css +56 -0
  294. package/public/favicon-16x16.png +0 -0
  295. package/public/favicon-32x32.png +0 -0
  296. package/public/favicon.ico +0 -0
  297. package/public/js/apps/apiDocsApp.js +235 -0
  298. package/public/js/apps/commandsReactApp.js +528 -0
  299. package/public/js/apps/createPackApp.js +1646 -0
  300. package/public/js/apps/homeReactApp.js +942 -0
  301. package/public/js/apps/loginReactApp.js +496 -0
  302. package/public/js/apps/stickersAdminApp.js +1753 -0
  303. package/public/js/apps/stickersApp.js +3797 -0
  304. package/public/js/apps/termsReactApp.js +528 -0
  305. package/public/js/apps/userApp.js +2540 -0
  306. package/public/js/apps/userProfile/actions.js +66 -0
  307. package/public/js/apps/userReactApp.js +547 -0
  308. package/public/js/catalog.js +950 -0
  309. package/public/pages/api-docs.html +40 -0
  310. package/public/pages/aup.html +158 -0
  311. package/public/pages/comandos.html +41 -0
  312. package/public/pages/dpa.html +227 -0
  313. package/public/pages/home.html +45 -0
  314. package/public/pages/licenca.html +182 -0
  315. package/public/pages/login.html +40 -0
  316. package/public/pages/notice-and-takedown.html +234 -0
  317. package/public/pages/politica-de-privacidade.html +251 -0
  318. package/public/pages/seo-bot-whatsapp-para-grupo.html +350 -0
  319. package/public/pages/seo-bot-whatsapp-sem-programar.html +350 -0
  320. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +350 -0
  321. package/public/pages/seo-como-criar-comandos-whatsapp.html +350 -0
  322. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +350 -0
  323. package/public/pages/seo-como-moderar-grupo-whatsapp.html +350 -0
  324. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +350 -0
  325. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +350 -0
  326. package/public/pages/stickers-admin.html +31 -0
  327. package/public/pages/stickers-create.html +41 -0
  328. package/public/pages/stickers.html +45 -0
  329. package/public/pages/suboperadores.html +237 -0
  330. package/public/pages/termos-de-uso-texto-integral.html +241 -0
  331. package/public/pages/termos-de-uso.html +41 -0
  332. package/public/pages/user-password-reset.html +32 -0
  333. package/public/pages/user-systemadm.html +508 -0
  334. package/public/pages/user.html +39 -0
  335. package/public/robots.txt +9 -0
  336. package/public/site.webmanifest +24 -0
  337. package/public/sitemap.xml +98 -0
  338. package/schemas/command-config.schema.json +582 -0
  339. package/scripts/baileys-compat-smoke.mjs +12 -0
  340. package/scripts/cache-bust.mjs +142 -0
  341. package/scripts/deploy.sh +916 -0
  342. package/scripts/email-broadcast-terms-update.mjs +170 -0
  343. package/scripts/enrich-command-discovery-fields.mjs +286 -0
  344. package/scripts/generate-command-config-schema.mjs +273 -0
  345. package/scripts/generate-commands-catalog.mjs +308 -0
  346. package/scripts/generate-module-agents.mjs +631 -0
  347. package/scripts/generate-seo-satellite-pages.mjs +400 -0
  348. package/scripts/github-deploy-notify.mjs +174 -0
  349. package/scripts/github-release-notify.mjs +219 -0
  350. package/scripts/release.sh +599 -0
  351. package/scripts/run-codeql-local.sh +116 -0
  352. package/scripts/run-prettier-all.mjs +25 -0
  353. package/scripts/security-smoketest.mjs +581 -0
  354. package/scripts/sticker-catalog-loadtest.mjs +210 -0
  355. package/scripts/sticker-worker-task.mjs +119 -0
  356. package/scripts/sync-readme-snapshot.mjs +133 -0
  357. package/scripts/validate-command-config-schema.mjs +130 -0
  358. package/scripts/validate-command-configs.mjs +15 -0
  359. package/scripts/wiki-sync.sh +191 -0
  360. package/server/auth/googleWebAuth/googleWebAuthRuntime.js +62 -0
  361. package/server/auth/googleWebAuth/googleWebAuthService.js +807 -0
  362. package/server/auth/jwt/webJwtService.js +147 -0
  363. package/server/auth/stickerCatalogAuthContext.js +165 -0
  364. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +189 -0
  365. package/server/auth/userPassword/index.js +14 -0
  366. package/server/auth/userPassword/userPasswordAuthService.js +422 -0
  367. package/server/auth/userPassword/userPasswordCrypto.js +199 -0
  368. package/server/auth/userPassword/userPasswordCrypto.test.js +76 -0
  369. package/server/auth/userPassword/userPasswordRecoveryService.js +728 -0
  370. package/server/auth/validation/authSchemas.js +236 -0
  371. package/server/auth/webAccount/webAccountHandlers.js +1434 -0
  372. package/server/controllers/admin/adminBanService.js +138 -0
  373. package/server/controllers/admin/adminPanelHandlers.js +2083 -0
  374. package/server/controllers/admin/stickerCatalogAdminContext.js +17 -0
  375. package/server/controllers/admin/systemAdminController.js +201 -0
  376. package/server/controllers/email/emailAutomationController.js +239 -0
  377. package/server/controllers/metricsController.js +21 -0
  378. package/server/controllers/seo/stickerCatalogSeoContext.js +514 -0
  379. package/server/controllers/sticker/nonCatalogHandlers.js +303 -0
  380. package/server/controllers/sticker/stickerCatalogController.js +4700 -0
  381. package/server/controllers/system/contactController.js +115 -0
  382. package/server/controllers/system/githubController.js +137 -0
  383. package/server/controllers/system/stickerCatalogSystemContext.js +758 -0
  384. package/server/controllers/system/storageController.js +154 -0
  385. package/server/controllers/system/systemController.js +135 -0
  386. package/server/controllers/system/systemMetricsController.js +156 -0
  387. package/server/controllers/system/visitController.js +90 -0
  388. package/server/controllers/userController.js +145 -0
  389. package/server/email/emailAutomationRuntime.js +225 -0
  390. package/server/email/emailAutomationService.js +125 -0
  391. package/server/email/emailOutboxRepository.js +282 -0
  392. package/server/email/emailTemplateService.js +480 -0
  393. package/server/email/emailTransportService.js +156 -0
  394. package/server/http/clientIp.js +95 -0
  395. package/server/http/httpRequestUtils.js +262 -0
  396. package/server/http/httpRequestUtils.test.js +80 -0
  397. package/server/http/httpServer.js +180 -0
  398. package/server/http/requestContext.js +20 -0
  399. package/server/http/siteRoutingUtils.js +87 -0
  400. package/server/index.js +1 -0
  401. package/server/middleware/cachePolicy.js +26 -0
  402. package/server/middleware/cachePolicyHelpers.js +1 -0
  403. package/server/middleware/endpointRateLimit.js +181 -0
  404. package/server/middleware/rateLimit.js +70 -0
  405. package/server/middleware/requireAdminAuth.js +48 -0
  406. package/server/middleware/securityHeaders.js +97 -0
  407. package/server/routes/admin/systemAdminRouter.js +64 -0
  408. package/server/routes/email/emailAutomationRouter.js +46 -0
  409. package/server/routes/health/healthRouter.js +41 -0
  410. package/server/routes/indexRouter.js +234 -0
  411. package/server/routes/metrics/metricsRouter.js +58 -0
  412. package/server/routes/static/staticPageRouter.js +134 -0
  413. package/server/routes/sticker/catalogHandlers/catalogAdminHttp.js +105 -0
  414. package/server/routes/sticker/catalogHandlers/catalogAuthHttp.js +77 -0
  415. package/server/routes/sticker/catalogHandlers/catalogPublicHttp.js +120 -0
  416. package/server/routes/sticker/catalogHandlers/catalogUploadHttp.js +83 -0
  417. package/server/routes/sticker/catalogRouter.js +77 -0
  418. package/server/routes/sticker/stickerApiRouter.js +84 -0
  419. package/server/routes/sticker/stickerDataRouter.js +145 -0
  420. package/server/routes/sticker/stickerSiteRouter.js +43 -0
  421. package/server/routes/user/userApiPaths.js +66 -0
  422. package/server/routes/user/userRouter.js +65 -0
  423. package/server/utils/safePath.js +26 -0
  424. package/utils/logger/loggerModule.js +35 -0
  425. package/vite.config.mjs +38 -0
@@ -0,0 +1,151 @@
1
+ import mysql from 'mysql2/promise';
2
+ import { promises as fs } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ import logger from '#logger';
7
+ import { dbConfig } from './index.js';
8
+
9
+ const dbToCreate = dbConfig.database;
10
+
11
+ const createDatabaseSQL = `
12
+ CREATE DATABASE IF NOT EXISTS \`${dbToCreate}\`
13
+ DEFAULT CHARACTER SET utf8mb4
14
+ DEFAULT COLLATE utf8mb4_unicode_ci;
15
+ `;
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+ const SCHEMA_SQL_PATH = path.join(__dirname, 'schema.sql');
20
+
21
+ /**
22
+ * Divide um arquivo SQL em statements individuais.
23
+ * Suporta aspas simples e duplas para não quebrar strings.
24
+ *
25
+ * @param {string} sql
26
+ * @returns {string[]}
27
+ */
28
+ function splitSqlStatements(sql) {
29
+ const statements = [];
30
+ let current = '';
31
+ let inSingleQuote = false;
32
+ let inDoubleQuote = false;
33
+ let escaped = false;
34
+
35
+ for (const char of sql) {
36
+ if (escaped) {
37
+ current += char;
38
+ escaped = false;
39
+ continue;
40
+ }
41
+
42
+ if (char === '\\') {
43
+ current += char;
44
+ escaped = true;
45
+ continue;
46
+ }
47
+
48
+ if (char === "'" && !inDoubleQuote) {
49
+ inSingleQuote = !inSingleQuote;
50
+ current += char;
51
+ continue;
52
+ }
53
+
54
+ if (char === '"' && !inSingleQuote) {
55
+ inDoubleQuote = !inDoubleQuote;
56
+ current += char;
57
+ continue;
58
+ }
59
+
60
+ if (char === ';' && !inSingleQuote && !inDoubleQuote) {
61
+ const statement = current.trim();
62
+ if (statement) statements.push(statement);
63
+ current = '';
64
+ continue;
65
+ }
66
+
67
+ current += char;
68
+ }
69
+
70
+ const trailing = current.trim();
71
+ if (trailing) statements.push(trailing);
72
+
73
+ return statements;
74
+ }
75
+
76
+ /**
77
+ * Executa o schema consolidado para garantir que todas as tabelas existam.
78
+ *
79
+ * @param {import('mysql2/promise').Connection} connection
80
+ * @returns {Promise<number>} Quantidade de statements executados.
81
+ */
82
+ async function runSchemaBootstrap(connection) {
83
+ let sqlContent = '';
84
+ try {
85
+ sqlContent = await fs.readFile(SCHEMA_SQL_PATH, 'utf8');
86
+ } catch (error) {
87
+ if (error?.code === 'ENOENT') {
88
+ const wrapped = new Error(`Arquivo de schema não encontrado em ${SCHEMA_SQL_PATH}.`);
89
+ wrapped.code = 'SCHEMA_SQL_NOT_FOUND';
90
+ throw wrapped;
91
+ }
92
+ throw error;
93
+ }
94
+
95
+ const statements = splitSqlStatements(sqlContent);
96
+ if (statements.length === 0) {
97
+ logger.warn('Arquivo schema.sql está vazio. Nenhuma tabela será criada.');
98
+ return 0;
99
+ }
100
+
101
+ for (const statement of statements) {
102
+ await connection.query(statement);
103
+ }
104
+
105
+ return statements.length;
106
+ }
107
+
108
+ /**
109
+ * Inicializa o banco de dados:
110
+ * 1) Conecta ao MySQL sem database
111
+ * 2) Cria o banco se não existir
112
+ * 3) Muda para o database correto
113
+ * 4) Executa o schema consolidado (sem migrations)
114
+ * 5) Encerra a conexão
115
+ *
116
+ * @returns {Promise<void>}
117
+ */
118
+ export default async function initializeDatabase() {
119
+ let connection;
120
+
121
+ try {
122
+ connection = await mysql.createConnection({
123
+ host: dbConfig.host,
124
+ user: dbConfig.user,
125
+ password: dbConfig.password,
126
+ });
127
+
128
+ await connection.query(createDatabaseSQL);
129
+ logger.info(`Banco de dados '${dbToCreate}' verificado/criado com sucesso.`);
130
+
131
+ await connection.changeUser({ database: dbToCreate });
132
+
133
+ const executedStatements = await runSchemaBootstrap(connection);
134
+
135
+ logger.info('Schema consolidado verificado/aplicado com sucesso (sem migrations).', {
136
+ executedStatements,
137
+ });
138
+ } catch (error) {
139
+ logger.error(`Erro ao inicializar o banco: ${error.code || ''} ${error.message}`);
140
+ process.exit(1);
141
+ } finally {
142
+ if (connection) {
143
+ await connection.end();
144
+ logger.info('Conexão com o MySQL encerrada após inicialização.');
145
+ }
146
+ }
147
+ }
148
+
149
+ if (process.argv[1] === __filename) {
150
+ initializeDatabase();
151
+ }
File without changes
@@ -0,0 +1,64 @@
1
+ -- D0 rollback
2
+ -- Run with mysql client:
3
+ -- mysql -u$DB_USER -p$DB_PASSWORD -h$DB_HOST $DB_NAME < database/migrations/20260307_d0_hardening_down.sql
4
+
5
+ SET @migration_key := '20260307_d0_hardening';
6
+
7
+ DROP PROCEDURE IF EXISTS __ensure_index;
8
+ DROP PROCEDURE IF EXISTS __drop_index_if_exists;
9
+
10
+ DELIMITER $$
11
+ CREATE PROCEDURE __ensure_index(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64), IN p_ddl TEXT)
12
+ BEGIN
13
+ IF NOT EXISTS (
14
+ SELECT 1
15
+ FROM information_schema.statistics
16
+ WHERE table_schema = DATABASE()
17
+ AND table_name = p_table_name
18
+ AND index_name = p_index_name
19
+ ) THEN
20
+ SET @ddl = p_ddl;
21
+ PREPARE stmt FROM @ddl;
22
+ EXECUTE stmt;
23
+ DEALLOCATE PREPARE stmt;
24
+ END IF;
25
+ END$$
26
+
27
+ CREATE PROCEDURE __drop_index_if_exists(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64))
28
+ BEGIN
29
+ IF EXISTS (
30
+ SELECT 1
31
+ FROM information_schema.statistics
32
+ WHERE table_schema = DATABASE()
33
+ AND table_name = p_table_name
34
+ AND index_name = p_index_name
35
+ ) THEN
36
+ SET @ddl = CONCAT('ALTER TABLE `', p_table_name, '` DROP INDEX `', p_index_name, '`');
37
+ PREPARE stmt FROM @ddl;
38
+ EXECUTE stmt;
39
+ DEALLOCATE PREPARE stmt;
40
+ END IF;
41
+ END$$
42
+ DELIMITER ;
43
+
44
+ -- Restore redundant indexes removed in D0
45
+ CALL __ensure_index('sticker_pack_item', 'idx_sticker_pack_item_pack_position', 'CREATE INDEX idx_sticker_pack_item_pack_position ON sticker_pack_item (pack_id, position)');
46
+ CALL __ensure_index('domain_event_outbox_dlq', 'idx_domain_event_outbox_dlq_outbox_event_id', 'CREATE INDEX idx_domain_event_outbox_dlq_outbox_event_id ON domain_event_outbox_dlq (outbox_event_id)');
47
+ CALL __ensure_index('sticker_worker_task_dlq', 'idx_sticker_worker_task_dlq_task_id', 'CREATE INDEX idx_sticker_worker_task_dlq_task_id ON sticker_worker_task_dlq (task_id)');
48
+
49
+ -- Drop indexes added in D0
50
+ CALL __drop_index_if_exists('messages', 'idx_messages_sender_timestamp');
51
+ CALL __drop_index_if_exists('messages', 'idx_messages_chat_sender_timestamp');
52
+ CALL __drop_index_if_exists('domain_event_outbox', 'idx_domain_event_outbox_status_locked');
53
+ CALL __drop_index_if_exists('email_outbox', 'idx_email_outbox_status_locked');
54
+ CALL __drop_index_if_exists('sticker_worker_task_queue', 'idx_sticker_worker_task_queue_status_locked');
55
+ CALL __drop_index_if_exists('sticker_asset_reprocess_queue', 'idx_sticker_asset_reprocess_queue_status_locked');
56
+
57
+ UPDATE schema_change_log
58
+ SET status = 'rolled_back',
59
+ notes = 'D0 rollback executed',
60
+ updated_at = CURRENT_TIMESTAMP
61
+ WHERE migration_key = @migration_key;
62
+
63
+ DROP PROCEDURE IF EXISTS __ensure_index;
64
+ DROP PROCEDURE IF EXISTS __drop_index_if_exists;
@@ -0,0 +1,79 @@
1
+ -- D0 (2026-03-07) - Non-breaking hardening
2
+ -- Scope: indexes for queue reclaim/perf, remove redundant indexes, migration audit table
3
+ -- Run with mysql client:
4
+ -- mysql -u$DB_USER -p$DB_PASSWORD -h$DB_HOST $DB_NAME < database/migrations/20260307_d0_hardening_up.sql
5
+
6
+ SET @migration_key := '20260307_d0_hardening';
7
+
8
+ CREATE TABLE IF NOT EXISTS schema_change_log (
9
+ migration_key VARCHAR(128) NOT NULL,
10
+ phase VARCHAR(32) NOT NULL,
11
+ status ENUM('applied', 'rolled_back') NOT NULL DEFAULT 'applied',
12
+ notes VARCHAR(255) DEFAULT NULL,
13
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
14
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
15
+ PRIMARY KEY (migration_key),
16
+ KEY idx_schema_change_log_phase_status (phase, status, updated_at)
17
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
18
+
19
+ DROP PROCEDURE IF EXISTS __ensure_index;
20
+ DROP PROCEDURE IF EXISTS __drop_index_if_exists;
21
+
22
+ DELIMITER $$
23
+ CREATE PROCEDURE __ensure_index(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64), IN p_ddl TEXT)
24
+ BEGIN
25
+ IF NOT EXISTS (
26
+ SELECT 1
27
+ FROM information_schema.statistics
28
+ WHERE table_schema = DATABASE()
29
+ AND table_name = p_table_name
30
+ AND index_name = p_index_name
31
+ ) THEN
32
+ SET @ddl = p_ddl;
33
+ PREPARE stmt FROM @ddl;
34
+ EXECUTE stmt;
35
+ DEALLOCATE PREPARE stmt;
36
+ END IF;
37
+ END$$
38
+
39
+ CREATE PROCEDURE __drop_index_if_exists(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64))
40
+ BEGIN
41
+ IF EXISTS (
42
+ SELECT 1
43
+ FROM information_schema.statistics
44
+ WHERE table_schema = DATABASE()
45
+ AND table_name = p_table_name
46
+ AND index_name = p_index_name
47
+ ) THEN
48
+ SET @ddl = CONCAT('ALTER TABLE `', p_table_name, '` DROP INDEX `', p_index_name, '`');
49
+ PREPARE stmt FROM @ddl;
50
+ EXECUTE stmt;
51
+ DEALLOCATE PREPARE stmt;
52
+ END IF;
53
+ END$$
54
+ DELIMITER ;
55
+
56
+ -- Performance indexes (non-breaking)
57
+ CALL __ensure_index('messages', 'idx_messages_sender_timestamp', 'CREATE INDEX idx_messages_sender_timestamp ON messages (sender_id, timestamp)');
58
+ CALL __ensure_index('messages', 'idx_messages_chat_sender_timestamp', 'CREATE INDEX idx_messages_chat_sender_timestamp ON messages (chat_id, sender_id, timestamp)');
59
+
60
+ CALL __ensure_index('domain_event_outbox', 'idx_domain_event_outbox_status_locked', 'CREATE INDEX idx_domain_event_outbox_status_locked ON domain_event_outbox (status, locked_at)');
61
+ CALL __ensure_index('email_outbox', 'idx_email_outbox_status_locked', 'CREATE INDEX idx_email_outbox_status_locked ON email_outbox (status, locked_at)');
62
+ CALL __ensure_index('sticker_worker_task_queue', 'idx_sticker_worker_task_queue_status_locked', 'CREATE INDEX idx_sticker_worker_task_queue_status_locked ON sticker_worker_task_queue (status, locked_at)');
63
+ CALL __ensure_index('sticker_asset_reprocess_queue', 'idx_sticker_asset_reprocess_queue_status_locked', 'CREATE INDEX idx_sticker_asset_reprocess_queue_status_locked ON sticker_asset_reprocess_queue (status, locked_at)');
64
+
65
+ -- Remove redundant indexes (safe)
66
+ CALL __drop_index_if_exists('sticker_pack_item', 'idx_sticker_pack_item_pack_position');
67
+ CALL __drop_index_if_exists('domain_event_outbox_dlq', 'idx_domain_event_outbox_dlq_outbox_event_id');
68
+ CALL __drop_index_if_exists('sticker_worker_task_dlq', 'idx_sticker_worker_task_dlq_task_id');
69
+
70
+ INSERT INTO schema_change_log (migration_key, phase, status, notes)
71
+ VALUES (@migration_key, 'D0', 'applied', 'Indexes hardening + redundant index cleanup')
72
+ ON DUPLICATE KEY UPDATE
73
+ phase = VALUES(phase),
74
+ status = 'applied',
75
+ notes = VALUES(notes),
76
+ updated_at = CURRENT_TIMESTAMP;
77
+
78
+ DROP PROCEDURE IF EXISTS __ensure_index;
79
+ DROP PROCEDURE IF EXISTS __drop_index_if_exists;
@@ -0,0 +1,11 @@
1
+ -- D1 rollback
2
+
3
+ SET @migration_key := '20260307_d1_terms_acceptance';
4
+
5
+ DROP TABLE IF EXISTS web_terms_acceptance_event;
6
+
7
+ UPDATE schema_change_log
8
+ SET status = 'rolled_back',
9
+ notes = 'D1 rollback executado',
10
+ updated_at = CURRENT_TIMESTAMP
11
+ WHERE migration_key = @migration_key;
@@ -0,0 +1,37 @@
1
+ -- D1 (2026-03-07) - Registro versionado de aceite juridico
2
+ -- Scope: trilha probatoria de aceite de Termos/Politicas (hash da versao + timestamp + IP + user agent)
3
+
4
+ SET @migration_key := '20260307_d1_terms_acceptance';
5
+
6
+ CREATE TABLE IF NOT EXISTS web_terms_acceptance_event (
7
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
8
+ event_id CHAR(36) NOT NULL,
9
+ document_key VARCHAR(64) NOT NULL,
10
+ document_version VARCHAR(64) NOT NULL,
11
+ document_version_hash CHAR(64) NOT NULL,
12
+ accepted_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
13
+ accepted_at_client TIMESTAMP NULL DEFAULT NULL,
14
+ source VARCHAR(32) NOT NULL DEFAULT 'web_login',
15
+ google_sub VARCHAR(80) DEFAULT NULL,
16
+ email VARCHAR(255) DEFAULT NULL,
17
+ owner_jid VARCHAR(120) DEFAULT NULL,
18
+ session_key VARCHAR(80) DEFAULT NULL,
19
+ ip_address VARCHAR(64) DEFAULT NULL,
20
+ user_agent VARCHAR(512) DEFAULT NULL,
21
+ metadata LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(metadata)),
22
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
23
+ PRIMARY KEY (id),
24
+ UNIQUE KEY uq_web_terms_acceptance_event_id (event_id),
25
+ KEY idx_web_terms_acceptance_doc_version (document_key, document_version, accepted_at),
26
+ KEY idx_web_terms_acceptance_identity (google_sub, email, owner_jid, accepted_at),
27
+ KEY idx_web_terms_acceptance_source_created (source, created_at),
28
+ KEY idx_web_terms_acceptance_session_created (session_key, created_at)
29
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
30
+
31
+ INSERT INTO schema_change_log (migration_key, phase, status, notes)
32
+ VALUES (@migration_key, 'D1', 'applied', 'Tabela de aceite versionado de termos/politicas')
33
+ ON DUPLICATE KEY UPDATE
34
+ phase = VALUES(phase),
35
+ status = 'applied',
36
+ notes = VALUES(notes),
37
+ updated_at = CURRENT_TIMESTAMP;
@@ -0,0 +1,75 @@
1
+ -- D2 rollback
2
+
3
+ SET @migration_key := '20260307_d2_auth_hardening';
4
+
5
+ DROP PROCEDURE IF EXISTS __drop_column_if_exists;
6
+ DROP PROCEDURE IF EXISTS __drop_index_if_exists;
7
+
8
+ DELIMITER $$
9
+ CREATE PROCEDURE __drop_column_if_exists(IN p_table_name VARCHAR(64), IN p_column_name VARCHAR(64), IN p_ddl TEXT)
10
+ BEGIN
11
+ IF EXISTS (
12
+ SELECT 1
13
+ FROM information_schema.columns
14
+ WHERE table_schema = DATABASE()
15
+ AND table_name = p_table_name
16
+ AND column_name = p_column_name
17
+ ) THEN
18
+ SET @ddl = p_ddl;
19
+ PREPARE stmt FROM @ddl;
20
+ EXECUTE stmt;
21
+ DEALLOCATE PREPARE stmt;
22
+ END IF;
23
+ END$$
24
+
25
+ CREATE PROCEDURE __drop_index_if_exists(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64), IN p_ddl TEXT)
26
+ BEGIN
27
+ IF EXISTS (
28
+ SELECT 1
29
+ FROM information_schema.statistics
30
+ WHERE table_schema = DATABASE()
31
+ AND table_name = p_table_name
32
+ AND index_name = p_index_name
33
+ ) THEN
34
+ SET @ddl = p_ddl;
35
+ PREPARE stmt FROM @ddl;
36
+ EXECUTE stmt;
37
+ DEALLOCATE PREPARE stmt;
38
+ END IF;
39
+ END$$
40
+ DELIMITER ;
41
+
42
+ CALL __drop_index_if_exists(
43
+ 'web_user_password_recovery_code',
44
+ 'idx_web_user_password_recovery_email_hash_created',
45
+ 'DROP INDEX idx_web_user_password_recovery_email_hash_created ON web_user_password_recovery_code'
46
+ );
47
+
48
+ CALL __drop_column_if_exists(
49
+ 'web_user_password_recovery_code',
50
+ 'email_hash',
51
+ 'ALTER TABLE web_user_password_recovery_code DROP COLUMN email_hash'
52
+ );
53
+
54
+ CALL __drop_column_if_exists(
55
+ 'web_user_password_recovery_code',
56
+ 'requested_ip_hash',
57
+ 'ALTER TABLE web_user_password_recovery_code DROP COLUMN requested_ip_hash'
58
+ );
59
+
60
+ CALL __drop_column_if_exists(
61
+ 'web_user_password_recovery_code',
62
+ 'requested_user_agent_hash',
63
+ 'ALTER TABLE web_user_password_recovery_code DROP COLUMN requested_user_agent_hash'
64
+ );
65
+
66
+ DROP TABLE IF EXISTS web_user_password_login_throttle;
67
+
68
+ DROP PROCEDURE IF EXISTS __drop_column_if_exists;
69
+ DROP PROCEDURE IF EXISTS __drop_index_if_exists;
70
+
71
+ UPDATE schema_change_log
72
+ SET status = 'rolled_back',
73
+ notes = 'D2 rollback executado',
74
+ updated_at = CURRENT_TIMESTAMP
75
+ WHERE migration_key = @migration_key;
@@ -0,0 +1,100 @@
1
+ -- D2 (2026-03-07) - Auth hardening follow-up
2
+ -- Scope: distributed login throttle + hashed sensitive metadata for password recovery
3
+
4
+ SET @migration_key := '20260307_d2_auth_hardening';
5
+
6
+ DROP PROCEDURE IF EXISTS __ensure_column;
7
+ DROP PROCEDURE IF EXISTS __ensure_index;
8
+
9
+ DELIMITER $$
10
+ CREATE PROCEDURE __ensure_column(IN p_table_name VARCHAR(64), IN p_column_name VARCHAR(64), IN p_ddl TEXT)
11
+ BEGIN
12
+ IF NOT EXISTS (
13
+ SELECT 1
14
+ FROM information_schema.columns
15
+ WHERE table_schema = DATABASE()
16
+ AND table_name = p_table_name
17
+ AND column_name = p_column_name
18
+ ) THEN
19
+ SET @ddl = p_ddl;
20
+ PREPARE stmt FROM @ddl;
21
+ EXECUTE stmt;
22
+ DEALLOCATE PREPARE stmt;
23
+ END IF;
24
+ END$$
25
+
26
+ CREATE PROCEDURE __ensure_index(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64), IN p_ddl TEXT)
27
+ BEGIN
28
+ IF NOT EXISTS (
29
+ SELECT 1
30
+ FROM information_schema.statistics
31
+ WHERE table_schema = DATABASE()
32
+ AND table_name = p_table_name
33
+ AND index_name = p_index_name
34
+ ) THEN
35
+ SET @ddl = p_ddl;
36
+ PREPARE stmt FROM @ddl;
37
+ EXECUTE stmt;
38
+ DEALLOCATE PREPARE stmt;
39
+ END IF;
40
+ END$$
41
+ DELIMITER ;
42
+
43
+ CREATE TABLE IF NOT EXISTS web_user_password_login_throttle (
44
+ identity_hash BINARY(32) NOT NULL,
45
+ failed_attempts SMALLINT UNSIGNED NOT NULL DEFAULT 0,
46
+ last_failed_at TIMESTAMP NULL DEFAULT NULL,
47
+ locked_until TIMESTAMP NULL DEFAULT NULL,
48
+ created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
49
+ updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
50
+ PRIMARY KEY (identity_hash),
51
+ KEY idx_web_user_password_login_throttle_locked_until (locked_until),
52
+ KEY idx_web_user_password_login_throttle_failed (failed_attempts, last_failed_at)
53
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
54
+
55
+ CALL __ensure_column(
56
+ 'web_user_password_recovery_code',
57
+ 'email_hash',
58
+ 'ALTER TABLE web_user_password_recovery_code ADD COLUMN email_hash BINARY(32) NULL AFTER email'
59
+ );
60
+
61
+ CALL __ensure_column(
62
+ 'web_user_password_recovery_code',
63
+ 'requested_ip_hash',
64
+ 'ALTER TABLE web_user_password_recovery_code ADD COLUMN requested_ip_hash BINARY(32) NULL AFTER requested_ip'
65
+ );
66
+
67
+ CALL __ensure_column(
68
+ 'web_user_password_recovery_code',
69
+ 'requested_user_agent_hash',
70
+ 'ALTER TABLE web_user_password_recovery_code ADD COLUMN requested_user_agent_hash BINARY(32) NULL AFTER requested_user_agent'
71
+ );
72
+
73
+ CALL __ensure_index(
74
+ 'web_user_password_recovery_code',
75
+ 'idx_web_user_password_recovery_email_hash_created',
76
+ 'CREATE INDEX idx_web_user_password_recovery_email_hash_created ON web_user_password_recovery_code (email_hash, created_at)'
77
+ );
78
+
79
+ CALL __ensure_index(
80
+ 'web_user_password_login_throttle',
81
+ 'idx_web_user_password_login_throttle_locked_until',
82
+ 'CREATE INDEX idx_web_user_password_login_throttle_locked_until ON web_user_password_login_throttle (locked_until)'
83
+ );
84
+
85
+ CALL __ensure_index(
86
+ 'web_user_password_login_throttle',
87
+ 'idx_web_user_password_login_throttle_failed',
88
+ 'CREATE INDEX idx_web_user_password_login_throttle_failed ON web_user_password_login_throttle (failed_attempts, last_failed_at)'
89
+ );
90
+
91
+ DROP PROCEDURE IF EXISTS __ensure_column;
92
+ DROP PROCEDURE IF EXISTS __ensure_index;
93
+
94
+ INSERT INTO schema_change_log (migration_key, phase, status, notes)
95
+ VALUES (@migration_key, 'D2', 'applied', 'Distributed login throttle + hashed recovery sensitive metadata')
96
+ ON DUPLICATE KEY UPDATE
97
+ phase = VALUES(phase),
98
+ status = 'applied',
99
+ notes = VALUES(notes),
100
+ updated_at = CURRENT_TIMESTAMP;
@@ -0,0 +1,53 @@
1
+ -- D+7 rollback
2
+
3
+ SET @migration_key := '20260314_d7_canonical_sender';
4
+
5
+ DROP PROCEDURE IF EXISTS __drop_column_if_exists;
6
+ DROP PROCEDURE IF EXISTS __drop_index_if_exists;
7
+
8
+ DELIMITER $$
9
+ CREATE PROCEDURE __drop_column_if_exists(IN p_table_name VARCHAR(64), IN p_column_name VARCHAR(64))
10
+ BEGIN
11
+ IF EXISTS (
12
+ SELECT 1
13
+ FROM information_schema.columns
14
+ WHERE table_schema = DATABASE()
15
+ AND table_name = p_table_name
16
+ AND column_name = p_column_name
17
+ ) THEN
18
+ SET @ddl = CONCAT('ALTER TABLE `', p_table_name, '` DROP COLUMN `', p_column_name, '`');
19
+ PREPARE stmt FROM @ddl;
20
+ EXECUTE stmt;
21
+ DEALLOCATE PREPARE stmt;
22
+ END IF;
23
+ END$$
24
+
25
+ CREATE PROCEDURE __drop_index_if_exists(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64))
26
+ BEGIN
27
+ IF EXISTS (
28
+ SELECT 1
29
+ FROM information_schema.statistics
30
+ WHERE table_schema = DATABASE()
31
+ AND table_name = p_table_name
32
+ AND index_name = p_index_name
33
+ ) THEN
34
+ SET @ddl = CONCAT('ALTER TABLE `', p_table_name, '` DROP INDEX `', p_index_name, '`');
35
+ PREPARE stmt FROM @ddl;
36
+ EXECUTE stmt;
37
+ DEALLOCATE PREPARE stmt;
38
+ END IF;
39
+ END$$
40
+ DELIMITER ;
41
+
42
+ CALL __drop_index_if_exists('messages', 'idx_messages_chat_canonical_sender_timestamp');
43
+ CALL __drop_index_if_exists('messages', 'idx_messages_canonical_sender_timestamp');
44
+ CALL __drop_column_if_exists('messages', 'canonical_sender_id');
45
+
46
+ UPDATE schema_change_log
47
+ SET status = 'rolled_back',
48
+ notes = 'D+7 rollback executed',
49
+ updated_at = CURRENT_TIMESTAMP
50
+ WHERE migration_key = @migration_key;
51
+
52
+ DROP PROCEDURE IF EXISTS __drop_column_if_exists;
53
+ DROP PROCEDURE IF EXISTS __drop_index_if_exists;
@@ -0,0 +1,114 @@
1
+ -- D+7 (2026-03-14) - Small migration
2
+ -- Scope: canonical sender column for high-volume ranking/analytics queries
3
+
4
+ SET @migration_key := '20260314_d7_canonical_sender';
5
+
6
+ DROP PROCEDURE IF EXISTS __ensure_column;
7
+ DROP PROCEDURE IF EXISTS __drop_column_if_exists;
8
+ DROP PROCEDURE IF EXISTS __ensure_index;
9
+ DROP PROCEDURE IF EXISTS __backfill_messages_canonical_sender;
10
+
11
+ DELIMITER $$
12
+ CREATE PROCEDURE __ensure_column(IN p_table_name VARCHAR(64), IN p_column_name VARCHAR(64), IN p_ddl TEXT)
13
+ BEGIN
14
+ IF NOT EXISTS (
15
+ SELECT 1
16
+ FROM information_schema.columns
17
+ WHERE table_schema = DATABASE()
18
+ AND table_name = p_table_name
19
+ AND column_name = p_column_name
20
+ ) THEN
21
+ SET @ddl = p_ddl;
22
+ PREPARE stmt FROM @ddl;
23
+ EXECUTE stmt;
24
+ DEALLOCATE PREPARE stmt;
25
+ END IF;
26
+ END$$
27
+
28
+ CREATE PROCEDURE __drop_column_if_exists(IN p_table_name VARCHAR(64), IN p_column_name VARCHAR(64))
29
+ BEGIN
30
+ IF EXISTS (
31
+ SELECT 1
32
+ FROM information_schema.columns
33
+ WHERE table_schema = DATABASE()
34
+ AND table_name = p_table_name
35
+ AND column_name = p_column_name
36
+ ) THEN
37
+ SET @ddl = CONCAT('ALTER TABLE `', p_table_name, '` DROP COLUMN `', p_column_name, '`');
38
+ PREPARE stmt FROM @ddl;
39
+ EXECUTE stmt;
40
+ DEALLOCATE PREPARE stmt;
41
+ END IF;
42
+ END$$
43
+
44
+ CREATE PROCEDURE __ensure_index(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64), IN p_ddl TEXT)
45
+ BEGIN
46
+ IF NOT EXISTS (
47
+ SELECT 1
48
+ FROM information_schema.statistics
49
+ WHERE table_schema = DATABASE()
50
+ AND table_name = p_table_name
51
+ AND index_name = p_index_name
52
+ ) THEN
53
+ SET @ddl = p_ddl;
54
+ PREPARE stmt FROM @ddl;
55
+ EXECUTE stmt;
56
+ DEALLOCATE PREPARE stmt;
57
+ END IF;
58
+ END$$
59
+
60
+ CREATE PROCEDURE __backfill_messages_canonical_sender(IN p_batch_size BIGINT UNSIGNED)
61
+ BEGIN
62
+ DECLARE v_min BIGINT UNSIGNED DEFAULT 0;
63
+ DECLARE v_max BIGINT UNSIGNED DEFAULT 0;
64
+ DECLARE v_cursor BIGINT UNSIGNED DEFAULT 0;
65
+
66
+ SELECT COALESCE(MIN(id), 0), COALESCE(MAX(id), 0)
67
+ INTO v_min, v_max
68
+ FROM messages
69
+ WHERE canonical_sender_id IS NULL;
70
+
71
+ SET v_cursor = v_min;
72
+
73
+ WHILE v_cursor > 0 AND v_cursor <= v_max DO
74
+ UPDATE messages m
75
+ LEFT JOIN lid_map lm
76
+ ON lm.lid = m.sender_id
77
+ AND lm.jid IS NOT NULL
78
+ SET m.canonical_sender_id = COALESCE(lm.jid, m.sender_id)
79
+ WHERE m.id BETWEEN v_cursor AND (v_cursor + p_batch_size - 1)
80
+ AND m.canonical_sender_id IS NULL;
81
+
82
+ SET v_cursor = v_cursor + p_batch_size;
83
+ END WHILE;
84
+ END$$
85
+ DELIMITER ;
86
+
87
+ CALL __ensure_column('messages', 'canonical_sender_id', 'ALTER TABLE messages ADD COLUMN canonical_sender_id VARCHAR(255) NULL AFTER sender_id');
88
+
89
+ CALL __ensure_index('messages', 'idx_messages_canonical_sender_timestamp', 'CREATE INDEX idx_messages_canonical_sender_timestamp ON messages (canonical_sender_id, timestamp)');
90
+ CALL __ensure_index('messages', 'idx_messages_chat_canonical_sender_timestamp', 'CREATE INDEX idx_messages_chat_canonical_sender_timestamp ON messages (chat_id, canonical_sender_id, timestamp)');
91
+
92
+ -- Backfill in chunks (adjust batch size according to table size / write pressure)
93
+ CALL __backfill_messages_canonical_sender(50000);
94
+
95
+ -- Safety pass for rows inserted during chunk loop
96
+ UPDATE messages m
97
+ LEFT JOIN lid_map lm
98
+ ON lm.lid = m.sender_id
99
+ AND lm.jid IS NOT NULL
100
+ SET m.canonical_sender_id = COALESCE(lm.jid, m.sender_id)
101
+ WHERE m.canonical_sender_id IS NULL;
102
+
103
+ INSERT INTO schema_change_log (migration_key, phase, status, notes)
104
+ VALUES (@migration_key, 'D+7', 'applied', 'messages.canonical_sender_id + backfill + indexes')
105
+ ON DUPLICATE KEY UPDATE
106
+ phase = VALUES(phase),
107
+ status = 'applied',
108
+ notes = VALUES(notes),
109
+ updated_at = CURRENT_TIMESTAMP;
110
+
111
+ DROP PROCEDURE IF EXISTS __ensure_column;
112
+ DROP PROCEDURE IF EXISTS __drop_column_if_exists;
113
+ DROP PROCEDURE IF EXISTS __ensure_index;
114
+ DROP PROCEDURE IF EXISTS __backfill_messages_canonical_sender;