@kaikybrofc/omnizap-system 2.1.8

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 (166) hide show
  1. package/.env.example +534 -0
  2. package/LICENSE +21 -0
  3. package/README.md +431 -0
  4. package/RELEASE-v2.1.2.md +83 -0
  5. package/app/config/adminIdentity.js +87 -0
  6. package/app/config/baileysConfig.js +693 -0
  7. package/app/config/groupUtils.js +388 -0
  8. package/app/connection/socketController.js +992 -0
  9. package/app/controllers/messageController.js +354 -0
  10. package/app/modules/adminModule/groupCommandHandlers.js +1294 -0
  11. package/app/modules/adminModule/groupEventHandlers.js +355 -0
  12. package/app/modules/aiModule/catCommand.js +1006 -0
  13. package/app/modules/broadcastModule/noticeCommand.js +416 -0
  14. package/app/modules/gameModule/diceCommand.js +67 -0
  15. package/app/modules/menuModule/common.js +311 -0
  16. package/app/modules/menuModule/menus.js +59 -0
  17. package/app/modules/playModule/playCommand.js +1615 -0
  18. package/app/modules/quoteModule/quoteCommand.js +851 -0
  19. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +786 -0
  20. package/app/modules/rpgPokemonModule/rpgBattleService.js +2082 -0
  21. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +760 -0
  22. package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
  23. package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +172 -0
  24. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
  25. package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
  26. package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
  27. package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
  28. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1859 -0
  29. package/app/modules/rpgPokemonModule/rpgPokemonService.js +6738 -0
  30. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
  31. package/app/modules/statsModule/globalRankingCommand.js +65 -0
  32. package/app/modules/statsModule/noMessageCommand.js +288 -0
  33. package/app/modules/statsModule/rankingCommand.js +60 -0
  34. package/app/modules/statsModule/rankingCommon.js +889 -0
  35. package/app/modules/stickerModule/addStickerMetadata.js +239 -0
  36. package/app/modules/stickerModule/convertToWebp.js +390 -0
  37. package/app/modules/stickerModule/stickerCommand.js +454 -0
  38. package/app/modules/stickerModule/stickerConvertCommand.js +156 -0
  39. package/app/modules/stickerModule/stickerTextCommand.js +657 -0
  40. package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
  41. package/app/modules/stickerPackModule/autoPackCollectorService.js +284 -0
  42. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +466 -0
  43. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +88 -0
  44. package/app/modules/stickerPackModule/semanticThemeClusterService.js +571 -0
  45. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +449 -0
  46. package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
  47. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +180 -0
  48. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +4078 -0
  49. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +598 -0
  50. package/app/modules/stickerPackModule/stickerClassificationService.js +588 -0
  51. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +102 -0
  52. package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +7506 -0
  53. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1095 -0
  54. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +108 -0
  55. package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
  56. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +110 -0
  57. package/app/modules/stickerPackModule/stickerPackItemRepository.js +440 -0
  58. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +337 -0
  59. package/app/modules/stickerPackModule/stickerPackMessageService.js +296 -0
  60. package/app/modules/stickerPackModule/stickerPackRepository.js +442 -0
  61. package/app/modules/stickerPackModule/stickerPackService.js +788 -0
  62. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +51 -0
  63. package/app/modules/stickerPackModule/stickerPackUtils.js +97 -0
  64. package/app/modules/stickerPackModule/stickerStorageService.js +507 -0
  65. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +233 -0
  66. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +205 -0
  67. package/app/modules/systemMetricsModule/pingCommand.js +421 -0
  68. package/app/modules/tiktokModule/tiktokCommand.js +798 -0
  69. package/app/modules/userModule/userCommand.js +1217 -0
  70. package/app/modules/waifuPicsModule/waifuPicsCommand.js +177 -0
  71. package/app/observability/metrics.js +734 -0
  72. package/app/services/captchaService.js +492 -0
  73. package/app/services/dbWriteQueue.js +572 -0
  74. package/app/services/groupMetadataService.js +279 -0
  75. package/app/services/lidMapService.js +663 -0
  76. package/app/services/messagePersistenceService.js +56 -0
  77. package/app/services/newsBroadcastService.js +351 -0
  78. package/app/services/pokeApiService.js +398 -0
  79. package/app/services/queueUtils.js +57 -0
  80. package/app/services/socketState.js +7 -0
  81. package/app/store/aiPromptStore.js +38 -0
  82. package/app/store/groupConfigStore.js +58 -0
  83. package/app/store/premiumUserStore.js +36 -0
  84. package/app/utils/antiLink/antiLinkModule.js +804 -0
  85. package/app/utils/http/getImageBufferModule.js +18 -0
  86. package/app/utils/json/jsonSanitizer.js +113 -0
  87. package/app/utils/json/jsonSanitizer.test.js +40 -0
  88. package/app/utils/logger/loggerModule.js +262 -0
  89. package/app/utils/systemMetrics/systemMetricsModule.js +91 -0
  90. package/database/index.js +2052 -0
  91. package/database/init.js +516 -0
  92. package/database/migrations/20260203_0001_sticker_packs.sql +54 -0
  93. package/database/migrations/20260210_0003_rpg_pokemon.sql +58 -0
  94. package/database/migrations/20260210_0004_rpg_shiny_biome.sql +9 -0
  95. package/database/migrations/20260210_0005_rpg_missions.sql +14 -0
  96. package/database/migrations/20260210_0006_rpg_world_pokedex_traits.sql +27 -0
  97. package/database/migrations/20260210_0007_rpg_raid_pvp.sql +56 -0
  98. package/database/migrations/20260210_0008_rpg_social_system.sql +195 -0
  99. package/database/migrations/20260211_0009_rpg_social_xp.sql +36 -0
  100. package/database/migrations/20260222_0010_remove_message_xp.sql +2 -0
  101. package/database/migrations/20260226_0011_sticker_asset_classification.sql +17 -0
  102. package/database/migrations/20260226_0012_sticker_pack_engagement.sql +16 -0
  103. package/database/migrations/20260226_0013_sticker_marketplace_intelligence.sql +19 -0
  104. package/database/migrations/20260226_0014_sticker_pack_publish_flow.sql +30 -0
  105. package/database/migrations/20260226_0014_sticker_worker_queues.sql +42 -0
  106. package/database/migrations/20260226_0015_sticker_auto_pack_curation_integrity.sql +18 -0
  107. package/database/migrations/20260226_0016_sticker_web_google_auth_persistence.sql +34 -0
  108. package/database/migrations/20260226_0017_sticker_web_admin_ban.sql +22 -0
  109. package/database/migrations/20260226_0018_sticker_web_admin_moderator.sql +18 -0
  110. package/database/migrations/20260227_0019_sticker_classification_v2_signals.sql +12 -0
  111. package/database/migrations/20260227_0020_semantic_theme_clusters.sql +35 -0
  112. package/docker-compose.yml +103 -0
  113. package/ecosystem.prod.config.cjs +35 -0
  114. package/eslint.config.js +61 -0
  115. package/index.js +437 -0
  116. package/ml/clip_classifier/Dockerfile +16 -0
  117. package/ml/clip_classifier/README.md +120 -0
  118. package/ml/clip_classifier/adaptive_scoring.py +40 -0
  119. package/ml/clip_classifier/classifier.py +654 -0
  120. package/ml/clip_classifier/embedding_store.py +481 -0
  121. package/ml/clip_classifier/env_loader.py +15 -0
  122. package/ml/clip_classifier/llm_label_expander.py +144 -0
  123. package/ml/clip_classifier/main.py +213 -0
  124. package/ml/clip_classifier/requirements.txt +10 -0
  125. package/ml/clip_classifier/similarity_engine.py +74 -0
  126. package/observability/alert-rules.yml +60 -0
  127. package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
  128. package/observability/grafana/dashboards/omnizap-overview.json +170 -0
  129. package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
  130. package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
  131. package/observability/loki-config.yml +38 -0
  132. package/observability/mysql-exporter.cnf +5 -0
  133. package/observability/mysql-setup.sql +46 -0
  134. package/observability/prometheus.yml +32 -0
  135. package/observability/promtail-config.yml +84 -0
  136. package/package.json +109 -0
  137. package/public/api-docs/index.html +144 -0
  138. package/public/css/github-project-panel.css +297 -0
  139. package/public/css/stickers-admin.css +1272 -0
  140. package/public/css/styles.css +671 -0
  141. package/public/index.html +1311 -0
  142. package/public/js/apps/apiDocsApp.js +310 -0
  143. package/public/js/apps/createPackApp.js +2069 -0
  144. package/public/js/apps/homeApp.js +396 -0
  145. package/public/js/apps/stickersAdminApp.js +1744 -0
  146. package/public/js/apps/stickersApp.js +4830 -0
  147. package/public/js/catalog.js +1019 -0
  148. package/public/js/github-panel/components/CommitList.js +34 -0
  149. package/public/js/github-panel/components/ErrorState.js +16 -0
  150. package/public/js/github-panel/components/GithubProjectPanel.js +106 -0
  151. package/public/js/github-panel/components/ReleaseList.js +38 -0
  152. package/public/js/github-panel/components/SkeletonPanel.js +22 -0
  153. package/public/js/github-panel/components/StatCard.js +15 -0
  154. package/public/js/github-panel/index.js +15 -0
  155. package/public/js/github-panel/useGithubRepoData.js +154 -0
  156. package/public/js/github-panel/vendor/react.js +11 -0
  157. package/public/js/runtime/react-runtime.js +19 -0
  158. package/public/licenca/index.html +106 -0
  159. package/public/stickers/admin/index.html +23 -0
  160. package/public/stickers/create/index.html +47 -0
  161. package/public/stickers/index.html +48 -0
  162. package/public/termos-de-uso/index.html +125 -0
  163. package/scripts/cache-bust.mjs +107 -0
  164. package/scripts/deploy.sh +458 -0
  165. package/scripts/github-deploy-notify.mjs +174 -0
  166. package/scripts/release.sh +129 -0
