@omnizap-system/omnizap 2.6.1 → 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +54 -9
- package/.github/workflows/ci.yml +3 -3
- package/.github/workflows/security-runner-hardening.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +1 -0
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +226 -55
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +143 -3
- package/app/configParts/sessionConfig.js +157 -0
- package/app/connection/baileysCompatibility.test.js +1 -1
- package/app/connection/groupOwnerWriteStateResolver.js +109 -0
- package/app/connection/socketController.js +625 -124
- package/app/connection/socketController.multiSession.test.js +108 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
- package/app/controllers/messageProcessingPipeline.js +88 -9
- package/app/controllers/messageProcessingPipeline.test.js +200 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +856 -14
- package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +132 -25
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +74 -16
- package/app/modules/playModule/playCommandConstants.js +13 -7
- package/app/modules/playModule/playCommandCore.js +4 -6
- package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
- package/app/modules/playModule/playConfigRuntime.js +5 -6
- package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +1 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/geminiService.js +131 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/moduleAiHelpCoreService.js +33 -4
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +51 -21
- package/app/services/messaging/newsBroadcastService.js +843 -27
- package/app/services/multiSession/assignmentBalancerService.js +457 -0
- package/app/services/multiSession/groupOwnershipRepository.js +381 -0
- package/app/services/multiSession/groupOwnershipService.js +890 -0
- package/app/services/multiSession/groupOwnershipService.test.js +309 -0
- package/app/services/multiSession/sessionRegistryService.js +293 -0
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +352 -16
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/database/index.js +6 -0
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +2 -2
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/ecosystem.prod.config.cjs +31 -11
- package/index.js +52 -18
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +12 -5
- package/public/comandos/commands-catalog.json +2253 -78
- package/public/js/apps/commandsReactApp.js +267 -87
- package/public/js/apps/createPackApp.js +3 -3
- package/public/js/apps/stickersApp.js +255 -103
- package/public/js/apps/termsReactApp.js +57 -8
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +96 -47
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/politica-de-privacidade.html +1 -1
- package/public/pages/stickers.html +5 -5
- package/public/pages/termos-de-uso-texto-integral.html +1 -1
- package/public/pages/termos-de-uso.html +1 -1
- package/public/pages/user-password-reset.html +3 -4
- package/public/pages/user-systemadm.html +8 -462
- package/public/pages/user.html +1 -1
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +10 -0
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +155 -0
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/security-web-surface-check.mjs +218 -0
- package/server/controllers/admin/adminPanelHandlers.js +253 -3
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/sticker/stickerCatalogController.js +9 -23
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +3 -2
- package/server/http/httpServer.js +8 -4
- package/server/middleware/securityHeaders.js +20 -1
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +30 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/static/staticPageRouter.js +27 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +2 -1
- package/app/modules/playModule/local/installYtDlp.js +0 -25
- package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
-- D+36 rollback
|
|
2
|
+
|
|
3
|
+
SET @migration_key := '20260412_d36_system_config_tables';
|
|
4
|
+
|
|
5
|
+
INSERT INTO `group_configs` (`id`, `config`, `updated_at`)
|
|
6
|
+
SELECT
|
|
7
|
+
'system:premium_users' AS `id`,
|
|
8
|
+
JSON_OBJECT('premiumUsers', COALESCE(JSON_ARRAYAGG(`id`), JSON_ARRAY())) AS `config`,
|
|
9
|
+
CURRENT_TIMESTAMP AS `updated_at`
|
|
10
|
+
FROM `system_premium_users`
|
|
11
|
+
ON DUPLICATE KEY UPDATE
|
|
12
|
+
`config` = VALUES(`config`),
|
|
13
|
+
`updated_at` = CURRENT_TIMESTAMP;
|
|
14
|
+
|
|
15
|
+
INSERT INTO `group_configs` (`id`, `config`, `updated_at`)
|
|
16
|
+
SELECT
|
|
17
|
+
'system:ai_prompts' AS `id`,
|
|
18
|
+
JSON_OBJECT('prompts', COALESCE(JSON_OBJECTAGG(`id`, `prompt`), JSON_OBJECT())) AS `config`,
|
|
19
|
+
CURRENT_TIMESTAMP AS `updated_at`
|
|
20
|
+
FROM `system_ai_prompts`
|
|
21
|
+
ON DUPLICATE KEY UPDATE
|
|
22
|
+
`config` = VALUES(`config`),
|
|
23
|
+
`updated_at` = CURRENT_TIMESTAMP;
|
|
24
|
+
|
|
25
|
+
DROP TABLE IF EXISTS `system_ai_prompts`;
|
|
26
|
+
DROP TABLE IF EXISTS `system_premium_users`;
|
|
27
|
+
|
|
28
|
+
UPDATE schema_change_log
|
|
29
|
+
SET status = 'rolled_back',
|
|
30
|
+
notes = 'D+36 rollback executed',
|
|
31
|
+
updated_at = CURRENT_TIMESTAMP
|
|
32
|
+
WHERE migration_key = @migration_key;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
-- D+36 (2026-04-12) - Split system config records from group_configs
|
|
2
|
+
-- Scope: move `system:premium_users` and `system:ai_prompts` to dedicated tables
|
|
3
|
+
|
|
4
|
+
SET @migration_key := '20260412_d36_system_config_tables';
|
|
5
|
+
|
|
6
|
+
CREATE TABLE IF NOT EXISTS `system_premium_users` (
|
|
7
|
+
`id` varchar(255) NOT NULL,
|
|
8
|
+
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
9
|
+
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
|
10
|
+
PRIMARY KEY (`id`)
|
|
11
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
12
|
+
|
|
13
|
+
CREATE TABLE IF NOT EXISTS `system_ai_prompts` (
|
|
14
|
+
`id` varchar(255) NOT NULL,
|
|
15
|
+
`prompt` longtext NOT NULL,
|
|
16
|
+
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
17
|
+
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
|
18
|
+
PRIMARY KEY (`id`)
|
|
19
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
20
|
+
|
|
21
|
+
INSERT INTO `system_premium_users` (`id`)
|
|
22
|
+
SELECT DISTINCT jt.premium_jid
|
|
23
|
+
FROM `group_configs` gc
|
|
24
|
+
JOIN JSON_TABLE(
|
|
25
|
+
COALESCE(JSON_EXTRACT(gc.config, '$.premiumUsers'), JSON_ARRAY()),
|
|
26
|
+
'$[*]' COLUMNS (`premium_jid` VARCHAR(255) PATH '$')
|
|
27
|
+
) jt
|
|
28
|
+
WHERE gc.id = 'system:premium_users'
|
|
29
|
+
AND jt.premium_jid IS NOT NULL
|
|
30
|
+
AND TRIM(jt.premium_jid) <> ''
|
|
31
|
+
ON DUPLICATE KEY UPDATE
|
|
32
|
+
`updated_at` = CURRENT_TIMESTAMP;
|
|
33
|
+
|
|
34
|
+
INSERT INTO `system_ai_prompts` (`id`, `prompt`)
|
|
35
|
+
SELECT jt.prompt_jid,
|
|
36
|
+
COALESCE(
|
|
37
|
+
JSON_UNQUOTE(
|
|
38
|
+
JSON_EXTRACT(
|
|
39
|
+
JSON_EXTRACT(gc.config, '$.prompts'),
|
|
40
|
+
CONCAT('$."', REPLACE(jt.prompt_jid, '"', '\\"'), '"')
|
|
41
|
+
)
|
|
42
|
+
),
|
|
43
|
+
''
|
|
44
|
+
) AS prompt_text
|
|
45
|
+
FROM `group_configs` gc
|
|
46
|
+
JOIN JSON_TABLE(
|
|
47
|
+
JSON_KEYS(COALESCE(JSON_EXTRACT(gc.config, '$.prompts'), JSON_OBJECT())),
|
|
48
|
+
'$[*]' COLUMNS (`prompt_jid` VARCHAR(255) PATH '$')
|
|
49
|
+
) jt
|
|
50
|
+
WHERE gc.id = 'system:ai_prompts'
|
|
51
|
+
AND jt.prompt_jid IS NOT NULL
|
|
52
|
+
AND TRIM(jt.prompt_jid) <> ''
|
|
53
|
+
ON DUPLICATE KEY UPDATE
|
|
54
|
+
`prompt` = VALUES(`prompt`),
|
|
55
|
+
`updated_at` = CURRENT_TIMESTAMP;
|
|
56
|
+
|
|
57
|
+
DELETE FROM `group_configs`
|
|
58
|
+
WHERE id IN ('system:premium_users', 'system:ai_prompts');
|
|
59
|
+
|
|
60
|
+
INSERT INTO schema_change_log (migration_key, phase, status, notes)
|
|
61
|
+
VALUES (@migration_key, 'D+36', 'applied', 'split system:premium_users and system:ai_prompts into dedicated tables')
|
|
62
|
+
ON DUPLICATE KEY UPDATE
|
|
63
|
+
phase = VALUES(phase),
|
|
64
|
+
status = 'applied',
|
|
65
|
+
notes = VALUES(notes),
|
|
66
|
+
updated_at = CURRENT_TIMESTAMP;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- D+37 rollback
|
|
2
|
+
|
|
3
|
+
SET @migration_key := '20260413_d37_group_user_warnings';
|
|
4
|
+
|
|
5
|
+
DROP TABLE IF EXISTS `group_user_warnings`;
|
|
6
|
+
|
|
7
|
+
UPDATE schema_change_log
|
|
8
|
+
SET status = 'rolled_back',
|
|
9
|
+
notes = 'D+37 rollback executed',
|
|
10
|
+
updated_at = CURRENT_TIMESTAMP
|
|
11
|
+
WHERE migration_key = @migration_key;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- D+37 (2026-04-13) - Group moderation warnings
|
|
2
|
+
-- Scope: track warnings per participant inside each group
|
|
3
|
+
|
|
4
|
+
SET @migration_key := '20260413_d37_group_user_warnings';
|
|
5
|
+
|
|
6
|
+
CREATE TABLE IF NOT EXISTS `group_user_warnings` (
|
|
7
|
+
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
8
|
+
`group_id` varchar(255) NOT NULL,
|
|
9
|
+
`participant_jid` varchar(255) NOT NULL,
|
|
10
|
+
`warned_by_jid` varchar(255) DEFAULT NULL,
|
|
11
|
+
`reason` text DEFAULT NULL,
|
|
12
|
+
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
|
13
|
+
PRIMARY KEY (`id`),
|
|
14
|
+
KEY `idx_group_user_warnings_lookup` (`group_id`,`participant_jid`,`created_at`),
|
|
15
|
+
KEY `idx_group_user_warnings_prune` (`group_id`,`participant_jid`,`id`)
|
|
16
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
17
|
+
|
|
18
|
+
INSERT INTO schema_change_log (migration_key, phase, status, notes)
|
|
19
|
+
VALUES (@migration_key, 'D+37', 'applied', 'created group_user_warnings table for moderation warnings')
|
|
20
|
+
ON DUPLICATE KEY UPDATE
|
|
21
|
+
phase = VALUES(phase),
|
|
22
|
+
status = 'applied',
|
|
23
|
+
notes = VALUES(notes),
|
|
24
|
+
updated_at = CURRENT_TIMESTAMP;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
-- D+38 rollback
|
|
2
|
+
|
|
3
|
+
SET @migration_key := '20260414_d38_multi_session_foundation';
|
|
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('message_analysis_event', 'idx_message_analysis_session_command_created');
|
|
43
|
+
CALL __drop_index_if_exists('message_analysis_event', 'idx_message_analysis_session_sender_created');
|
|
44
|
+
CALL __drop_index_if_exists('message_analysis_event', 'idx_message_analysis_session_chat_created');
|
|
45
|
+
CALL __drop_index_if_exists('message_analysis_event', 'idx_message_analysis_session_created');
|
|
46
|
+
|
|
47
|
+
CALL __drop_index_if_exists('baileys_event_journal', 'idx_baileys_event_session_message_created');
|
|
48
|
+
CALL __drop_index_if_exists('baileys_event_journal', 'idx_baileys_event_session_chat_created');
|
|
49
|
+
CALL __drop_index_if_exists('baileys_event_journal', 'idx_baileys_event_session_name_created');
|
|
50
|
+
CALL __drop_index_if_exists('baileys_event_journal', 'idx_baileys_event_session_created');
|
|
51
|
+
|
|
52
|
+
CALL __drop_index_if_exists('messages', 'idx_messages_session_canonical_sender_timestamp');
|
|
53
|
+
CALL __drop_index_if_exists('messages', 'idx_messages_session_sender_timestamp');
|
|
54
|
+
CALL __drop_index_if_exists('messages', 'idx_messages_session_chat_timestamp');
|
|
55
|
+
CALL __drop_index_if_exists('messages', 'idx_messages_session_message_id');
|
|
56
|
+
|
|
57
|
+
CALL __drop_column_if_exists('message_analysis_event', 'session_id');
|
|
58
|
+
CALL __drop_column_if_exists('baileys_event_journal', 'session_id');
|
|
59
|
+
CALL __drop_column_if_exists('messages', 'session_id');
|
|
60
|
+
|
|
61
|
+
DROP TABLE IF EXISTS `group_assignment_history`;
|
|
62
|
+
DROP TABLE IF EXISTS `group_assignment`;
|
|
63
|
+
DROP TABLE IF EXISTS `wa_session_registry`;
|
|
64
|
+
|
|
65
|
+
UPDATE schema_change_log
|
|
66
|
+
SET status = 'rolled_back',
|
|
67
|
+
notes = 'D+38 rollback executed',
|
|
68
|
+
updated_at = CURRENT_TIMESTAMP
|
|
69
|
+
WHERE migration_key = @migration_key;
|
|
70
|
+
|
|
71
|
+
DROP PROCEDURE IF EXISTS __drop_column_if_exists;
|
|
72
|
+
DROP PROCEDURE IF EXISTS __drop_index_if_exists;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
-- D+38 (2026-04-14) - Multi-session foundation
|
|
2
|
+
-- Scope:
|
|
3
|
+
-- 1) add group ownership/session registry tables
|
|
4
|
+
-- 2) add session_id scoping columns to high-traffic WhatsApp tables
|
|
5
|
+
-- 3) add session-aware composite indexes
|
|
6
|
+
|
|
7
|
+
SET @migration_key := '20260414_d38_multi_session_foundation';
|
|
8
|
+
|
|
9
|
+
DROP PROCEDURE IF EXISTS __ensure_column;
|
|
10
|
+
DROP PROCEDURE IF EXISTS __ensure_index;
|
|
11
|
+
|
|
12
|
+
DELIMITER $$
|
|
13
|
+
CREATE PROCEDURE __ensure_column(IN p_table_name VARCHAR(64), IN p_column_name VARCHAR(64), IN p_ddl TEXT)
|
|
14
|
+
BEGIN
|
|
15
|
+
IF NOT EXISTS (
|
|
16
|
+
SELECT 1
|
|
17
|
+
FROM information_schema.columns
|
|
18
|
+
WHERE table_schema = DATABASE()
|
|
19
|
+
AND table_name = p_table_name
|
|
20
|
+
AND column_name = p_column_name
|
|
21
|
+
) THEN
|
|
22
|
+
SET @ddl = p_ddl;
|
|
23
|
+
PREPARE stmt FROM @ddl;
|
|
24
|
+
EXECUTE stmt;
|
|
25
|
+
DEALLOCATE PREPARE stmt;
|
|
26
|
+
END IF;
|
|
27
|
+
END$$
|
|
28
|
+
|
|
29
|
+
CREATE PROCEDURE __ensure_index(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64), IN p_ddl TEXT)
|
|
30
|
+
BEGIN
|
|
31
|
+
IF NOT EXISTS (
|
|
32
|
+
SELECT 1
|
|
33
|
+
FROM information_schema.statistics
|
|
34
|
+
WHERE table_schema = DATABASE()
|
|
35
|
+
AND table_name = p_table_name
|
|
36
|
+
AND index_name = p_index_name
|
|
37
|
+
) THEN
|
|
38
|
+
SET @ddl = p_ddl;
|
|
39
|
+
PREPARE stmt FROM @ddl;
|
|
40
|
+
EXECUTE stmt;
|
|
41
|
+
DEALLOCATE PREPARE stmt;
|
|
42
|
+
END IF;
|
|
43
|
+
END$$
|
|
44
|
+
DELIMITER ;
|
|
45
|
+
|
|
46
|
+
CREATE TABLE IF NOT EXISTS `wa_session_registry` (
|
|
47
|
+
`session_id` varchar(64) NOT NULL,
|
|
48
|
+
`bot_jid` varchar(255) DEFAULT NULL,
|
|
49
|
+
`status` varchar(24) NOT NULL DEFAULT 'offline',
|
|
50
|
+
`capacity_weight` int(10) unsigned NOT NULL DEFAULT 1,
|
|
51
|
+
`current_score` decimal(12,4) NOT NULL DEFAULT 0.0000,
|
|
52
|
+
`last_heartbeat_at` datetime DEFAULT NULL,
|
|
53
|
+
`last_connected_at` datetime DEFAULT NULL,
|
|
54
|
+
`last_disconnected_at` datetime DEFAULT NULL,
|
|
55
|
+
`metadata` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),
|
|
56
|
+
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
|
57
|
+
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
|
58
|
+
PRIMARY KEY (`session_id`),
|
|
59
|
+
KEY `idx_wa_session_registry_status_updated` (`status`,`updated_at`),
|
|
60
|
+
KEY `idx_wa_session_registry_heartbeat` (`last_heartbeat_at`)
|
|
61
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
62
|
+
|
|
63
|
+
CREATE TABLE IF NOT EXISTS `group_assignment` (
|
|
64
|
+
`group_jid` varchar(255) NOT NULL,
|
|
65
|
+
`owner_session_id` varchar(64) NOT NULL,
|
|
66
|
+
`lease_expires_at` datetime NOT NULL,
|
|
67
|
+
`cooldown_until` datetime DEFAULT NULL,
|
|
68
|
+
`assignment_version` bigint(20) unsigned NOT NULL DEFAULT 1,
|
|
69
|
+
`pinned` tinyint(1) NOT NULL DEFAULT 0,
|
|
70
|
+
`last_reason` varchar(64) DEFAULT NULL,
|
|
71
|
+
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
|
72
|
+
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
|
73
|
+
PRIMARY KEY (`group_jid`),
|
|
74
|
+
KEY `idx_group_assignment_owner_lease` (`owner_session_id`,`lease_expires_at`),
|
|
75
|
+
KEY `idx_group_assignment_lease` (`lease_expires_at`),
|
|
76
|
+
KEY `idx_group_assignment_cooldown` (`cooldown_until`),
|
|
77
|
+
KEY `idx_group_assignment_pinned_updated` (`pinned`,`updated_at`),
|
|
78
|
+
CONSTRAINT `fk_group_assignment_owner_session` FOREIGN KEY (`owner_session_id`) REFERENCES `wa_session_registry` (`session_id`) ON UPDATE CASCADE
|
|
79
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
80
|
+
|
|
81
|
+
CREATE TABLE IF NOT EXISTS `group_assignment_history` (
|
|
82
|
+
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
83
|
+
`group_jid` varchar(255) NOT NULL,
|
|
84
|
+
`previous_session_id` varchar(64) DEFAULT NULL,
|
|
85
|
+
`new_session_id` varchar(64) NOT NULL,
|
|
86
|
+
`change_reason` varchar(64) DEFAULT NULL,
|
|
87
|
+
`changed_by` varchar(64) NOT NULL DEFAULT 'system',
|
|
88
|
+
`assignment_version` bigint(20) unsigned NOT NULL,
|
|
89
|
+
`metadata` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),
|
|
90
|
+
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
|
91
|
+
PRIMARY KEY (`id`),
|
|
92
|
+
KEY `idx_group_assignment_history_group_created` (`group_jid`,`created_at`),
|
|
93
|
+
KEY `idx_group_assignment_history_new_session_created` (`new_session_id`,`created_at`),
|
|
94
|
+
KEY `idx_group_assignment_history_prev_session_created` (`previous_session_id`,`created_at`)
|
|
95
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
96
|
+
|
|
97
|
+
CALL __ensure_column('messages', 'session_id', 'ALTER TABLE messages ADD COLUMN session_id VARCHAR(64) NOT NULL DEFAULT ''default'' AFTER message_id');
|
|
98
|
+
CALL __ensure_column('baileys_event_journal', 'session_id', 'ALTER TABLE baileys_event_journal ADD COLUMN session_id VARCHAR(64) NOT NULL DEFAULT ''default'' AFTER id');
|
|
99
|
+
CALL __ensure_column('message_analysis_event', 'session_id', 'ALTER TABLE message_analysis_event ADD COLUMN session_id VARCHAR(64) NOT NULL DEFAULT ''default'' AFTER id');
|
|
100
|
+
|
|
101
|
+
CALL __ensure_index('messages', 'idx_messages_session_message_id', 'CREATE INDEX idx_messages_session_message_id ON messages (session_id, message_id)');
|
|
102
|
+
CALL __ensure_index('messages', 'idx_messages_session_chat_timestamp', 'CREATE INDEX idx_messages_session_chat_timestamp ON messages (session_id, chat_id, timestamp)');
|
|
103
|
+
CALL __ensure_index('messages', 'idx_messages_session_sender_timestamp', 'CREATE INDEX idx_messages_session_sender_timestamp ON messages (session_id, sender_id, timestamp)');
|
|
104
|
+
CALL __ensure_index('messages', 'idx_messages_session_canonical_sender_timestamp', 'CREATE INDEX idx_messages_session_canonical_sender_timestamp ON messages (session_id, canonical_sender_id, timestamp)');
|
|
105
|
+
|
|
106
|
+
CALL __ensure_index('baileys_event_journal', 'idx_baileys_event_session_created', 'CREATE INDEX idx_baileys_event_session_created ON baileys_event_journal (session_id, created_at)');
|
|
107
|
+
CALL __ensure_index('baileys_event_journal', 'idx_baileys_event_session_name_created', 'CREATE INDEX idx_baileys_event_session_name_created ON baileys_event_journal (session_id, event_name, created_at)');
|
|
108
|
+
CALL __ensure_index('baileys_event_journal', 'idx_baileys_event_session_chat_created', 'CREATE INDEX idx_baileys_event_session_chat_created ON baileys_event_journal (session_id, chat_id, created_at)');
|
|
109
|
+
CALL __ensure_index('baileys_event_journal', 'idx_baileys_event_session_message_created', 'CREATE INDEX idx_baileys_event_session_message_created ON baileys_event_journal (session_id, message_id, created_at)');
|
|
110
|
+
|
|
111
|
+
CALL __ensure_index('message_analysis_event', 'idx_message_analysis_session_created', 'CREATE INDEX idx_message_analysis_session_created ON message_analysis_event (session_id, created_at)');
|
|
112
|
+
CALL __ensure_index('message_analysis_event', 'idx_message_analysis_session_chat_created', 'CREATE INDEX idx_message_analysis_session_chat_created ON message_analysis_event (session_id, chat_id, created_at)');
|
|
113
|
+
CALL __ensure_index('message_analysis_event', 'idx_message_analysis_session_sender_created', 'CREATE INDEX idx_message_analysis_session_sender_created ON message_analysis_event (session_id, sender_id, created_at)');
|
|
114
|
+
CALL __ensure_index('message_analysis_event', 'idx_message_analysis_session_command_created', 'CREATE INDEX idx_message_analysis_session_command_created ON message_analysis_event (session_id, command_name, created_at)');
|
|
115
|
+
|
|
116
|
+
INSERT INTO schema_change_log (migration_key, phase, status, notes)
|
|
117
|
+
VALUES (@migration_key, 'D+38', 'applied', 'multi-session foundation tables and session-scoped indexes')
|
|
118
|
+
ON DUPLICATE KEY UPDATE
|
|
119
|
+
phase = VALUES(phase),
|
|
120
|
+
status = 'applied',
|
|
121
|
+
notes = VALUES(notes),
|
|
122
|
+
updated_at = CURRENT_TIMESTAMP;
|
|
123
|
+
|
|
124
|
+
DROP PROCEDURE IF EXISTS __ensure_column;
|
|
125
|
+
DROP PROCEDURE IF EXISTS __ensure_index;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
-- D+39 rollback
|
|
2
|
+
|
|
3
|
+
SET @migration_key := '20260414_d39_multi_session_cutover';
|
|
4
|
+
|
|
5
|
+
DROP PROCEDURE IF EXISTS __drop_index_if_exists;
|
|
6
|
+
DROP PROCEDURE IF EXISTS __ensure_unique_index;
|
|
7
|
+
DROP PROCEDURE IF EXISTS __ensure_index;
|
|
8
|
+
DROP PROCEDURE IF EXISTS __assert_no_global_message_duplicates;
|
|
9
|
+
|
|
10
|
+
DELIMITER $$
|
|
11
|
+
CREATE PROCEDURE __drop_index_if_exists(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64))
|
|
12
|
+
BEGIN
|
|
13
|
+
IF 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 = CONCAT('ALTER TABLE `', p_table_name, '` DROP INDEX `', p_index_name, '`');
|
|
21
|
+
PREPARE stmt FROM @ddl;
|
|
22
|
+
EXECUTE stmt;
|
|
23
|
+
DEALLOCATE PREPARE stmt;
|
|
24
|
+
END IF;
|
|
25
|
+
END$$
|
|
26
|
+
|
|
27
|
+
CREATE PROCEDURE __ensure_unique_index(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64), IN p_ddl TEXT)
|
|
28
|
+
BEGIN
|
|
29
|
+
IF NOT 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
|
+
AND non_unique = 0
|
|
36
|
+
) THEN
|
|
37
|
+
SET @ddl = p_ddl;
|
|
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 __assert_no_global_message_duplicates()
|
|
61
|
+
BEGIN
|
|
62
|
+
DECLARE v_dup_exists INT DEFAULT 0;
|
|
63
|
+
|
|
64
|
+
SELECT
|
|
65
|
+
CASE
|
|
66
|
+
WHEN EXISTS (
|
|
67
|
+
SELECT 1
|
|
68
|
+
FROM messages
|
|
69
|
+
GROUP BY message_id
|
|
70
|
+
HAVING COUNT(*) > 1
|
|
71
|
+
LIMIT 1
|
|
72
|
+
) THEN 1
|
|
73
|
+
ELSE 0
|
|
74
|
+
END
|
|
75
|
+
INTO v_dup_exists;
|
|
76
|
+
|
|
77
|
+
IF v_dup_exists = 1 THEN
|
|
78
|
+
SIGNAL SQLSTATE '45000'
|
|
79
|
+
SET MESSAGE_TEXT = 'Rollback D+39 bloqueado: existem message_id duplicados entre sessoes.';
|
|
80
|
+
END IF;
|
|
81
|
+
END$$
|
|
82
|
+
DELIMITER ;
|
|
83
|
+
|
|
84
|
+
CALL __assert_no_global_message_duplicates();
|
|
85
|
+
|
|
86
|
+
CALL __drop_index_if_exists('messages', 'uq_messages_session_message_id');
|
|
87
|
+
|
|
88
|
+
-- Retorna ao estado da fase D+38:
|
|
89
|
+
-- 1) índice único global em message_id
|
|
90
|
+
-- 2) índice não-único de apoio em (session_id, message_id)
|
|
91
|
+
CALL __ensure_unique_index('messages', 'message_id', 'CREATE UNIQUE INDEX message_id ON messages (message_id)');
|
|
92
|
+
CALL __ensure_index('messages', 'idx_messages_session_message_id', 'CREATE INDEX idx_messages_session_message_id ON messages (session_id, message_id)');
|
|
93
|
+
|
|
94
|
+
UPDATE schema_change_log
|
|
95
|
+
SET status = 'rolled_back',
|
|
96
|
+
notes = 'D+39 rollback executed',
|
|
97
|
+
updated_at = CURRENT_TIMESTAMP
|
|
98
|
+
WHERE migration_key = @migration_key;
|
|
99
|
+
|
|
100
|
+
DROP PROCEDURE IF EXISTS __drop_index_if_exists;
|
|
101
|
+
DROP PROCEDURE IF EXISTS __ensure_unique_index;
|
|
102
|
+
DROP PROCEDURE IF EXISTS __ensure_index;
|
|
103
|
+
DROP PROCEDURE IF EXISTS __assert_no_global_message_duplicates;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
-- D+39 (2026-04-14) - Multi-session message uniqueness cutover
|
|
2
|
+
-- Scope: replace global unique(message_id) with unique(session_id, message_id)
|
|
3
|
+
|
|
4
|
+
SET @migration_key := '20260414_d39_multi_session_cutover';
|
|
5
|
+
|
|
6
|
+
DROP PROCEDURE IF EXISTS __assert_column_exists;
|
|
7
|
+
DROP PROCEDURE IF EXISTS __drop_index_if_exists;
|
|
8
|
+
DROP PROCEDURE IF EXISTS __ensure_unique_index;
|
|
9
|
+
|
|
10
|
+
DELIMITER $$
|
|
11
|
+
CREATE PROCEDURE __assert_column_exists(IN p_table_name VARCHAR(64), IN p_column_name VARCHAR(64))
|
|
12
|
+
BEGIN
|
|
13
|
+
IF NOT EXISTS (
|
|
14
|
+
SELECT 1
|
|
15
|
+
FROM information_schema.columns
|
|
16
|
+
WHERE table_schema = DATABASE()
|
|
17
|
+
AND table_name = p_table_name
|
|
18
|
+
AND column_name = p_column_name
|
|
19
|
+
) THEN
|
|
20
|
+
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'D+39 requer coluna session_id previamente criada (execute D+38 antes).';
|
|
21
|
+
END IF;
|
|
22
|
+
END$$
|
|
23
|
+
|
|
24
|
+
CREATE PROCEDURE __drop_index_if_exists(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64))
|
|
25
|
+
BEGIN
|
|
26
|
+
IF EXISTS (
|
|
27
|
+
SELECT 1
|
|
28
|
+
FROM information_schema.statistics
|
|
29
|
+
WHERE table_schema = DATABASE()
|
|
30
|
+
AND table_name = p_table_name
|
|
31
|
+
AND index_name = p_index_name
|
|
32
|
+
) THEN
|
|
33
|
+
SET @ddl = CONCAT('ALTER TABLE `', p_table_name, '` DROP INDEX `', p_index_name, '`');
|
|
34
|
+
PREPARE stmt FROM @ddl;
|
|
35
|
+
EXECUTE stmt;
|
|
36
|
+
DEALLOCATE PREPARE stmt;
|
|
37
|
+
END IF;
|
|
38
|
+
END$$
|
|
39
|
+
|
|
40
|
+
CREATE PROCEDURE __ensure_unique_index(IN p_table_name VARCHAR(64), IN p_index_name VARCHAR(64), IN p_ddl TEXT)
|
|
41
|
+
BEGIN
|
|
42
|
+
IF NOT EXISTS (
|
|
43
|
+
SELECT 1
|
|
44
|
+
FROM information_schema.statistics
|
|
45
|
+
WHERE table_schema = DATABASE()
|
|
46
|
+
AND table_name = p_table_name
|
|
47
|
+
AND index_name = p_index_name
|
|
48
|
+
AND non_unique = 0
|
|
49
|
+
) THEN
|
|
50
|
+
SET @ddl = p_ddl;
|
|
51
|
+
PREPARE stmt FROM @ddl;
|
|
52
|
+
EXECUTE stmt;
|
|
53
|
+
DEALLOCATE PREPARE stmt;
|
|
54
|
+
END IF;
|
|
55
|
+
END$$
|
|
56
|
+
DELIMITER ;
|
|
57
|
+
|
|
58
|
+
CALL __assert_column_exists('messages', 'session_id');
|
|
59
|
+
|
|
60
|
+
-- Remove índice legado global e índice não-único redundante da fase D+38.
|
|
61
|
+
CALL __drop_index_if_exists('messages', 'message_id');
|
|
62
|
+
CALL __drop_index_if_exists('messages', 'uq_messages_message_id');
|
|
63
|
+
CALL __drop_index_if_exists('messages', 'uniq_messages_message_id');
|
|
64
|
+
CALL __drop_index_if_exists('messages', 'idx_messages_session_message_id');
|
|
65
|
+
|
|
66
|
+
-- Novo contrato de unicidade por sessão.
|
|
67
|
+
CALL __ensure_unique_index(
|
|
68
|
+
'messages',
|
|
69
|
+
'uq_messages_session_message_id',
|
|
70
|
+
'CREATE UNIQUE INDEX uq_messages_session_message_id ON messages (session_id, message_id)'
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
INSERT INTO schema_change_log (migration_key, phase, status, notes)
|
|
74
|
+
VALUES (@migration_key, 'D+39', 'applied', 'messages uniqueness changed to (session_id, message_id)')
|
|
75
|
+
ON DUPLICATE KEY UPDATE
|
|
76
|
+
phase = VALUES(phase),
|
|
77
|
+
status = 'applied',
|
|
78
|
+
notes = VALUES(notes),
|
|
79
|
+
updated_at = CURRENT_TIMESTAMP;
|
|
80
|
+
|
|
81
|
+
DROP PROCEDURE IF EXISTS __assert_column_exists;
|
|
82
|
+
DROP PROCEDURE IF EXISTS __drop_index_if_exists;
|
|
83
|
+
DROP PROCEDURE IF EXISTS __ensure_unique_index;
|