@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.
- package/.env.example +534 -0
- package/LICENSE +21 -0
- package/README.md +431 -0
- package/RELEASE-v2.1.2.md +83 -0
- package/app/config/adminIdentity.js +87 -0
- package/app/config/baileysConfig.js +693 -0
- package/app/config/groupUtils.js +388 -0
- package/app/connection/socketController.js +992 -0
- package/app/controllers/messageController.js +354 -0
- package/app/modules/adminModule/groupCommandHandlers.js +1294 -0
- package/app/modules/adminModule/groupEventHandlers.js +355 -0
- package/app/modules/aiModule/catCommand.js +1006 -0
- package/app/modules/broadcastModule/noticeCommand.js +416 -0
- package/app/modules/gameModule/diceCommand.js +67 -0
- package/app/modules/menuModule/common.js +311 -0
- package/app/modules/menuModule/menus.js +59 -0
- package/app/modules/playModule/playCommand.js +1615 -0
- package/app/modules/quoteModule/quoteCommand.js +851 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +786 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.js +2082 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +760 -0
- package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
- package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +172 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
- package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
- package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1859 -0
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +6738 -0
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
- package/app/modules/statsModule/globalRankingCommand.js +65 -0
- package/app/modules/statsModule/noMessageCommand.js +288 -0
- package/app/modules/statsModule/rankingCommand.js +60 -0
- package/app/modules/statsModule/rankingCommon.js +889 -0
- package/app/modules/stickerModule/addStickerMetadata.js +239 -0
- package/app/modules/stickerModule/convertToWebp.js +390 -0
- package/app/modules/stickerModule/stickerCommand.js +454 -0
- package/app/modules/stickerModule/stickerConvertCommand.js +156 -0
- package/app/modules/stickerModule/stickerTextCommand.js +657 -0
- package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
- package/app/modules/stickerPackModule/autoPackCollectorService.js +284 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +466 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +88 -0
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +571 -0
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +449 -0
- package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +180 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +4078 -0
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +598 -0
- package/app/modules/stickerPackModule/stickerClassificationService.js +588 -0
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +102 -0
- package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +7506 -0
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1095 -0
- package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +108 -0
- package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +110 -0
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +440 -0
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +337 -0
- package/app/modules/stickerPackModule/stickerPackMessageService.js +296 -0
- package/app/modules/stickerPackModule/stickerPackRepository.js +442 -0
- package/app/modules/stickerPackModule/stickerPackService.js +788 -0
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +51 -0
- package/app/modules/stickerPackModule/stickerPackUtils.js +97 -0
- package/app/modules/stickerPackModule/stickerStorageService.js +507 -0
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +233 -0
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +205 -0
- package/app/modules/systemMetricsModule/pingCommand.js +421 -0
- package/app/modules/tiktokModule/tiktokCommand.js +798 -0
- package/app/modules/userModule/userCommand.js +1217 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +177 -0
- package/app/observability/metrics.js +734 -0
- package/app/services/captchaService.js +492 -0
- package/app/services/dbWriteQueue.js +572 -0
- package/app/services/groupMetadataService.js +279 -0
- package/app/services/lidMapService.js +663 -0
- package/app/services/messagePersistenceService.js +56 -0
- package/app/services/newsBroadcastService.js +351 -0
- package/app/services/pokeApiService.js +398 -0
- package/app/services/queueUtils.js +57 -0
- package/app/services/socketState.js +7 -0
- package/app/store/aiPromptStore.js +38 -0
- package/app/store/groupConfigStore.js +58 -0
- package/app/store/premiumUserStore.js +36 -0
- package/app/utils/antiLink/antiLinkModule.js +804 -0
- package/app/utils/http/getImageBufferModule.js +18 -0
- package/app/utils/json/jsonSanitizer.js +113 -0
- package/app/utils/json/jsonSanitizer.test.js +40 -0
- package/app/utils/logger/loggerModule.js +262 -0
- package/app/utils/systemMetrics/systemMetricsModule.js +91 -0
- package/database/index.js +2052 -0
- package/database/init.js +516 -0
- package/database/migrations/20260203_0001_sticker_packs.sql +54 -0
- package/database/migrations/20260210_0003_rpg_pokemon.sql +58 -0
- package/database/migrations/20260210_0004_rpg_shiny_biome.sql +9 -0
- package/database/migrations/20260210_0005_rpg_missions.sql +14 -0
- package/database/migrations/20260210_0006_rpg_world_pokedex_traits.sql +27 -0
- package/database/migrations/20260210_0007_rpg_raid_pvp.sql +56 -0
- package/database/migrations/20260210_0008_rpg_social_system.sql +195 -0
- package/database/migrations/20260211_0009_rpg_social_xp.sql +36 -0
- package/database/migrations/20260222_0010_remove_message_xp.sql +2 -0
- package/database/migrations/20260226_0011_sticker_asset_classification.sql +17 -0
- package/database/migrations/20260226_0012_sticker_pack_engagement.sql +16 -0
- package/database/migrations/20260226_0013_sticker_marketplace_intelligence.sql +19 -0
- package/database/migrations/20260226_0014_sticker_pack_publish_flow.sql +30 -0
- package/database/migrations/20260226_0014_sticker_worker_queues.sql +42 -0
- package/database/migrations/20260226_0015_sticker_auto_pack_curation_integrity.sql +18 -0
- package/database/migrations/20260226_0016_sticker_web_google_auth_persistence.sql +34 -0
- package/database/migrations/20260226_0017_sticker_web_admin_ban.sql +22 -0
- package/database/migrations/20260226_0018_sticker_web_admin_moderator.sql +18 -0
- package/database/migrations/20260227_0019_sticker_classification_v2_signals.sql +12 -0
- package/database/migrations/20260227_0020_semantic_theme_clusters.sql +35 -0
- package/docker-compose.yml +103 -0
- package/ecosystem.prod.config.cjs +35 -0
- package/eslint.config.js +61 -0
- package/index.js +437 -0
- package/ml/clip_classifier/Dockerfile +16 -0
- package/ml/clip_classifier/README.md +120 -0
- package/ml/clip_classifier/adaptive_scoring.py +40 -0
- package/ml/clip_classifier/classifier.py +654 -0
- package/ml/clip_classifier/embedding_store.py +481 -0
- package/ml/clip_classifier/env_loader.py +15 -0
- package/ml/clip_classifier/llm_label_expander.py +144 -0
- package/ml/clip_classifier/main.py +213 -0
- package/ml/clip_classifier/requirements.txt +10 -0
- package/ml/clip_classifier/similarity_engine.py +74 -0
- package/observability/alert-rules.yml +60 -0
- package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
- package/observability/grafana/dashboards/omnizap-overview.json +170 -0
- package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
- package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
- package/observability/loki-config.yml +38 -0
- package/observability/mysql-exporter.cnf +5 -0
- package/observability/mysql-setup.sql +46 -0
- package/observability/prometheus.yml +32 -0
- package/observability/promtail-config.yml +84 -0
- package/package.json +109 -0
- package/public/api-docs/index.html +144 -0
- package/public/css/github-project-panel.css +297 -0
- package/public/css/stickers-admin.css +1272 -0
- package/public/css/styles.css +671 -0
- package/public/index.html +1311 -0
- package/public/js/apps/apiDocsApp.js +310 -0
- package/public/js/apps/createPackApp.js +2069 -0
- package/public/js/apps/homeApp.js +396 -0
- package/public/js/apps/stickersAdminApp.js +1744 -0
- package/public/js/apps/stickersApp.js +4830 -0
- package/public/js/catalog.js +1019 -0
- package/public/js/github-panel/components/CommitList.js +34 -0
- package/public/js/github-panel/components/ErrorState.js +16 -0
- package/public/js/github-panel/components/GithubProjectPanel.js +106 -0
- package/public/js/github-panel/components/ReleaseList.js +38 -0
- package/public/js/github-panel/components/SkeletonPanel.js +22 -0
- package/public/js/github-panel/components/StatCard.js +15 -0
- package/public/js/github-panel/index.js +15 -0
- package/public/js/github-panel/useGithubRepoData.js +154 -0
- package/public/js/github-panel/vendor/react.js +11 -0
- package/public/js/runtime/react-runtime.js +19 -0
- package/public/licenca/index.html +106 -0
- package/public/stickers/admin/index.html +23 -0
- package/public/stickers/create/index.html +47 -0
- package/public/stickers/index.html +48 -0
- package/public/termos-de-uso/index.html +125 -0
- package/scripts/cache-bust.mjs +107 -0
- package/scripts/deploy.sh +458 -0
- package/scripts/github-deploy-notify.mjs +174 -0
- package/scripts/release.sh +129 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
2
|
+
|
|
3
|
+
const CATALOG_COMPLETE_PACK_TARGET = Math.max(1, Number(process.env.STICKER_PACK_MAX_ITEMS) || 30);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normaliza linha da tabela de packs para formato usado no domínio.
|
|
7
|
+
*
|
|
8
|
+
* @param {Record<string, unknown>|null|undefined} row Linha retornada da query.
|
|
9
|
+
* @returns {object|null} Pack normalizado.
|
|
10
|
+
*/
|
|
11
|
+
const normalizeStickerPackRow = (row) => {
|
|
12
|
+
if (!row) return null;
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
id: row.id,
|
|
16
|
+
owner_jid: row.owner_jid,
|
|
17
|
+
name: row.name,
|
|
18
|
+
publisher: row.publisher,
|
|
19
|
+
description: row.description,
|
|
20
|
+
pack_key: row.pack_key,
|
|
21
|
+
cover_sticker_id: row.cover_sticker_id,
|
|
22
|
+
visibility: row.visibility,
|
|
23
|
+
status: row.status || 'published',
|
|
24
|
+
pack_status: row.pack_status || 'ready',
|
|
25
|
+
pack_theme_key: row.pack_theme_key || null,
|
|
26
|
+
pack_volume: row.pack_volume !== null && row.pack_volume !== undefined ? Number(row.pack_volume) : null,
|
|
27
|
+
is_auto_pack: row.is_auto_pack === 1 || row.is_auto_pack === true,
|
|
28
|
+
last_rebalanced_at: row.last_rebalanced_at || null,
|
|
29
|
+
version: Number(row.version || 1),
|
|
30
|
+
created_at: row.created_at,
|
|
31
|
+
updated_at: row.updated_at,
|
|
32
|
+
deleted_at: row.deleted_at,
|
|
33
|
+
sticker_count: row.sticker_count !== null && row.sticker_count !== undefined ? Number(row.sticker_count) : undefined,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Busca pack por ID.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} packId ID interno do pack.
|
|
41
|
+
* @param {{ includeDeleted?: boolean, connection?: import('mysql2/promise').PoolConnection|null }} [options]
|
|
42
|
+
* @returns {Promise<object|null>} Pack encontrado.
|
|
43
|
+
*/
|
|
44
|
+
export async function findStickerPackById(packId, { includeDeleted = false, connection = null } = {}) {
|
|
45
|
+
const rows = await executeQuery(
|
|
46
|
+
`SELECT p.*,
|
|
47
|
+
(SELECT COUNT(*) FROM ${TABLES.STICKER_PACK_ITEM} i WHERE i.pack_id = p.id) AS sticker_count
|
|
48
|
+
FROM ${TABLES.STICKER_PACK} p
|
|
49
|
+
WHERE p.id = ? ${includeDeleted ? '' : 'AND p.deleted_at IS NULL'}
|
|
50
|
+
LIMIT 1`,
|
|
51
|
+
[packId],
|
|
52
|
+
connection,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return normalizeStickerPackRow(rows?.[0] || null);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Busca pack por chave pública (pack_key).
|
|
60
|
+
*
|
|
61
|
+
* @param {string} packKey Chave pública do pack.
|
|
62
|
+
* @param {{ includeDeleted?: boolean, connection?: import('mysql2/promise').PoolConnection|null }} [options]
|
|
63
|
+
* @returns {Promise<object|null>} Pack encontrado.
|
|
64
|
+
*/
|
|
65
|
+
export async function findStickerPackByPackKey(packKey, { includeDeleted = false, connection = null } = {}) {
|
|
66
|
+
const rows = await executeQuery(
|
|
67
|
+
`SELECT p.*,
|
|
68
|
+
(SELECT COUNT(*) FROM ${TABLES.STICKER_PACK_ITEM} i WHERE i.pack_id = p.id) AS sticker_count
|
|
69
|
+
FROM ${TABLES.STICKER_PACK} p
|
|
70
|
+
WHERE p.pack_key = ? ${includeDeleted ? '' : 'AND p.deleted_at IS NULL'}
|
|
71
|
+
LIMIT 1`,
|
|
72
|
+
[packKey],
|
|
73
|
+
connection,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return normalizeStickerPackRow(rows?.[0] || null);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Busca pack do dono por ID, pack_key ou nome.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} ownerJid JID do dono.
|
|
83
|
+
* @param {string} identifier ID, chave ou nome do pack.
|
|
84
|
+
* @param {{ includeDeleted?: boolean, connection?: import('mysql2/promise').PoolConnection|null }} [options]
|
|
85
|
+
* @returns {Promise<object|null>} Pack encontrado.
|
|
86
|
+
*/
|
|
87
|
+
export async function findStickerPackByOwnerAndIdentifier(
|
|
88
|
+
ownerJid,
|
|
89
|
+
identifier,
|
|
90
|
+
{ includeDeleted = false, connection = null } = {},
|
|
91
|
+
) {
|
|
92
|
+
if (!identifier) return null;
|
|
93
|
+
|
|
94
|
+
const idOrPack = await executeQuery(
|
|
95
|
+
`SELECT p.*,
|
|
96
|
+
(SELECT COUNT(*) FROM ${TABLES.STICKER_PACK_ITEM} i WHERE i.pack_id = p.id) AS sticker_count
|
|
97
|
+
FROM ${TABLES.STICKER_PACK} p
|
|
98
|
+
WHERE p.owner_jid = ?
|
|
99
|
+
${includeDeleted ? '' : 'AND p.deleted_at IS NULL'}
|
|
100
|
+
AND (p.id = ? OR p.pack_key = ?)
|
|
101
|
+
ORDER BY p.updated_at DESC
|
|
102
|
+
LIMIT 1`,
|
|
103
|
+
[ownerJid, identifier, identifier],
|
|
104
|
+
connection,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (idOrPack?.[0]) {
|
|
108
|
+
return normalizeStickerPackRow(idOrPack[0]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const byName = await executeQuery(
|
|
112
|
+
`SELECT p.*,
|
|
113
|
+
(SELECT COUNT(*) FROM ${TABLES.STICKER_PACK_ITEM} i WHERE i.pack_id = p.id) AS sticker_count
|
|
114
|
+
FROM ${TABLES.STICKER_PACK} p
|
|
115
|
+
WHERE p.owner_jid = ?
|
|
116
|
+
${includeDeleted ? '' : 'AND p.deleted_at IS NULL'}
|
|
117
|
+
AND LOWER(p.name) = LOWER(?)
|
|
118
|
+
ORDER BY p.updated_at DESC
|
|
119
|
+
LIMIT 1`,
|
|
120
|
+
[ownerJid, identifier],
|
|
121
|
+
connection,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return normalizeStickerPackRow(byName?.[0] || null);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Lista packs de um usuário com paginação simples.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} ownerJid JID do dono.
|
|
131
|
+
* @param {{ includeDeleted?: boolean, limit?: number, offset?: number, connection?: import('mysql2/promise').PoolConnection|null }} [options]
|
|
132
|
+
* @returns {Promise<object[]>} Lista de packs.
|
|
133
|
+
*/
|
|
134
|
+
export async function listStickerPacksByOwner(
|
|
135
|
+
ownerJid,
|
|
136
|
+
{ includeDeleted = false, limit = 50, offset = 0, connection = null } = {},
|
|
137
|
+
) {
|
|
138
|
+
const safeLimit = Math.max(1, Number(limit) || 50);
|
|
139
|
+
const safeOffset = Math.max(0, Number(offset) || 0);
|
|
140
|
+
|
|
141
|
+
const rows = await executeQuery(
|
|
142
|
+
`SELECT p.*,
|
|
143
|
+
(SELECT COUNT(*) FROM ${TABLES.STICKER_PACK_ITEM} i WHERE i.pack_id = p.id) AS sticker_count
|
|
144
|
+
FROM ${TABLES.STICKER_PACK} p
|
|
145
|
+
WHERE p.owner_jid = ? ${includeDeleted ? '' : 'AND p.deleted_at IS NULL'}
|
|
146
|
+
ORDER BY p.updated_at DESC
|
|
147
|
+
LIMIT ${safeLimit} OFFSET ${safeOffset}`,
|
|
148
|
+
[ownerJid],
|
|
149
|
+
connection,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
return rows.map((row) => normalizeStickerPackRow(row));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Lista packs automáticos de curadoria para um conjunto de owners.
|
|
157
|
+
*
|
|
158
|
+
* @param {{
|
|
159
|
+
* ownerJids?: string[],
|
|
160
|
+
* includeArchived?: boolean,
|
|
161
|
+
* themeKey?: string,
|
|
162
|
+
* limit?: number,
|
|
163
|
+
* offset?: number,
|
|
164
|
+
* connection?: import('mysql2/promise').PoolConnection|null,
|
|
165
|
+
* }} [options]
|
|
166
|
+
* @returns {Promise<object[]>}
|
|
167
|
+
*/
|
|
168
|
+
export async function listStickerAutoPacksForCuration({
|
|
169
|
+
ownerJids = [],
|
|
170
|
+
includeArchived = true,
|
|
171
|
+
themeKey = '',
|
|
172
|
+
includeLegacyMarkers = true,
|
|
173
|
+
limit = 2000,
|
|
174
|
+
offset = 0,
|
|
175
|
+
connection = null,
|
|
176
|
+
} = {}) {
|
|
177
|
+
const owners = Array.from(new Set((Array.isArray(ownerJids) ? ownerJids : []).filter(Boolean)));
|
|
178
|
+
if (!owners.length) return [];
|
|
179
|
+
|
|
180
|
+
const safeLimit = Math.max(1, Math.min(5000, Number(limit) || 2000));
|
|
181
|
+
const safeOffset = Math.max(0, Number(offset) || 0);
|
|
182
|
+
const ownerPlaceholders = owners.map(() => '?').join(', ');
|
|
183
|
+
|
|
184
|
+
const whereClauses = [
|
|
185
|
+
'p.deleted_at IS NULL',
|
|
186
|
+
`p.owner_jid IN (${ownerPlaceholders})`,
|
|
187
|
+
];
|
|
188
|
+
const params = [...owners];
|
|
189
|
+
|
|
190
|
+
if (includeLegacyMarkers) {
|
|
191
|
+
whereClauses.push("(p.is_auto_pack = 1 OR p.description LIKE '%[auto-theme:%' OR p.description LIKE '%[auto-tag:%')");
|
|
192
|
+
} else {
|
|
193
|
+
whereClauses.push('p.is_auto_pack = 1');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const normalizedThemeKey = String(themeKey || '').trim().toLowerCase();
|
|
197
|
+
if (normalizedThemeKey) {
|
|
198
|
+
whereClauses.push('LOWER(COALESCE(p.pack_theme_key, \'\')) = ?');
|
|
199
|
+
params.push(normalizedThemeKey);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!includeArchived) {
|
|
203
|
+
whereClauses.push("COALESCE(p.pack_status, 'ready') <> 'archived'");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const rows = await executeQuery(
|
|
207
|
+
`SELECT p.*,
|
|
208
|
+
(SELECT COUNT(*) FROM ${TABLES.STICKER_PACK_ITEM} i WHERE i.pack_id = p.id) AS sticker_count
|
|
209
|
+
FROM ${TABLES.STICKER_PACK} p
|
|
210
|
+
WHERE ${whereClauses.join(' AND ')}
|
|
211
|
+
ORDER BY
|
|
212
|
+
COALESCE(p.pack_theme_key, '') ASC,
|
|
213
|
+
COALESCE(p.pack_volume, 0) ASC,
|
|
214
|
+
p.updated_at DESC
|
|
215
|
+
LIMIT ${safeLimit} OFFSET ${safeOffset}`,
|
|
216
|
+
params,
|
|
217
|
+
connection,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return rows.map((row) => normalizeStickerPackRow(row));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Lista packs públicos para catálogo web com busca e paginação.
|
|
225
|
+
*
|
|
226
|
+
* @param {{
|
|
227
|
+
* visibility?: 'public'|'unlisted'|'all',
|
|
228
|
+
* search?: string,
|
|
229
|
+
* limit?: number,
|
|
230
|
+
* offset?: number,
|
|
231
|
+
* connection?: import('mysql2/promise').PoolConnection|null,
|
|
232
|
+
* }} [options] Filtros de listagem.
|
|
233
|
+
* @returns {Promise<{ packs: object[], hasMore: boolean }>} Resultado paginado.
|
|
234
|
+
*/
|
|
235
|
+
export async function listStickerPacksForCatalog({
|
|
236
|
+
visibility = 'public',
|
|
237
|
+
search = '',
|
|
238
|
+
limit = 24,
|
|
239
|
+
offset = 0,
|
|
240
|
+
connection = null,
|
|
241
|
+
} = {}) {
|
|
242
|
+
const safeLimit = Math.max(1, Math.min(60, Number(limit) || 24));
|
|
243
|
+
const safeOffset = Math.max(0, Number(offset) || 0);
|
|
244
|
+
const safeLimitWithSentinel = safeLimit + 1;
|
|
245
|
+
|
|
246
|
+
const normalizedVisibility = String(visibility || 'public').trim().toLowerCase();
|
|
247
|
+
const visibilityValues =
|
|
248
|
+
normalizedVisibility === 'all'
|
|
249
|
+
? ['public', 'unlisted']
|
|
250
|
+
: normalizedVisibility === 'unlisted'
|
|
251
|
+
? ['unlisted']
|
|
252
|
+
: ['public'];
|
|
253
|
+
|
|
254
|
+
const normalizedSearch = String(search || '').trim().toLowerCase().slice(0, 120);
|
|
255
|
+
const params = [...visibilityValues];
|
|
256
|
+
const whereClauses = [
|
|
257
|
+
'p.deleted_at IS NULL',
|
|
258
|
+
"p.status = 'published'",
|
|
259
|
+
"COALESCE(p.pack_status, 'ready') = 'ready'",
|
|
260
|
+
`p.visibility IN (${visibilityValues.map(() => '?').join(', ')})`,
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
if (normalizedSearch) {
|
|
264
|
+
const like = `%${normalizedSearch}%`;
|
|
265
|
+
whereClauses.push(
|
|
266
|
+
'(LOWER(p.name) LIKE ? OR LOWER(p.publisher) LIKE ? OR LOWER(COALESCE(p.description, \'\')) LIKE ? OR LOWER(p.pack_key) LIKE ?)',
|
|
267
|
+
);
|
|
268
|
+
params.push(like, like, like, like);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const rows = await executeQuery(
|
|
272
|
+
`SELECT p.*,
|
|
273
|
+
(SELECT COUNT(*) FROM ${TABLES.STICKER_PACK_ITEM} i WHERE i.pack_id = p.id) AS sticker_count
|
|
274
|
+
FROM ${TABLES.STICKER_PACK} p
|
|
275
|
+
WHERE ${whereClauses.join(' AND ')}
|
|
276
|
+
ORDER BY
|
|
277
|
+
(sticker_count >= ${CATALOG_COMPLETE_PACK_TARGET}) DESC,
|
|
278
|
+
sticker_count DESC,
|
|
279
|
+
p.updated_at DESC
|
|
280
|
+
LIMIT ${safeLimitWithSentinel} OFFSET ${safeOffset}`,
|
|
281
|
+
params,
|
|
282
|
+
connection,
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const hasMore = rows.length > safeLimit;
|
|
286
|
+
return {
|
|
287
|
+
packs: rows.slice(0, safeLimit).map((row) => normalizeStickerPackRow(row)),
|
|
288
|
+
hasMore,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Cria um registro de pack.
|
|
294
|
+
*
|
|
295
|
+
* @param {object} pack Dados do pack.
|
|
296
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
297
|
+
* @returns {Promise<object|null>} Pack criado.
|
|
298
|
+
*/
|
|
299
|
+
export async function createStickerPack(pack, connection = null) {
|
|
300
|
+
await executeQuery(
|
|
301
|
+
`INSERT INTO ${TABLES.STICKER_PACK}
|
|
302
|
+
(
|
|
303
|
+
id,
|
|
304
|
+
owner_jid,
|
|
305
|
+
name,
|
|
306
|
+
publisher,
|
|
307
|
+
description,
|
|
308
|
+
pack_key,
|
|
309
|
+
cover_sticker_id,
|
|
310
|
+
visibility,
|
|
311
|
+
status,
|
|
312
|
+
pack_status,
|
|
313
|
+
pack_theme_key,
|
|
314
|
+
pack_volume,
|
|
315
|
+
is_auto_pack,
|
|
316
|
+
last_rebalanced_at,
|
|
317
|
+
version
|
|
318
|
+
)
|
|
319
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
320
|
+
[
|
|
321
|
+
pack.id,
|
|
322
|
+
pack.owner_jid,
|
|
323
|
+
pack.name,
|
|
324
|
+
pack.publisher,
|
|
325
|
+
pack.description ?? null,
|
|
326
|
+
pack.pack_key,
|
|
327
|
+
pack.cover_sticker_id ?? null,
|
|
328
|
+
pack.visibility,
|
|
329
|
+
pack.status ?? 'published',
|
|
330
|
+
pack.pack_status ?? 'ready',
|
|
331
|
+
pack.pack_theme_key ?? null,
|
|
332
|
+
pack.pack_volume ?? null,
|
|
333
|
+
pack.is_auto_pack ? 1 : 0,
|
|
334
|
+
pack.last_rebalanced_at ?? null,
|
|
335
|
+
pack.version ?? 1,
|
|
336
|
+
],
|
|
337
|
+
connection,
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
return findStickerPackById(pack.id, { includeDeleted: true, connection });
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const UPDATE_FIELD_MAP = {
|
|
344
|
+
name: 'name',
|
|
345
|
+
publisher: 'publisher',
|
|
346
|
+
description: 'description',
|
|
347
|
+
pack_key: 'pack_key',
|
|
348
|
+
cover_sticker_id: 'cover_sticker_id',
|
|
349
|
+
visibility: 'visibility',
|
|
350
|
+
status: 'status',
|
|
351
|
+
pack_status: 'pack_status',
|
|
352
|
+
pack_theme_key: 'pack_theme_key',
|
|
353
|
+
pack_volume: 'pack_volume',
|
|
354
|
+
is_auto_pack: 'is_auto_pack',
|
|
355
|
+
last_rebalanced_at: 'last_rebalanced_at',
|
|
356
|
+
deleted_at: 'deleted_at',
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Atualiza campos permitidos de um pack.
|
|
361
|
+
*
|
|
362
|
+
* @param {string} packId ID do pack.
|
|
363
|
+
* @param {Record<string, unknown>} fields Campos para atualização.
|
|
364
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
365
|
+
* @returns {Promise<object|null>} Pack atualizado.
|
|
366
|
+
*/
|
|
367
|
+
export async function updateStickerPackFields(packId, fields, connection = null) {
|
|
368
|
+
const setClauses = [];
|
|
369
|
+
const params = [];
|
|
370
|
+
|
|
371
|
+
for (const [field, column] of Object.entries(UPDATE_FIELD_MAP)) {
|
|
372
|
+
if (!(field in fields)) continue;
|
|
373
|
+
setClauses.push(`${column} = ?`);
|
|
374
|
+
params.push(fields[field]);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!setClauses.length) {
|
|
378
|
+
return findStickerPackById(packId, { includeDeleted: true, connection });
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
setClauses.push('version = version + 1');
|
|
382
|
+
setClauses.push('updated_at = CURRENT_TIMESTAMP');
|
|
383
|
+
|
|
384
|
+
await executeQuery(
|
|
385
|
+
`UPDATE ${TABLES.STICKER_PACK}
|
|
386
|
+
SET ${setClauses.join(', ')}
|
|
387
|
+
WHERE id = ?`,
|
|
388
|
+
[...params, packId],
|
|
389
|
+
connection,
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
return findStickerPackById(packId, { includeDeleted: true, connection });
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Marca um pack como deletado sem remover dados físicos.
|
|
397
|
+
*
|
|
398
|
+
* @param {string} packId ID do pack.
|
|
399
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
400
|
+
* @returns {Promise<object|null>} Pack atualizado.
|
|
401
|
+
*/
|
|
402
|
+
export async function softDeleteStickerPack(packId, connection = null) {
|
|
403
|
+
return updateStickerPackFields(
|
|
404
|
+
packId,
|
|
405
|
+
{
|
|
406
|
+
deleted_at: new Date(),
|
|
407
|
+
},
|
|
408
|
+
connection,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Verifica se a chave pública (pack_key) está disponível.
|
|
414
|
+
*
|
|
415
|
+
* @param {string} packKey Chave candidata.
|
|
416
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
417
|
+
* @returns {Promise<boolean>} `true` quando não existe pack com essa chave.
|
|
418
|
+
*/
|
|
419
|
+
export async function ensureUniquePackKey(packKey, connection = null) {
|
|
420
|
+
const existing = await findStickerPackByPackKey(packKey, { includeDeleted: true, connection });
|
|
421
|
+
return !existing;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Incrementa versão e timestamp de atualização do pack.
|
|
426
|
+
*
|
|
427
|
+
* @param {string} packId ID do pack.
|
|
428
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
429
|
+
* @returns {Promise<object|null>} Pack atualizado.
|
|
430
|
+
*/
|
|
431
|
+
export async function bumpStickerPackVersion(packId, connection = null) {
|
|
432
|
+
await executeQuery(
|
|
433
|
+
`UPDATE ${TABLES.STICKER_PACK}
|
|
434
|
+
SET version = version + 1,
|
|
435
|
+
updated_at = CURRENT_TIMESTAMP
|
|
436
|
+
WHERE id = ?`,
|
|
437
|
+
[packId],
|
|
438
|
+
connection,
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
return findStickerPackById(packId, { includeDeleted: true, connection });
|
|
442
|
+
}
|