@@ -0,0 +1,516 @@
1
+ import mysql from 'mysql2/promise';
2
+ import logger from '../app/utils/logger/loggerModule.js';
3
+ import { dbConfig, TABLES } from './index.js';
4
+ import { promises as fs } from 'node:fs';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ /**
9
+ * Nome do banco de dados que será criado ou validado.
10
+ * Já vem resolvido com sufixo _dev ou _prod via dbConfig.
11
+ * @type {string}
12
+ */
13
+ const dbToCreate = dbConfig.database;
14
+
15
+ /**
16
+ * SQL para criar o banco de dados caso ainda não exista.
17
+ * Usa:
18
+ * - utf8mb4: suporte total a emojis e caracteres unicode
19
+ * - utf8mb4_unicode_ci: collation consistente para buscas
20
+ */
21
+ const createDatabaseSQL = `
22
+ CREATE DATABASE IF NOT EXISTS \`${dbToCreate}\`
23
+ DEFAULT CHARACTER SET utf8mb4
24
+ DEFAULT COLLATE utf8mb4_unicode_ci;
25
+ `;
26
+
27
+ /**
28
+ * Tabela MESSAGES
29
+ * Armazena todas as mensagens processadas pelo sistema.
30
+ *
31
+ * Campos importantes:
32
+ * - message_id: ID global do WhatsApp (único)
33
+ * - chat_id: conversa (grupo ou privado)
34
+ * - sender_id: remetente da mensagem
35
+ * - content: texto extraído
36
+ * - raw_message: JSON original da Baileys
37
+ * - timestamp: timestamp da mensagem no WhatsApp
38
+ *
39
+ * Índices:
40
+ * - chat_id + timestamp → leitura de histórico por conversa
41
+ * - sender_id → estatísticas por usuário
42
+ * - timestamp → ordenação global
43
+ */
44
+ const createMessagesTableSQL = `
45
+ CREATE TABLE IF NOT EXISTS ${TABLES.MESSAGES} (
46
+ id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
47
+ message_id VARCHAR(255) UNIQUE NOT NULL,
48
+ chat_id VARCHAR(255) NOT NULL,
49
+ sender_id VARCHAR(255),
50
+ content TEXT,
51
+ raw_message JSON,
52
+ timestamp TIMESTAMP NULL,
53
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
54
+ INDEX idx_chat_timestamp (chat_id, timestamp),
55
+ INDEX idx_sender (sender_id),
56
+ INDEX idx_timestamp (timestamp)
57
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
58
+ `;
59
+
60
+ /**
61
+ * Tabela CHATS
62
+ * Representa uma conversa (grupo ou privado).
63
+ *
64
+ * - id → JID do WhatsApp
65
+ * - name → nome do grupo ou contato
66
+ * - raw_chat → JSON bruto retornado pela Baileys
67
+ */
68
+ const createChatsTableSQL = `
69
+ CREATE TABLE IF NOT EXISTS ${TABLES.CHATS} (
70
+ id VARCHAR(255) PRIMARY KEY NOT NULL,
71
+ name VARCHAR(255),
72
+ raw_chat JSON,
73
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
74
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
75
+ `;
76
+
77
+ /**
78
+ * Tabela GROUPS_METADATA
79
+ * Armazena informações normalizadas de grupos.
80
+ *
81
+ * Usada para:
82
+ * - admin commands
83
+ * - estatísticas
84
+ * - sincronização de participantes
85
+ *
86
+ * participants é JSON para armazenar:
87
+ * [{ jid, lid, admin }, ...]
88
+ */
89
+ const createGroupsMetadataTableSQL = `
90
+ CREATE TABLE IF NOT EXISTS ${TABLES.GROUPS_METADATA} (
91
+ id VARCHAR(255) PRIMARY KEY NOT NULL,
92
+ subject VARCHAR(255),
93
+ description TEXT,
94
+ owner_jid VARCHAR(255),
95
+ creation BIGINT,
96
+ participants JSON,
97
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
98
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
99
+ `;
100
+
101
+ /**
102
+ * Tabela GROUP_CONFIGS
103
+ * Configurações específicas por grupo.
104
+ *
105
+ * Exemplo:
106
+ * - prefixo de comandos
107
+ * - anti-link
108
+ * - modo restrito
109
+ */
110
+ const createGroupConfigsTableSQL = `
111
+ CREATE TABLE IF NOT EXISTS ${TABLES.GROUP_CONFIGS} (
112
+ id VARCHAR(255) PRIMARY KEY NOT NULL,
113
+ config JSON,
114
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
115
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
116
+ `;
117
+
118
+ /**
119
+ * Tabela LID_MAP
120
+ * Mapeia IDs temporários do WhatsApp (LID) para JIDs reais.
121
+ *
122
+ * Isso resolve problemas de:
123
+ * - mensagens vindo com "xxx@lid"
124
+ * - alternância entre lid e s.whatsapp.net
125
+ *
126
+ * Campos:
127
+ * - lid → chave primária
128
+ * - jid → jid real associado
129
+ * - first_seen → quando apareceu
130
+ * - last_seen → última vez visto
131
+ * - source → origem do dado (message, group, etc)
132
+ */
133
+ const createLidMapTableSQL = `
134
+ CREATE TABLE IF NOT EXISTS ${TABLES.LID_MAP} (
135
+ lid VARCHAR(64) PRIMARY KEY,
136
+ jid VARCHAR(64) NULL,
137
+ first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
138
+ last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
139
+ source VARCHAR(32),
140
+ INDEX idx_lid_map_jid (jid),
141
+ INDEX idx_lid_map_last_seen (last_seen)
142
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
143
+ `;
144
+
145
+ /**
146
+ * Tabela STICKER_ASSET
147
+ * Armazena os arquivos de figurinha persistidos no storage local.
148
+ */
149
+ const createStickerAssetTableSQL = `
150
+ CREATE TABLE IF NOT EXISTS ${TABLES.STICKER_ASSET} (
151
+ id CHAR(36) PRIMARY KEY,
152
+ owner_jid VARCHAR(255) NOT NULL,
153
+ sha256 CHAR(64) NOT NULL,
154
+ mimetype VARCHAR(64) NOT NULL,
155
+ is_animated TINYINT(1) NOT NULL DEFAULT 0,
156
+ width INT UNSIGNED NULL,
157
+ height INT UNSIGNED NULL,
158
+ size_bytes INT UNSIGNED NOT NULL,
159
+ storage_path VARCHAR(1024) NOT NULL,
160
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
161
+ UNIQUE KEY uq_sticker_asset_sha256 (sha256),
162
+ INDEX idx_sticker_asset_owner_created (owner_jid, created_at)
163
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
164
+ `;
165
+
166
+ /**
167
+ * Tabela STICKER_PACK
168
+ * Metadados do pack (dono, nome, publisher, capa, visibilidade).
169
+ */
170
+ const createStickerPackTableSQL = `
171
+ CREATE TABLE IF NOT EXISTS ${TABLES.STICKER_PACK} (
172
+ id CHAR(36) PRIMARY KEY,
173
+ owner_jid VARCHAR(255) NOT NULL,
174
+ name VARCHAR(120) NOT NULL,
175
+ publisher VARCHAR(120) NOT NULL,
176
+ description TEXT NULL,
177
+ pack_key VARCHAR(160) NOT NULL,
178
+ cover_sticker_id CHAR(36) NULL,
179
+ visibility ENUM('private', 'public', 'unlisted') NOT NULL DEFAULT 'private',
180
+ status ENUM('draft', 'uploading', 'processing', 'published', 'failed') NOT NULL DEFAULT 'published',
181
+ version INT UNSIGNED NOT NULL DEFAULT 1,
182
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
183
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
184
+ deleted_at TIMESTAMP NULL DEFAULT NULL,
185
+ UNIQUE KEY uq_sticker_pack_pack_key (pack_key),
186
+ INDEX idx_sticker_pack_owner_deleted (owner_jid, deleted_at),
187
+ INDEX idx_sticker_pack_owner_updated (owner_jid, updated_at),
188
+ CONSTRAINT fk_sticker_pack_cover
189
+ FOREIGN KEY (cover_sticker_id) REFERENCES ${TABLES.STICKER_ASSET}(id)
190
+ ON DELETE SET NULL ON UPDATE CASCADE
191
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
192
+ `;
193
+
194
+ /**
195
+ * Tabela STICKER_PACK_ITEM
196
+ * Relação N:N ordenada entre pack e figurinhas.
197
+ */
198
+ const createStickerPackItemTableSQL = `
199
+ CREATE TABLE IF NOT EXISTS ${TABLES.STICKER_PACK_ITEM} (
200
+ id CHAR(36) PRIMARY KEY,
201
+ pack_id CHAR(36) NOT NULL,
202
+ sticker_id CHAR(36) NOT NULL,
203
+ position INT UNSIGNED NOT NULL,
204
+ emojis JSON NULL,
205
+ accessibility_label VARCHAR(255) NULL,
206
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
207
+ UNIQUE KEY uq_sticker_pack_item_pack_sticker (pack_id, sticker_id),
208
+ UNIQUE KEY uq_sticker_pack_item_pack_position (pack_id, position),
209
+ INDEX idx_sticker_pack_item_pack_position (pack_id, position),
210
+ CONSTRAINT fk_sticker_pack_item_pack
211
+ FOREIGN KEY (pack_id) REFERENCES ${TABLES.STICKER_PACK}(id)
212
+ ON DELETE CASCADE ON UPDATE CASCADE,
213
+ CONSTRAINT fk_sticker_pack_item_asset
214
+ FOREIGN KEY (sticker_id) REFERENCES ${TABLES.STICKER_ASSET}(id)
215
+ ON DELETE RESTRICT ON UPDATE CASCADE
216
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
217
+ `;
218
+
219
+ /**
220
+ * Tabela STICKER_PACK_WEB_UPLOAD
221
+ * Controle de upload idempotente do fluxo web (upload_id + hash por pack).
222
+ */
223
+ const createStickerPackWebUploadTableSQL = `
224
+ CREATE TABLE IF NOT EXISTS ${TABLES.STICKER_PACK_WEB_UPLOAD} (
225
+ id CHAR(36) PRIMARY KEY,
226
+ pack_id CHAR(36) NOT NULL,
227
+ upload_id VARCHAR(120) NOT NULL,
228
+ sticker_hash CHAR(64) NOT NULL,
229
+ source_mimetype VARCHAR(64) NULL,
230
+ upload_status ENUM('pending', 'processing', 'done', 'failed') NOT NULL DEFAULT 'pending',
231
+ sticker_id CHAR(36) NULL,
232
+ error_code VARCHAR(64) NULL,
233
+ error_message VARCHAR(255) NULL,
234
+ attempt_count INT UNSIGNED NOT NULL DEFAULT 0,
235
+ last_attempt_at TIMESTAMP NULL DEFAULT NULL,
236
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
237
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
238
+ UNIQUE KEY uq_sticker_pack_web_upload_pack_upload_id (pack_id, upload_id),
239
+ UNIQUE KEY uq_sticker_pack_web_upload_pack_hash (pack_id, sticker_hash),
240
+ INDEX idx_sticker_pack_web_upload_pack_status (pack_id, upload_status),
241
+ INDEX idx_sticker_pack_web_upload_pack_updated (pack_id, updated_at),
242
+ CONSTRAINT fk_sticker_pack_web_upload_pack
243
+ FOREIGN KEY (pack_id) REFERENCES ${TABLES.STICKER_PACK}(id)
244
+ ON DELETE CASCADE ON UPDATE CASCADE,
245
+ CONSTRAINT fk_sticker_pack_web_upload_sticker
246
+ FOREIGN KEY (sticker_id) REFERENCES ${TABLES.STICKER_ASSET}(id)
247
+ ON DELETE SET NULL ON UPDATE CASCADE
248
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
249
+ `;
250
+
251
+ /**
252
+ * Tabela STICKER_ASSET_CLASSIFICATION
253
+ * Classificação de conteúdo por asset (CLIP), com score completo.
254
+ */
255
+ const createStickerAssetClassificationTableSQL = `
256
+ CREATE TABLE IF NOT EXISTS ${TABLES.STICKER_ASSET_CLASSIFICATION} (
257
+ asset_id CHAR(36) PRIMARY KEY,
258
+ provider VARCHAR(64) NOT NULL DEFAULT 'clip',
259
+ model_name VARCHAR(120) NULL,
260
+ classification_version VARCHAR(32) NOT NULL DEFAULT 'v1',
261
+ category VARCHAR(120) NULL,
262
+ confidence DECIMAL(6,5) NULL,
263
+ nsfw_score DECIMAL(6,5) NULL,
264
+ is_nsfw TINYINT(1) NOT NULL DEFAULT 0,
265
+ all_scores JSON NULL,
266
+ classified_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
267
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
268
+ CONSTRAINT fk_sticker_asset_classification_asset
269
+ FOREIGN KEY (asset_id) REFERENCES ${TABLES.STICKER_ASSET}(id)
270
+ ON DELETE CASCADE ON UPDATE CASCADE,
271
+ INDEX idx_sticker_asset_classification_category (category),
272
+ INDEX idx_sticker_asset_classification_nsfw (is_nsfw)
273
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
274
+ `;
275
+
276
+ /**
277
+ * Tabela STICKER_PACK_ENGAGEMENT
278
+ * Armazena métricas reais de interação no catálogo web (cliques/likes/dislikes).
279
+ */
280
+ const createStickerPackEngagementTableSQL = `
281
+ CREATE TABLE IF NOT EXISTS ${TABLES.STICKER_PACK_ENGAGEMENT} (
282
+ pack_id CHAR(36) PRIMARY KEY,
283
+ open_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
284
+ like_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
285
+ dislike_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
286
+ last_opened_at TIMESTAMP NULL DEFAULT NULL,
287
+ last_interacted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
288
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
289
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
290
+ CONSTRAINT fk_sticker_pack_engagement_pack
291
+ FOREIGN KEY (pack_id) REFERENCES ${TABLES.STICKER_PACK}(id)
292
+ ON DELETE CASCADE ON UPDATE CASCADE,
293
+ INDEX idx_sticker_pack_engagement_updated (updated_at),
294
+ INDEX idx_sticker_pack_engagement_like (like_count),
295
+ INDEX idx_sticker_pack_engagement_open (open_count)
296
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
297
+ `;
298
+
299
+ /**
300
+ * Tabela STICKER_PACK_INTERACTION_EVENT
301
+ * Histórico de interações para tendência, recomendação e perfis de criador.
302
+ */
303
+ const createStickerPackInteractionEventTableSQL = `
304
+ CREATE TABLE IF NOT EXISTS ${TABLES.STICKER_PACK_INTERACTION_EVENT} (
305
+ id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
306
+ pack_id CHAR(36) NOT NULL,
307
+ interaction ENUM('open', 'like', 'dislike') NOT NULL,
308
+ actor_key VARCHAR(120) NULL,
309
+ session_key VARCHAR(120) NULL,
310
+ source VARCHAR(32) NULL,
311
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
312
+ CONSTRAINT fk_sticker_pack_interaction_pack
313
+ FOREIGN KEY (pack_id) REFERENCES ${TABLES.STICKER_PACK}(id)
314
+ ON DELETE CASCADE ON UPDATE CASCADE,
315
+ INDEX idx_sticker_pack_interaction_pack_created (pack_id, created_at),
316
+ INDEX idx_sticker_pack_interaction_actor_created (actor_key, created_at),
317
+ INDEX idx_sticker_pack_interaction_session_created (session_key, created_at),
318
+ INDEX idx_sticker_pack_interaction_type_created (interaction, created_at)
319
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
320
+ `;
321
+
322
+ const createSchemaMigrationsTableSQL = `
323
+ CREATE TABLE IF NOT EXISTS schema_migrations (
324
+ name VARCHAR(255) PRIMARY KEY,
325
+ executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
326
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
327
+ `;
328
+
329
+ const __filename = fileURLToPath(import.meta.url);
330
+ const __dirname = path.dirname(__filename);
331
+
332
+ const MIGRATIONS_DIR = path.join(__dirname, 'migrations');
333
+
334
+ /**
335
+ * Divide um arquivo SQL em statements individuais.
336
+ * Suporta aspas simples e duplas para não quebrar strings.
337
+ *
338
+ * @param {string} sql
339
+ * @returns {string[]}
340
+ */
341
+ function splitSqlStatements(sql) {
342
+ const statements = [];
343
+ let current = '';
344
+ let inSingleQuote = false;
345
+ let inDoubleQuote = false;
346
+ let escaped = false;
347
+
348
+ for (const char of sql) {
349
+ if (escaped) {
350
+ current += char;
351
+ escaped = false;
352
+ continue;
353
+ }
354
+
355
+ if (char === '\\') {
356
+ current += char;
357
+ escaped = true;
358
+ continue;
359
+ }
360
+
361
+ if (char === "'" && !inDoubleQuote) {
362
+ inSingleQuote = !inSingleQuote;
363
+ current += char;
364
+ continue;
365
+ }
366
+
367
+ if (char === '"' && !inSingleQuote) {
368
+ inDoubleQuote = !inDoubleQuote;
369
+ current += char;
370
+ continue;
371
+ }
372
+
373
+ if (char === ';' && !inSingleQuote && !inDoubleQuote) {
374
+ const statement = current.trim();
375
+ if (statement) {
376
+ statements.push(statement);
377
+ }
378
+ current = '';
379
+ continue;
380
+ }
381
+
382
+ current += char;
383
+ }
384
+
385
+ const trailing = current.trim();
386
+ if (trailing) {
387
+ statements.push(trailing);
388
+ }
389
+
390
+ return statements;
391
+ }
392
+
393
+ /**
394
+ * Executa migrações SQL idempotentes a partir de `database/migrations`.
395
+ *
396
+ * @param {import('mysql2/promise').Connection} connection
397
+ * @returns {Promise<number>} Quantidade de migrações aplicadas.
398
+ */
399
+ async function runSqlMigrations(connection) {
400
+ await connection.query(createSchemaMigrationsTableSQL);
401
+
402
+ let files = [];
403
+ try {
404
+ files = await fs.readdir(MIGRATIONS_DIR);
405
+ } catch (error) {
406
+ if (error.code === 'ENOENT') {
407
+ logger.info('Diretório de migrações não encontrado. Seguindo sem migrações extras.');
408
+ return 0;
409
+ }
410
+ throw error;
411
+ }
412
+
413
+ const migrationFiles = files.filter((file) => file.endsWith('.sql')).sort();
414
+ if (migrationFiles.length === 0) return 0;
415
+
416
+ const [rows] = await connection.query('SELECT name FROM schema_migrations');
417
+ const applied = new Set((rows || []).map((row) => row.name));
418
+
419
+ let appliedCount = 0;
420
+
421
+ for (const fileName of migrationFiles) {
422
+ if (applied.has(fileName)) continue;
423
+
424
+ const filePath = path.join(MIGRATIONS_DIR, fileName);
425
+ const sqlContent = await fs.readFile(filePath, 'utf8');
426
+ const statements = splitSqlStatements(sqlContent);
427
+
428
+ if (statements.length === 0) {
429
+ await connection.query('INSERT INTO schema_migrations (name) VALUES (?)', [fileName]);
430
+ appliedCount += 1;
431
+ continue;
432
+ }
433
+
434
+ logger.info(`Aplicando migração SQL: ${fileName}`);
435
+ for (const statement of statements) {
436
+ await connection.query(statement);
437
+ }
438
+
439
+ await connection.query('INSERT INTO schema_migrations (name) VALUES (?)', [fileName]);
440
+ appliedCount += 1;
441
+ }
442
+
443
+ return appliedCount;
444
+ }
445
+
446
+ /**
447
+ * Inicializa o banco de dados:
448
+ * 1) Conecta ao MySQL sem database
449
+ * 2) Cria o banco se não existir
450
+ * 3) Muda para o database correto
451
+ * 4) Cria todas as tabelas
452
+ * 5) Encerra a conexão
453
+ *
454
+ * Este script é idempotente:
455
+ * pode ser rodado várias vezes sem quebrar nada.
456
+ *
457
+ * @returns {Promise<void>}
458
+ */
459
+ export default async function initializeDatabase() {
460
+ let connection;
461
+
462
+ try {
463
+ connection = await mysql.createConnection({
464
+ host: dbConfig.host,
465
+ user: dbConfig.user,
466
+ password: dbConfig.password,
467
+ });
468
+
469
+ // Cria/verifica o banco
470
+ await connection.query(createDatabaseSQL);
471
+ logger.info(`Banco de dados '${dbToCreate}' verificado/criado com sucesso.`);
472
+
473
+ // Seleciona o banco recém-criado
474
+ await connection.changeUser({ database: dbToCreate });
475
+
476
+ // Cria todas as tabelas em paralelo
477
+ await Promise.all([
478
+ connection.query(createMessagesTableSQL),
479
+ connection.query(createChatsTableSQL),
480
+ connection.query(createGroupsMetadataTableSQL),
481
+ connection.query(createGroupConfigsTableSQL),
482
+ connection.query(createLidMapTableSQL),
483
+ connection.query(createStickerAssetTableSQL),
484
+ ]);
485
+
486
+ // Ordem importa por conta das FKs: pack depende de asset e item depende de pack+asset.
487
+ await connection.query(createStickerPackTableSQL);
488
+ await connection.query(createStickerPackItemTableSQL);
489
+ await connection.query(createStickerPackWebUploadTableSQL);
490
+ await connection.query(createStickerAssetClassificationTableSQL);
491
+ await connection.query(createStickerPackEngagementTableSQL);
492
+ await connection.query(createStickerPackInteractionEventTableSQL);
493
+
494
+ const appliedMigrations = await runSqlMigrations(connection);
495
+
496
+ logger.info('Todas as tabelas foram verificadas/criadas com sucesso.', {
497
+ appliedMigrations,
498
+ });
499
+ } catch (error) {
500
+ logger.error(`Erro ao inicializar o banco: ${error.code || ''} ${error.message}`);
501
+ process.exit(1);
502
+ } finally {
503
+ if (connection) {
504
+ await connection.end();
505
+ logger.info('Conexão com o MySQL encerrada após inicialização.');
506
+ }
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Permite que este arquivo seja executado diretamente:
512
+ * node database/init.js
513
+ */
514
+ if (process.argv[1] === __filename) {
515
+ initializeDatabase();
516
+ }
@@ -0,0 +1,54 @@
1
+ CREATE TABLE IF NOT EXISTS sticker_asset (
2
+ id CHAR(36) PRIMARY KEY,
3
+ owner_jid VARCHAR(255) NOT NULL,
4
+ sha256 CHAR(64) NOT NULL,
5
+ mimetype VARCHAR(64) NOT NULL,
6
+ is_animated TINYINT(1) NOT NULL DEFAULT 0,
7
+ width INT UNSIGNED NULL,
8
+ height INT UNSIGNED NULL,
9
+ size_bytes INT UNSIGNED NOT NULL,
10
+ storage_path VARCHAR(1024) NOT NULL,
11
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
12
+ UNIQUE KEY uq_sticker_asset_sha256 (sha256),
13
+ INDEX idx_sticker_asset_owner_created (owner_jid, created_at)
14
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
15
+
16
+ CREATE TABLE IF NOT EXISTS sticker_pack (
17
+ id CHAR(36) PRIMARY KEY,
18
+ owner_jid VARCHAR(255) NOT NULL,
19
+ name VARCHAR(120) NOT NULL,
20
+ publisher VARCHAR(120) NOT NULL,
21
+ description TEXT NULL,
22
+ pack_key VARCHAR(160) NOT NULL,
23
+ cover_sticker_id CHAR(36) NULL,
24
+ visibility ENUM('private', 'public', 'unlisted') NOT NULL DEFAULT 'private',
25
+ version INT UNSIGNED NOT NULL DEFAULT 1,
26
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
27
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
28
+ deleted_at TIMESTAMP NULL DEFAULT NULL,
29
+ UNIQUE KEY uq_sticker_pack_pack_key (pack_key),
30
+ INDEX idx_sticker_pack_owner_deleted (owner_jid, deleted_at),
31
+ INDEX idx_sticker_pack_owner_updated (owner_jid, updated_at),
32
+ CONSTRAINT fk_sticker_pack_cover
33
+ FOREIGN KEY (cover_sticker_id) REFERENCES sticker_asset(id)
34
+ ON DELETE SET NULL ON UPDATE CASCADE
35
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
36
+
37
+ CREATE TABLE IF NOT EXISTS sticker_pack_item (
38
+ id CHAR(36) PRIMARY KEY,
39
+ pack_id CHAR(36) NOT NULL,
40
+ sticker_id CHAR(36) NOT NULL,
41
+ position INT UNSIGNED NOT NULL,
42
+ emojis JSON NULL,
43
+ accessibility_label VARCHAR(255) NULL,
44
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
45
+ UNIQUE KEY uq_sticker_pack_item_pack_sticker (pack_id, sticker_id),
46
+ UNIQUE KEY uq_sticker_pack_item_pack_position (pack_id, position),
47
+ INDEX idx_sticker_pack_item_pack_position (pack_id, position),
48
+ CONSTRAINT fk_sticker_pack_item_pack
49
+ FOREIGN KEY (pack_id) REFERENCES sticker_pack(id)
50
+ ON DELETE CASCADE ON UPDATE CASCADE,
51
+ CONSTRAINT fk_sticker_pack_item_asset
52
+ FOREIGN KEY (sticker_id) REFERENCES sticker_asset(id)
53
+ ON DELETE RESTRICT ON UPDATE CASCADE
54
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,58 @@
1
+ CREATE TABLE IF NOT EXISTS rpg_player (
2
+ jid VARCHAR(255) PRIMARY KEY,
3
+ level INT UNSIGNED NOT NULL DEFAULT 1,
4
+ xp BIGINT UNSIGNED NOT NULL DEFAULT 0,
5
+ gold BIGINT UNSIGNED NOT NULL DEFAULT 200,
6
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
7
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
8
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
9
+
10
+ CREATE TABLE IF NOT EXISTS rpg_player_pokemon (
11
+ id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
12
+ owner_jid VARCHAR(255) NOT NULL,
13
+ poke_id INT UNSIGNED NOT NULL,
14
+ nickname VARCHAR(120) NULL,
15
+ level INT UNSIGNED NOT NULL DEFAULT 5,
16
+ xp BIGINT UNSIGNED NOT NULL DEFAULT 0,
17
+ current_hp INT UNSIGNED NOT NULL,
18
+ ivs_json JSON NOT NULL,
19
+ moves_json JSON NOT NULL,
20
+ is_active TINYINT(1) NOT NULL DEFAULT 0,
21
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
22
+ INDEX idx_rpg_player_pokemon_owner (owner_jid),
23
+ INDEX idx_rpg_player_pokemon_owner_active (owner_jid, is_active),
24
+ CONSTRAINT fk_rpg_player_pokemon_owner
25
+ FOREIGN KEY (owner_jid) REFERENCES rpg_player(jid)
26
+ ON DELETE CASCADE ON UPDATE CASCADE
27
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
28
+
29
+ CREATE TABLE IF NOT EXISTS rpg_battle_state (
30
+ chat_jid VARCHAR(255) PRIMARY KEY,
31
+ owner_jid VARCHAR(255) NOT NULL,
32
+ my_pokemon_id BIGINT UNSIGNED NOT NULL,
33
+ enemy_snapshot_json JSON NOT NULL,
34
+ turn INT UNSIGNED NOT NULL DEFAULT 1,
35
+ expires_at DATETIME NOT NULL,
36
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
37
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
38
+ UNIQUE KEY uq_rpg_battle_owner (owner_jid),
39
+ INDEX idx_rpg_battle_expires_at (expires_at),
40
+ CONSTRAINT fk_rpg_battle_owner
41
+ FOREIGN KEY (owner_jid) REFERENCES rpg_player(jid)
42
+ ON DELETE CASCADE ON UPDATE CASCADE,
43
+ CONSTRAINT fk_rpg_battle_my_pokemon
44
+ FOREIGN KEY (my_pokemon_id) REFERENCES rpg_player_pokemon(id)
45
+ ON DELETE CASCADE ON UPDATE CASCADE
46
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
47
+
48
+ CREATE TABLE IF NOT EXISTS rpg_player_inventory (
49
+ owner_jid VARCHAR(255) NOT NULL,
50
+ item_key VARCHAR(64) NOT NULL,
51
+ quantity INT UNSIGNED NOT NULL DEFAULT 0,
52
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
53
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
54
+ PRIMARY KEY (owner_jid, item_key),
55
+ CONSTRAINT fk_rpg_inventory_owner
56
+ FOREIGN KEY (owner_jid) REFERENCES rpg_player(jid)
57
+ ON DELETE CASCADE ON UPDATE CASCADE
58
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,9 @@
1
+ ALTER TABLE rpg_player_pokemon
2
+ ADD COLUMN is_shiny TINYINT(1) NOT NULL DEFAULT 0 AFTER moves_json;
3
+
4
+ CREATE TABLE IF NOT EXISTS rpg_group_biome (
5
+ group_jid VARCHAR(255) PRIMARY KEY,
6
+ biome_key VARCHAR(64) NOT NULL,
7
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
8
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
9
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,14 @@
1
+ CREATE TABLE IF NOT EXISTS rpg_player_mission_progress (
2
+ owner_jid VARCHAR(255) PRIMARY KEY,
3
+ daily_ref_date DATE NOT NULL,
4
+ daily_progress_json JSON NOT NULL,
5
+ daily_claimed_at DATETIME NULL,
6
+ weekly_ref_date DATE NOT NULL,
7
+ weekly_progress_json JSON NOT NULL,
8
+ weekly_claimed_at DATETIME NULL,
9
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
10
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
11
+ CONSTRAINT fk_rpg_mission_owner
12
+ FOREIGN KEY (owner_jid) REFERENCES rpg_player(jid)
13
+ ON DELETE CASCADE ON UPDATE CASCADE
14
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,27 @@
1
+ ALTER TABLE rpg_player_pokemon
2
+ ADD COLUMN nature_key VARCHAR(64) NULL AFTER moves_json,
3
+ ADD COLUMN ability_key VARCHAR(64) NULL AFTER nature_key,
4
+ ADD COLUMN ability_name VARCHAR(120) NULL AFTER ability_key;
5
+
6
+ CREATE TABLE IF NOT EXISTS rpg_player_pokedex (
7
+ owner_jid VARCHAR(255) NOT NULL,
8
+ poke_id INT UNSIGNED NOT NULL,
9
+ first_captured_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
10
+ PRIMARY KEY (owner_jid, poke_id),
11
+ INDEX idx_rpg_pokedex_owner (owner_jid),
12
+ CONSTRAINT fk_rpg_pokedex_owner
13
+ FOREIGN KEY (owner_jid) REFERENCES rpg_player(jid)
14
+ ON DELETE CASCADE ON UPDATE CASCADE
15
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
16
+
17
+ CREATE TABLE IF NOT EXISTS rpg_player_travel (
18
+ owner_jid VARCHAR(255) PRIMARY KEY,
19
+ region_key VARCHAR(120) NULL,
20
+ location_key VARCHAR(120) NULL,
21
+ location_area_key VARCHAR(120) NULL,
22
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
23
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
24
+ CONSTRAINT fk_rpg_travel_owner
25
+ FOREIGN KEY (owner_jid) REFERENCES rpg_player(jid)
26
+ ON DELETE CASCADE ON UPDATE CASCADE
27
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;