@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,108 @@
|
|
|
1
|
+
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
2
|
+
|
|
3
|
+
const normalizeEngagementRow = (row) => ({
|
|
4
|
+
pack_id: row?.pack_id || null,
|
|
5
|
+
open_count: Number(row?.open_count || 0),
|
|
6
|
+
like_count: Number(row?.like_count || 0),
|
|
7
|
+
dislike_count: Number(row?.dislike_count || 0),
|
|
8
|
+
score: Number(row?.like_count || 0) - Number(row?.dislike_count || 0),
|
|
9
|
+
updated_at: row?.updated_at || null,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const EMPTY_ENGAGEMENT = Object.freeze({
|
|
13
|
+
open_count: 0,
|
|
14
|
+
like_count: 0,
|
|
15
|
+
dislike_count: 0,
|
|
16
|
+
score: 0,
|
|
17
|
+
updated_at: null,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const getEmptyStickerPackEngagement = () => ({ ...EMPTY_ENGAGEMENT });
|
|
21
|
+
|
|
22
|
+
export async function listStickerPackEngagementByPackIds(packIds, connection = null) {
|
|
23
|
+
const ids = Array.from(new Set((Array.isArray(packIds) ? packIds : []).filter(Boolean)));
|
|
24
|
+
if (!ids.length) return new Map();
|
|
25
|
+
|
|
26
|
+
const placeholders = ids.map(() => '?').join(', ');
|
|
27
|
+
const rows = await executeQuery(
|
|
28
|
+
`SELECT pack_id, open_count, like_count, dislike_count, updated_at
|
|
29
|
+
FROM ${TABLES.STICKER_PACK_ENGAGEMENT}
|
|
30
|
+
WHERE pack_id IN (${placeholders})`,
|
|
31
|
+
ids,
|
|
32
|
+
connection,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const byPackId = new Map();
|
|
36
|
+
rows.forEach((row) => {
|
|
37
|
+
const normalized = normalizeEngagementRow(row);
|
|
38
|
+
if (normalized.pack_id) byPackId.set(normalized.pack_id, normalized);
|
|
39
|
+
});
|
|
40
|
+
return byPackId;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function getStickerPackEngagementByPackId(packId, connection = null) {
|
|
44
|
+
if (!packId) return getEmptyStickerPackEngagement();
|
|
45
|
+
const rows = await executeQuery(
|
|
46
|
+
`SELECT pack_id, open_count, like_count, dislike_count, updated_at
|
|
47
|
+
FROM ${TABLES.STICKER_PACK_ENGAGEMENT}
|
|
48
|
+
WHERE pack_id = ?
|
|
49
|
+
LIMIT 1`,
|
|
50
|
+
[packId],
|
|
51
|
+
connection,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (!rows?.[0]) return getEmptyStickerPackEngagement();
|
|
55
|
+
return normalizeEngagementRow(rows[0]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function incrementStickerPackOpen(packId, connection = null) {
|
|
59
|
+
if (!packId) return getEmptyStickerPackEngagement();
|
|
60
|
+
|
|
61
|
+
await executeQuery(
|
|
62
|
+
`INSERT INTO ${TABLES.STICKER_PACK_ENGAGEMENT}
|
|
63
|
+
(pack_id, open_count, like_count, dislike_count, last_opened_at)
|
|
64
|
+
VALUES (?, 1, 0, 0, CURRENT_TIMESTAMP)
|
|
65
|
+
ON DUPLICATE KEY UPDATE
|
|
66
|
+
open_count = open_count + 1,
|
|
67
|
+
last_opened_at = CURRENT_TIMESTAMP,
|
|
68
|
+
updated_at = CURRENT_TIMESTAMP`,
|
|
69
|
+
[packId],
|
|
70
|
+
connection,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return getStickerPackEngagementByPackId(packId, connection);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function incrementStickerPackLike(packId, connection = null) {
|
|
77
|
+
if (!packId) return getEmptyStickerPackEngagement();
|
|
78
|
+
|
|
79
|
+
await executeQuery(
|
|
80
|
+
`INSERT INTO ${TABLES.STICKER_PACK_ENGAGEMENT}
|
|
81
|
+
(pack_id, open_count, like_count, dislike_count)
|
|
82
|
+
VALUES (?, 0, 1, 0)
|
|
83
|
+
ON DUPLICATE KEY UPDATE
|
|
84
|
+
like_count = like_count + 1,
|
|
85
|
+
updated_at = CURRENT_TIMESTAMP`,
|
|
86
|
+
[packId],
|
|
87
|
+
connection,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return getStickerPackEngagementByPackId(packId, connection);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function incrementStickerPackDislike(packId, connection = null) {
|
|
94
|
+
if (!packId) return getEmptyStickerPackEngagement();
|
|
95
|
+
|
|
96
|
+
await executeQuery(
|
|
97
|
+
`INSERT INTO ${TABLES.STICKER_PACK_ENGAGEMENT}
|
|
98
|
+
(pack_id, open_count, like_count, dislike_count)
|
|
99
|
+
VALUES (?, 0, 0, 1)
|
|
100
|
+
ON DUPLICATE KEY UPDATE
|
|
101
|
+
dislike_count = dislike_count + 1,
|
|
102
|
+
updated_at = CURRENT_TIMESTAMP`,
|
|
103
|
+
[packId],
|
|
104
|
+
connection,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return getStickerPackEngagementByPackId(packId, connection);
|
|
108
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Erro padrão para operações relacionadas a packs de figurinha.
|
|
3
|
+
*/
|
|
4
|
+
export class StickerPackError extends Error {
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} code Código semântico do erro.
|
|
7
|
+
* @param {string} message Mensagem amigável para logs/cliente.
|
|
8
|
+
* @param {unknown} [details=null] Objeto técnico opcional para diagnóstico.
|
|
9
|
+
*/
|
|
10
|
+
constructor(code, message, details = null) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'StickerPackError';
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.details = details;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Catálogo de códigos de erro usados pelo domínio de sticker pack.
|
|
20
|
+
*/
|
|
21
|
+
export const STICKER_PACK_ERROR_CODES = {
|
|
22
|
+
INVALID_INPUT: 'INVALID_INPUT',
|
|
23
|
+
PACK_NOT_FOUND: 'PACK_NOT_FOUND',
|
|
24
|
+
NOT_ALLOWED: 'NOT_ALLOWED',
|
|
25
|
+
STICKER_NOT_FOUND: 'STICKER_NOT_FOUND',
|
|
26
|
+
PACK_LIMIT_REACHED: 'PACK_LIMIT_REACHED',
|
|
27
|
+
DUPLICATE_STICKER: 'DUPLICATE_STICKER',
|
|
28
|
+
STORAGE_ERROR: 'STORAGE_ERROR',
|
|
29
|
+
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
|
30
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
2
|
+
|
|
3
|
+
const clamp = (value, fallback, min, max) => {
|
|
4
|
+
const numeric = Number(value);
|
|
5
|
+
if (!Number.isFinite(numeric)) return fallback;
|
|
6
|
+
return Math.max(min, Math.min(max, Math.floor(numeric)));
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const normalizeInteraction = (value) => {
|
|
10
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
11
|
+
if (['open', 'like', 'dislike'].includes(normalized)) return normalized;
|
|
12
|
+
return null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const sanitizeKey = (value, maxLength = 120) => {
|
|
16
|
+
const normalized = String(value || '')
|
|
17
|
+
.trim()
|
|
18
|
+
.replace(/\s+/g, ' ')
|
|
19
|
+
.slice(0, maxLength);
|
|
20
|
+
return normalized || null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export async function createStickerPackInteractionEvent(
|
|
24
|
+
{ packId, interaction, actorKey = null, sessionKey = null, source = null },
|
|
25
|
+
connection = null,
|
|
26
|
+
) {
|
|
27
|
+
const normalizedInteraction = normalizeInteraction(interaction);
|
|
28
|
+
if (!packId || !normalizedInteraction) return false;
|
|
29
|
+
|
|
30
|
+
await executeQuery(
|
|
31
|
+
`INSERT INTO ${TABLES.STICKER_PACK_INTERACTION_EVENT}
|
|
32
|
+
(pack_id, interaction, actor_key, session_key, source)
|
|
33
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
34
|
+
[packId, normalizedInteraction, sanitizeKey(actorKey), sanitizeKey(sessionKey), sanitizeKey(source, 32)],
|
|
35
|
+
connection,
|
|
36
|
+
);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function listStickerPackInteractionStatsByPackIds(
|
|
41
|
+
packIds,
|
|
42
|
+
{ horizonHours = 24, baselineDays = 7 } = {},
|
|
43
|
+
connection = null,
|
|
44
|
+
) {
|
|
45
|
+
const ids = Array.from(new Set((Array.isArray(packIds) ? packIds : []).filter(Boolean)));
|
|
46
|
+
if (!ids.length) return new Map();
|
|
47
|
+
|
|
48
|
+
const safeHorizonHours = clamp(horizonHours, 24, 1, 240);
|
|
49
|
+
const safeBaselineDays = clamp(baselineDays, 7, 2, 60);
|
|
50
|
+
const placeholders = ids.map(() => '?').join(', ');
|
|
51
|
+
|
|
52
|
+
const rows = await executeQuery(
|
|
53
|
+
`SELECT
|
|
54
|
+
pack_id,
|
|
55
|
+
SUM(CASE WHEN interaction = 'open' AND created_at >= (UTC_TIMESTAMP() - INTERVAL ${safeHorizonHours} HOUR) THEN 1 ELSE 0 END) AS open_horizon,
|
|
56
|
+
SUM(CASE WHEN interaction = 'open' AND created_at >= (UTC_TIMESTAMP() - INTERVAL ${safeBaselineDays} DAY) THEN 1 ELSE 0 END) AS open_baseline,
|
|
57
|
+
SUM(CASE WHEN interaction = 'like' AND created_at >= (UTC_TIMESTAMP() - INTERVAL ${safeHorizonHours} HOUR) THEN 1 ELSE 0 END) AS like_horizon,
|
|
58
|
+
SUM(CASE WHEN interaction = 'like' AND created_at >= (UTC_TIMESTAMP() - INTERVAL ${safeBaselineDays} DAY) THEN 1 ELSE 0 END) AS like_baseline,
|
|
59
|
+
SUM(CASE WHEN interaction = 'dislike' AND created_at >= (UTC_TIMESTAMP() - INTERVAL ${safeHorizonHours} HOUR) THEN 1 ELSE 0 END) AS dislike_horizon,
|
|
60
|
+
SUM(CASE WHEN interaction = 'dislike' AND created_at >= (UTC_TIMESTAMP() - INTERVAL ${safeBaselineDays} DAY) THEN 1 ELSE 0 END) AS dislike_baseline
|
|
61
|
+
FROM ${TABLES.STICKER_PACK_INTERACTION_EVENT}
|
|
62
|
+
WHERE pack_id IN (${placeholders})
|
|
63
|
+
GROUP BY pack_id`,
|
|
64
|
+
ids,
|
|
65
|
+
connection,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const byPackId = new Map();
|
|
69
|
+
for (const row of rows) {
|
|
70
|
+
byPackId.set(row.pack_id, {
|
|
71
|
+
pack_id: row.pack_id,
|
|
72
|
+
open_horizon: Number(row.open_horizon || 0),
|
|
73
|
+
open_baseline: Number(row.open_baseline || 0),
|
|
74
|
+
like_horizon: Number(row.like_horizon || 0),
|
|
75
|
+
like_baseline: Number(row.like_baseline || 0),
|
|
76
|
+
dislike_horizon: Number(row.dislike_horizon || 0),
|
|
77
|
+
dislike_baseline: Number(row.dislike_baseline || 0),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return byPackId;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function listViewerRecentPackIds(
|
|
84
|
+
viewerKey,
|
|
85
|
+
{ days = 30, limit = 120 } = {},
|
|
86
|
+
connection = null,
|
|
87
|
+
) {
|
|
88
|
+
const normalizedViewer = sanitizeKey(viewerKey);
|
|
89
|
+
if (!normalizedViewer) return [];
|
|
90
|
+
|
|
91
|
+
const safeDays = clamp(days, 30, 1, 180);
|
|
92
|
+
const safeLimit = clamp(limit, 120, 5, 500);
|
|
93
|
+
const rows = await executeQuery(
|
|
94
|
+
`SELECT pack_id, COUNT(*) AS interactions, MAX(created_at) AS last_interaction_at
|
|
95
|
+
FROM ${TABLES.STICKER_PACK_INTERACTION_EVENT}
|
|
96
|
+
WHERE actor_key = ?
|
|
97
|
+
AND created_at >= (UTC_TIMESTAMP() - INTERVAL ${safeDays} DAY)
|
|
98
|
+
GROUP BY pack_id
|
|
99
|
+
ORDER BY interactions DESC, last_interaction_at DESC
|
|
100
|
+
LIMIT ${safeLimit}`,
|
|
101
|
+
[normalizedViewer],
|
|
102
|
+
connection,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return rows.map((row) => ({
|
|
106
|
+
pack_id: row.pack_id,
|
|
107
|
+
interactions: Number(row.interactions || 0),
|
|
108
|
+
last_interaction_at: row.last_interaction_at || null,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Faz parse resiliente de JSON vindo do banco.
|
|
5
|
+
*
|
|
6
|
+
* @param {unknown} value Valor bruto.
|
|
7
|
+
* @param {unknown} [fallback=null] Valor fallback em caso de erro.
|
|
8
|
+
* @returns {unknown} Valor convertido ou fallback.
|
|
9
|
+
*/
|
|
10
|
+
const parseJson = (value, fallback = null) => {
|
|
11
|
+
if (value === null || value === undefined) return fallback;
|
|
12
|
+
if (typeof value === 'object') return value;
|
|
13
|
+
if (Buffer.isBuffer(value)) {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(value.toString('utf8'));
|
|
16
|
+
} catch {
|
|
17
|
+
return fallback;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (typeof value === 'string') {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(value);
|
|
24
|
+
} catch {
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return fallback;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Normaliza um item do pack com dados opcionais de asset.
|
|
34
|
+
*
|
|
35
|
+
* @param {Record<string, unknown>|null|undefined} row Linha crua retornada da query.
|
|
36
|
+
* @returns {object|null} Item normalizado.
|
|
37
|
+
*/
|
|
38
|
+
const normalizeItemRow = (row) => {
|
|
39
|
+
if (!row) return null;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
id: row.id,
|
|
43
|
+
pack_id: row.pack_id,
|
|
44
|
+
sticker_id: row.sticker_id,
|
|
45
|
+
position: Number(row.position || 0),
|
|
46
|
+
emojis: parseJson(row.emojis, []),
|
|
47
|
+
accessibility_label: row.accessibility_label || null,
|
|
48
|
+
created_at: row.created_at,
|
|
49
|
+
asset: row.asset_id
|
|
50
|
+
? {
|
|
51
|
+
id: row.asset_id,
|
|
52
|
+
owner_jid: row.asset_owner_jid,
|
|
53
|
+
sha256: row.asset_sha256,
|
|
54
|
+
mimetype: row.asset_mimetype,
|
|
55
|
+
is_animated: row.asset_is_animated === 1 || row.asset_is_animated === true,
|
|
56
|
+
width: row.asset_width !== null && row.asset_width !== undefined ? Number(row.asset_width) : null,
|
|
57
|
+
height: row.asset_height !== null && row.asset_height !== undefined ? Number(row.asset_height) : null,
|
|
58
|
+
size_bytes:
|
|
59
|
+
row.asset_size_bytes !== null && row.asset_size_bytes !== undefined ? Number(row.asset_size_bytes) : 0,
|
|
60
|
+
storage_path: row.asset_storage_path,
|
|
61
|
+
created_at: row.asset_created_at,
|
|
62
|
+
}
|
|
63
|
+
: null,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Lista itens de um pack em ordem de posição.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} packId ID do pack.
|
|
71
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
72
|
+
* @returns {Promise<object[]>} Itens do pack.
|
|
73
|
+
*/
|
|
74
|
+
export async function listStickerPackItems(packId, connection = null) {
|
|
75
|
+
const rows = await executeQuery(
|
|
76
|
+
`SELECT
|
|
77
|
+
i.*,
|
|
78
|
+
a.id AS asset_id,
|
|
79
|
+
a.owner_jid AS asset_owner_jid,
|
|
80
|
+
a.sha256 AS asset_sha256,
|
|
81
|
+
a.mimetype AS asset_mimetype,
|
|
82
|
+
a.is_animated AS asset_is_animated,
|
|
83
|
+
a.width AS asset_width,
|
|
84
|
+
a.height AS asset_height,
|
|
85
|
+
a.size_bytes AS asset_size_bytes,
|
|
86
|
+
a.storage_path AS asset_storage_path,
|
|
87
|
+
a.created_at AS asset_created_at
|
|
88
|
+
FROM ${TABLES.STICKER_PACK_ITEM} i
|
|
89
|
+
LEFT JOIN ${TABLES.STICKER_ASSET} a ON a.id = i.sticker_id
|
|
90
|
+
WHERE i.pack_id = ?
|
|
91
|
+
ORDER BY i.position ASC`,
|
|
92
|
+
[packId],
|
|
93
|
+
connection,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return rows.map((row) => normalizeItemRow(row));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Lista itens de múltiplos packs em uma única consulta.
|
|
101
|
+
*
|
|
102
|
+
* @param {string[]} packIds IDs dos packs.
|
|
103
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null]
|
|
104
|
+
* @returns {Promise<object[]>}
|
|
105
|
+
*/
|
|
106
|
+
export async function listStickerPackItemsByPackIds(packIds, connection = null) {
|
|
107
|
+
if (!Array.isArray(packIds) || packIds.length === 0) return [];
|
|
108
|
+
const uniquePackIds = Array.from(new Set(packIds.filter(Boolean)));
|
|
109
|
+
if (!uniquePackIds.length) return [];
|
|
110
|
+
|
|
111
|
+
const placeholders = uniquePackIds.map(() => '?').join(', ');
|
|
112
|
+
const rows = await executeQuery(
|
|
113
|
+
`SELECT
|
|
114
|
+
i.*,
|
|
115
|
+
a.id AS asset_id,
|
|
116
|
+
a.owner_jid AS asset_owner_jid,
|
|
117
|
+
a.sha256 AS asset_sha256,
|
|
118
|
+
a.mimetype AS asset_mimetype,
|
|
119
|
+
a.is_animated AS asset_is_animated,
|
|
120
|
+
a.width AS asset_width,
|
|
121
|
+
a.height AS asset_height,
|
|
122
|
+
a.size_bytes AS asset_size_bytes,
|
|
123
|
+
a.storage_path AS asset_storage_path,
|
|
124
|
+
a.created_at AS asset_created_at
|
|
125
|
+
FROM ${TABLES.STICKER_PACK_ITEM} i
|
|
126
|
+
LEFT JOIN ${TABLES.STICKER_ASSET} a ON a.id = i.sticker_id
|
|
127
|
+
WHERE i.pack_id IN (${placeholders})
|
|
128
|
+
ORDER BY i.pack_id ASC, i.position ASC`,
|
|
129
|
+
uniquePackIds,
|
|
130
|
+
connection,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
return rows.map((row) => normalizeItemRow(row));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Busca item do pack pelo sticker_id.
|
|
138
|
+
*
|
|
139
|
+
* @param {string} packId ID do pack.
|
|
140
|
+
* @param {string} stickerId ID do asset/sticker.
|
|
141
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
142
|
+
* @returns {Promise<object|null>} Item encontrado.
|
|
143
|
+
*/
|
|
144
|
+
export async function getStickerPackItemByStickerId(packId, stickerId, connection = null) {
|
|
145
|
+
const rows = await executeQuery(
|
|
146
|
+
`SELECT i.* FROM ${TABLES.STICKER_PACK_ITEM} i
|
|
147
|
+
WHERE i.pack_id = ? AND i.sticker_id = ?
|
|
148
|
+
LIMIT 1`,
|
|
149
|
+
[packId, stickerId],
|
|
150
|
+
connection,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return normalizeItemRow(rows?.[0] || null);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Busca item pela posição ordinal no pack.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} packId ID do pack.
|
|
160
|
+
* @param {number} position Posição do item.
|
|
161
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
162
|
+
* @returns {Promise<object|null>} Item encontrado.
|
|
163
|
+
*/
|
|
164
|
+
export async function getStickerPackItemByPosition(packId, position, connection = null) {
|
|
165
|
+
const rows = await executeQuery(
|
|
166
|
+
`SELECT i.* FROM ${TABLES.STICKER_PACK_ITEM} i
|
|
167
|
+
WHERE i.pack_id = ? AND i.position = ?
|
|
168
|
+
LIMIT 1`,
|
|
169
|
+
[packId, position],
|
|
170
|
+
connection,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return normalizeItemRow(rows?.[0] || null);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Conta quantos itens existem em um pack.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} packId ID do pack.
|
|
180
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
181
|
+
* @returns {Promise<number>} Total de itens.
|
|
182
|
+
*/
|
|
183
|
+
export async function countStickerPackItems(packId, connection = null) {
|
|
184
|
+
const rows = await executeQuery(
|
|
185
|
+
`SELECT COUNT(*) AS total FROM ${TABLES.STICKER_PACK_ITEM} WHERE pack_id = ?`,
|
|
186
|
+
[packId],
|
|
187
|
+
connection,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
return Number(rows?.[0]?.total || 0);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Conta quantas referências um sticker possui em todos os packs.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} stickerId ID do sticker/asset.
|
|
197
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
198
|
+
* @returns {Promise<number>} Total de referências em packs.
|
|
199
|
+
*/
|
|
200
|
+
export async function countStickerPackItemRefsByStickerId(stickerId, connection = null) {
|
|
201
|
+
const rows = await executeQuery(
|
|
202
|
+
`SELECT COUNT(*) AS total FROM ${TABLES.STICKER_PACK_ITEM} WHERE sticker_id = ?`,
|
|
203
|
+
[stickerId],
|
|
204
|
+
connection,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
return Number(rows?.[0]?.total || 0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Obtém a maior posição atualmente usada no pack.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} packId ID do pack.
|
|
214
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
215
|
+
* @returns {Promise<number>} Maior posição encontrada.
|
|
216
|
+
*/
|
|
217
|
+
export async function getMaxStickerPackPosition(packId, connection = null) {
|
|
218
|
+
const rows = await executeQuery(
|
|
219
|
+
`SELECT MAX(position) AS max_position FROM ${TABLES.STICKER_PACK_ITEM} WHERE pack_id = ?`,
|
|
220
|
+
[packId],
|
|
221
|
+
connection,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const maxValue = rows?.[0]?.max_position;
|
|
225
|
+
return maxValue !== null && maxValue !== undefined ? Number(maxValue) : 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Cria um novo item dentro de um pack.
|
|
230
|
+
*
|
|
231
|
+
* @param {object} item Dados do item.
|
|
232
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
233
|
+
* @returns {Promise<object|null>} Item criado.
|
|
234
|
+
*/
|
|
235
|
+
export async function createStickerPackItem(item, connection = null) {
|
|
236
|
+
await executeQuery(
|
|
237
|
+
`INSERT INTO ${TABLES.STICKER_PACK_ITEM}
|
|
238
|
+
(id, pack_id, sticker_id, position, emojis, accessibility_label)
|
|
239
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
240
|
+
[
|
|
241
|
+
item.id,
|
|
242
|
+
item.pack_id,
|
|
243
|
+
item.sticker_id,
|
|
244
|
+
item.position,
|
|
245
|
+
item.emojis ? JSON.stringify(item.emojis) : JSON.stringify([]),
|
|
246
|
+
item.accessibility_label ?? null,
|
|
247
|
+
],
|
|
248
|
+
connection,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
return getStickerPackItemByStickerId(item.pack_id, item.sticker_id, connection);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Atualiza metadados do item (emojis/acessibilidade).
|
|
256
|
+
*
|
|
257
|
+
* @param {string} packId ID do pack.
|
|
258
|
+
* @param {string} stickerId ID do sticker.
|
|
259
|
+
* @param {Record<string, unknown>} fields Campos alteráveis.
|
|
260
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
261
|
+
* @returns {Promise<object|null>} Item atualizado.
|
|
262
|
+
*/
|
|
263
|
+
export async function updateStickerPackItemMetadata(packId, stickerId, fields, connection = null) {
|
|
264
|
+
const clauses = [];
|
|
265
|
+
const params = [];
|
|
266
|
+
|
|
267
|
+
if ('emojis' in fields) {
|
|
268
|
+
clauses.push('emojis = ?');
|
|
269
|
+
params.push(fields.emojis ? JSON.stringify(fields.emojis) : JSON.stringify([]));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if ('accessibility_label' in fields) {
|
|
273
|
+
clauses.push('accessibility_label = ?');
|
|
274
|
+
params.push(fields.accessibility_label ?? null);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!clauses.length) {
|
|
278
|
+
return getStickerPackItemByStickerId(packId, stickerId, connection);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
await executeQuery(
|
|
282
|
+
`UPDATE ${TABLES.STICKER_PACK_ITEM}
|
|
283
|
+
SET ${clauses.join(', ')}
|
|
284
|
+
WHERE pack_id = ? AND sticker_id = ?`,
|
|
285
|
+
[...params, packId, stickerId],
|
|
286
|
+
connection,
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
return getStickerPackItemByStickerId(packId, stickerId, connection);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Remove item por sticker_id e retorna o item removido.
|
|
294
|
+
*
|
|
295
|
+
* @param {string} packId ID do pack.
|
|
296
|
+
* @param {string} stickerId ID do sticker.
|
|
297
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
298
|
+
* @returns {Promise<object|null>} Item removido.
|
|
299
|
+
*/
|
|
300
|
+
export async function removeStickerPackItemByStickerId(packId, stickerId, connection = null) {
|
|
301
|
+
const item = await getStickerPackItemByStickerId(packId, stickerId, connection);
|
|
302
|
+
if (!item) return null;
|
|
303
|
+
|
|
304
|
+
await executeQuery(
|
|
305
|
+
`DELETE FROM ${TABLES.STICKER_PACK_ITEM}
|
|
306
|
+
WHERE pack_id = ? AND sticker_id = ?`,
|
|
307
|
+
[packId, stickerId],
|
|
308
|
+
connection,
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
return item;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Remove item por posição e retorna o item removido.
|
|
316
|
+
*
|
|
317
|
+
* @param {string} packId ID do pack.
|
|
318
|
+
* @param {number} position Posição do item.
|
|
319
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
320
|
+
* @returns {Promise<object|null>} Item removido.
|
|
321
|
+
*/
|
|
322
|
+
export async function removeStickerPackItemByPosition(packId, position, connection = null) {
|
|
323
|
+
const item = await getStickerPackItemByPosition(packId, position, connection);
|
|
324
|
+
if (!item) return null;
|
|
325
|
+
|
|
326
|
+
await executeQuery(
|
|
327
|
+
`DELETE FROM ${TABLES.STICKER_PACK_ITEM}
|
|
328
|
+
WHERE pack_id = ? AND position = ?`,
|
|
329
|
+
[packId, position],
|
|
330
|
+
connection,
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
return item;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Remove todos os itens de um pack.
|
|
338
|
+
*
|
|
339
|
+
* @param {string} packId ID do pack.
|
|
340
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
341
|
+
* @returns {Promise<number>} Quantidade de linhas removidas.
|
|
342
|
+
*/
|
|
343
|
+
export async function removeStickerPackItemsByPackId(packId, connection = null) {
|
|
344
|
+
const result = await executeQuery(
|
|
345
|
+
`DELETE FROM ${TABLES.STICKER_PACK_ITEM}
|
|
346
|
+
WHERE pack_id = ?`,
|
|
347
|
+
[packId],
|
|
348
|
+
connection,
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
return Number(result?.affectedRows || 0);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Reordena automaticamente os itens após remover uma posição.
|
|
356
|
+
*
|
|
357
|
+
* @param {string} packId ID do pack.
|
|
358
|
+
* @param {number} removedPosition Posição removida.
|
|
359
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
360
|
+
* @returns {Promise<void>}
|
|
361
|
+
*/
|
|
362
|
+
export async function shiftStickerPackPositionsAfter(packId, removedPosition, connection = null) {
|
|
363
|
+
const SHIFT_OFFSET = 10000;
|
|
364
|
+
|
|
365
|
+
// Fase 1: move posições para uma faixa temporária, evitando colisão no índice único (pack_id, position).
|
|
366
|
+
await executeQuery(
|
|
367
|
+
`UPDATE ${TABLES.STICKER_PACK_ITEM}
|
|
368
|
+
SET position = position + ?
|
|
369
|
+
WHERE pack_id = ? AND position > ?`,
|
|
370
|
+
[SHIFT_OFFSET, packId, removedPosition],
|
|
371
|
+
connection,
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
// Fase 2: normaliza removendo o offset e deslocando 1 posição para preencher o "gap" do item removido.
|
|
375
|
+
await executeQuery(
|
|
376
|
+
`UPDATE ${TABLES.STICKER_PACK_ITEM}
|
|
377
|
+
SET position = position - ?
|
|
378
|
+
WHERE pack_id = ? AND position > ?`,
|
|
379
|
+
[SHIFT_OFFSET + 1, packId, removedPosition + SHIFT_OFFSET],
|
|
380
|
+
connection,
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Aplica uma nova ordem explícita para os stickers do pack.
|
|
386
|
+
*
|
|
387
|
+
* @param {string} packId ID do pack.
|
|
388
|
+
* @param {string[]} orderedStickerIds IDs na ordem desejada.
|
|
389
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
390
|
+
* @returns {Promise<void>}
|
|
391
|
+
*/
|
|
392
|
+
export async function bulkUpdateStickerPackPositions(packId, orderedStickerIds, connection = null) {
|
|
393
|
+
if (!Array.isArray(orderedStickerIds) || orderedStickerIds.length === 0) return;
|
|
394
|
+
|
|
395
|
+
await executeQuery(
|
|
396
|
+
`UPDATE ${TABLES.STICKER_PACK_ITEM}
|
|
397
|
+
SET position = position + 10000
|
|
398
|
+
WHERE pack_id = ?`,
|
|
399
|
+
[packId],
|
|
400
|
+
connection,
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
for (let index = 0; index < orderedStickerIds.length; index += 1) {
|
|
404
|
+
const stickerId = orderedStickerIds[index];
|
|
405
|
+
await executeQuery(
|
|
406
|
+
`UPDATE ${TABLES.STICKER_PACK_ITEM}
|
|
407
|
+
SET position = ?
|
|
408
|
+
WHERE pack_id = ? AND sticker_id = ?`,
|
|
409
|
+
[index + 1, packId, stickerId],
|
|
410
|
+
connection,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Clona todos os itens de um pack para outro.
|
|
417
|
+
*
|
|
418
|
+
* @param {string} sourcePackId Pack de origem.
|
|
419
|
+
* @param {string} targetPackId Pack de destino.
|
|
420
|
+
* @param {import('mysql2/promise').PoolConnection|null} [connection=null] Conexão transacional opcional.
|
|
421
|
+
* @returns {Promise<void>}
|
|
422
|
+
*/
|
|
423
|
+
export async function cloneStickerPackItems(sourcePackId, targetPackId, connection = null) {
|
|
424
|
+
const items = await listStickerPackItems(sourcePackId, connection);
|
|
425
|
+
for (const item of items) {
|
|
426
|
+
await executeQuery(
|
|
427
|
+
`INSERT INTO ${TABLES.STICKER_PACK_ITEM}
|
|
428
|
+
(id, pack_id, sticker_id, position, emojis, accessibility_label)
|
|
429
|
+
VALUES (UUID(), ?, ?, ?, ?, ?)`,
|
|
430
|
+
[
|
|
431
|
+
targetPackId,
|
|
432
|
+
item.sticker_id,
|
|
433
|
+
item.position,
|
|
434
|
+
item.emojis ? JSON.stringify(item.emojis) : JSON.stringify([]),
|
|
435
|
+
item.accessibility_label ?? null,
|
|
436
|
+
],
|
|
437
|
+
connection,
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|