@kaikybrofc/omnizap-system 2.3.4 → 2.3.6
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 +541 -468
- package/.github/workflows/codeql.yml +101 -0
- package/README.md +14 -14
- package/app/modules/stickerModule/stickerCommand.js +1 -6
- package/app/modules/stickerModule/stickerTextCommand.js +1 -6
- package/ml/clip_classifier/requirements.txt +2 -2
- package/package.json +1 -1
- package/public/index.html +8 -8
- package/public/js/apps/homeApp.js +22 -18
- package/public/js/apps/loginApp.js +3 -1
- package/public/js/apps/stickersApp.js +145 -319
- package/public/js/apps/userProfileApp.js +0 -9
- package/public/user/index.html +224 -120
- package/server/controllers/admin/adminBanService.js +138 -0
- package/server/controllers/admin/adminPanelHandlers.js +1965 -0
- package/server/controllers/{systemAdminController.js → admin/systemAdminController.js} +2 -2
- package/server/controllers/{stickerCatalogController.js → sticker/stickerCatalogController.js} +129 -2116
- package/server/controllers/userController.js +1 -1
- package/server/routes/admin/systemAdminRouter.js +1 -1
- package/server/routes/indexRouter.js +3 -3
- package/server/routes/{stickerCatalog → sticker}/stickerApiRouter.js +1 -1
- package/server/routes/{stickerCatalog → sticker}/stickerDataRouter.js +1 -1
- package/server/routes/{stickerCatalog → sticker}/stickerSiteRouter.js +1 -1
- /package/server/controllers/{stickerCatalog → sticker}/nonCatalogHandlers.js +0 -0
- /package/server/routes/{stickerCatalog → sticker}/catalogHandlers/catalogAdminHttp.js +0 -0
- /package/server/routes/{stickerCatalog → sticker}/catalogHandlers/catalogAuthHttp.js +0 -0
- /package/server/routes/{stickerCatalog → sticker}/catalogHandlers/catalogPublicHttp.js +0 -0
- /package/server/routes/{stickerCatalog → sticker}/catalogHandlers/catalogUploadHttp.js +0 -0
- /package/server/routes/{stickerCatalog → sticker}/catalogRouter.js +0 -0
package/server/controllers/{stickerCatalogController.js → sticker/stickerCatalogController.js}
RENAMED
|
@@ -1,36 +1,38 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { createHash,
|
|
4
|
-
import { URL
|
|
5
|
-
|
|
6
|
-
import { executeQuery, pool, TABLES } from '
|
|
7
|
-
import { getJidUser, normalizeJid, resolveBotJid } from '
|
|
8
|
-
import { getAdminPhone, getAdminRawValue, resolveAdminJid } from '
|
|
9
|
-
import { getActiveSocket } from '
|
|
10
|
-
import { extractUserIdInfo, resolveUserId } from '
|
|
11
|
-
import { resolveWhatsAppOwnerJidFromLoginPayload, toWhatsAppOwnerJid, toWhatsAppPhoneDigits } from '
|
|
12
|
-
import logger from '
|
|
13
|
-
import { getSystemMetrics } from '
|
|
14
|
-
import { listStickerPacksForCatalog, findStickerPackByPackKey, listStickerPacksByOwner, bumpStickerPackVersion, findStickerPackByOwnerAndIdentifier, softDeleteStickerPack, updateStickerPackFields } from '
|
|
15
|
-
import { listStickerPackItems, countStickerPackItemRefsByStickerId, createStickerPackItem, getStickerPackItemByStickerId, removeStickerPackItemByStickerId, removeStickerPackItemsByPackId } from '
|
|
16
|
-
import { listClassifiedStickerAssetsWithoutPack, listStickerAssetsWithoutPack, deleteStickerAssetById, findStickerAssetsByIds } from '
|
|
17
|
-
import { deleteStickerAssetClassificationByAssetId, findStickerClassificationByAssetId, listStickerClassificationsByAssetIds } from '
|
|
18
|
-
import { decoratePackClassificationSummary, decorateStickerClassification, getPackClassificationSummaryByAssetIds } from '
|
|
19
|
-
import { getEmptyStickerPackEngagement, getStickerPackEngagementByPackId, incrementStickerPackDislike, incrementStickerPackLike, incrementStickerPackOpen, listStickerPackEngagementByPackIds } from '
|
|
20
|
-
import { createStickerPackInteractionEvent, listStickerPackInteractionStatsByPackIds, listViewerRecentPackIds } from '
|
|
21
|
-
import { buildCreatorRanking, buildIntentCollections, buildPersonalizedRecommendations, buildViewerTagAffinity, computePackSignals } from '
|
|
22
|
-
import { listStickerPackScoreSnapshotsByPackIds } from '
|
|
23
|
-
import { createCatalogApiRouter } from '
|
|
24
|
-
import { createStickerCatalogNonCatalogHandlers } from './
|
|
25
|
-
import { createGoogleWebAuthService } from '
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import
|
|
32
|
-
import {
|
|
33
|
-
import
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
+
import { URL } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import { executeQuery, pool, TABLES } from '../../../database/index.js';
|
|
7
|
+
import { getJidUser, normalizeJid, resolveBotJid } from '../../../app/config/baileysConfig.js';
|
|
8
|
+
import { getAdminPhone, getAdminRawValue, resolveAdminJid } from '../../../app/config/adminIdentity.js';
|
|
9
|
+
import { getActiveSocket } from '../../../app/services/socketState.js';
|
|
10
|
+
import { extractUserIdInfo, resolveUserId } from '../../../app/services/lidMapService.js';
|
|
11
|
+
import { resolveWhatsAppOwnerJidFromLoginPayload, toWhatsAppOwnerJid, toWhatsAppPhoneDigits } from '../../../app/services/whatsappLoginLinkService.js';
|
|
12
|
+
import logger from '../../../app/utils/logger/loggerModule.js';
|
|
13
|
+
import { getSystemMetrics } from '../../../app/utils/systemMetrics/systemMetricsModule.js';
|
|
14
|
+
import { listStickerPacksForCatalog, findStickerPackByPackKey, listStickerPacksByOwner, bumpStickerPackVersion, findStickerPackByOwnerAndIdentifier, softDeleteStickerPack, updateStickerPackFields } from '../../../app/modules/stickerPackModule/stickerPackRepository.js';
|
|
15
|
+
import { listStickerPackItems, countStickerPackItemRefsByStickerId, createStickerPackItem, getStickerPackItemByStickerId, removeStickerPackItemByStickerId, removeStickerPackItemsByPackId } from '../../../app/modules/stickerPackModule/stickerPackItemRepository.js';
|
|
16
|
+
import { listClassifiedStickerAssetsWithoutPack, listStickerAssetsWithoutPack, deleteStickerAssetById, findStickerAssetsByIds } from '../../../app/modules/stickerPackModule/stickerAssetRepository.js';
|
|
17
|
+
import { deleteStickerAssetClassificationByAssetId, findStickerClassificationByAssetId, listStickerClassificationsByAssetIds } from '../../../app/modules/stickerPackModule/stickerAssetClassificationRepository.js';
|
|
18
|
+
import { decoratePackClassificationSummary, decorateStickerClassification, getPackClassificationSummaryByAssetIds } from '../../../app/modules/stickerPackModule/stickerClassificationService.js';
|
|
19
|
+
import { getEmptyStickerPackEngagement, getStickerPackEngagementByPackId, incrementStickerPackDislike, incrementStickerPackLike, incrementStickerPackOpen, listStickerPackEngagementByPackIds } from '../../../app/modules/stickerPackModule/stickerPackEngagementRepository.js';
|
|
20
|
+
import { createStickerPackInteractionEvent, listStickerPackInteractionStatsByPackIds, listViewerRecentPackIds } from '../../../app/modules/stickerPackModule/stickerPackInteractionEventRepository.js';
|
|
21
|
+
import { buildCreatorRanking, buildIntentCollections, buildPersonalizedRecommendations, buildViewerTagAffinity, computePackSignals } from '../../../app/modules/stickerPackModule/stickerPackMarketplaceService.js';
|
|
22
|
+
import { listStickerPackScoreSnapshotsByPackIds } from '../../../app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js';
|
|
23
|
+
import { createCatalogApiRouter } from '../../routes/sticker/catalogRouter.js';
|
|
24
|
+
import { createStickerCatalogNonCatalogHandlers } from './nonCatalogHandlers.js';
|
|
25
|
+
import { createGoogleWebAuthService } from '../../auth/googleWebAuth/googleWebAuthService.js';
|
|
26
|
+
import { createStickerCatalogAdminBanService } from '../admin/adminBanService.js';
|
|
27
|
+
import { createStickerCatalogAdminHandlers } from '../admin/adminPanelHandlers.js';
|
|
28
|
+
import { buildAdminMenu, buildAiMenu, buildAnimeMenu, buildMediaMenu, buildMenuCaption, buildQuoteMenu, buildStatsMenu, buildStickerMenu } from '../../../app/modules/menuModule/common.js';
|
|
29
|
+
import { getMarketplaceDriftSnapshot } from '../../../app/modules/stickerPackModule/stickerMarketplaceDriftService.js';
|
|
30
|
+
import { getStickerAssetExternalUrl, getStickerStorageConfig, readStickerAssetBuffer, saveStickerAssetFromBuffer } from '../../../app/modules/stickerPackModule/stickerStorageService.js';
|
|
31
|
+
import { convertToWebp } from '../../../app/modules/stickerModule/convertToWebp.js';
|
|
32
|
+
import { sanitizeText } from '../../../app/modules/stickerPackModule/stickerPackUtils.js';
|
|
33
|
+
import stickerPackService from '../../../app/modules/stickerPackModule/stickerPackServiceRuntime.js';
|
|
34
|
+
import { STICKER_PACK_ERROR_CODES, StickerPackError } from '../../../app/modules/stickerPackModule/stickerPackErrors.js';
|
|
35
|
+
import { getFeatureFlagsSnapshot, isFeatureEnabled, refreshFeatureFlags } from '../../../app/services/featureFlagService.js';
|
|
34
36
|
|
|
35
37
|
const parseEnvBool = (value, fallback) => {
|
|
36
38
|
if (value === undefined || value === null || value === '') return fallback;
|
|
@@ -127,13 +129,6 @@ const PACK_CREATE_MAX_ITEMS = Math.max(1, Number(process.env.STICKER_PACK_MAX_IT
|
|
|
127
129
|
const PACK_CREATE_MAX_PACKS_PER_OWNER = parseMaxPacksPerOwnerLimit(process.env.STICKER_PACK_MAX_PACKS_PER_OWNER, 50);
|
|
128
130
|
const PACK_WEB_EDIT_TOKEN_TTL_MS = Math.max(60_000, Number(process.env.STICKER_WEB_EDIT_TOKEN_TTL_MS) || 6 * 60 * 60 * 1000);
|
|
129
131
|
const STICKER_WEB_GOOGLE_CLIENT_ID = String(process.env.STICKER_WEB_GOOGLE_CLIENT_ID || '').trim();
|
|
130
|
-
const ADMIN_PANEL_EMAIL = String(process.env.ADM_EMAIL || '')
|
|
131
|
-
.trim()
|
|
132
|
-
.toLowerCase();
|
|
133
|
-
const ADMIN_PANEL_PASSWORD = String(process.env.ADM_PANEL_PASSWORD || process.env.ADM_PANEL || '').trim();
|
|
134
|
-
const ADMIN_PANEL_ENABLED = Boolean(ADMIN_PANEL_EMAIL && ADMIN_PANEL_PASSWORD);
|
|
135
|
-
const ADMIN_PANEL_SESSION_TTL_MS = Math.max(10 * 60 * 1000, Number(process.env.ADM_PANEL_SESSION_TTL_MS) || 12 * 60 * 60 * 1000);
|
|
136
|
-
const ADMIN_MODERATOR_PASSWORD_MIN_LENGTH = Math.max(6, Number(process.env.ADM_MODERATOR_PASSWORD_MIN_LENGTH) || 8);
|
|
137
132
|
const STICKER_WEB_GOOGLE_AUTH_REQUIRED = parseEnvBool(process.env.STICKER_WEB_GOOGLE_AUTH_REQUIRED, Boolean(STICKER_WEB_GOOGLE_CLIENT_ID));
|
|
138
133
|
const STICKER_WEB_GOOGLE_SESSION_TTL_MS = Math.max(5 * 60 * 1000, Number(process.env.STICKER_WEB_GOOGLE_SESSION_TTL_MS) || 7 * 24 * 60 * 60 * 1000);
|
|
139
134
|
const STICKER_CATALOG_ONLY_CLASSIFIED = parseEnvBool(process.env.STICKER_CATALOG_ONLY_CLASSIFIED, true);
|
|
@@ -186,8 +181,6 @@ const { maxStickerBytes: MAX_STICKER_UPLOAD_BYTES } = getStickerStorageConfig();
|
|
|
186
181
|
const MAX_STICKER_SOURCE_UPLOAD_BYTES = Math.max(MAX_STICKER_UPLOAD_BYTES, Number(process.env.STICKER_WEB_UPLOAD_SOURCE_MAX_BYTES) || 20 * 1024 * 1024);
|
|
187
182
|
const ALLOWED_WEB_UPLOAD_VIDEO_MIMETYPES = new Set(['video/mp4', 'video/webm', 'video/quicktime', 'video/x-m4v']);
|
|
188
183
|
const webPackEditTokenMap = new Map();
|
|
189
|
-
const adminPanelSessionMap = new Map();
|
|
190
|
-
const ADMIN_PANEL_SESSION_COOKIE_NAME = 'omnizap_admin_panel_session';
|
|
191
184
|
const WEB_VISITOR_COOKIE_NAME = 'omnizap_vid';
|
|
192
185
|
const WEB_SESSION_COOKIE_NAME = 'omnizap_sid';
|
|
193
186
|
const PACK_WEB_STATUS_VALUES = new Set(['draft', 'uploading', 'processing', 'published', 'failed']);
|
|
@@ -203,7 +196,6 @@ const staleDraftCleanupState = {
|
|
|
203
196
|
running: false,
|
|
204
197
|
lastRunAt: 0,
|
|
205
198
|
};
|
|
206
|
-
let adminPanelSessionPruneAt = 0;
|
|
207
199
|
|
|
208
200
|
const hasPathPrefix = (pathname, prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`);
|
|
209
201
|
const isPackPubliclyVisible = (pack) => {
|
|
@@ -488,52 +480,6 @@ const normalizeEmail = (value) =>
|
|
|
488
480
|
.trim()
|
|
489
481
|
.toLowerCase()
|
|
490
482
|
.slice(0, 255);
|
|
491
|
-
const constantTimeStringEqual = (a, b) => {
|
|
492
|
-
const left = Buffer.from(String(a || ''), 'utf8');
|
|
493
|
-
const right = Buffer.from(String(b || ''), 'utf8');
|
|
494
|
-
if (left.length !== right.length) return false;
|
|
495
|
-
try {
|
|
496
|
-
return timingSafeEqual(left, right);
|
|
497
|
-
} catch {
|
|
498
|
-
return false;
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
const normalizeAdminPanelRole = (value, fallback = 'owner') => {
|
|
502
|
-
const normalized = String(value || '')
|
|
503
|
-
.trim()
|
|
504
|
-
.toLowerCase();
|
|
505
|
-
if (normalized === 'moderator') return 'moderator';
|
|
506
|
-
if (normalized === 'owner') return 'owner';
|
|
507
|
-
return fallback;
|
|
508
|
-
};
|
|
509
|
-
const hashAdminModeratorPassword = (password) => {
|
|
510
|
-
const normalized = String(password || '');
|
|
511
|
-
const salt = randomBytes(16).toString('hex');
|
|
512
|
-
const hash = scryptSync(normalized, salt, 64).toString('hex');
|
|
513
|
-
return `scrypt$${salt}$${hash}`;
|
|
514
|
-
};
|
|
515
|
-
const verifyAdminModeratorPassword = (password, encodedHash) => {
|
|
516
|
-
const raw = String(encodedHash || '').trim();
|
|
517
|
-
if (!raw) return false;
|
|
518
|
-
const parts = raw.split('$');
|
|
519
|
-
if (parts.length !== 3 || parts[0] !== 'scrypt') return false;
|
|
520
|
-
const salt = String(parts[1] || '').trim();
|
|
521
|
-
const expectedHex = String(parts[2] || '').trim();
|
|
522
|
-
if (!salt || !expectedHex) return false;
|
|
523
|
-
let expectedBuffer;
|
|
524
|
-
try {
|
|
525
|
-
expectedBuffer = Buffer.from(expectedHex, 'hex');
|
|
526
|
-
} catch {
|
|
527
|
-
return false;
|
|
528
|
-
}
|
|
529
|
-
if (!expectedBuffer.length) return false;
|
|
530
|
-
const derived = scryptSync(String(password || ''), salt, expectedBuffer.length);
|
|
531
|
-
try {
|
|
532
|
-
return timingSafeEqual(expectedBuffer, derived);
|
|
533
|
-
} catch {
|
|
534
|
-
return false;
|
|
535
|
-
}
|
|
536
|
-
};
|
|
537
483
|
|
|
538
484
|
const clampUploadErrorMessage = (message) =>
|
|
539
485
|
String(message || '')
|
|
@@ -878,136 +824,26 @@ const buildGoogleOwnerJid = (googleSub) => {
|
|
|
878
824
|
return normalizeJid(`g${normalizedSub}@google.oauth`) || '';
|
|
879
825
|
};
|
|
880
826
|
|
|
881
|
-
|
|
882
|
-
if (!row || typeof row !== 'object') return null;
|
|
883
|
-
return {
|
|
884
|
-
id: String(row.id || '').trim(),
|
|
885
|
-
google_sub: normalizeGoogleSubject(row.google_sub),
|
|
886
|
-
email: normalizeEmail(row.email),
|
|
887
|
-
owner_jid: normalizeJid(row.owner_jid) || null,
|
|
888
|
-
reason: sanitizeText(row.reason || '', 255, { allowEmpty: true }) || null,
|
|
889
|
-
created_by_google_sub: normalizeGoogleSubject(row.created_by_google_sub),
|
|
890
|
-
created_by_email: normalizeEmail(row.created_by_email),
|
|
891
|
-
created_at: toIsoOrNull(row.created_at),
|
|
892
|
-
updated_at: toIsoOrNull(row.updated_at),
|
|
893
|
-
revoked_at: toIsoOrNull(row.revoked_at),
|
|
894
|
-
};
|
|
895
|
-
};
|
|
896
|
-
|
|
897
|
-
const findActiveAdminBanForIdentity = async ({ googleSub = '', email = '', ownerJid = '' } = {}) => {
|
|
898
|
-
const normalizedSub = normalizeGoogleSubject(googleSub);
|
|
899
|
-
const normalizedEmail = normalizeEmail(email);
|
|
900
|
-
const normalizedOwnerJid = normalizeJid(ownerJid) || '';
|
|
901
|
-
const clauses = [];
|
|
902
|
-
const params = [];
|
|
903
|
-
if (normalizedSub) {
|
|
904
|
-
clauses.push('google_sub = ?');
|
|
905
|
-
params.push(normalizedSub);
|
|
906
|
-
}
|
|
907
|
-
if (normalizedEmail) {
|
|
908
|
-
clauses.push('email = ?');
|
|
909
|
-
params.push(normalizedEmail);
|
|
910
|
-
}
|
|
911
|
-
if (normalizedOwnerJid) {
|
|
912
|
-
clauses.push('owner_jid = ?');
|
|
913
|
-
params.push(normalizedOwnerJid);
|
|
914
|
-
}
|
|
915
|
-
if (!clauses.length) return null;
|
|
916
|
-
|
|
917
|
-
const rows = await executeQuery(
|
|
918
|
-
`SELECT *
|
|
919
|
-
FROM ${TABLES.STICKER_WEB_ADMIN_BAN}
|
|
920
|
-
WHERE revoked_at IS NULL
|
|
921
|
-
AND (${clauses.join(' OR ')})
|
|
922
|
-
ORDER BY created_at DESC
|
|
923
|
-
LIMIT 1`,
|
|
924
|
-
params,
|
|
925
|
-
);
|
|
926
|
-
return mapAdminBanRow(Array.isArray(rows) ? rows[0] : null);
|
|
927
|
-
};
|
|
928
|
-
|
|
929
|
-
const listAdminBans = async ({ activeOnly = false, limit = 100 } = {}) => {
|
|
930
|
-
const safeLimit = Math.max(1, Math.min(500, Number(limit || 100)));
|
|
931
|
-
const rows = await executeQuery(
|
|
932
|
-
`SELECT *
|
|
933
|
-
FROM ${TABLES.STICKER_WEB_ADMIN_BAN}
|
|
934
|
-
${activeOnly ? 'WHERE revoked_at IS NULL' : ''}
|
|
935
|
-
ORDER BY created_at DESC
|
|
936
|
-
LIMIT ${safeLimit}`,
|
|
937
|
-
);
|
|
938
|
-
return (Array.isArray(rows) ? rows : []).map(mapAdminBanRow).filter(Boolean);
|
|
939
|
-
};
|
|
940
|
-
|
|
941
|
-
const createAdminBanRecord = async ({ googleSub = '', email = '', ownerJid = '', reason = '', adminSession = null }) => {
|
|
942
|
-
const normalizedSub = normalizeGoogleSubject(googleSub);
|
|
943
|
-
const normalizedEmail = normalizeEmail(email);
|
|
944
|
-
const normalizedOwnerJid = normalizeJid(ownerJid) || '';
|
|
945
|
-
if (!normalizedSub && !normalizedEmail && !normalizedOwnerJid) {
|
|
946
|
-
const error = new Error('Informe google_sub, email ou owner_jid para banir.');
|
|
947
|
-
error.statusCode = 400;
|
|
948
|
-
throw error;
|
|
949
|
-
}
|
|
950
|
-
const existing = await findActiveAdminBanForIdentity({
|
|
951
|
-
googleSub: normalizedSub,
|
|
952
|
-
email: normalizedEmail,
|
|
953
|
-
ownerJid: normalizedOwnerJid,
|
|
954
|
-
});
|
|
955
|
-
if (existing) return { created: false, ban: existing };
|
|
956
|
-
|
|
957
|
-
const banId = randomUUID();
|
|
958
|
-
await executeQuery(
|
|
959
|
-
`INSERT INTO ${TABLES.STICKER_WEB_ADMIN_BAN}
|
|
960
|
-
(id, google_sub, email, owner_jid, reason, created_by_google_sub, created_by_email)
|
|
961
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
962
|
-
[banId, normalizedSub || null, normalizedEmail || null, normalizedOwnerJid || null, sanitizeText(reason || '', 255, { allowEmpty: true }) || null, normalizeGoogleSubject(adminSession?.googleSub) || null, normalizeEmail(adminSession?.email) || null],
|
|
963
|
-
);
|
|
964
|
-
|
|
965
|
-
if (normalizedSub || normalizedEmail || normalizedOwnerJid) {
|
|
966
|
-
await revokeGoogleWebSessionsByIdentity({
|
|
967
|
-
googleSub: normalizedSub,
|
|
968
|
-
email: normalizedEmail,
|
|
969
|
-
ownerJid: normalizedOwnerJid,
|
|
970
|
-
}).catch(() => {});
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
const rows = await executeQuery(`SELECT * FROM ${TABLES.STICKER_WEB_ADMIN_BAN} WHERE id = ? LIMIT 1`, [banId]);
|
|
974
|
-
return { created: true, ban: mapAdminBanRow(Array.isArray(rows) ? rows[0] : null) };
|
|
975
|
-
};
|
|
827
|
+
let revokeGoogleWebSessionsByIdentityBridge = async () => 0;
|
|
976
828
|
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
WHERE id = ?`,
|
|
988
|
-
[normalizedId],
|
|
989
|
-
);
|
|
990
|
-
const rows = await executeQuery(`SELECT * FROM ${TABLES.STICKER_WEB_ADMIN_BAN} WHERE id = ? LIMIT 1`, [normalizedId]);
|
|
991
|
-
return mapAdminBanRow(Array.isArray(rows) ? rows[0] : null);
|
|
992
|
-
};
|
|
829
|
+
const adminBanService = createStickerCatalogAdminBanService({
|
|
830
|
+
executeQuery,
|
|
831
|
+
tables: TABLES,
|
|
832
|
+
sanitizeText,
|
|
833
|
+
normalizeGoogleSubject,
|
|
834
|
+
normalizeEmail,
|
|
835
|
+
normalizeJid,
|
|
836
|
+
toIsoOrNull,
|
|
837
|
+
revokeGoogleWebSessionsByIdentity: (payload) => revokeGoogleWebSessionsByIdentityBridge(payload),
|
|
838
|
+
});
|
|
993
839
|
|
|
994
|
-
const
|
|
995
|
-
const ban = await findActiveAdminBanForIdentity({ googleSub: sub, email, ownerJid });
|
|
996
|
-
if (!ban) return null;
|
|
997
|
-
const error = new Error('Conta bloqueada pela administracao.');
|
|
998
|
-
error.statusCode = 403;
|
|
999
|
-
error.code = 'ADMIN_BANNED';
|
|
1000
|
-
error.ban = ban;
|
|
1001
|
-
throw error;
|
|
1002
|
-
};
|
|
840
|
+
const { listAdminBans, createAdminBanRecord, revokeAdminBanRecord, assertGoogleIdentityNotBanned } = adminBanService;
|
|
1003
841
|
|
|
1004
842
|
const googleWebSessionDbTouchIntervalMs = Math.max(30_000, Number(process.env.STICKER_WEB_GOOGLE_SESSION_DB_TOUCH_INTERVAL_MS) || 60_000);
|
|
1005
843
|
const googleWebSessionDbPruneIntervalMs = Math.max(5 * 60 * 1000, Number(process.env.STICKER_WEB_GOOGLE_SESSION_DB_PRUNE_INTERVAL_MS) || 60 * 60 * 1000);
|
|
1006
844
|
const configuredGoogleWebSessionCookiePath = normalizeBasePath(process.env.STICKER_WEB_GOOGLE_SESSION_COOKIE_PATH, '/');
|
|
1007
845
|
const googleWebSessionCookiePath = '/';
|
|
1008
|
-
const googleWebLegacyCookiePaths = Array.from(
|
|
1009
|
-
new Set([configuredGoogleWebSessionCookiePath, STICKER_API_BASE_PATH, `${STICKER_API_BASE_PATH}/auth`, STICKER_WEB_PATH, STICKER_LOGIN_WEB_PATH]),
|
|
1010
|
-
);
|
|
846
|
+
const googleWebLegacyCookiePaths = Array.from(new Set([configuredGoogleWebSessionCookiePath, STICKER_API_BASE_PATH, `${STICKER_API_BASE_PATH}/auth`, STICKER_WEB_PATH, STICKER_LOGIN_WEB_PATH]));
|
|
1011
847
|
|
|
1012
848
|
const googleWebAuth = createGoogleWebAuthService({
|
|
1013
849
|
executeQuery,
|
|
@@ -1039,380 +875,7 @@ const googleWebAuth = createGoogleWebAuthService({
|
|
|
1039
875
|
});
|
|
1040
876
|
|
|
1041
877
|
const { upsertGoogleWebUserRecord, resolveGoogleWebSessionFromRequest, mapGoogleSessionResponseData, handleGoogleAuthSessionRequest, revokeGoogleWebSessionsByIdentity } = googleWebAuth;
|
|
1042
|
-
|
|
1043
|
-
const mapAdminModeratorRow = (row) => {
|
|
1044
|
-
if (!row || typeof row !== 'object') return null;
|
|
1045
|
-
return {
|
|
1046
|
-
google_sub: normalizeGoogleSubject(row.google_sub),
|
|
1047
|
-
email: normalizeEmail(row.email),
|
|
1048
|
-
owner_jid: normalizeJid(row.owner_jid) || null,
|
|
1049
|
-
name: sanitizeText(row.name || '', 120, { allowEmpty: true }) || null,
|
|
1050
|
-
created_by_google_sub: normalizeGoogleSubject(row.created_by_google_sub),
|
|
1051
|
-
created_by_email: normalizeEmail(row.created_by_email),
|
|
1052
|
-
updated_by_google_sub: normalizeGoogleSubject(row.updated_by_google_sub),
|
|
1053
|
-
updated_by_email: normalizeEmail(row.updated_by_email),
|
|
1054
|
-
last_login_at: toIsoOrNull(row.last_login_at),
|
|
1055
|
-
created_at: toIsoOrNull(row.created_at),
|
|
1056
|
-
updated_at: toIsoOrNull(row.updated_at),
|
|
1057
|
-
revoked_at: toIsoOrNull(row.revoked_at),
|
|
1058
|
-
active: !row.revoked_at,
|
|
1059
|
-
};
|
|
1060
|
-
};
|
|
1061
|
-
|
|
1062
|
-
const listAdminModerators = async ({ activeOnly = false, limit = 200 } = {}) => {
|
|
1063
|
-
const safeLimit = Math.max(1, Math.min(500, Number(limit || 200)));
|
|
1064
|
-
const rows = await executeQuery(
|
|
1065
|
-
`SELECT google_sub, email, owner_jid, name, created_by_google_sub, created_by_email, updated_by_google_sub, updated_by_email,
|
|
1066
|
-
last_login_at, created_at, updated_at, revoked_at
|
|
1067
|
-
FROM ${TABLES.STICKER_WEB_ADMIN_MODERATOR}
|
|
1068
|
-
${activeOnly ? 'WHERE revoked_at IS NULL' : ''}
|
|
1069
|
-
ORDER BY updated_at DESC
|
|
1070
|
-
LIMIT ${safeLimit}`,
|
|
1071
|
-
);
|
|
1072
|
-
return (Array.isArray(rows) ? rows : []).map(mapAdminModeratorRow).filter(Boolean);
|
|
1073
|
-
};
|
|
1074
|
-
|
|
1075
|
-
const findAdminModeratorByGoogleSub = async (googleSub, { activeOnly = false } = {}) => {
|
|
1076
|
-
const normalizedSub = normalizeGoogleSubject(googleSub);
|
|
1077
|
-
if (!normalizedSub) return null;
|
|
1078
|
-
const rows = await executeQuery(
|
|
1079
|
-
`SELECT *
|
|
1080
|
-
FROM ${TABLES.STICKER_WEB_ADMIN_MODERATOR}
|
|
1081
|
-
WHERE google_sub = ?
|
|
1082
|
-
${activeOnly ? 'AND revoked_at IS NULL' : ''}
|
|
1083
|
-
LIMIT 1`,
|
|
1084
|
-
[normalizedSub],
|
|
1085
|
-
);
|
|
1086
|
-
return Array.isArray(rows) && rows[0] ? rows[0] : null;
|
|
1087
|
-
};
|
|
1088
|
-
|
|
1089
|
-
const resolveKnownGoogleUserForModerator = async ({ googleSub = '', email = '', ownerJid = '' } = {}) => {
|
|
1090
|
-
const normalizedSub = normalizeGoogleSubject(googleSub);
|
|
1091
|
-
const normalizedEmail = normalizeEmail(email);
|
|
1092
|
-
const normalizedOwnerJid = normalizeJid(ownerJid) || '';
|
|
1093
|
-
const clauses = [];
|
|
1094
|
-
const params = [];
|
|
1095
|
-
|
|
1096
|
-
if (normalizedSub) {
|
|
1097
|
-
clauses.push('google_sub = ?');
|
|
1098
|
-
params.push(normalizedSub);
|
|
1099
|
-
}
|
|
1100
|
-
if (normalizedEmail) {
|
|
1101
|
-
clauses.push('email = ?');
|
|
1102
|
-
params.push(normalizedEmail);
|
|
1103
|
-
}
|
|
1104
|
-
if (normalizedOwnerJid) {
|
|
1105
|
-
clauses.push('owner_jid = ?');
|
|
1106
|
-
params.push(normalizedOwnerJid);
|
|
1107
|
-
}
|
|
1108
|
-
if (!clauses.length) return null;
|
|
1109
|
-
|
|
1110
|
-
const rows = await executeQuery(
|
|
1111
|
-
`SELECT google_sub, email, owner_jid, name
|
|
1112
|
-
FROM ${TABLES.STICKER_WEB_GOOGLE_USER}
|
|
1113
|
-
WHERE ${clauses.join(' OR ')}
|
|
1114
|
-
ORDER BY COALESCE(last_seen_at, last_login_at, updated_at, created_at) DESC
|
|
1115
|
-
LIMIT 1`,
|
|
1116
|
-
params,
|
|
1117
|
-
);
|
|
1118
|
-
const row = Array.isArray(rows) ? rows[0] : null;
|
|
1119
|
-
if (!row) return null;
|
|
1120
|
-
const resolvedGoogleSub = normalizeGoogleSubject(row.google_sub);
|
|
1121
|
-
const resolvedEmail = normalizeEmail(row.email);
|
|
1122
|
-
const resolvedOwnerJid = normalizeJid(row.owner_jid) || '';
|
|
1123
|
-
if (!resolvedGoogleSub || !resolvedEmail || !resolvedOwnerJid) return null;
|
|
1124
|
-
return {
|
|
1125
|
-
google_sub: resolvedGoogleSub,
|
|
1126
|
-
email: resolvedEmail,
|
|
1127
|
-
owner_jid: resolvedOwnerJid,
|
|
1128
|
-
name: sanitizeText(row.name || '', 120, { allowEmpty: true }) || null,
|
|
1129
|
-
};
|
|
1130
|
-
};
|
|
1131
|
-
|
|
1132
|
-
const findActiveAdminModeratorForIdentity = async ({ googleSub = '', email = '', ownerJid = '' } = {}) => {
|
|
1133
|
-
const normalizedSub = normalizeGoogleSubject(googleSub);
|
|
1134
|
-
const normalizedEmail = normalizeEmail(email);
|
|
1135
|
-
const normalizedOwnerJid = normalizeJid(ownerJid) || '';
|
|
1136
|
-
const clauses = [];
|
|
1137
|
-
const params = [];
|
|
1138
|
-
if (normalizedSub) {
|
|
1139
|
-
clauses.push('google_sub = ?');
|
|
1140
|
-
params.push(normalizedSub);
|
|
1141
|
-
}
|
|
1142
|
-
if (normalizedEmail) {
|
|
1143
|
-
clauses.push('email = ?');
|
|
1144
|
-
params.push(normalizedEmail);
|
|
1145
|
-
}
|
|
1146
|
-
if (normalizedOwnerJid) {
|
|
1147
|
-
clauses.push('owner_jid = ?');
|
|
1148
|
-
params.push(normalizedOwnerJid);
|
|
1149
|
-
}
|
|
1150
|
-
if (!clauses.length) return null;
|
|
1151
|
-
|
|
1152
|
-
const rows = await executeQuery(
|
|
1153
|
-
`SELECT *
|
|
1154
|
-
FROM ${TABLES.STICKER_WEB_ADMIN_MODERATOR}
|
|
1155
|
-
WHERE revoked_at IS NULL
|
|
1156
|
-
AND (${clauses.join(' OR ')})
|
|
1157
|
-
ORDER BY updated_at DESC
|
|
1158
|
-
LIMIT 1`,
|
|
1159
|
-
params,
|
|
1160
|
-
);
|
|
1161
|
-
return Array.isArray(rows) && rows[0] ? rows[0] : null;
|
|
1162
|
-
};
|
|
1163
|
-
|
|
1164
|
-
const pruneModeratorAdminPanelSessions = ({ googleSub = '', email = '', ownerJid = '' } = {}) => {
|
|
1165
|
-
const normalizedSub = normalizeGoogleSubject(googleSub);
|
|
1166
|
-
const normalizedEmail = normalizeEmail(email);
|
|
1167
|
-
const normalizedOwnerJid = normalizeJid(ownerJid) || '';
|
|
1168
|
-
if (!normalizedSub && !normalizedEmail && !normalizedOwnerJid) return;
|
|
1169
|
-
|
|
1170
|
-
for (const [token, session] of adminPanelSessionMap.entries()) {
|
|
1171
|
-
if (!session || normalizeAdminPanelRole(session.role, 'owner') !== 'moderator') continue;
|
|
1172
|
-
const sessionSub = normalizeGoogleSubject(session.googleSub);
|
|
1173
|
-
const sessionEmail = normalizeEmail(session.email);
|
|
1174
|
-
const sessionOwner = normalizeJid(session.ownerJid) || '';
|
|
1175
|
-
if ((normalizedSub && sessionSub === normalizedSub) || (normalizedEmail && sessionEmail === normalizedEmail) || (normalizedOwnerJid && sessionOwner === normalizedOwnerJid)) {
|
|
1176
|
-
adminPanelSessionMap.delete(token);
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
};
|
|
1180
|
-
|
|
1181
|
-
const upsertAdminModeratorRecord = async ({ googleSub = '', email = '', ownerJid = '', password = '', adminSession = null }) => {
|
|
1182
|
-
const cleanPassword = String(password || '').trim();
|
|
1183
|
-
if (cleanPassword.length < ADMIN_MODERATOR_PASSWORD_MIN_LENGTH) {
|
|
1184
|
-
const error = new Error(`Senha do moderador deve ter no minimo ${ADMIN_MODERATOR_PASSWORD_MIN_LENGTH} caracteres.`);
|
|
1185
|
-
error.statusCode = 400;
|
|
1186
|
-
throw error;
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
const knownUser = await resolveKnownGoogleUserForModerator({ googleSub, email, ownerJid });
|
|
1190
|
-
if (!knownUser?.google_sub) {
|
|
1191
|
-
const error = new Error('Somente usuarios Google logados no site podem virar moderadores.');
|
|
1192
|
-
error.statusCode = 400;
|
|
1193
|
-
throw error;
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
const existing = await findAdminModeratorByGoogleSub(knownUser.google_sub);
|
|
1197
|
-
const passwordHash = hashAdminModeratorPassword(cleanPassword);
|
|
1198
|
-
await executeQuery(
|
|
1199
|
-
`INSERT INTO ${TABLES.STICKER_WEB_ADMIN_MODERATOR}
|
|
1200
|
-
(google_sub, email, owner_jid, name, password_hash, created_by_google_sub, created_by_email, updated_by_google_sub, updated_by_email, revoked_at)
|
|
1201
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)
|
|
1202
|
-
ON DUPLICATE KEY UPDATE
|
|
1203
|
-
email = VALUES(email),
|
|
1204
|
-
owner_jid = VALUES(owner_jid),
|
|
1205
|
-
name = VALUES(name),
|
|
1206
|
-
password_hash = VALUES(password_hash),
|
|
1207
|
-
updated_by_google_sub = VALUES(updated_by_google_sub),
|
|
1208
|
-
updated_by_email = VALUES(updated_by_email),
|
|
1209
|
-
revoked_at = NULL`,
|
|
1210
|
-
[knownUser.google_sub, knownUser.email, knownUser.owner_jid, knownUser.name || null, passwordHash, normalizeGoogleSubject(adminSession?.googleSub) || null, normalizeEmail(adminSession?.email) || null, normalizeGoogleSubject(adminSession?.googleSub) || null, normalizeEmail(adminSession?.email) || null],
|
|
1211
|
-
);
|
|
1212
|
-
|
|
1213
|
-
pruneModeratorAdminPanelSessions({
|
|
1214
|
-
googleSub: knownUser.google_sub,
|
|
1215
|
-
email: knownUser.email,
|
|
1216
|
-
ownerJid: knownUser.owner_jid,
|
|
1217
|
-
});
|
|
1218
|
-
|
|
1219
|
-
const fresh = await findAdminModeratorByGoogleSub(knownUser.google_sub);
|
|
1220
|
-
return {
|
|
1221
|
-
created: !existing,
|
|
1222
|
-
moderator: mapAdminModeratorRow(fresh),
|
|
1223
|
-
};
|
|
1224
|
-
};
|
|
1225
|
-
|
|
1226
|
-
const revokeAdminModeratorRecord = async (googleSub, adminSession = null) => {
|
|
1227
|
-
const normalizedSub = normalizeGoogleSubject(googleSub);
|
|
1228
|
-
if (!normalizedSub) {
|
|
1229
|
-
const error = new Error('google_sub invalido.');
|
|
1230
|
-
error.statusCode = 400;
|
|
1231
|
-
throw error;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
await executeQuery(
|
|
1235
|
-
`UPDATE ${TABLES.STICKER_WEB_ADMIN_MODERATOR}
|
|
1236
|
-
SET revoked_at = COALESCE(revoked_at, UTC_TIMESTAMP()),
|
|
1237
|
-
updated_by_google_sub = ?,
|
|
1238
|
-
updated_by_email = ?
|
|
1239
|
-
WHERE google_sub = ?`,
|
|
1240
|
-
[normalizeGoogleSubject(adminSession?.googleSub) || null, normalizeEmail(adminSession?.email) || null, normalizedSub],
|
|
1241
|
-
);
|
|
1242
|
-
|
|
1243
|
-
const fresh = await findAdminModeratorByGoogleSub(normalizedSub);
|
|
1244
|
-
if (!fresh) {
|
|
1245
|
-
const error = new Error('Moderador nao encontrado.');
|
|
1246
|
-
error.statusCode = 404;
|
|
1247
|
-
throw error;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
pruneModeratorAdminPanelSessions({
|
|
1251
|
-
googleSub: normalizedSub,
|
|
1252
|
-
email: normalizeEmail(fresh.email),
|
|
1253
|
-
ownerJid: normalizeJid(fresh.owner_jid) || '',
|
|
1254
|
-
});
|
|
1255
|
-
|
|
1256
|
-
return mapAdminModeratorRow(fresh);
|
|
1257
|
-
};
|
|
1258
|
-
|
|
1259
|
-
const touchAdminModeratorLastLogin = async (moderatorRow, googleSession) => {
|
|
1260
|
-
const normalizedSub = normalizeGoogleSubject(moderatorRow?.google_sub || googleSession?.sub);
|
|
1261
|
-
if (!normalizedSub) return;
|
|
1262
|
-
await executeQuery(
|
|
1263
|
-
`UPDATE ${TABLES.STICKER_WEB_ADMIN_MODERATOR}
|
|
1264
|
-
SET email = ?,
|
|
1265
|
-
owner_jid = ?,
|
|
1266
|
-
name = ?,
|
|
1267
|
-
last_login_at = UTC_TIMESTAMP(),
|
|
1268
|
-
updated_by_google_sub = ?,
|
|
1269
|
-
updated_by_email = ?
|
|
1270
|
-
WHERE google_sub = ?`,
|
|
1271
|
-
[normalizeEmail(googleSession?.email || moderatorRow?.email) || null, normalizeJid(googleSession?.ownerJid || moderatorRow?.owner_jid) || null, sanitizeText(googleSession?.name || moderatorRow?.name || '', 120, { allowEmpty: true }) || null, normalizeGoogleSubject(googleSession?.sub || moderatorRow?.google_sub) || null, normalizeEmail(googleSession?.email || moderatorRow?.email) || null, normalizedSub],
|
|
1272
|
-
).catch(() => {});
|
|
1273
|
-
};
|
|
1274
|
-
|
|
1275
|
-
const pruneExpiredAdminPanelSessions = () => {
|
|
1276
|
-
const now = Date.now();
|
|
1277
|
-
if (now - adminPanelSessionPruneAt < 30_000) return;
|
|
1278
|
-
adminPanelSessionPruneAt = now;
|
|
1279
|
-
for (const [token, session] of adminPanelSessionMap.entries()) {
|
|
1280
|
-
if (!session || Number(session.expiresAt || 0) <= now) {
|
|
1281
|
-
adminPanelSessionMap.delete(token);
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
};
|
|
1285
|
-
|
|
1286
|
-
const getAdminPanelSessionTokenFromRequest = (req) => {
|
|
1287
|
-
const direct = getCookieValuesFromRequest(req, ADMIN_PANEL_SESSION_COOKIE_NAME);
|
|
1288
|
-
if (direct.length > 0) return direct[0];
|
|
1289
|
-
const cookies = parseCookies(req);
|
|
1290
|
-
return String(cookies[ADMIN_PANEL_SESSION_COOKIE_NAME] || '').trim();
|
|
1291
|
-
};
|
|
1292
|
-
|
|
1293
|
-
const clearAdminPanelSessionCookie = (req, res) => {
|
|
1294
|
-
appendSetCookie(
|
|
1295
|
-
res,
|
|
1296
|
-
buildCookieString(ADMIN_PANEL_SESSION_COOKIE_NAME, '', req, {
|
|
1297
|
-
maxAgeSeconds: 0,
|
|
1298
|
-
}),
|
|
1299
|
-
);
|
|
1300
|
-
// Also clear host-only variant (legacy cookie written without Domain).
|
|
1301
|
-
appendSetCookie(
|
|
1302
|
-
res,
|
|
1303
|
-
buildCookieString(ADMIN_PANEL_SESSION_COOKIE_NAME, '', req, {
|
|
1304
|
-
maxAgeSeconds: 0,
|
|
1305
|
-
domain: false,
|
|
1306
|
-
}),
|
|
1307
|
-
);
|
|
1308
|
-
};
|
|
1309
|
-
|
|
1310
|
-
const createAdminPanelSession = (googleSession, { role = 'owner' } = {}) => {
|
|
1311
|
-
pruneExpiredAdminPanelSessions();
|
|
1312
|
-
const now = Date.now();
|
|
1313
|
-
const normalizedRole = normalizeAdminPanelRole(role, 'owner');
|
|
1314
|
-
const token = randomUUID();
|
|
1315
|
-
const session = {
|
|
1316
|
-
token,
|
|
1317
|
-
role: normalizedRole,
|
|
1318
|
-
googleSub: normalizeGoogleSubject(googleSession?.sub),
|
|
1319
|
-
ownerJid: normalizeJid(googleSession?.ownerJid) || '',
|
|
1320
|
-
email: normalizeEmail(googleSession?.email),
|
|
1321
|
-
name: sanitizeText(googleSession?.name || '', 120, { allowEmpty: true }) || 'Administrador',
|
|
1322
|
-
picture: String(googleSession?.picture || '').trim() || '',
|
|
1323
|
-
createdAt: now,
|
|
1324
|
-
expiresAt: now + ADMIN_PANEL_SESSION_TTL_MS,
|
|
1325
|
-
};
|
|
1326
|
-
adminPanelSessionMap.set(token, session);
|
|
1327
|
-
return session;
|
|
1328
|
-
};
|
|
1329
|
-
|
|
1330
|
-
const resolveAdminPanelSessionFromRequest = (req) => {
|
|
1331
|
-
if (!ADMIN_PANEL_ENABLED) return null;
|
|
1332
|
-
pruneExpiredAdminPanelSessions();
|
|
1333
|
-
const token = getAdminPanelSessionTokenFromRequest(req);
|
|
1334
|
-
if (!token) return null;
|
|
1335
|
-
const session = adminPanelSessionMap.get(token);
|
|
1336
|
-
if (!session) return null;
|
|
1337
|
-
if (Number(session.expiresAt || 0) <= Date.now()) {
|
|
1338
|
-
adminPanelSessionMap.delete(token);
|
|
1339
|
-
return null;
|
|
1340
|
-
}
|
|
1341
|
-
return session;
|
|
1342
|
-
};
|
|
1343
|
-
|
|
1344
|
-
const mapAdminPanelSessionResponseData = (session) =>
|
|
1345
|
-
session
|
|
1346
|
-
? {
|
|
1347
|
-
authenticated: true,
|
|
1348
|
-
role: normalizeAdminPanelRole(session.role, 'owner'),
|
|
1349
|
-
capabilities: {
|
|
1350
|
-
can_manage_moderators: normalizeAdminPanelRole(session.role, 'owner') === 'owner',
|
|
1351
|
-
},
|
|
1352
|
-
user: {
|
|
1353
|
-
google_sub: session.googleSub,
|
|
1354
|
-
owner_jid: session.ownerJid,
|
|
1355
|
-
email: session.email,
|
|
1356
|
-
name: session.name,
|
|
1357
|
-
picture: session.picture || null,
|
|
1358
|
-
},
|
|
1359
|
-
expires_at: toIsoOrNull(session.expiresAt),
|
|
1360
|
-
}
|
|
1361
|
-
: {
|
|
1362
|
-
authenticated: false,
|
|
1363
|
-
role: null,
|
|
1364
|
-
capabilities: {
|
|
1365
|
-
can_manage_moderators: false,
|
|
1366
|
-
},
|
|
1367
|
-
user: null,
|
|
1368
|
-
expires_at: null,
|
|
1369
|
-
};
|
|
1370
|
-
|
|
1371
|
-
const isOwnerGoogleSessionAllowed = (googleSession) => {
|
|
1372
|
-
if (!ADMIN_PANEL_ENABLED) return false;
|
|
1373
|
-
if (!googleSession?.sub || !googleSession?.ownerJid) return false;
|
|
1374
|
-
const email = normalizeEmail(googleSession.email);
|
|
1375
|
-
return Boolean(email && email === ADMIN_PANEL_EMAIL);
|
|
1376
|
-
};
|
|
1377
|
-
|
|
1378
|
-
const resolveAdminPanelLoginEligibility = async (googleSession) => {
|
|
1379
|
-
if (!ADMIN_PANEL_ENABLED || !googleSession?.sub || !googleSession?.ownerJid) {
|
|
1380
|
-
return { eligible: false, role: '', moderator: null };
|
|
1381
|
-
}
|
|
1382
|
-
if (isOwnerGoogleSessionAllowed(googleSession)) {
|
|
1383
|
-
return { eligible: true, role: 'owner', moderator: null };
|
|
1384
|
-
}
|
|
1385
|
-
const moderator = await findActiveAdminModeratorForIdentity({
|
|
1386
|
-
googleSub: googleSession.sub,
|
|
1387
|
-
email: googleSession.email,
|
|
1388
|
-
ownerJid: googleSession.ownerJid,
|
|
1389
|
-
});
|
|
1390
|
-
if (!moderator) return { eligible: false, role: '', moderator: null };
|
|
1391
|
-
return { eligible: true, role: 'moderator', moderator };
|
|
1392
|
-
};
|
|
1393
|
-
|
|
1394
|
-
const requireAdminPanelSession = (req, res) => {
|
|
1395
|
-
if (!ADMIN_PANEL_ENABLED) {
|
|
1396
|
-
sendJson(req, res, 404, { error: 'Painel admin desabilitado.' });
|
|
1397
|
-
return null;
|
|
1398
|
-
}
|
|
1399
|
-
const session = resolveAdminPanelSessionFromRequest(req);
|
|
1400
|
-
if (!session) {
|
|
1401
|
-
sendJson(req, res, 401, { error: 'Sessao admin invalida ou expirada.' });
|
|
1402
|
-
return null;
|
|
1403
|
-
}
|
|
1404
|
-
return session;
|
|
1405
|
-
};
|
|
1406
|
-
|
|
1407
|
-
const requireOwnerAdminPanelSession = (req, res) => {
|
|
1408
|
-
const session = requireAdminPanelSession(req, res);
|
|
1409
|
-
if (!session) return null;
|
|
1410
|
-
if (normalizeAdminPanelRole(session.role, 'owner') !== 'owner') {
|
|
1411
|
-
sendJson(req, res, 403, { error: 'Somente o dono pode gerenciar moderadores.' });
|
|
1412
|
-
return null;
|
|
1413
|
-
}
|
|
1414
|
-
return session;
|
|
1415
|
-
};
|
|
878
|
+
revokeGoogleWebSessionsByIdentityBridge = revokeGoogleWebSessionsByIdentity;
|
|
1416
879
|
|
|
1417
880
|
const sendAsset = (req, res, buffer, mimetype = 'image/webp') => {
|
|
1418
881
|
const maxAgeSeconds = Math.max(60 * 60 * 24, ASSET_CACHE_SECONDS);
|
|
@@ -3359,34 +2822,23 @@ const handleMarketplaceStatsRequest = async (req, res, url) => {
|
|
|
3359
2822
|
};
|
|
3360
2823
|
|
|
3361
2824
|
const buildHomeRealtimeSnapshot = async ({ systemSummary = null } = {}) => {
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
);
|
|
3374
|
-
messagesToday = Number(row?.messages_today || 0);
|
|
3375
|
-
spamBlockedToday = Number(row?.spam_blocked_today || 0);
|
|
3376
|
-
} catch (error) {
|
|
3377
|
-
analyticsError = error?.message || 'message_analysis_event_unavailable';
|
|
3378
|
-
}
|
|
3379
|
-
|
|
3380
|
-
const botsOnline = systemSummary?.bot?.connected ? 1 : 0;
|
|
3381
|
-
const uptime = String(systemSummary?.process?.uptime || '').trim() || null;
|
|
2825
|
+
const totalUsersRaw = Number(systemSummary?.platform?.total_users);
|
|
2826
|
+
const totalMessagesRaw = Number(systemSummary?.usage?.total_messages);
|
|
2827
|
+
const totalCommandsRaw = Number(systemSummary?.usage?.total_commands);
|
|
2828
|
+
const httpLatencyP95Ms = Number(systemSummary?.observability?.http_latency_p95_ms);
|
|
2829
|
+
const lagP99Ms = Number(systemSummary?.observability?.lag_p99_ms);
|
|
2830
|
+
const resolvedLatencyMs = Number.isFinite(httpLatencyP95Ms) ? httpLatencyP95Ms : Number.isFinite(lagP99Ms) ? lagP99Ms : null;
|
|
2831
|
+
|
|
2832
|
+
const totalUsers = Number.isFinite(totalUsersRaw) ? Math.max(0, Math.round(totalUsersRaw)) : null;
|
|
2833
|
+
const totalMessages = Number.isFinite(totalMessagesRaw) ? Math.max(0, Math.round(totalMessagesRaw)) : null;
|
|
2834
|
+
const totalCommands = Number.isFinite(totalCommandsRaw) ? Math.max(0, Math.round(totalCommandsRaw)) : null;
|
|
2835
|
+
const systemLatencyMs = Number.isFinite(resolvedLatencyMs) ? Math.max(0, Number(resolvedLatencyMs.toFixed(2))) : null;
|
|
3382
2836
|
|
|
3383
2837
|
return {
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
analytics_ok: analyticsError === null,
|
|
3389
|
-
analytics_error: analyticsError,
|
|
2838
|
+
total_users: totalUsers,
|
|
2839
|
+
total_messages: totalMessages,
|
|
2840
|
+
total_commands: totalCommands,
|
|
2841
|
+
system_latency_ms: systemLatencyMs,
|
|
3390
2842
|
updated_at: new Date().toISOString(),
|
|
3391
2843
|
};
|
|
3392
2844
|
};
|
|
@@ -5702,6 +5154,7 @@ const buildSystemSummarySnapshot = async () => {
|
|
|
5702
5154
|
let prometheus = null;
|
|
5703
5155
|
let prometheusError = null;
|
|
5704
5156
|
let platformError = null;
|
|
5157
|
+
let usageError = null;
|
|
5705
5158
|
|
|
5706
5159
|
const socketReadyState = resolveSocketReadyState(activeSocket);
|
|
5707
5160
|
const botJid = resolveActiveSocketBotJid(activeSocket) || null;
|
|
@@ -5714,6 +5167,11 @@ const buildSystemSummarySnapshot = async () => {
|
|
|
5714
5167
|
total_groups: null,
|
|
5715
5168
|
total_chats: null,
|
|
5716
5169
|
};
|
|
5170
|
+
let usage = {
|
|
5171
|
+
total_messages: null,
|
|
5172
|
+
total_commands: null,
|
|
5173
|
+
total_commands_source: TABLES.MESSAGE_ANALYSIS_EVENT,
|
|
5174
|
+
};
|
|
5717
5175
|
|
|
5718
5176
|
try {
|
|
5719
5177
|
prometheus = await fetchPrometheusSummary();
|
|
@@ -5750,6 +5208,27 @@ const buildSystemSummarySnapshot = async () => {
|
|
|
5750
5208
|
platformError = error?.message || 'Falha ao consultar totais de usuários/grupos';
|
|
5751
5209
|
}
|
|
5752
5210
|
|
|
5211
|
+
try {
|
|
5212
|
+
const [messageTotalsRows, commandTotalsRows] = await Promise.all([
|
|
5213
|
+
executeQuery(`SELECT COUNT(*) AS total_messages FROM ${TABLES.MESSAGES}`),
|
|
5214
|
+
executeQuery(
|
|
5215
|
+
`SELECT COUNT(*) AS total_commands
|
|
5216
|
+
FROM ${TABLES.MESSAGE_ANALYSIS_EVENT}
|
|
5217
|
+
WHERE is_command = 1
|
|
5218
|
+
AND COALESCE(is_from_bot, 0) = 0`,
|
|
5219
|
+
),
|
|
5220
|
+
]);
|
|
5221
|
+
const messageTotals = messageTotalsRows?.[0] || {};
|
|
5222
|
+
const commandTotals = commandTotalsRows?.[0] || {};
|
|
5223
|
+
usage = {
|
|
5224
|
+
...usage,
|
|
5225
|
+
total_messages: Number(messageTotals?.total_messages || 0),
|
|
5226
|
+
total_commands: Number(commandTotals?.total_commands || 0),
|
|
5227
|
+
};
|
|
5228
|
+
} catch (error) {
|
|
5229
|
+
usageError = error?.message || 'Falha ao consultar totais de mensagens/comandos';
|
|
5230
|
+
}
|
|
5231
|
+
|
|
5753
5232
|
const hostCpuPercent = Number(system.usoCpuPercentual);
|
|
5754
5233
|
const hostMemoryPercent = Number(system.usoMemoriaPercentual);
|
|
5755
5234
|
const statusReasons = [];
|
|
@@ -5771,6 +5250,7 @@ const buildSystemSummarySnapshot = async () => {
|
|
|
5771
5250
|
ready_state: socketReadyState,
|
|
5772
5251
|
},
|
|
5773
5252
|
platform,
|
|
5253
|
+
usage,
|
|
5774
5254
|
host: {
|
|
5775
5255
|
cpu_percent: system.usoCpuPercentual,
|
|
5776
5256
|
memory_percent: system.usoMemoriaPercentual,
|
|
@@ -5797,6 +5277,7 @@ const buildSystemSummarySnapshot = async () => {
|
|
|
5797
5277
|
metrics_ok: Boolean(prometheus),
|
|
5798
5278
|
metrics_error: prometheusError,
|
|
5799
5279
|
platform_error: platformError,
|
|
5280
|
+
usage_error: usageError,
|
|
5800
5281
|
},
|
|
5801
5282
|
};
|
|
5802
5283
|
};
|
|
@@ -6719,1512 +6200,44 @@ const handlePackInteractionRequest = async (req, res, packKey, interaction, url)
|
|
|
6719
6200
|
});
|
|
6720
6201
|
};
|
|
6721
6202
|
|
|
6722
|
-
const
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
action,
|
|
6761
|
-
target_type,
|
|
6762
|
-
target_id,
|
|
6763
|
-
status,
|
|
6764
|
-
details
|
|
6765
|
-
)
|
|
6766
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
6767
|
-
[randomUUID(), adminRole, adminGoogleSub, adminEmail, adminOwnerJid, normalizedAction, normalizedTargetType, sanitizeText(targetId || '', 255, { allowEmpty: true }) || null, normalizedStatus, detailsJson],
|
|
6768
|
-
);
|
|
6769
|
-
return true;
|
|
6770
|
-
} catch (error) {
|
|
6771
|
-
if (error?.code === 'ER_NO_SUCH_TABLE') return false;
|
|
6772
|
-
logger.warn('Falha ao registrar auditoria admin.', {
|
|
6773
|
-
action: 'admin_audit_insert_failed',
|
|
6774
|
-
error: error?.message,
|
|
6775
|
-
audit_action: normalizedAction,
|
|
6776
|
-
});
|
|
6777
|
-
return false;
|
|
6778
|
-
}
|
|
6779
|
-
};
|
|
6780
|
-
|
|
6781
|
-
const listAdminAuditLog = async ({ limit = 80 } = {}) => {
|
|
6782
|
-
const safeLimit = Math.max(1, Math.min(500, Number(limit || 80)));
|
|
6783
|
-
try {
|
|
6784
|
-
const rows = await executeQuery(
|
|
6785
|
-
`SELECT
|
|
6786
|
-
id,
|
|
6787
|
-
admin_role,
|
|
6788
|
-
admin_google_sub,
|
|
6789
|
-
admin_email,
|
|
6790
|
-
admin_owner_jid,
|
|
6791
|
-
action,
|
|
6792
|
-
target_type,
|
|
6793
|
-
target_id,
|
|
6794
|
-
status,
|
|
6795
|
-
details,
|
|
6796
|
-
created_at
|
|
6797
|
-
FROM ${TABLES.ADMIN_ACTION_AUDIT}
|
|
6798
|
-
ORDER BY created_at DESC
|
|
6799
|
-
LIMIT ${safeLimit}`,
|
|
6800
|
-
);
|
|
6801
|
-
|
|
6802
|
-
return (Array.isArray(rows) ? rows : []).map((row) => ({
|
|
6803
|
-
id: String(row?.id || '').trim(),
|
|
6804
|
-
admin_role: normalizeAdminPanelRole(row?.admin_role, 'owner'),
|
|
6805
|
-
admin_google_sub: normalizeGoogleSubject(row?.admin_google_sub),
|
|
6806
|
-
admin_email: normalizeEmail(row?.admin_email) || null,
|
|
6807
|
-
admin_owner_jid: normalizeJid(row?.admin_owner_jid) || null,
|
|
6808
|
-
action: String(row?.action || '').trim(),
|
|
6809
|
-
target_type: String(row?.target_type || '').trim() || null,
|
|
6810
|
-
target_id: String(row?.target_id || '').trim() || null,
|
|
6811
|
-
status: String(row?.status || '').trim() || 'success',
|
|
6812
|
-
details: safeParseJsonObject(row?.details),
|
|
6813
|
-
created_at: toIsoOrNull(row?.created_at),
|
|
6814
|
-
}));
|
|
6815
|
-
} catch (error) {
|
|
6816
|
-
if (error?.code === 'ER_NO_SUCH_TABLE') return [];
|
|
6817
|
-
throw error;
|
|
6818
|
-
}
|
|
6819
|
-
};
|
|
6820
|
-
|
|
6821
|
-
const listAdminFeatureFlagsDetailed = async ({ limit = 300 } = {}) => {
|
|
6822
|
-
const safeLimit = Math.max(1, Math.min(500, Number(limit || 300)));
|
|
6823
|
-
try {
|
|
6824
|
-
const rows = await executeQuery(
|
|
6825
|
-
`SELECT
|
|
6826
|
-
flag_name,
|
|
6827
|
-
is_enabled,
|
|
6828
|
-
rollout_percent,
|
|
6829
|
-
description,
|
|
6830
|
-
updated_by,
|
|
6831
|
-
updated_at
|
|
6832
|
-
FROM ${TABLES.FEATURE_FLAG}
|
|
6833
|
-
ORDER BY flag_name ASC
|
|
6834
|
-
LIMIT ${safeLimit}`,
|
|
6835
|
-
);
|
|
6836
|
-
return (Array.isArray(rows) ? rows : []).map((row) => ({
|
|
6837
|
-
flag_name: sanitizeText(row?.flag_name || '', 120, { allowEmpty: false }) || '',
|
|
6838
|
-
is_enabled: Number(row?.is_enabled || 0) === 1,
|
|
6839
|
-
rollout_percent: Math.max(0, Math.min(100, Number(row?.rollout_percent || 0))),
|
|
6840
|
-
description: sanitizeText(row?.description || '', 255, { allowEmpty: true }) || null,
|
|
6841
|
-
updated_by: sanitizeText(row?.updated_by || '', 120, { allowEmpty: true }) || null,
|
|
6842
|
-
updated_at: toIsoOrNull(row?.updated_at),
|
|
6843
|
-
}));
|
|
6844
|
-
} catch (error) {
|
|
6845
|
-
if (error?.code === 'ER_NO_SUCH_TABLE') {
|
|
6846
|
-
const fallback = await getFeatureFlagsSnapshot().catch(() => []);
|
|
6847
|
-
return (Array.isArray(fallback) ? fallback : []).map((entry) => ({
|
|
6848
|
-
flag_name: sanitizeText(entry?.flag_name || '', 120, { allowEmpty: false }) || '',
|
|
6849
|
-
is_enabled: Boolean(entry?.is_enabled),
|
|
6850
|
-
rollout_percent: Math.max(0, Math.min(100, Number(entry?.rollout_percent || 0))),
|
|
6851
|
-
description: null,
|
|
6852
|
-
updated_by: null,
|
|
6853
|
-
updated_at: null,
|
|
6854
|
-
}));
|
|
6855
|
-
}
|
|
6856
|
-
throw error;
|
|
6857
|
-
}
|
|
6858
|
-
};
|
|
6859
|
-
|
|
6860
|
-
const upsertAdminFeatureFlagRecord = async ({ adminSession = null, flagName = '', isEnabled = false, rolloutPercent = 100, description = '' } = {}) => {
|
|
6861
|
-
const normalizedFlagName = sanitizeAuditActionText(flagName, 120);
|
|
6862
|
-
if (!normalizedFlagName) {
|
|
6863
|
-
const error = new Error('flag_name invalido.');
|
|
6864
|
-
error.statusCode = 400;
|
|
6865
|
-
throw error;
|
|
6866
|
-
}
|
|
6867
|
-
const normalizedRollout = Math.max(0, Math.min(100, Math.floor(Number(rolloutPercent) || 0)));
|
|
6868
|
-
const normalizedEnabled = isEnabled ? 1 : 0;
|
|
6869
|
-
const normalizedDescription = sanitizeText(description || '', 255, { allowEmpty: true }) || null;
|
|
6870
|
-
const updatedBy = normalizeEmail(adminSession?.email) || normalizeGoogleSubject(adminSession?.googleSub) || 'admin';
|
|
6871
|
-
|
|
6872
|
-
await executeQuery(
|
|
6873
|
-
`INSERT INTO ${TABLES.FEATURE_FLAG}
|
|
6874
|
-
(flag_name, is_enabled, rollout_percent, description, updated_by)
|
|
6875
|
-
VALUES (?, ?, ?, ?, ?)
|
|
6876
|
-
ON DUPLICATE KEY UPDATE
|
|
6877
|
-
is_enabled = VALUES(is_enabled),
|
|
6878
|
-
rollout_percent = VALUES(rollout_percent),
|
|
6879
|
-
description = COALESCE(VALUES(description), description),
|
|
6880
|
-
updated_by = VALUES(updated_by),
|
|
6881
|
-
updated_at = CURRENT_TIMESTAMP`,
|
|
6882
|
-
[normalizedFlagName, normalizedEnabled, normalizedRollout, normalizedDescription, sanitizeText(updatedBy, 120, { allowEmpty: true }) || null],
|
|
6883
|
-
);
|
|
6884
|
-
|
|
6885
|
-
await refreshFeatureFlags({ force: true }).catch(() => {});
|
|
6886
|
-
const rows = await executeQuery(
|
|
6887
|
-
`SELECT flag_name, is_enabled, rollout_percent, description, updated_by, updated_at
|
|
6888
|
-
FROM ${TABLES.FEATURE_FLAG}
|
|
6889
|
-
WHERE flag_name = ?
|
|
6890
|
-
LIMIT 1`,
|
|
6891
|
-
[normalizedFlagName],
|
|
6892
|
-
);
|
|
6893
|
-
const row = Array.isArray(rows) ? rows[0] : null;
|
|
6894
|
-
return {
|
|
6895
|
-
flag_name: sanitizeText(row?.flag_name || normalizedFlagName, 120, { allowEmpty: false }) || normalizedFlagName,
|
|
6896
|
-
is_enabled: Number(row?.is_enabled || 0) === 1,
|
|
6897
|
-
rollout_percent: Math.max(0, Math.min(100, Number(row?.rollout_percent ?? normalizedRollout))),
|
|
6898
|
-
description: sanitizeText(row?.description || '', 255, { allowEmpty: true }) || null,
|
|
6899
|
-
updated_by: sanitizeText(row?.updated_by || '', 120, { allowEmpty: true }) || null,
|
|
6900
|
-
updated_at: toIsoOrNull(row?.updated_at) || new Date().toISOString(),
|
|
6901
|
-
};
|
|
6902
|
-
};
|
|
6903
|
-
|
|
6904
|
-
const getAdminMessageFlowDailyStats = async () => {
|
|
6905
|
-
try {
|
|
6906
|
-
const [row] = await executeQuery(
|
|
6907
|
-
`SELECT
|
|
6908
|
-
COUNT(*) AS messages_today,
|
|
6909
|
-
SUM(CASE WHEN processing_result = 'blocked_antilink' THEN 1 ELSE 0 END) AS spam_blocked_today,
|
|
6910
|
-
SUM(CASE WHEN processing_result = 'auth_required' THEN 1 ELSE 0 END) AS suspicious_today
|
|
6911
|
-
FROM ${TABLES.MESSAGE_ANALYSIS_EVENT}
|
|
6912
|
-
WHERE created_at >= UTC_DATE()`,
|
|
6913
|
-
);
|
|
6914
|
-
return {
|
|
6915
|
-
messages_today: Number(row?.messages_today || 0),
|
|
6916
|
-
spam_blocked_today: Number(row?.spam_blocked_today || 0),
|
|
6917
|
-
suspicious_today: Number(row?.suspicious_today || 0),
|
|
6918
|
-
available: true,
|
|
6919
|
-
};
|
|
6920
|
-
} catch (error) {
|
|
6921
|
-
if (error?.code === 'ER_NO_SUCH_TABLE') {
|
|
6922
|
-
return {
|
|
6923
|
-
messages_today: null,
|
|
6924
|
-
spam_blocked_today: null,
|
|
6925
|
-
suspicious_today: null,
|
|
6926
|
-
available: false,
|
|
6927
|
-
};
|
|
6928
|
-
}
|
|
6929
|
-
throw error;
|
|
6930
|
-
}
|
|
6931
|
-
};
|
|
6932
|
-
|
|
6933
|
-
const listRecentModerationEvents = async ({ limit = 40 } = {}) => {
|
|
6934
|
-
const safeLimit = Math.max(1, Math.min(200, Number(limit || 40)));
|
|
6935
|
-
try {
|
|
6936
|
-
const rows = await executeQuery(
|
|
6937
|
-
`SELECT
|
|
6938
|
-
id,
|
|
6939
|
-
message_id,
|
|
6940
|
-
chat_id,
|
|
6941
|
-
sender_id,
|
|
6942
|
-
sender_name,
|
|
6943
|
-
processing_result,
|
|
6944
|
-
command_name,
|
|
6945
|
-
error_code,
|
|
6946
|
-
metadata,
|
|
6947
|
-
created_at
|
|
6948
|
-
FROM ${TABLES.MESSAGE_ANALYSIS_EVENT}
|
|
6949
|
-
WHERE processing_result IN ('blocked_antilink', 'auth_required')
|
|
6950
|
-
ORDER BY created_at DESC
|
|
6951
|
-
LIMIT ${safeLimit}`,
|
|
6952
|
-
);
|
|
6953
|
-
|
|
6954
|
-
return (Array.isArray(rows) ? rows : []).map((row) => {
|
|
6955
|
-
const processingResult = String(row?.processing_result || '')
|
|
6956
|
-
.trim()
|
|
6957
|
-
.toLowerCase();
|
|
6958
|
-
const metadata = safeParseJsonObject(row?.metadata);
|
|
6959
|
-
const isAntiLink = processingResult === 'blocked_antilink';
|
|
6960
|
-
const title = isAntiLink ? 'Anti-link bloqueou mensagem' : 'Tentativa suspeita detectada';
|
|
6961
|
-
const severity = isAntiLink ? 'medium' : 'high';
|
|
6962
|
-
const sender = sanitizeText(row?.sender_name || row?.sender_id || '', 120, { allowEmpty: true }) || String(row?.sender_id || '').trim() || 'desconhecido';
|
|
6963
|
-
const chatId = String(row?.chat_id || '').trim() || 'chat_desconhecido';
|
|
6964
|
-
return {
|
|
6965
|
-
id: `mae:${row?.id || ''}`,
|
|
6966
|
-
event_type: isAntiLink ? 'anti_link' : 'suspicious',
|
|
6967
|
-
severity,
|
|
6968
|
-
title,
|
|
6969
|
-
subtitle: `${sender} em ${chatId}`,
|
|
6970
|
-
chat_id: chatId,
|
|
6971
|
-
sender_id: String(row?.sender_id || '').trim() || null,
|
|
6972
|
-
sender_name: sanitizeText(row?.sender_name || '', 120, { allowEmpty: true }) || null,
|
|
6973
|
-
message_id: String(row?.message_id || '').trim() || null,
|
|
6974
|
-
processing_result: processingResult,
|
|
6975
|
-
command_name: sanitizeText(row?.command_name || '', 64, { allowEmpty: true }) || null,
|
|
6976
|
-
error_code: sanitizeText(row?.error_code || '', 96, { allowEmpty: true }) || null,
|
|
6977
|
-
metadata,
|
|
6978
|
-
created_at: toIsoOrNull(row?.created_at),
|
|
6979
|
-
};
|
|
6980
|
-
});
|
|
6981
|
-
} catch (error) {
|
|
6982
|
-
if (error?.code === 'ER_NO_SUCH_TABLE') return [];
|
|
6983
|
-
throw error;
|
|
6984
|
-
}
|
|
6985
|
-
};
|
|
6986
|
-
|
|
6987
|
-
const buildModerationQueueSnapshot = async ({ limit = 50 } = {}) => {
|
|
6988
|
-
const [analysisEvents, bans] = await Promise.all([listRecentModerationEvents({ limit: Math.max(10, limit) }), listAdminBans({ activeOnly: false, limit: Math.max(10, Math.floor(limit / 2)) })]);
|
|
6989
|
-
|
|
6990
|
-
const banEvents = (Array.isArray(bans) ? bans : []).map((ban) => ({
|
|
6991
|
-
id: `ban:${ban?.id || ''}`,
|
|
6992
|
-
event_type: 'ban',
|
|
6993
|
-
severity: ban?.revoked_at ? 'low' : 'critical',
|
|
6994
|
-
title: ban?.revoked_at ? 'Ban revogado' : 'Conta bloqueada',
|
|
6995
|
-
subtitle: sanitizeText(ban?.email || ban?.owner_jid || ban?.google_sub || '', 160, { allowEmpty: true }) || 'identidade indisponivel',
|
|
6996
|
-
ban_id: String(ban?.id || '').trim(),
|
|
6997
|
-
reason: sanitizeText(ban?.reason || '', 255, { allowEmpty: true }) || null,
|
|
6998
|
-
created_at: toIsoOrNull(ban?.created_at),
|
|
6999
|
-
revoked_at: toIsoOrNull(ban?.revoked_at),
|
|
7000
|
-
metadata: {
|
|
7001
|
-
google_sub: ban?.google_sub || null,
|
|
7002
|
-
email: ban?.email || null,
|
|
7003
|
-
owner_jid: ban?.owner_jid || null,
|
|
7004
|
-
},
|
|
7005
|
-
}));
|
|
7006
|
-
|
|
7007
|
-
const combined = [...(Array.isArray(analysisEvents) ? analysisEvents : []), ...banEvents];
|
|
7008
|
-
combined.sort((left, right) => {
|
|
7009
|
-
const leftTs = Date.parse(String(left?.created_at || left?.revoked_at || 0)) || 0;
|
|
7010
|
-
const rightTs = Date.parse(String(right?.created_at || right?.revoked_at || 0)) || 0;
|
|
7011
|
-
return rightTs - leftTs;
|
|
7012
|
-
});
|
|
7013
|
-
return combined.slice(0, Math.max(1, Math.min(200, Number(limit || 50))));
|
|
7014
|
-
};
|
|
7015
|
-
|
|
7016
|
-
const buildAdminSystemHealthSnapshot = ({ systemSummary = null, systemMeta = null } = {}) => {
|
|
7017
|
-
const hostCpu = Number(systemSummary?.host?.cpu_percent);
|
|
7018
|
-
const hostRam = Number(systemSummary?.host?.memory_percent);
|
|
7019
|
-
const latencyP95 = Number(systemSummary?.observability?.http_latency_p95_ms);
|
|
7020
|
-
const queuePending = Number(systemSummary?.observability?.queue_peak);
|
|
7021
|
-
const hasMetricsError = Boolean(systemMeta?.metrics_error);
|
|
7022
|
-
const hasPlatformError = Boolean(systemMeta?.platform_error);
|
|
7023
|
-
const dbStatus = hasPlatformError ? 'degraded' : hasMetricsError ? 'unknown' : 'ok';
|
|
7024
|
-
|
|
7025
|
-
return {
|
|
7026
|
-
cpu_percent: Number.isFinite(hostCpu) ? hostCpu : null,
|
|
7027
|
-
ram_percent: Number.isFinite(hostRam) ? hostRam : null,
|
|
7028
|
-
http_latency_p95_ms: Number.isFinite(latencyP95) ? latencyP95 : null,
|
|
7029
|
-
queue_pending: Number.isFinite(queuePending) ? queuePending : null,
|
|
7030
|
-
db_status: dbStatus,
|
|
7031
|
-
db_total_queries: Number(systemSummary?.observability?.db_total ?? 0) || 0,
|
|
7032
|
-
db_slow_queries: Number(systemSummary?.observability?.db_slow ?? 0) || 0,
|
|
7033
|
-
bot_status: String(systemSummary?.bot?.connection_status || '').trim() || 'unknown',
|
|
7034
|
-
updated_at: toIsoOrNull(systemSummary?.updated_at),
|
|
7035
|
-
};
|
|
7036
|
-
};
|
|
7037
|
-
|
|
7038
|
-
const buildAdminAlertSnapshot = ({ dashboardQuick = null, systemHealth = null, systemSummary = null, systemMeta = null } = {}) => {
|
|
7039
|
-
const alerts = [];
|
|
7040
|
-
const updatedAt = toIsoOrNull(systemSummary?.updated_at) || new Date().toISOString();
|
|
7041
|
-
const pushAlert = (severity, code, title, message) => {
|
|
7042
|
-
alerts.push({
|
|
7043
|
-
id: `${code}:${severity}`,
|
|
7044
|
-
severity,
|
|
7045
|
-
code,
|
|
7046
|
-
title,
|
|
7047
|
-
message,
|
|
7048
|
-
created_at: updatedAt,
|
|
7049
|
-
});
|
|
7050
|
-
};
|
|
7051
|
-
|
|
7052
|
-
const botStatus = String(systemSummary?.bot?.connection_status || '').toLowerCase();
|
|
7053
|
-
if (botStatus && botStatus !== 'online') {
|
|
7054
|
-
pushAlert('critical', 'bot_offline', 'Bot fora do ar', `Status atual: ${botStatus}.`);
|
|
7055
|
-
}
|
|
7056
|
-
|
|
7057
|
-
if (Number.isFinite(systemHealth?.cpu_percent) && systemHealth.cpu_percent >= 90) {
|
|
7058
|
-
pushAlert('high', 'cpu_high', 'CPU alta', `Uso de CPU em ${systemHealth.cpu_percent.toFixed(1)}%.`);
|
|
7059
|
-
}
|
|
7060
|
-
if (Number.isFinite(systemHealth?.ram_percent) && systemHealth.ram_percent >= 90) {
|
|
7061
|
-
pushAlert('high', 'ram_high', 'RAM alta', `Uso de RAM em ${systemHealth.ram_percent.toFixed(1)}%.`);
|
|
7062
|
-
}
|
|
7063
|
-
if (Number.isFinite(systemHealth?.queue_pending) && systemHealth.queue_pending >= 100) {
|
|
7064
|
-
pushAlert('medium', 'queue_high', 'Fila pendente alta', `Backlog detectado (${Math.round(systemHealth.queue_pending)}).`);
|
|
7065
|
-
}
|
|
7066
|
-
if (Number.isFinite(dashboardQuick?.errors_5xx) && dashboardQuick.errors_5xx > 0) {
|
|
7067
|
-
pushAlert('medium', 'http_5xx', 'Erros HTTP 5xx detectados', `${Math.round(dashboardQuick.errors_5xx)} eventos 5xx desde o boot de métricas.`);
|
|
7068
|
-
}
|
|
7069
|
-
if (systemMeta?.platform_error) {
|
|
7070
|
-
pushAlert('high', 'db_platform_error', 'Erro de banco/plataforma', String(systemMeta.platform_error).slice(0, 200));
|
|
7071
|
-
}
|
|
7072
|
-
if (systemMeta?.metrics_error) {
|
|
7073
|
-
pushAlert('low', 'metrics_unavailable', 'Métricas indisponíveis', String(systemMeta.metrics_error).slice(0, 200));
|
|
7074
|
-
}
|
|
7075
|
-
|
|
7076
|
-
return alerts;
|
|
7077
|
-
};
|
|
7078
|
-
|
|
7079
|
-
const listAdminActiveGoogleWebSessions = async ({ limit = 200 } = {}) => {
|
|
7080
|
-
const safeLimit = Math.max(1, Math.min(500, Number(limit || 200)));
|
|
7081
|
-
const rows = await executeQuery(
|
|
7082
|
-
`SELECT session_token, google_sub, owner_jid, owner_phone, email, name, picture_url, created_at, last_seen_at, expires_at
|
|
7083
|
-
FROM ${TABLES.STICKER_WEB_GOOGLE_SESSION}
|
|
7084
|
-
WHERE revoked_at IS NULL
|
|
7085
|
-
AND expires_at > UTC_TIMESTAMP()
|
|
7086
|
-
ORDER BY COALESCE(last_seen_at, created_at) DESC
|
|
7087
|
-
LIMIT ${safeLimit}`,
|
|
7088
|
-
);
|
|
7089
|
-
return (Array.isArray(rows) ? rows : []).map((row) => ({
|
|
7090
|
-
session_token: String(row.session_token || '').trim(),
|
|
7091
|
-
google_sub: normalizeGoogleSubject(row.google_sub),
|
|
7092
|
-
owner_jid: normalizeJid(row.owner_jid) || null,
|
|
7093
|
-
owner_phone: toWhatsAppPhoneDigits(row.owner_phone || row.owner_jid) || null,
|
|
7094
|
-
email: normalizeEmail(row.email) || null,
|
|
7095
|
-
name: sanitizeText(row.name || '', 120, { allowEmpty: true }) || null,
|
|
7096
|
-
picture: String(row.picture_url || '').trim() || null,
|
|
7097
|
-
created_at: toIsoOrNull(row.created_at),
|
|
7098
|
-
last_seen_at: toIsoOrNull(row.last_seen_at),
|
|
7099
|
-
expires_at: toIsoOrNull(row.expires_at),
|
|
7100
|
-
}));
|
|
7101
|
-
};
|
|
7102
|
-
|
|
7103
|
-
const listAdminKnownGoogleUsers = async ({ limit = 200 } = {}) => {
|
|
7104
|
-
const safeLimit = Math.max(1, Math.min(500, Number(limit || 200)));
|
|
7105
|
-
const rows = await executeQuery(
|
|
7106
|
-
`SELECT google_sub, owner_jid, owner_phone, email, name, picture_url, created_at, updated_at, last_login_at, last_seen_at
|
|
7107
|
-
FROM ${TABLES.STICKER_WEB_GOOGLE_USER}
|
|
7108
|
-
ORDER BY COALESCE(last_seen_at, last_login_at, updated_at, created_at) DESC
|
|
7109
|
-
LIMIT ${safeLimit}`,
|
|
7110
|
-
);
|
|
7111
|
-
return (Array.isArray(rows) ? rows : []).map((row) => ({
|
|
7112
|
-
google_sub: normalizeGoogleSubject(row.google_sub),
|
|
7113
|
-
owner_jid: normalizeJid(row.owner_jid) || null,
|
|
7114
|
-
owner_phone: toWhatsAppPhoneDigits(row.owner_phone || row.owner_jid) || null,
|
|
7115
|
-
email: normalizeEmail(row.email) || null,
|
|
7116
|
-
name: sanitizeText(row.name || '', 120, { allowEmpty: true }) || null,
|
|
7117
|
-
picture: String(row.picture_url || '').trim() || null,
|
|
7118
|
-
created_at: toIsoOrNull(row.created_at),
|
|
7119
|
-
updated_at: toIsoOrNull(row.updated_at),
|
|
7120
|
-
last_login_at: toIsoOrNull(row.last_login_at),
|
|
7121
|
-
last_seen_at: toIsoOrNull(row.last_seen_at),
|
|
7122
|
-
}));
|
|
7123
|
-
};
|
|
7124
|
-
|
|
7125
|
-
const getWebVisitSummary = async ({ rangeDays = 7, topPathsLimit = 10 } = {}) => {
|
|
7126
|
-
const safeRangeDays = Math.max(1, Math.min(90, Number(rangeDays || 7)));
|
|
7127
|
-
const safeTopPathsLimit = Math.max(1, Math.min(30, Number(topPathsLimit || 10)));
|
|
7128
|
-
|
|
7129
|
-
try {
|
|
7130
|
-
const [countersRows, topPathsRows] = await Promise.all([
|
|
7131
|
-
executeQuery(
|
|
7132
|
-
`SELECT
|
|
7133
|
-
COUNT(*) AS total_events,
|
|
7134
|
-
SUM(CASE WHEN created_at >= (UTC_TIMESTAMP() - INTERVAL 1 DAY) THEN 1 ELSE 0 END) AS events_24h,
|
|
7135
|
-
SUM(CASE WHEN created_at >= (UTC_TIMESTAMP() - INTERVAL ${safeRangeDays} DAY) THEN 1 ELSE 0 END) AS events_range,
|
|
7136
|
-
COUNT(DISTINCT CASE WHEN created_at >= (UTC_TIMESTAMP() - INTERVAL ${safeRangeDays} DAY) THEN visitor_key END) AS unique_visitors_range,
|
|
7137
|
-
COUNT(DISTINCT CASE WHEN created_at >= (UTC_TIMESTAMP() - INTERVAL ${safeRangeDays} DAY) THEN session_key END) AS unique_sessions_range
|
|
7138
|
-
FROM ${TABLES.WEB_VISIT_EVENT}`,
|
|
7139
|
-
),
|
|
7140
|
-
executeQuery(
|
|
7141
|
-
`SELECT page_path, COUNT(*) AS total
|
|
7142
|
-
FROM ${TABLES.WEB_VISIT_EVENT}
|
|
7143
|
-
WHERE created_at >= (UTC_TIMESTAMP() - INTERVAL ${safeRangeDays} DAY)
|
|
7144
|
-
GROUP BY page_path
|
|
7145
|
-
ORDER BY total DESC
|
|
7146
|
-
LIMIT ${safeTopPathsLimit}`,
|
|
7147
|
-
),
|
|
7148
|
-
]);
|
|
7149
|
-
|
|
7150
|
-
const counters = Array.isArray(countersRows) ? countersRows[0] || {} : {};
|
|
7151
|
-
const topPaths = (Array.isArray(topPathsRows) ? topPathsRows : []).map((row) => ({
|
|
7152
|
-
page_path: normalizeVisitPath(row?.page_path || '/'),
|
|
7153
|
-
total: Number(row?.total || 0),
|
|
7154
|
-
}));
|
|
7155
|
-
|
|
7156
|
-
return {
|
|
7157
|
-
range_days: safeRangeDays,
|
|
7158
|
-
total_events: Number(counters?.total_events || 0),
|
|
7159
|
-
events_24h: Number(counters?.events_24h || 0),
|
|
7160
|
-
events_range: Number(counters?.events_range || 0),
|
|
7161
|
-
unique_visitors_range: Number(counters?.unique_visitors_range || 0),
|
|
7162
|
-
unique_sessions_range: Number(counters?.unique_sessions_range || 0),
|
|
7163
|
-
top_paths: topPaths,
|
|
7164
|
-
};
|
|
7165
|
-
} catch (error) {
|
|
7166
|
-
if (error?.code === 'ER_NO_SUCH_TABLE') return null;
|
|
7167
|
-
throw error;
|
|
7168
|
-
}
|
|
7169
|
-
};
|
|
7170
|
-
|
|
7171
|
-
const listAdminPacks = async (url) => {
|
|
7172
|
-
const q = sanitizeText(url?.searchParams?.get('q') || '', 120, { allowEmpty: true }) || '';
|
|
7173
|
-
const owner = normalizeJid(url?.searchParams?.get('owner_jid') || '') || '';
|
|
7174
|
-
const limit = Math.max(1, Math.min(200, Number(url?.searchParams?.get('limit') || 50)));
|
|
7175
|
-
const params = [];
|
|
7176
|
-
const where = ['p.deleted_at IS NULL'];
|
|
7177
|
-
if (q) {
|
|
7178
|
-
where.push('(p.pack_key LIKE ? OR p.name LIKE ? OR p.publisher LIKE ? OR p.owner_jid LIKE ?)');
|
|
7179
|
-
const like = `%${q}%`;
|
|
7180
|
-
params.push(like, like, like, like);
|
|
7181
|
-
}
|
|
7182
|
-
if (owner) {
|
|
7183
|
-
where.push('p.owner_jid = ?');
|
|
7184
|
-
params.push(owner);
|
|
7185
|
-
}
|
|
7186
|
-
const rows = await executeQuery(
|
|
7187
|
-
`SELECT
|
|
7188
|
-
p.id,
|
|
7189
|
-
p.pack_key,
|
|
7190
|
-
p.owner_jid,
|
|
7191
|
-
p.name,
|
|
7192
|
-
p.publisher,
|
|
7193
|
-
p.visibility,
|
|
7194
|
-
p.status,
|
|
7195
|
-
p.pack_status,
|
|
7196
|
-
p.is_auto_pack,
|
|
7197
|
-
p.pack_theme_key,
|
|
7198
|
-
p.pack_volume,
|
|
7199
|
-
p.created_at,
|
|
7200
|
-
p.updated_at,
|
|
7201
|
-
p.cover_sticker_id,
|
|
7202
|
-
(SELECT COUNT(*) FROM ${TABLES.STICKER_PACK_ITEM} i WHERE i.pack_id = p.id) AS stickers_count,
|
|
7203
|
-
COALESCE(e.open_count, 0) AS open_count,
|
|
7204
|
-
COALESCE(e.like_count, 0) AS like_count,
|
|
7205
|
-
COALESCE(e.dislike_count, 0) AS dislike_count
|
|
7206
|
-
FROM ${TABLES.STICKER_PACK} p
|
|
7207
|
-
LEFT JOIN ${TABLES.STICKER_PACK_ENGAGEMENT} e ON e.pack_id = p.id
|
|
7208
|
-
WHERE ${where.join(' AND ')}
|
|
7209
|
-
ORDER BY p.updated_at DESC
|
|
7210
|
-
LIMIT ${limit}`,
|
|
7211
|
-
params,
|
|
7212
|
-
);
|
|
7213
|
-
return (Array.isArray(rows) ? rows : []).map((row) => ({
|
|
7214
|
-
id: String(row.id || ''),
|
|
7215
|
-
pack_key: String(row.pack_key || ''),
|
|
7216
|
-
owner_jid: normalizeJid(row.owner_jid) || null,
|
|
7217
|
-
name: String(row.name || ''),
|
|
7218
|
-
publisher: String(row.publisher || ''),
|
|
7219
|
-
visibility: String(row.visibility || ''),
|
|
7220
|
-
status: String(row.status || ''),
|
|
7221
|
-
pack_status: String(row.pack_status || 'ready'),
|
|
7222
|
-
is_auto_pack: Boolean(Number(row.is_auto_pack || 0)),
|
|
7223
|
-
pack_theme_key: String(row.pack_theme_key || '').trim() || null,
|
|
7224
|
-
pack_volume: Number.isFinite(Number(row.pack_volume)) ? Number(row.pack_volume) : null,
|
|
7225
|
-
created_at: toIsoOrNull(row.created_at),
|
|
7226
|
-
updated_at: toIsoOrNull(row.updated_at),
|
|
7227
|
-
cover_sticker_id: String(row.cover_sticker_id || '').trim() || null,
|
|
7228
|
-
stickers_count: Number(row.stickers_count || 0),
|
|
7229
|
-
open_count: Number(row.open_count || 0),
|
|
7230
|
-
like_count: Number(row.like_count || 0),
|
|
7231
|
-
dislike_count: Number(row.dislike_count || 0),
|
|
7232
|
-
web_url: `${STICKER_WEB_PATH}/${encodeURIComponent(String(row.pack_key || ''))}`,
|
|
7233
|
-
}));
|
|
7234
|
-
};
|
|
7235
|
-
|
|
7236
|
-
const buildAdminOverviewPayload = async ({ adminSession = null } = {}) => {
|
|
7237
|
-
const [marketplaceStats, activeSessions, knownUsers, bans, packsCountRows, stickersCountRows, recentPacks, visitSummary, systemSummaryPayload, messageFlowDaily, moderationQueue, auditLog, featureFlags] = await Promise.all([getMarketplaceGlobalStatsCached().catch(() => null), listAdminActiveGoogleWebSessions({ limit: 80 }), listAdminKnownGoogleUsers({ limit: 120 }), listAdminBans({ activeOnly: true, limit: 120 }), executeQuery(`SELECT COUNT(*) AS total FROM ${TABLES.STICKER_PACK} WHERE deleted_at IS NULL`), executeQuery(`SELECT COUNT(*) AS total FROM ${TABLES.STICKER_ASSET}`), listAdminPacks({ searchParams: new URLSearchParams([['limit', '30']]) }), getWebVisitSummary({ rangeDays: 7, topPathsLimit: 10 }).catch(() => null), getSystemSummaryCached().catch(() => null), getAdminMessageFlowDailyStats().catch(() => ({ messages_today: null, spam_blocked_today: null, suspicious_today: null, available: false })), buildModerationQueueSnapshot({ limit: 80 }).catch(() => []), listAdminAuditLog({ limit: 120 }).catch(() => []), listAdminFeatureFlagsDetailed({ limit: 300 }).catch(() => [])]);
|
|
7238
|
-
|
|
7239
|
-
const systemSummary = systemSummaryPayload?.data || null;
|
|
7240
|
-
const systemMeta = systemSummaryPayload?.meta || null;
|
|
7241
|
-
const botsOnline = systemSummary?.bot?.connected ? 1 : 0;
|
|
7242
|
-
const errors5xx = Number(systemSummary?.observability?.http_5xx_total ?? 0);
|
|
7243
|
-
const dashboardQuick = {
|
|
7244
|
-
bots_online: botsOnline,
|
|
7245
|
-
messages_today: Number(messageFlowDaily?.messages_today ?? 0),
|
|
7246
|
-
spam_blocked_today: Number(messageFlowDaily?.spam_blocked_today ?? 0),
|
|
7247
|
-
uptime: String(systemSummary?.process?.uptime || '').trim() || 'n/d',
|
|
7248
|
-
errors_5xx: Number.isFinite(errors5xx) ? Math.max(0, errors5xx) : 0,
|
|
7249
|
-
};
|
|
7250
|
-
const systemHealth = buildAdminSystemHealthSnapshot({ systemSummary, systemMeta });
|
|
7251
|
-
const alerts = buildAdminAlertSnapshot({ dashboardQuick, systemHealth, systemSummary, systemMeta });
|
|
7252
|
-
|
|
7253
|
-
return {
|
|
7254
|
-
admin_session: mapAdminPanelSessionResponseData(adminSession),
|
|
7255
|
-
marketplace_stats: marketplaceStats,
|
|
7256
|
-
counters: {
|
|
7257
|
-
total_packs_any_status: Number(packsCountRows?.[0]?.total || 0),
|
|
7258
|
-
total_stickers_any_status: Number(stickersCountRows?.[0]?.total || 0),
|
|
7259
|
-
active_google_sessions: Number(activeSessions.length || 0),
|
|
7260
|
-
known_google_users: Number(knownUsers.length || 0),
|
|
7261
|
-
active_bans: Number(bans.length || 0),
|
|
7262
|
-
visit_events_24h: Number(visitSummary?.events_24h || 0),
|
|
7263
|
-
visit_events_7d: Number(visitSummary?.events_range || 0),
|
|
7264
|
-
unique_visitors_7d: Number(visitSummary?.unique_visitors_range || 0),
|
|
7265
|
-
},
|
|
7266
|
-
dashboard_quick: dashboardQuick,
|
|
7267
|
-
moderation_queue: moderationQueue,
|
|
7268
|
-
users_sessions: {
|
|
7269
|
-
active_sessions: activeSessions,
|
|
7270
|
-
users: knownUsers,
|
|
7271
|
-
blocked_accounts: bans,
|
|
7272
|
-
},
|
|
7273
|
-
system_health: systemHealth,
|
|
7274
|
-
audit_log: auditLog,
|
|
7275
|
-
feature_flags: featureFlags,
|
|
7276
|
-
alerts,
|
|
7277
|
-
operational_shortcuts: [
|
|
7278
|
-
{ action: 'restart_worker', label: 'Reiniciar worker', description: 'Destrava filas em processamento e recoloca em pending.' },
|
|
7279
|
-
{ action: 'clear_cache', label: 'Limpar cache', description: 'Invalida caches internos de catálogo, ranking e resumo.' },
|
|
7280
|
-
{ action: 'reprocess_jobs', label: 'Reprocessar jobs', description: 'Agenda ciclos de classificação/curadoria no worker.' },
|
|
7281
|
-
],
|
|
7282
|
-
active_sessions: activeSessions,
|
|
7283
|
-
users: knownUsers,
|
|
7284
|
-
bans,
|
|
7285
|
-
recent_packs: recentPacks,
|
|
7286
|
-
visit_metrics: visitSummary,
|
|
7287
|
-
system_summary: systemSummary,
|
|
7288
|
-
system_meta: systemMeta,
|
|
7289
|
-
message_flow_daily: messageFlowDaily,
|
|
7290
|
-
updated_at: new Date().toISOString(),
|
|
7291
|
-
};
|
|
7292
|
-
};
|
|
7293
|
-
|
|
7294
|
-
const findAdminPackContextByKey = async (rawPackKey) => {
|
|
7295
|
-
const packKey = sanitizeText(rawPackKey, 160, { allowEmpty: false });
|
|
7296
|
-
if (!packKey) return null;
|
|
7297
|
-
const basePack = await findStickerPackByPackKey(packKey);
|
|
7298
|
-
if (!basePack) return null;
|
|
7299
|
-
const ownerJid = normalizeJid(basePack.owner_jid) || '';
|
|
7300
|
-
if (!ownerJid) return null;
|
|
7301
|
-
const fullPack = await stickerPackService.getPackInfo({ ownerJid, identifier: basePack.pack_key });
|
|
7302
|
-
return { basePack, fullPack, ownerJid, packKey: basePack.pack_key };
|
|
7303
|
-
};
|
|
7304
|
-
|
|
7305
|
-
const handleAdminPanelSessionRequest = async (req, res) => {
|
|
7306
|
-
if (!ADMIN_PANEL_ENABLED) {
|
|
7307
|
-
sendJson(req, res, 404, { error: 'Painel admin desabilitado.' });
|
|
7308
|
-
return;
|
|
7309
|
-
}
|
|
7310
|
-
|
|
7311
|
-
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
7312
|
-
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
7313
|
-
const adminSession = resolveAdminPanelSessionFromRequest(req);
|
|
7314
|
-
const eligibility = await resolveAdminPanelLoginEligibility(googleSession);
|
|
7315
|
-
sendJson(req, res, 200, {
|
|
7316
|
-
data: {
|
|
7317
|
-
google: mapGoogleSessionResponseData(googleSession),
|
|
7318
|
-
eligible_google_login: Boolean(eligibility.eligible),
|
|
7319
|
-
eligible_role: eligibility.role || null,
|
|
7320
|
-
session: mapAdminPanelSessionResponseData(adminSession),
|
|
7321
|
-
},
|
|
7322
|
-
});
|
|
7323
|
-
return;
|
|
7324
|
-
}
|
|
7325
|
-
|
|
7326
|
-
if (req.method === 'DELETE') {
|
|
7327
|
-
const token = getAdminPanelSessionTokenFromRequest(req);
|
|
7328
|
-
const adminSession = resolveAdminPanelSessionFromRequest(req);
|
|
7329
|
-
if (token) adminPanelSessionMap.delete(token);
|
|
7330
|
-
clearAdminPanelSessionCookie(req, res);
|
|
7331
|
-
await createAdminActionAuditEvent({
|
|
7332
|
-
adminSession,
|
|
7333
|
-
action: 'admin_session_logout',
|
|
7334
|
-
targetType: 'admin_session',
|
|
7335
|
-
targetId: token || 'cookie_clear',
|
|
7336
|
-
});
|
|
7337
|
-
sendJson(req, res, 200, { data: { cleared: true } });
|
|
7338
|
-
return;
|
|
7339
|
-
}
|
|
7340
|
-
|
|
7341
|
-
if (req.method !== 'POST') {
|
|
7342
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7343
|
-
return;
|
|
7344
|
-
}
|
|
7345
|
-
|
|
7346
|
-
let payload = {};
|
|
7347
|
-
try {
|
|
7348
|
-
payload = await readJsonBody(req);
|
|
7349
|
-
} catch (error) {
|
|
7350
|
-
sendJson(req, res, Number(error?.statusCode || 400), { error: error?.message || 'Body inválido.' });
|
|
7351
|
-
return;
|
|
7352
|
-
}
|
|
7353
|
-
|
|
7354
|
-
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
7355
|
-
const eligibility = await resolveAdminPanelLoginEligibility(googleSession);
|
|
7356
|
-
if (!eligibility.eligible) {
|
|
7357
|
-
sendJson(req, res, 403, { error: 'Conta Google sem permissao para o painel admin.' });
|
|
7358
|
-
return;
|
|
7359
|
-
}
|
|
7360
|
-
const password = String(payload?.password || '').trim();
|
|
7361
|
-
let sessionRole = 'owner';
|
|
7362
|
-
if (eligibility.role === 'owner') {
|
|
7363
|
-
if (!password || !constantTimeStringEqual(password, ADMIN_PANEL_PASSWORD)) {
|
|
7364
|
-
sendJson(req, res, 401, { error: 'Senha do painel admin invalida.' });
|
|
7365
|
-
return;
|
|
7366
|
-
}
|
|
7367
|
-
sessionRole = 'owner';
|
|
7368
|
-
} else if (eligibility.role === 'moderator') {
|
|
7369
|
-
const moderatorHash = String(eligibility?.moderator?.password_hash || '').trim();
|
|
7370
|
-
if (!password || !verifyAdminModeratorPassword(password, moderatorHash)) {
|
|
7371
|
-
sendJson(req, res, 401, { error: 'Senha do moderador invalida.' });
|
|
7372
|
-
return;
|
|
7373
|
-
}
|
|
7374
|
-
sessionRole = 'moderator';
|
|
7375
|
-
await touchAdminModeratorLastLogin(eligibility.moderator, googleSession).catch(() => {});
|
|
7376
|
-
} else {
|
|
7377
|
-
sendJson(req, res, 403, { error: 'Conta Google sem permissao para o painel admin.' });
|
|
7378
|
-
return;
|
|
7379
|
-
}
|
|
7380
|
-
|
|
7381
|
-
const session = createAdminPanelSession(googleSession, { role: sessionRole });
|
|
7382
|
-
appendSetCookie(
|
|
7383
|
-
res,
|
|
7384
|
-
buildCookieString(ADMIN_PANEL_SESSION_COOKIE_NAME, session.token, req, {
|
|
7385
|
-
maxAgeSeconds: Math.floor(ADMIN_PANEL_SESSION_TTL_MS / 1000),
|
|
7386
|
-
}),
|
|
7387
|
-
);
|
|
7388
|
-
sendJson(req, res, 200, {
|
|
7389
|
-
data: {
|
|
7390
|
-
google: mapGoogleSessionResponseData(googleSession),
|
|
7391
|
-
eligible_google_login: true,
|
|
7392
|
-
eligible_role: sessionRole,
|
|
7393
|
-
session: mapAdminPanelSessionResponseData(session),
|
|
7394
|
-
},
|
|
7395
|
-
});
|
|
7396
|
-
await createAdminActionAuditEvent({
|
|
7397
|
-
adminSession: session,
|
|
7398
|
-
action: 'admin_session_login',
|
|
7399
|
-
targetType: 'admin_session',
|
|
7400
|
-
targetId: session.token,
|
|
7401
|
-
details: { role: sessionRole },
|
|
7402
|
-
});
|
|
7403
|
-
};
|
|
7404
|
-
|
|
7405
|
-
const handleAdminOverviewRequest = async (req, res) => {
|
|
7406
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
7407
|
-
if (!adminSession) return;
|
|
7408
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7409
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7410
|
-
return;
|
|
7411
|
-
}
|
|
7412
|
-
|
|
7413
|
-
const overview = await buildAdminOverviewPayload({ adminSession });
|
|
7414
|
-
sendJson(req, res, 200, { data: overview });
|
|
7415
|
-
};
|
|
7416
|
-
|
|
7417
|
-
const handleAdminUsersRequest = async (req, res, url) => {
|
|
7418
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
7419
|
-
if (!adminSession) return;
|
|
7420
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7421
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7422
|
-
return;
|
|
7423
|
-
}
|
|
7424
|
-
const limit = Math.max(1, Math.min(500, Number(url?.searchParams?.get('limit') || 200)));
|
|
7425
|
-
const [activeSessions, users] = await Promise.all([listAdminActiveGoogleWebSessions({ limit }), listAdminKnownGoogleUsers({ limit })]);
|
|
7426
|
-
sendJson(req, res, 200, { data: { active_sessions: activeSessions, users } });
|
|
7427
|
-
};
|
|
7428
|
-
|
|
7429
|
-
const handleAdminForceLogoutRequest = async (req, res) => {
|
|
7430
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
7431
|
-
if (!adminSession) return;
|
|
7432
|
-
if (req.method !== 'POST') {
|
|
7433
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7434
|
-
return;
|
|
7435
|
-
}
|
|
7436
|
-
|
|
7437
|
-
let payload = {};
|
|
7438
|
-
try {
|
|
7439
|
-
payload = await readJsonBody(req);
|
|
7440
|
-
} catch (error) {
|
|
7441
|
-
sendJson(req, res, Number(error?.statusCode || 400), { error: error?.message || 'Body inválido.' });
|
|
7442
|
-
return;
|
|
7443
|
-
}
|
|
7444
|
-
|
|
7445
|
-
let googleSub = normalizeGoogleSubject(payload?.google_sub || '');
|
|
7446
|
-
let email = normalizeEmail(payload?.email || '');
|
|
7447
|
-
let ownerJid = normalizeJid(payload?.owner_jid || '') || '';
|
|
7448
|
-
const sessionToken = sanitizeText(payload?.session_token || '', 36, { allowEmpty: true }) || '';
|
|
7449
|
-
|
|
7450
|
-
if (sessionToken) {
|
|
7451
|
-
const rows = await executeQuery(
|
|
7452
|
-
`SELECT google_sub, email, owner_jid
|
|
7453
|
-
FROM ${TABLES.STICKER_WEB_GOOGLE_SESSION}
|
|
7454
|
-
WHERE session_token = ?
|
|
7455
|
-
LIMIT 1`,
|
|
7456
|
-
[sessionToken],
|
|
7457
|
-
).catch(() => []);
|
|
7458
|
-
const row = Array.isArray(rows) ? rows[0] : null;
|
|
7459
|
-
if (row) {
|
|
7460
|
-
googleSub = normalizeGoogleSubject(row.google_sub || googleSub);
|
|
7461
|
-
email = normalizeEmail(row.email || email);
|
|
7462
|
-
ownerJid = normalizeJid(row.owner_jid || ownerJid) || ownerJid;
|
|
7463
|
-
}
|
|
7464
|
-
}
|
|
7465
|
-
|
|
7466
|
-
if (!googleSub && !email && !ownerJid) {
|
|
7467
|
-
sendJson(req, res, 400, { error: 'Informe session_token, google_sub, email ou owner_jid.' });
|
|
7468
|
-
return;
|
|
7469
|
-
}
|
|
7470
|
-
|
|
7471
|
-
const removed = await revokeGoogleWebSessionsByIdentity({
|
|
7472
|
-
googleSub,
|
|
7473
|
-
email,
|
|
7474
|
-
ownerJid,
|
|
7475
|
-
}).catch(() => 0);
|
|
7476
|
-
|
|
7477
|
-
await createAdminActionAuditEvent({
|
|
7478
|
-
adminSession,
|
|
7479
|
-
action: 'force_logout',
|
|
7480
|
-
targetType: 'google_web_session',
|
|
7481
|
-
targetId: sessionToken || googleSub || email || ownerJid,
|
|
7482
|
-
details: { removed_sessions: Number(removed || 0), google_sub: googleSub || null, email: email || null, owner_jid: ownerJid || null },
|
|
7483
|
-
});
|
|
7484
|
-
|
|
7485
|
-
sendJson(req, res, 200, {
|
|
7486
|
-
data: {
|
|
7487
|
-
removed_sessions: Number(removed || 0),
|
|
7488
|
-
target: {
|
|
7489
|
-
session_token: sessionToken || null,
|
|
7490
|
-
google_sub: googleSub || null,
|
|
7491
|
-
email: email || null,
|
|
7492
|
-
owner_jid: ownerJid || null,
|
|
7493
|
-
},
|
|
7494
|
-
},
|
|
7495
|
-
});
|
|
7496
|
-
};
|
|
7497
|
-
|
|
7498
|
-
const handleAdminFeatureFlagsRequest = async (req, res) => {
|
|
7499
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
7500
|
-
if (!adminSession) return;
|
|
7501
|
-
|
|
7502
|
-
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
7503
|
-
const flags = await listAdminFeatureFlagsDetailed({ limit: 400 }).catch(() => []);
|
|
7504
|
-
sendJson(req, res, 200, { data: { flags } });
|
|
7505
|
-
return;
|
|
7506
|
-
}
|
|
7507
|
-
|
|
7508
|
-
if (req.method !== 'POST') {
|
|
7509
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7510
|
-
return;
|
|
7511
|
-
}
|
|
7512
|
-
|
|
7513
|
-
let payload = {};
|
|
7514
|
-
try {
|
|
7515
|
-
payload = await readJsonBody(req);
|
|
7516
|
-
} catch (error) {
|
|
7517
|
-
sendJson(req, res, Number(error?.statusCode || 400), { error: error?.message || 'Body inválido.' });
|
|
7518
|
-
return;
|
|
7519
|
-
}
|
|
7520
|
-
|
|
7521
|
-
try {
|
|
7522
|
-
const flag = await upsertAdminFeatureFlagRecord({
|
|
7523
|
-
adminSession,
|
|
7524
|
-
flagName: payload?.flag_name,
|
|
7525
|
-
isEnabled: Boolean(payload?.is_enabled),
|
|
7526
|
-
rolloutPercent: payload?.rollout_percent,
|
|
7527
|
-
description: payload?.description,
|
|
7528
|
-
});
|
|
7529
|
-
await createAdminActionAuditEvent({
|
|
7530
|
-
adminSession,
|
|
7531
|
-
action: 'feature_flag_update',
|
|
7532
|
-
targetType: 'feature_flag',
|
|
7533
|
-
targetId: flag.flag_name,
|
|
7534
|
-
details: {
|
|
7535
|
-
is_enabled: flag.is_enabled,
|
|
7536
|
-
rollout_percent: flag.rollout_percent,
|
|
7537
|
-
},
|
|
7538
|
-
});
|
|
7539
|
-
sendJson(req, res, 200, { data: { flag } });
|
|
7540
|
-
} catch (error) {
|
|
7541
|
-
sendJson(req, res, Number(error?.statusCode || 400), { error: error?.message || 'Falha ao atualizar feature flag.' });
|
|
7542
|
-
}
|
|
7543
|
-
};
|
|
7544
|
-
|
|
7545
|
-
const handleAdminOpsActionRequest = async (req, res) => {
|
|
7546
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
7547
|
-
if (!adminSession) return;
|
|
7548
|
-
if (req.method !== 'POST') {
|
|
7549
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7550
|
-
return;
|
|
7551
|
-
}
|
|
7552
|
-
|
|
7553
|
-
let payload = {};
|
|
7554
|
-
try {
|
|
7555
|
-
payload = await readJsonBody(req);
|
|
7556
|
-
} catch (error) {
|
|
7557
|
-
sendJson(req, res, Number(error?.statusCode || 400), { error: error?.message || 'Body inválido.' });
|
|
7558
|
-
return;
|
|
7559
|
-
}
|
|
7560
|
-
|
|
7561
|
-
const action = sanitizeAuditActionText(payload?.action || '', 64);
|
|
7562
|
-
if (!action) {
|
|
7563
|
-
sendJson(req, res, 400, { error: 'Informe a ação operacional.' });
|
|
7564
|
-
return;
|
|
7565
|
-
}
|
|
7566
|
-
|
|
7567
|
-
try {
|
|
7568
|
-
if (action === 'clear_cache') {
|
|
7569
|
-
invalidateStickerCatalogDerivedCaches();
|
|
7570
|
-
await createAdminActionAuditEvent({
|
|
7571
|
-
adminSession,
|
|
7572
|
-
action: 'ops_clear_cache',
|
|
7573
|
-
targetType: 'cache',
|
|
7574
|
-
targetId: 'global',
|
|
7575
|
-
});
|
|
7576
|
-
sendJson(req, res, 200, { data: { action, success: true, message: 'Caches internos invalidados com sucesso.', updated_at: new Date().toISOString() } });
|
|
7577
|
-
return;
|
|
7578
|
-
}
|
|
7579
|
-
|
|
7580
|
-
if (action === 'restart_worker') {
|
|
7581
|
-
const [tasksResult, reprocessResult] = await Promise.all([
|
|
7582
|
-
executeQuery(
|
|
7583
|
-
`UPDATE ${TABLES.STICKER_WORKER_TASK_QUEUE}
|
|
7584
|
-
SET status = 'pending',
|
|
7585
|
-
worker_token = NULL,
|
|
7586
|
-
locked_at = NULL,
|
|
7587
|
-
updated_at = CURRENT_TIMESTAMP
|
|
7588
|
-
WHERE status = 'processing'`,
|
|
7589
|
-
).catch(() => ({ affectedRows: 0 })),
|
|
7590
|
-
executeQuery(
|
|
7591
|
-
`UPDATE ${TABLES.STICKER_ASSET_REPROCESS_QUEUE}
|
|
7592
|
-
SET status = 'pending',
|
|
7593
|
-
worker_token = NULL,
|
|
7594
|
-
locked_at = NULL,
|
|
7595
|
-
updated_at = CURRENT_TIMESTAMP
|
|
7596
|
-
WHERE status = 'processing'`,
|
|
7597
|
-
).catch(() => ({ affectedRows: 0 })),
|
|
7598
|
-
]);
|
|
7599
|
-
|
|
7600
|
-
const released = Number(tasksResult?.affectedRows || 0) + Number(reprocessResult?.affectedRows || 0);
|
|
7601
|
-
await createAdminActionAuditEvent({
|
|
7602
|
-
adminSession,
|
|
7603
|
-
action: 'ops_restart_worker',
|
|
7604
|
-
targetType: 'worker',
|
|
7605
|
-
targetId: 'queues',
|
|
7606
|
-
details: {
|
|
7607
|
-
released_tasks: released,
|
|
7608
|
-
task_queue: Number(tasksResult?.affectedRows || 0),
|
|
7609
|
-
reprocess_queue: Number(reprocessResult?.affectedRows || 0),
|
|
7610
|
-
},
|
|
7611
|
-
});
|
|
7612
|
-
sendJson(req, res, 200, {
|
|
7613
|
-
data: {
|
|
7614
|
-
action,
|
|
7615
|
-
success: true,
|
|
7616
|
-
released_processing_items: released,
|
|
7617
|
-
message: released > 0 ? 'Itens em processamento foram recolocados em pending.' : 'Nenhum item travado encontrado nas filas.',
|
|
7618
|
-
updated_at: new Date().toISOString(),
|
|
7619
|
-
},
|
|
7620
|
-
});
|
|
7621
|
-
return;
|
|
7622
|
-
}
|
|
7623
|
-
|
|
7624
|
-
if (action === 'reprocess_jobs') {
|
|
7625
|
-
const payloadJson = JSON.stringify({
|
|
7626
|
-
source: 'admin_panel',
|
|
7627
|
-
requested_by: normalizeEmail(adminSession?.email) || normalizeGoogleSubject(adminSession?.googleSub) || 'admin',
|
|
7628
|
-
requested_at: new Date().toISOString(),
|
|
7629
|
-
});
|
|
7630
|
-
await executeQuery(
|
|
7631
|
-
`INSERT INTO ${TABLES.STICKER_WORKER_TASK_QUEUE}
|
|
7632
|
-
(task_type, payload, priority, scheduled_at, status, max_attempts)
|
|
7633
|
-
VALUES
|
|
7634
|
-
('classification_cycle', ?, 10, UTC_TIMESTAMP(), 'pending', 5),
|
|
7635
|
-
('curation_cycle', ?, 12, UTC_TIMESTAMP(), 'pending', 5)`,
|
|
7636
|
-
[payloadJson, payloadJson],
|
|
7637
|
-
);
|
|
7638
|
-
await createAdminActionAuditEvent({
|
|
7639
|
-
adminSession,
|
|
7640
|
-
action: 'ops_reprocess_jobs',
|
|
7641
|
-
targetType: 'worker',
|
|
7642
|
-
targetId: 'classification_cycle,curation_cycle',
|
|
7643
|
-
});
|
|
7644
|
-
sendJson(req, res, 200, {
|
|
7645
|
-
data: {
|
|
7646
|
-
action,
|
|
7647
|
-
success: true,
|
|
7648
|
-
enqueued_tasks: 2,
|
|
7649
|
-
message: 'Ciclos de classificação e curadoria foram agendados.',
|
|
7650
|
-
updated_at: new Date().toISOString(),
|
|
7651
|
-
},
|
|
7652
|
-
});
|
|
7653
|
-
return;
|
|
7654
|
-
}
|
|
7655
|
-
|
|
7656
|
-
sendJson(req, res, 400, { error: 'Ação operacional inválida.' });
|
|
7657
|
-
} catch (error) {
|
|
7658
|
-
sendJson(req, res, 500, { error: error?.message || 'Falha ao executar ação operacional.' });
|
|
7659
|
-
}
|
|
7660
|
-
};
|
|
7661
|
-
|
|
7662
|
-
const handleAdminSearchRequest = async (req, res, url) => {
|
|
7663
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
7664
|
-
if (!adminSession) return;
|
|
7665
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7666
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7667
|
-
return;
|
|
7668
|
-
}
|
|
7669
|
-
|
|
7670
|
-
const q = sanitizeText(url?.searchParams?.get('q') || '', 120, { allowEmpty: true }) || '';
|
|
7671
|
-
const limit = Math.max(1, Math.min(30, Number(url?.searchParams?.get('limit') || 12)));
|
|
7672
|
-
if (!q) {
|
|
7673
|
-
sendJson(req, res, 200, {
|
|
7674
|
-
data: {
|
|
7675
|
-
q: '',
|
|
7676
|
-
totals: { users: 0, sessions: 0, groups: 0, packs: 0 },
|
|
7677
|
-
results: { users: [], sessions: [], groups: [], packs: [] },
|
|
7678
|
-
},
|
|
7679
|
-
});
|
|
7680
|
-
return;
|
|
7681
|
-
}
|
|
7682
|
-
|
|
7683
|
-
const like = `%${q}%`;
|
|
7684
|
-
|
|
7685
|
-
const [usersRows, sessionsRows, groupsRows, packs] = await Promise.all([
|
|
7686
|
-
executeQuery(
|
|
7687
|
-
`SELECT google_sub, email, name, owner_jid, owner_phone, last_seen_at, last_login_at
|
|
7688
|
-
FROM ${TABLES.STICKER_WEB_GOOGLE_USER}
|
|
7689
|
-
WHERE google_sub LIKE ? OR email LIKE ? OR name LIKE ? OR owner_jid LIKE ? OR owner_phone LIKE ?
|
|
7690
|
-
ORDER BY COALESCE(last_seen_at, last_login_at, created_at) DESC
|
|
7691
|
-
LIMIT ${limit}`,
|
|
7692
|
-
[like, like, like, like, like],
|
|
7693
|
-
).catch(() => []),
|
|
7694
|
-
executeQuery(
|
|
7695
|
-
`SELECT session_token, google_sub, email, name, owner_jid, owner_phone, last_seen_at, expires_at
|
|
7696
|
-
FROM ${TABLES.STICKER_WEB_GOOGLE_SESSION}
|
|
7697
|
-
WHERE revoked_at IS NULL
|
|
7698
|
-
AND expires_at > UTC_TIMESTAMP()
|
|
7699
|
-
AND (session_token LIKE ? OR google_sub LIKE ? OR email LIKE ? OR name LIKE ? OR owner_jid LIKE ? OR owner_phone LIKE ?)
|
|
7700
|
-
ORDER BY COALESCE(last_seen_at, created_at) DESC
|
|
7701
|
-
LIMIT ${limit}`,
|
|
7702
|
-
[like, like, like, like, like, like],
|
|
7703
|
-
).catch(() => []),
|
|
7704
|
-
executeQuery(
|
|
7705
|
-
`SELECT
|
|
7706
|
-
gm.id,
|
|
7707
|
-
COALESCE(NULLIF(gm.subject, ''), ch.name, gm.id) AS subject,
|
|
7708
|
-
gm.owner_jid,
|
|
7709
|
-
gm.updated_at
|
|
7710
|
-
FROM ${TABLES.GROUPS_METADATA} gm
|
|
7711
|
-
LEFT JOIN ${TABLES.CHATS} ch ON ch.id = gm.id
|
|
7712
|
-
WHERE gm.id LIKE ? OR gm.subject LIKE ? OR ch.name LIKE ? OR gm.owner_jid LIKE ?
|
|
7713
|
-
ORDER BY gm.updated_at DESC
|
|
7714
|
-
LIMIT ${limit}`,
|
|
7715
|
-
[like, like, like, like],
|
|
7716
|
-
).catch(() => []),
|
|
7717
|
-
listAdminPacks({
|
|
7718
|
-
searchParams: new URLSearchParams([
|
|
7719
|
-
['q', q],
|
|
7720
|
-
['limit', String(limit)],
|
|
7721
|
-
]),
|
|
7722
|
-
}).catch(() => []),
|
|
7723
|
-
]);
|
|
7724
|
-
|
|
7725
|
-
const users = (Array.isArray(usersRows) ? usersRows : []).map((row) => ({
|
|
7726
|
-
google_sub: normalizeGoogleSubject(row?.google_sub),
|
|
7727
|
-
email: normalizeEmail(row?.email) || null,
|
|
7728
|
-
name: sanitizeText(row?.name || '', 120, { allowEmpty: true }) || null,
|
|
7729
|
-
owner_jid: normalizeJid(row?.owner_jid) || null,
|
|
7730
|
-
owner_phone: toWhatsAppPhoneDigits(row?.owner_phone || row?.owner_jid) || null,
|
|
7731
|
-
last_seen_at: toIsoOrNull(row?.last_seen_at),
|
|
7732
|
-
last_login_at: toIsoOrNull(row?.last_login_at),
|
|
7733
|
-
}));
|
|
7734
|
-
|
|
7735
|
-
const sessions = (Array.isArray(sessionsRows) ? sessionsRows : []).map((row) => ({
|
|
7736
|
-
session_token: String(row?.session_token || '').trim() || null,
|
|
7737
|
-
google_sub: normalizeGoogleSubject(row?.google_sub),
|
|
7738
|
-
email: normalizeEmail(row?.email) || null,
|
|
7739
|
-
name: sanitizeText(row?.name || '', 120, { allowEmpty: true }) || null,
|
|
7740
|
-
owner_jid: normalizeJid(row?.owner_jid) || null,
|
|
7741
|
-
owner_phone: toWhatsAppPhoneDigits(row?.owner_phone || row?.owner_jid) || null,
|
|
7742
|
-
last_seen_at: toIsoOrNull(row?.last_seen_at),
|
|
7743
|
-
expires_at: toIsoOrNull(row?.expires_at),
|
|
7744
|
-
}));
|
|
7745
|
-
|
|
7746
|
-
const groups = (Array.isArray(groupsRows) ? groupsRows : []).map((row) => ({
|
|
7747
|
-
id: String(row?.id || '').trim(),
|
|
7748
|
-
subject: sanitizeText(row?.subject || row?.id || '', 255, { allowEmpty: true }) || String(row?.id || '').trim(),
|
|
7749
|
-
owner_jid: normalizeJid(row?.owner_jid) || null,
|
|
7750
|
-
updated_at: toIsoOrNull(row?.updated_at),
|
|
7751
|
-
}));
|
|
7752
|
-
|
|
7753
|
-
sendJson(req, res, 200, {
|
|
7754
|
-
data: {
|
|
7755
|
-
q,
|
|
7756
|
-
totals: {
|
|
7757
|
-
users: users.length,
|
|
7758
|
-
sessions: sessions.length,
|
|
7759
|
-
groups: groups.length,
|
|
7760
|
-
packs: Array.isArray(packs) ? packs.length : 0,
|
|
7761
|
-
},
|
|
7762
|
-
results: {
|
|
7763
|
-
users,
|
|
7764
|
-
sessions,
|
|
7765
|
-
groups,
|
|
7766
|
-
packs: Array.isArray(packs) ? packs : [],
|
|
7767
|
-
},
|
|
7768
|
-
},
|
|
7769
|
-
});
|
|
7770
|
-
};
|
|
7771
|
-
|
|
7772
|
-
const toCsvValue = (value) => {
|
|
7773
|
-
const normalized = value === null || value === undefined ? '' : String(value);
|
|
7774
|
-
if (/[",\n;]/.test(normalized)) {
|
|
7775
|
-
return `"${normalized.replaceAll('"', '""')}"`;
|
|
7776
|
-
}
|
|
7777
|
-
return normalized;
|
|
7778
|
-
};
|
|
7779
|
-
|
|
7780
|
-
const buildCsv = (rows = [], headers = []) => {
|
|
7781
|
-
const safeRows = Array.isArray(rows) ? rows : [];
|
|
7782
|
-
const safeHeaders = Array.isArray(headers) ? headers : [];
|
|
7783
|
-
const lines = [];
|
|
7784
|
-
lines.push(safeHeaders.map((header) => toCsvValue(header)).join(','));
|
|
7785
|
-
for (const row of safeRows) {
|
|
7786
|
-
lines.push(
|
|
7787
|
-
safeHeaders
|
|
7788
|
-
.map((header) => {
|
|
7789
|
-
const value = row && typeof row === 'object' ? row[header] : '';
|
|
7790
|
-
return toCsvValue(value);
|
|
7791
|
-
})
|
|
7792
|
-
.join(','),
|
|
7793
|
-
);
|
|
7794
|
-
}
|
|
7795
|
-
return `${lines.join('\n')}\n`;
|
|
7796
|
-
};
|
|
7797
|
-
|
|
7798
|
-
const handleAdminExportRequest = async (req, res, url) => {
|
|
7799
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
7800
|
-
if (!adminSession) return;
|
|
7801
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7802
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7803
|
-
return;
|
|
7804
|
-
}
|
|
7805
|
-
|
|
7806
|
-
const format = String(url?.searchParams?.get('format') || 'json')
|
|
7807
|
-
.trim()
|
|
7808
|
-
.toLowerCase();
|
|
7809
|
-
const type = String(url?.searchParams?.get('type') || 'metrics')
|
|
7810
|
-
.trim()
|
|
7811
|
-
.toLowerCase();
|
|
7812
|
-
|
|
7813
|
-
const overview = await buildAdminOverviewPayload({ adminSession });
|
|
7814
|
-
const exportData =
|
|
7815
|
-
type === 'events'
|
|
7816
|
-
? {
|
|
7817
|
-
moderation_queue: overview?.moderation_queue || [],
|
|
7818
|
-
audit_log: overview?.audit_log || [],
|
|
7819
|
-
blocked_accounts: overview?.users_sessions?.blocked_accounts || [],
|
|
7820
|
-
}
|
|
7821
|
-
: {
|
|
7822
|
-
dashboard_quick: overview?.dashboard_quick || null,
|
|
7823
|
-
counters: overview?.counters || null,
|
|
7824
|
-
system_health: overview?.system_health || null,
|
|
7825
|
-
alerts: overview?.alerts || [],
|
|
7826
|
-
feature_flags: overview?.feature_flags || [],
|
|
7827
|
-
};
|
|
7828
|
-
|
|
7829
|
-
await createAdminActionAuditEvent({
|
|
7830
|
-
adminSession,
|
|
7831
|
-
action: 'export_data',
|
|
7832
|
-
targetType: 'admin_export',
|
|
7833
|
-
targetId: `${type}.${format}`,
|
|
7834
|
-
details: { type, format },
|
|
7835
|
-
});
|
|
7836
|
-
|
|
7837
|
-
if (format !== 'csv') {
|
|
7838
|
-
sendJson(req, res, 200, {
|
|
7839
|
-
data: {
|
|
7840
|
-
type,
|
|
7841
|
-
format: 'json',
|
|
7842
|
-
exported_at: new Date().toISOString(),
|
|
7843
|
-
payload: exportData,
|
|
7844
|
-
},
|
|
7845
|
-
});
|
|
7846
|
-
return;
|
|
7847
|
-
}
|
|
7848
|
-
|
|
7849
|
-
let headers = [];
|
|
7850
|
-
let rows = [];
|
|
7851
|
-
|
|
7852
|
-
if (type === 'events') {
|
|
7853
|
-
headers = ['section', 'id', 'event_type', 'severity', 'title', 'subtitle', 'status', 'created_at'];
|
|
7854
|
-
rows = [
|
|
7855
|
-
...(Array.isArray(exportData?.moderation_queue) ? exportData.moderation_queue : []).map((item) => ({
|
|
7856
|
-
section: 'moderation_queue',
|
|
7857
|
-
id: item?.id || '',
|
|
7858
|
-
event_type: item?.event_type || '',
|
|
7859
|
-
severity: item?.severity || '',
|
|
7860
|
-
title: item?.title || '',
|
|
7861
|
-
subtitle: item?.subtitle || '',
|
|
7862
|
-
status: item?.revoked_at ? 'revoked' : item?.status || '',
|
|
7863
|
-
created_at: item?.created_at || item?.revoked_at || '',
|
|
7864
|
-
})),
|
|
7865
|
-
...(Array.isArray(exportData?.audit_log) ? exportData.audit_log : []).map((item) => ({
|
|
7866
|
-
section: 'audit_log',
|
|
7867
|
-
id: item?.id || '',
|
|
7868
|
-
event_type: item?.action || '',
|
|
7869
|
-
severity: item?.status || '',
|
|
7870
|
-
title: item?.target_type || '',
|
|
7871
|
-
subtitle: item?.target_id || '',
|
|
7872
|
-
status: item?.status || '',
|
|
7873
|
-
created_at: item?.created_at || '',
|
|
7874
|
-
})),
|
|
7875
|
-
...(Array.isArray(exportData?.blocked_accounts) ? exportData.blocked_accounts : []).map((item) => ({
|
|
7876
|
-
section: 'blocked_accounts',
|
|
7877
|
-
id: item?.id || '',
|
|
7878
|
-
event_type: 'ban',
|
|
7879
|
-
severity: item?.revoked_at ? 'low' : 'critical',
|
|
7880
|
-
title: item?.email || item?.owner_jid || item?.google_sub || '',
|
|
7881
|
-
subtitle: item?.reason || '',
|
|
7882
|
-
status: item?.revoked_at ? 'revoked' : 'active',
|
|
7883
|
-
created_at: item?.created_at || '',
|
|
7884
|
-
})),
|
|
7885
|
-
];
|
|
7886
|
-
} else {
|
|
7887
|
-
headers = ['section', 'key', 'value'];
|
|
7888
|
-
const dashboard = exportData?.dashboard_quick || {};
|
|
7889
|
-
const counters = exportData?.counters || {};
|
|
7890
|
-
const health = exportData?.system_health || {};
|
|
7891
|
-
const alerts = Array.isArray(exportData?.alerts) ? exportData.alerts : [];
|
|
7892
|
-
const flags = Array.isArray(exportData?.feature_flags) ? exportData.feature_flags : [];
|
|
7893
|
-
rows = [...Object.entries(dashboard).map(([key, value]) => ({ section: 'dashboard_quick', key, value })), ...Object.entries(counters).map(([key, value]) => ({ section: 'counters', key, value })), ...Object.entries(health).map(([key, value]) => ({ section: 'system_health', key, value })), ...alerts.map((alert, index) => ({ section: 'alerts', key: `${index + 1}:${alert?.code || 'alert'}`, value: `${alert?.severity || ''} ${alert?.title || ''}`.trim() })), ...flags.map((flag) => ({ section: 'feature_flags', key: flag?.flag_name || '', value: `${flag?.is_enabled ? 'on' : 'off'} (${flag?.rollout_percent || 0}%)` }))];
|
|
7894
|
-
}
|
|
7895
|
-
|
|
7896
|
-
const csv = buildCsv(rows, headers);
|
|
7897
|
-
const stamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-');
|
|
7898
|
-
res.statusCode = 200;
|
|
7899
|
-
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
7900
|
-
res.setHeader('Content-Disposition', `attachment; filename="admin-${type}-${stamp}.csv"`);
|
|
7901
|
-
res.end(csv);
|
|
7902
|
-
};
|
|
7903
|
-
|
|
7904
|
-
const handleAdminModeratorsRequest = async (req, res) => {
|
|
7905
|
-
const adminSession = requireOwnerAdminPanelSession(req, res);
|
|
7906
|
-
if (!adminSession) return;
|
|
7907
|
-
|
|
7908
|
-
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
7909
|
-
const moderators = await listAdminModerators({ activeOnly: false, limit: 500 });
|
|
7910
|
-
sendJson(req, res, 200, { data: { moderators } });
|
|
7911
|
-
return;
|
|
7912
|
-
}
|
|
7913
|
-
|
|
7914
|
-
if (req.method !== 'POST') {
|
|
7915
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7916
|
-
return;
|
|
7917
|
-
}
|
|
7918
|
-
|
|
7919
|
-
let payload = {};
|
|
7920
|
-
try {
|
|
7921
|
-
payload = await readJsonBody(req);
|
|
7922
|
-
} catch (error) {
|
|
7923
|
-
sendJson(req, res, Number(error?.statusCode || 400), { error: error?.message || 'Body inválido.' });
|
|
7924
|
-
return;
|
|
7925
|
-
}
|
|
7926
|
-
|
|
7927
|
-
try {
|
|
7928
|
-
const result = await upsertAdminModeratorRecord({
|
|
7929
|
-
googleSub: payload?.google_sub,
|
|
7930
|
-
email: payload?.email,
|
|
7931
|
-
ownerJid: payload?.owner_jid,
|
|
7932
|
-
password: payload?.password,
|
|
7933
|
-
adminSession,
|
|
7934
|
-
});
|
|
7935
|
-
await createAdminActionAuditEvent({
|
|
7936
|
-
adminSession,
|
|
7937
|
-
action: result.created ? 'moderator_create' : 'moderator_update',
|
|
7938
|
-
targetType: 'moderator',
|
|
7939
|
-
targetId: result?.moderator?.google_sub || payload?.google_sub || '',
|
|
7940
|
-
details: { created: Boolean(result.created) },
|
|
7941
|
-
});
|
|
7942
|
-
sendJson(req, res, result.created ? 201 : 200, {
|
|
7943
|
-
data: {
|
|
7944
|
-
created: result.created,
|
|
7945
|
-
moderator: result.moderator,
|
|
7946
|
-
},
|
|
7947
|
-
});
|
|
7948
|
-
} catch (error) {
|
|
7949
|
-
sendJson(req, res, Number(error?.statusCode || 400), { error: error?.message || 'Falha ao salvar moderador.' });
|
|
7950
|
-
}
|
|
7951
|
-
};
|
|
7952
|
-
|
|
7953
|
-
const handleAdminModeratorDeleteRequest = async (req, res, googleSub) => {
|
|
7954
|
-
const adminSession = requireOwnerAdminPanelSession(req, res);
|
|
7955
|
-
if (!adminSession) return;
|
|
7956
|
-
if (req.method !== 'DELETE') {
|
|
7957
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7958
|
-
return;
|
|
7959
|
-
}
|
|
7960
|
-
try {
|
|
7961
|
-
const moderator = await revokeAdminModeratorRecord(googleSub, adminSession);
|
|
7962
|
-
await createAdminActionAuditEvent({
|
|
7963
|
-
adminSession,
|
|
7964
|
-
action: 'moderator_revoke',
|
|
7965
|
-
targetType: 'moderator',
|
|
7966
|
-
targetId: moderator?.google_sub || googleSub,
|
|
7967
|
-
});
|
|
7968
|
-
sendJson(req, res, 200, { data: { revoked: true, moderator } });
|
|
7969
|
-
} catch (error) {
|
|
7970
|
-
sendJson(req, res, Number(error?.statusCode || 400), { error: error?.message || 'Falha ao remover moderador.' });
|
|
7971
|
-
}
|
|
7972
|
-
};
|
|
7973
|
-
|
|
7974
|
-
const handleAdminPacksRequest = async (req, res, url) => {
|
|
7975
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
7976
|
-
if (!adminSession) return;
|
|
7977
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7978
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7979
|
-
return;
|
|
7980
|
-
}
|
|
7981
|
-
const packs = await listAdminPacks(url);
|
|
7982
|
-
sendJson(req, res, 200, { data: packs });
|
|
7983
|
-
};
|
|
7984
|
-
|
|
7985
|
-
const handleAdminPackDetailsRequest = async (req, res, packKey) => {
|
|
7986
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
7987
|
-
if (!adminSession) return;
|
|
7988
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7989
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7990
|
-
return;
|
|
7991
|
-
}
|
|
7992
|
-
const context = await findAdminPackContextByKey(packKey);
|
|
7993
|
-
if (!context?.fullPack) {
|
|
7994
|
-
sendJson(req, res, 404, { error: 'Pack nao encontrado.' });
|
|
7995
|
-
return;
|
|
7996
|
-
}
|
|
7997
|
-
const data = await buildManagedPackResponseData(context.fullPack);
|
|
7998
|
-
sendJson(req, res, 200, { data });
|
|
7999
|
-
};
|
|
8000
|
-
|
|
8001
|
-
const handleAdminPackDeleteRequest = async (req, res, packKey) => {
|
|
8002
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
8003
|
-
if (!adminSession) return;
|
|
8004
|
-
if (req.method !== 'DELETE') {
|
|
8005
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
8006
|
-
return;
|
|
8007
|
-
}
|
|
8008
|
-
const context = await findAdminPackContextByKey(packKey);
|
|
8009
|
-
const normalizedPackKey = sanitizeText(packKey, 160, { allowEmpty: false }) || String(packKey || '');
|
|
8010
|
-
if (!context?.fullPack) {
|
|
8011
|
-
sendManagedMutationStatus(req, res, 'already_deleted', {
|
|
8012
|
-
deleted: false,
|
|
8013
|
-
pack_key: normalizedPackKey,
|
|
8014
|
-
admin: true,
|
|
8015
|
-
});
|
|
8016
|
-
return;
|
|
8017
|
-
}
|
|
8018
|
-
const result = await deleteManagedPackWithCleanup({
|
|
8019
|
-
ownerJid: context.ownerJid,
|
|
8020
|
-
identifier: context.packKey,
|
|
8021
|
-
fallbackPack: context.fullPack,
|
|
8022
|
-
});
|
|
8023
|
-
await createAdminActionAuditEvent({
|
|
8024
|
-
adminSession,
|
|
8025
|
-
action: 'pack_delete',
|
|
8026
|
-
targetType: 'pack',
|
|
8027
|
-
targetId: result?.deletedPack?.pack_key || context.packKey || packKey,
|
|
8028
|
-
details: {
|
|
8029
|
-
removed_sticker_count: Number(result?.removedCount || 0),
|
|
8030
|
-
missing: Boolean(result?.missing),
|
|
8031
|
-
},
|
|
8032
|
-
});
|
|
8033
|
-
sendManagedMutationStatus(req, res, 'deleted', {
|
|
8034
|
-
admin: true,
|
|
8035
|
-
deleted: !result?.missing,
|
|
8036
|
-
pack_key: result?.deletedPack?.pack_key || context.packKey,
|
|
8037
|
-
id: result?.deletedPack?.id || context.fullPack?.id || null,
|
|
8038
|
-
deleted_at: toIsoOrNull(result?.deletedPack?.deleted_at || new Date()),
|
|
8039
|
-
removed_sticker_count: Number(result?.removedCount || 0),
|
|
8040
|
-
});
|
|
8041
|
-
};
|
|
8042
|
-
|
|
8043
|
-
const handleAdminPackStickerDeleteRequest = async (req, res, packKey, stickerId) => {
|
|
8044
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
8045
|
-
if (!adminSession) return;
|
|
8046
|
-
if (req.method !== 'DELETE') {
|
|
8047
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
8048
|
-
return;
|
|
8049
|
-
}
|
|
8050
|
-
const context = await findAdminPackContextByKey(packKey);
|
|
8051
|
-
const normalizedStickerId = sanitizeText(stickerId, 36, { allowEmpty: false });
|
|
8052
|
-
if (!context?.fullPack) {
|
|
8053
|
-
sendManagedMutationStatus(req, res, 'already_deleted', {
|
|
8054
|
-
admin: true,
|
|
8055
|
-
pack_key: sanitizeText(packKey, 160, { allowEmpty: true }) || String(packKey || ''),
|
|
8056
|
-
sticker_id: normalizedStickerId || null,
|
|
8057
|
-
});
|
|
8058
|
-
return;
|
|
8059
|
-
}
|
|
8060
|
-
try {
|
|
8061
|
-
const result = await stickerPackService.removeStickerFromPack({
|
|
8062
|
-
ownerJid: context.ownerJid,
|
|
8063
|
-
identifier: context.packKey,
|
|
8064
|
-
selector: normalizedStickerId,
|
|
8065
|
-
});
|
|
8066
|
-
invalidateStickerCatalogDerivedCaches();
|
|
8067
|
-
if (normalizedStickerId) {
|
|
8068
|
-
await cleanupOrphanStickerAssets([normalizedStickerId], { reason: 'admin_remove_sticker' });
|
|
8069
|
-
}
|
|
8070
|
-
await createAdminActionAuditEvent({
|
|
8071
|
-
adminSession,
|
|
8072
|
-
action: 'pack_sticker_delete',
|
|
8073
|
-
targetType: 'sticker',
|
|
8074
|
-
targetId: normalizedStickerId || stickerId,
|
|
8075
|
-
details: {
|
|
8076
|
-
pack_key: context.packKey,
|
|
8077
|
-
},
|
|
8078
|
-
});
|
|
8079
|
-
await sendManagedPackMutationStatus(req, res, 'updated', result?.pack || context.fullPack, {
|
|
8080
|
-
admin: true,
|
|
8081
|
-
pack_key: context.packKey,
|
|
8082
|
-
removed_sticker_id: normalizedStickerId || null,
|
|
8083
|
-
});
|
|
8084
|
-
} catch (error) {
|
|
8085
|
-
const mapped = mapStickerPackWebManageError(error);
|
|
8086
|
-
sendJson(req, res, mapped.statusCode, { error: mapped.message, code: mapped.code });
|
|
8087
|
-
}
|
|
8088
|
-
};
|
|
8089
|
-
|
|
8090
|
-
const handleAdminGlobalStickerDeleteRequest = async (req, res, stickerId) => {
|
|
8091
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
8092
|
-
if (!adminSession) return;
|
|
8093
|
-
if (req.method !== 'DELETE') {
|
|
8094
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
8095
|
-
return;
|
|
8096
|
-
}
|
|
8097
|
-
const normalizedStickerId = sanitizeText(stickerId, 36, { allowEmpty: false });
|
|
8098
|
-
if (!normalizedStickerId) {
|
|
8099
|
-
sendJson(req, res, 400, { error: 'sticker_id invalido.' });
|
|
8100
|
-
return;
|
|
8101
|
-
}
|
|
8102
|
-
|
|
8103
|
-
const refs = await executeQuery(
|
|
8104
|
-
`SELECT p.pack_key, p.owner_jid
|
|
8105
|
-
FROM ${TABLES.STICKER_PACK_ITEM} i
|
|
8106
|
-
INNER JOIN ${TABLES.STICKER_PACK} p ON p.id = i.pack_id
|
|
8107
|
-
WHERE i.sticker_id = ?
|
|
8108
|
-
AND p.deleted_at IS NULL`,
|
|
8109
|
-
[normalizedStickerId],
|
|
8110
|
-
);
|
|
8111
|
-
|
|
8112
|
-
let removedFromPacks = 0;
|
|
8113
|
-
let removeErrors = 0;
|
|
8114
|
-
for (const ref of Array.isArray(refs) ? refs : []) {
|
|
8115
|
-
try {
|
|
8116
|
-
const ownerJid = normalizeJid(ref.owner_jid) || '';
|
|
8117
|
-
const packKey = sanitizeText(ref.pack_key, 160, { allowEmpty: false });
|
|
8118
|
-
if (!ownerJid || !packKey) continue;
|
|
8119
|
-
await stickerPackService.removeStickerFromPack({
|
|
8120
|
-
ownerJid,
|
|
8121
|
-
identifier: packKey,
|
|
8122
|
-
selector: normalizedStickerId,
|
|
8123
|
-
});
|
|
8124
|
-
removedFromPacks += 1;
|
|
8125
|
-
} catch {
|
|
8126
|
-
removeErrors += 1;
|
|
8127
|
-
}
|
|
8128
|
-
}
|
|
8129
|
-
|
|
8130
|
-
const cleanup = await cleanupOrphanStickerAssets([normalizedStickerId], { reason: 'admin_delete_sticker_global' });
|
|
8131
|
-
invalidateStickerCatalogDerivedCaches();
|
|
8132
|
-
await createAdminActionAuditEvent({
|
|
8133
|
-
adminSession,
|
|
8134
|
-
action: 'global_sticker_delete',
|
|
8135
|
-
targetType: 'sticker',
|
|
8136
|
-
targetId: normalizedStickerId,
|
|
8137
|
-
details: {
|
|
8138
|
-
removed_from_packs: removedFromPacks,
|
|
8139
|
-
remove_errors: removeErrors,
|
|
8140
|
-
cleanup_deleted: Number(cleanup?.deleted || 0),
|
|
8141
|
-
},
|
|
8142
|
-
});
|
|
8143
|
-
sendJson(req, res, 200, {
|
|
8144
|
-
data: {
|
|
8145
|
-
success: true,
|
|
8146
|
-
sticker_id: normalizedStickerId,
|
|
8147
|
-
removed_from_packs: removedFromPacks,
|
|
8148
|
-
remove_errors: removeErrors,
|
|
8149
|
-
cleanup,
|
|
8150
|
-
deleted: Number(cleanup?.deleted || 0) > 0,
|
|
8151
|
-
},
|
|
8152
|
-
});
|
|
8153
|
-
};
|
|
8154
|
-
|
|
8155
|
-
const handleAdminBansRequest = async (req, res) => {
|
|
8156
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
8157
|
-
if (!adminSession) return;
|
|
8158
|
-
|
|
8159
|
-
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
8160
|
-
const bans = await listAdminBans({ activeOnly: false, limit: 200 });
|
|
8161
|
-
sendJson(req, res, 200, { data: bans });
|
|
8162
|
-
return;
|
|
8163
|
-
}
|
|
8164
|
-
|
|
8165
|
-
if (req.method !== 'POST') {
|
|
8166
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
8167
|
-
return;
|
|
8168
|
-
}
|
|
8169
|
-
|
|
8170
|
-
let payload = {};
|
|
8171
|
-
try {
|
|
8172
|
-
payload = await readJsonBody(req);
|
|
8173
|
-
} catch (error) {
|
|
8174
|
-
sendJson(req, res, Number(error?.statusCode || 400), { error: error?.message || 'Body inválido.' });
|
|
8175
|
-
return;
|
|
8176
|
-
}
|
|
8177
|
-
|
|
8178
|
-
try {
|
|
8179
|
-
const result = await createAdminBanRecord({
|
|
8180
|
-
googleSub: payload?.google_sub,
|
|
8181
|
-
email: payload?.email,
|
|
8182
|
-
ownerJid: payload?.owner_jid,
|
|
8183
|
-
reason: payload?.reason,
|
|
8184
|
-
adminSession,
|
|
8185
|
-
});
|
|
8186
|
-
await createAdminActionAuditEvent({
|
|
8187
|
-
adminSession,
|
|
8188
|
-
action: result.created ? 'ban_create' : 'ban_existing',
|
|
8189
|
-
targetType: 'ban',
|
|
8190
|
-
targetId: result?.ban?.id || '',
|
|
8191
|
-
details: {
|
|
8192
|
-
google_sub: result?.ban?.google_sub || payload?.google_sub || null,
|
|
8193
|
-
email: result?.ban?.email || payload?.email || null,
|
|
8194
|
-
owner_jid: result?.ban?.owner_jid || payload?.owner_jid || null,
|
|
8195
|
-
},
|
|
8196
|
-
});
|
|
8197
|
-
sendJson(req, res, result.created ? 201 : 200, {
|
|
8198
|
-
data: {
|
|
8199
|
-
created: result.created,
|
|
8200
|
-
ban: result.ban,
|
|
8201
|
-
},
|
|
8202
|
-
});
|
|
8203
|
-
} catch (error) {
|
|
8204
|
-
sendJson(req, res, Number(error?.statusCode || 400), { error: error?.message || 'Falha ao banir usuário.' });
|
|
8205
|
-
}
|
|
8206
|
-
};
|
|
8207
|
-
|
|
8208
|
-
const handleAdminBanRevokeRequest = async (req, res, banId) => {
|
|
8209
|
-
const adminSession = requireAdminPanelSession(req, res);
|
|
8210
|
-
if (!adminSession) return;
|
|
8211
|
-
if (req.method !== 'DELETE') {
|
|
8212
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
8213
|
-
return;
|
|
8214
|
-
}
|
|
8215
|
-
try {
|
|
8216
|
-
const ban = await revokeAdminBanRecord(banId);
|
|
8217
|
-
await createAdminActionAuditEvent({
|
|
8218
|
-
adminSession,
|
|
8219
|
-
action: 'ban_revoke',
|
|
8220
|
-
targetType: 'ban',
|
|
8221
|
-
targetId: ban?.id || banId,
|
|
8222
|
-
});
|
|
8223
|
-
sendJson(req, res, 200, { data: { revoked: true, ban } });
|
|
8224
|
-
} catch (error) {
|
|
8225
|
-
sendJson(req, res, Number(error?.statusCode || 400), { error: error?.message || 'Falha ao revogar ban.' });
|
|
8226
|
-
}
|
|
8227
|
-
};
|
|
6203
|
+
const { handleAdminPanelSessionRequest, handleAdminOverviewRequest, handleAdminUsersRequest, handleAdminForceLogoutRequest, handleAdminFeatureFlagsRequest, handleAdminOpsActionRequest, handleAdminSearchRequest, handleAdminExportRequest, handleAdminModeratorsRequest, handleAdminModeratorDeleteRequest, handleAdminPacksRequest, handleAdminPackDetailsRequest, handleAdminPackDeleteRequest, handleAdminPackStickerDeleteRequest, handleAdminGlobalStickerDeleteRequest, handleAdminBansRequest, handleAdminBanRevokeRequest } = createStickerCatalogAdminHandlers({
|
|
6204
|
+
executeQuery,
|
|
6205
|
+
tables: TABLES,
|
|
6206
|
+
logger,
|
|
6207
|
+
sendJson,
|
|
6208
|
+
readJsonBody,
|
|
6209
|
+
parseCookies,
|
|
6210
|
+
getCookieValuesFromRequest,
|
|
6211
|
+
appendSetCookie,
|
|
6212
|
+
buildCookieString,
|
|
6213
|
+
sanitizeText,
|
|
6214
|
+
normalizeGoogleSubject,
|
|
6215
|
+
normalizeEmail,
|
|
6216
|
+
normalizeJid,
|
|
6217
|
+
toIsoOrNull,
|
|
6218
|
+
toWhatsAppPhoneDigits,
|
|
6219
|
+
mapGoogleSessionResponseData,
|
|
6220
|
+
resolveGoogleWebSessionFromRequest,
|
|
6221
|
+
revokeGoogleWebSessionsByIdentity,
|
|
6222
|
+
getMarketplaceGlobalStatsCached,
|
|
6223
|
+
getSystemSummaryCached,
|
|
6224
|
+
getFeatureFlagsSnapshot,
|
|
6225
|
+
refreshFeatureFlags,
|
|
6226
|
+
listAdminBans,
|
|
6227
|
+
createAdminBanRecord,
|
|
6228
|
+
revokeAdminBanRecord,
|
|
6229
|
+
normalizeVisitPath,
|
|
6230
|
+
stickerWebPath: STICKER_WEB_PATH,
|
|
6231
|
+
findStickerPackByPackKey,
|
|
6232
|
+
stickerPackService,
|
|
6233
|
+
buildManagedPackResponseData,
|
|
6234
|
+
sendManagedMutationStatus,
|
|
6235
|
+
sendManagedPackMutationStatus,
|
|
6236
|
+
deleteManagedPackWithCleanup,
|
|
6237
|
+
mapStickerPackWebManageError,
|
|
6238
|
+
cleanupOrphanStickerAssets,
|
|
6239
|
+
invalidateStickerCatalogDerivedCaches,
|
|
6240
|
+
});
|
|
8228
6241
|
|
|
8229
6242
|
const catalogApiRouter = createCatalogApiRouter({
|
|
8230
6243
|
apiBasePath: STICKER_API_BASE_PATH,
|