@kaikybrofc/omnizap-system 2.2.4 → 2.2.5
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 +5 -0
- package/README.md +13 -13
- package/app/modules/stickerPackModule/catalogHandlers/catalogAdminHttp.js +68 -0
- package/app/modules/stickerPackModule/catalogHandlers/catalogAuthHttp.js +34 -0
- package/app/modules/stickerPackModule/catalogHandlers/catalogPublicHttp.js +179 -0
- package/app/modules/stickerPackModule/catalogHandlers/catalogUploadHttp.js +92 -0
- package/app/modules/stickerPackModule/catalogRouter.js +79 -0
- package/app/modules/stickerPackModule/domainEventOutboxRepository.js +243 -0
- package/app/modules/stickerPackModule/domainEvents.js +61 -0
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +21 -0
- package/app/modules/stickerPackModule/stickerAssetRepository.js +19 -0
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +55 -15
- package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +238 -0
- package/app/modules/stickerPackModule/stickerDomainEventBus.js +71 -0
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +198 -0
- package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
- package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +537 -529
- package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +44 -0
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +18 -0
- package/app/modules/stickerPackModule/stickerPackRepository.js +51 -0
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +191 -0
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +301 -0
- package/app/modules/stickerPackModule/stickerStorageService.js +111 -10
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +21 -0
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +59 -7
- package/app/observability/metrics.js +169 -0
- package/app/services/featureFlagService.js +137 -0
- package/database/index.js +5 -0
- package/database/migrations/20260228_0022_sticker_scale_indexes.sql +16 -0
- package/database/migrations/20260228_0023_sticker_pack_score_snapshot.sql +25 -0
- package/database/migrations/20260228_0024_domain_event_outbox.sql +42 -0
- package/database/migrations/20260228_0025_sticker_worker_task_idempotency_dlq.sql +23 -0
- package/database/migrations/20260228_0026_feature_flags.sql +21 -0
- package/ecosystem.prod.config.cjs +70 -9
- package/index.js +26 -0
- package/package.json +5 -1
- package/public/index.html +30 -3
- package/scripts/sticker-catalog-loadtest.mjs +208 -0
- package/scripts/sticker-worker-task.mjs +122 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash, randomBytes, randomUUID, scryptSync, timingSafeEqual } from 'node:crypto';
|
|
4
|
+
import { URL, URLSearchParams } from 'node:url';
|
|
4
5
|
import axios from 'axios';
|
|
5
6
|
|
|
6
7
|
import { executeQuery, pool, TABLES } from '../../../database/index.js';
|
|
@@ -64,6 +65,8 @@ import {
|
|
|
64
65
|
buildViewerTagAffinity,
|
|
65
66
|
computePackSignals,
|
|
66
67
|
} from './stickerPackMarketplaceService.js';
|
|
68
|
+
import { listStickerPackScoreSnapshotsByPackIds } from './stickerPackScoreSnapshotRepository.js';
|
|
69
|
+
import { createCatalogApiRouter } from './catalogRouter.js';
|
|
67
70
|
import {
|
|
68
71
|
buildAdminMenu,
|
|
69
72
|
buildAiMenu,
|
|
@@ -75,11 +78,17 @@ import {
|
|
|
75
78
|
buildStickerMenu,
|
|
76
79
|
} from '../menuModule/common.js';
|
|
77
80
|
import { getMarketplaceDriftSnapshot } from './stickerMarketplaceDriftService.js';
|
|
78
|
-
import {
|
|
81
|
+
import {
|
|
82
|
+
getStickerAssetExternalUrl,
|
|
83
|
+
getStickerStorageConfig,
|
|
84
|
+
readStickerAssetBuffer,
|
|
85
|
+
saveStickerAssetFromBuffer,
|
|
86
|
+
} from './stickerStorageService.js';
|
|
79
87
|
import { convertToWebp } from '../stickerModule/convertToWebp.js';
|
|
80
88
|
import { sanitizeText } from './stickerPackUtils.js';
|
|
81
89
|
import stickerPackService from './stickerPackServiceRuntime.js';
|
|
82
90
|
import { STICKER_PACK_ERROR_CODES, StickerPackError } from './stickerPackErrors.js';
|
|
91
|
+
import { isFeatureEnabled } from '../../services/featureFlagService.js';
|
|
83
92
|
|
|
84
93
|
const parseEnvBool = (value, fallback) => {
|
|
85
94
|
if (value === undefined || value === null || value === '') return fallback;
|
|
@@ -203,6 +212,19 @@ const GITHUB_REPOSITORY = String(process.env.GITHUB_REPOSITORY || 'Kaikygr/omniz
|
|
|
203
212
|
const GITHUB_TOKEN = String(process.env.GITHUB_TOKEN || '').trim();
|
|
204
213
|
const GITHUB_PROJECT_CACHE_SECONDS = clampInt(process.env.GITHUB_PROJECT_CACHE_SECONDS, 300, 30, 3600);
|
|
205
214
|
const GLOBAL_RANK_REFRESH_SECONDS = clampInt(process.env.GLOBAL_RANK_REFRESH_SECONDS, 600, 60, 3600);
|
|
215
|
+
const CATALOG_LIST_CACHE_SECONDS = clampInt(process.env.STICKER_CATALOG_LIST_CACHE_SECONDS, 90, 15, 900);
|
|
216
|
+
const CATALOG_CREATOR_RANKING_CACHE_SECONDS = clampInt(
|
|
217
|
+
process.env.STICKER_CATALOG_CREATOR_RANKING_CACHE_SECONDS,
|
|
218
|
+
120,
|
|
219
|
+
15,
|
|
220
|
+
900,
|
|
221
|
+
);
|
|
222
|
+
const CATALOG_PACK_PAYLOAD_CACHE_SECONDS = clampInt(
|
|
223
|
+
process.env.STICKER_CATALOG_PACK_PAYLOAD_CACHE_SECONDS,
|
|
224
|
+
300,
|
|
225
|
+
30,
|
|
226
|
+
1800,
|
|
227
|
+
);
|
|
206
228
|
const MARKETPLACE_GLOBAL_STATS_API_PATH = '/api/marketplace/stats';
|
|
207
229
|
const MARKETPLACE_GLOBAL_STATS_CACHE_SECONDS = clampInt(process.env.MARKETPLACE_GLOBAL_STATS_CACHE_SECONDS, 45, 30, 60);
|
|
208
230
|
const HOME_MARKETPLACE_STATS_CACHE_SECONDS = clampInt(process.env.HOME_MARKETPLACE_STATS_CACHE_SECONDS, 45, 10, 300);
|
|
@@ -218,6 +240,14 @@ const SITE_CANONICAL_REDIRECT_ENABLED = parseEnvBool(process.env.SITE_CANONICAL_
|
|
|
218
240
|
const SITE_ORIGIN = String(process.env.SITE_ORIGIN || `${SITE_CANONICAL_SCHEME}://${SITE_CANONICAL_HOST}`)
|
|
219
241
|
.trim()
|
|
220
242
|
.replace(/\/+$/, '');
|
|
243
|
+
const SITE_COOKIE_DOMAIN = String(process.env.SITE_COOKIE_DOMAIN || SITE_CANONICAL_HOST)
|
|
244
|
+
.trim()
|
|
245
|
+
.toLowerCase()
|
|
246
|
+
.replace(/^https?:\/\//, '')
|
|
247
|
+
.split('/')[0]
|
|
248
|
+
.split(':')[0]
|
|
249
|
+
.replace(/^\.+/, '')
|
|
250
|
+
.replace(/\.+$/, '');
|
|
221
251
|
const SITEMAP_MAX_PACKS = clampInt(process.env.STICKER_SITEMAP_MAX_PACKS, 45000, 100, 50000);
|
|
222
252
|
const SITEMAP_CACHE_SECONDS = clampInt(process.env.STICKER_SITEMAP_CACHE_SECONDS, 180, 30, 3600);
|
|
223
253
|
const SEO_DISCOVERY_LINK_LIMIT = clampInt(process.env.STICKER_SEO_DISCOVERY_LINK_LIMIT, 60, 10, 200);
|
|
@@ -295,6 +325,9 @@ const MARKETPLACE_GLOBAL_STATS_CACHE = {
|
|
|
295
325
|
pending: null,
|
|
296
326
|
};
|
|
297
327
|
const HOME_MARKETPLACE_STATS_CACHE = new Map();
|
|
328
|
+
const CATALOG_LIST_CACHE = new Map();
|
|
329
|
+
const CATALOG_CREATOR_RANKING_CACHE = new Map();
|
|
330
|
+
const CATALOG_PACK_PAYLOAD_CACHE = new Map();
|
|
298
331
|
const SYSTEM_SUMMARY_CACHE = {
|
|
299
332
|
expiresAt: 0,
|
|
300
333
|
value: null,
|
|
@@ -324,6 +357,69 @@ const formatDuration = (totalSeconds) => {
|
|
|
324
357
|
return days > 0 ? `${days}d ${hhmmss}` : hhmmss;
|
|
325
358
|
};
|
|
326
359
|
|
|
360
|
+
const buildCacheKey = (parts) => JSON.stringify(parts);
|
|
361
|
+
|
|
362
|
+
const getCacheBucket = (cacheMap, key) => {
|
|
363
|
+
let bucket = cacheMap.get(key);
|
|
364
|
+
if (!bucket) {
|
|
365
|
+
bucket = {
|
|
366
|
+
expiresAt: 0,
|
|
367
|
+
value: null,
|
|
368
|
+
pending: null,
|
|
369
|
+
};
|
|
370
|
+
cacheMap.set(key, bucket);
|
|
371
|
+
}
|
|
372
|
+
return bucket;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const getCachedSnapshot = async ({
|
|
376
|
+
cacheMap,
|
|
377
|
+
key,
|
|
378
|
+
ttlSeconds,
|
|
379
|
+
staleWhileRefresh = true,
|
|
380
|
+
staleOnError = true,
|
|
381
|
+
load,
|
|
382
|
+
}) => {
|
|
383
|
+
const bucket = getCacheBucket(cacheMap, key);
|
|
384
|
+
const now = Date.now();
|
|
385
|
+
const hasValue = bucket.value !== null;
|
|
386
|
+
const hasFreshValue = hasValue && now < bucket.expiresAt;
|
|
387
|
+
|
|
388
|
+
if (hasFreshValue) {
|
|
389
|
+
return bucket.value;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (!bucket.pending) {
|
|
393
|
+
bucket.pending = Promise.resolve()
|
|
394
|
+
.then(load)
|
|
395
|
+
.then((value) => {
|
|
396
|
+
bucket.value = value;
|
|
397
|
+
bucket.expiresAt = Date.now() + ttlSeconds * 1000;
|
|
398
|
+
return value;
|
|
399
|
+
})
|
|
400
|
+
.finally(() => {
|
|
401
|
+
bucket.pending = null;
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (hasValue && staleWhileRefresh) {
|
|
406
|
+
return bucket.value;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
return await bucket.pending;
|
|
411
|
+
} catch (error) {
|
|
412
|
+
if (hasValue && staleOnError) return bucket.value;
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const canUseRankingSnapshotRead = async (subjectKey = 'catalog') =>
|
|
418
|
+
isFeatureEnabled('enable_ranking_snapshot_read', {
|
|
419
|
+
fallback: true,
|
|
420
|
+
subjectKey,
|
|
421
|
+
});
|
|
422
|
+
|
|
327
423
|
const sendJson = (req, res, statusCode, payload) => {
|
|
328
424
|
const body = JSON.stringify(payload);
|
|
329
425
|
res.statusCode = statusCode;
|
|
@@ -362,6 +458,22 @@ const toRequestHost = (req) =>
|
|
|
362
458
|
.replace(/\.$/, '')
|
|
363
459
|
.split(':')[0];
|
|
364
460
|
|
|
461
|
+
const isIpLiteralHost = (value) => {
|
|
462
|
+
const host = String(value || '').trim().toLowerCase();
|
|
463
|
+
if (!host) return false;
|
|
464
|
+
if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(host)) return true;
|
|
465
|
+
return host.includes(':');
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const resolveCookieDomainForRequest = (req) => {
|
|
469
|
+
if (!SITE_COOKIE_DOMAIN || isIpLiteralHost(SITE_COOKIE_DOMAIN)) return '';
|
|
470
|
+
const requestHost = toRequestHost(req);
|
|
471
|
+
if (!requestHost || isIpLiteralHost(requestHost) || requestHost === 'localhost') return '';
|
|
472
|
+
if (requestHost === SITE_COOKIE_DOMAIN) return SITE_COOKIE_DOMAIN;
|
|
473
|
+
if (requestHost.endsWith(`.${SITE_COOKIE_DOMAIN}`)) return SITE_COOKIE_DOMAIN;
|
|
474
|
+
return '';
|
|
475
|
+
};
|
|
476
|
+
|
|
365
477
|
const maybeRedirectToCanonicalHost = (req, res, url) => {
|
|
366
478
|
if (!SITE_CANONICAL_REDIRECT_ENABLED) return false;
|
|
367
479
|
if (!['GET', 'HEAD'].includes(req.method || '')) return false;
|
|
@@ -395,6 +507,33 @@ const parseCookies = (req) => {
|
|
|
395
507
|
}, {});
|
|
396
508
|
};
|
|
397
509
|
|
|
510
|
+
const getCookieValuesFromRequest = (req, cookieName) => {
|
|
511
|
+
const target = String(cookieName || '').trim();
|
|
512
|
+
if (!target) return [];
|
|
513
|
+
const raw = String(req?.headers?.cookie || '');
|
|
514
|
+
if (!raw) return [];
|
|
515
|
+
|
|
516
|
+
const values = [];
|
|
517
|
+
for (const chunk of raw.split(';')) {
|
|
518
|
+
const trimmed = String(chunk || '').trim();
|
|
519
|
+
if (!trimmed) continue;
|
|
520
|
+
const separatorIndex = trimmed.indexOf('=');
|
|
521
|
+
if (separatorIndex <= 0) continue;
|
|
522
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
523
|
+
if (key !== target) continue;
|
|
524
|
+
const encodedValue = trimmed.slice(separatorIndex + 1).trim();
|
|
525
|
+
if (!encodedValue) continue;
|
|
526
|
+
let decodedValue = encodedValue;
|
|
527
|
+
try {
|
|
528
|
+
decodedValue = decodeURIComponent(encodedValue);
|
|
529
|
+
} catch {}
|
|
530
|
+
const normalizedValue = String(decodedValue || '').trim();
|
|
531
|
+
if (!normalizedValue) continue;
|
|
532
|
+
if (!values.includes(normalizedValue)) values.push(normalizedValue);
|
|
533
|
+
}
|
|
534
|
+
return values;
|
|
535
|
+
};
|
|
536
|
+
|
|
398
537
|
const isRequestSecure = (req) => {
|
|
399
538
|
const proto = String(req?.headers?.['x-forwarded-proto'] || '').split(',')[0].trim().toLowerCase();
|
|
400
539
|
if (proto) return proto === 'https';
|
|
@@ -503,6 +642,11 @@ const runSqlTransaction = async (handler) => {
|
|
|
503
642
|
const buildCookieString = (name, value, req, options = {}) => {
|
|
504
643
|
const parts = [`${name}=${encodeURIComponent(String(value ?? ''))}`];
|
|
505
644
|
parts.push(`Path=${options.path || '/'}`);
|
|
645
|
+
const cookieDomain =
|
|
646
|
+
options.domain === false
|
|
647
|
+
? ''
|
|
648
|
+
: String(options.domain || resolveCookieDomainForRequest(req)).trim();
|
|
649
|
+
if (cookieDomain) parts.push(`Domain=${cookieDomain}`);
|
|
506
650
|
if (options.httpOnly !== false) parts.push('HttpOnly');
|
|
507
651
|
parts.push(`SameSite=${options.sameSite || 'Lax'}`);
|
|
508
652
|
if (isRequestSecure(req)) parts.push('Secure');
|
|
@@ -914,11 +1058,16 @@ const pruneExpiredGoogleSessions = () => {
|
|
|
914
1058
|
}
|
|
915
1059
|
};
|
|
916
1060
|
|
|
917
|
-
const
|
|
1061
|
+
const getGoogleWebSessionTokensFromRequest = (req) => {
|
|
1062
|
+
const direct = getCookieValuesFromRequest(req, GOOGLE_WEB_SESSION_COOKIE_NAME);
|
|
1063
|
+
if (direct.length > 0) return direct;
|
|
918
1064
|
const cookies = parseCookies(req);
|
|
919
|
-
|
|
1065
|
+
const fallback = String(cookies[GOOGLE_WEB_SESSION_COOKIE_NAME] || '').trim();
|
|
1066
|
+
return fallback ? [fallback] : [];
|
|
920
1067
|
};
|
|
921
1068
|
|
|
1069
|
+
const getGoogleWebSessionTokenFromRequest = (req) => getGoogleWebSessionTokensFromRequest(req)[0] || '';
|
|
1070
|
+
|
|
922
1071
|
const normalizeGoogleWebSessionRow = (row) => {
|
|
923
1072
|
if (!row || typeof row !== 'object') return null;
|
|
924
1073
|
const token = String(row.session_token || '').trim();
|
|
@@ -1574,6 +1723,8 @@ const pruneExpiredAdminPanelSessions = () => {
|
|
|
1574
1723
|
};
|
|
1575
1724
|
|
|
1576
1725
|
const getAdminPanelSessionTokenFromRequest = (req) => {
|
|
1726
|
+
const direct = getCookieValuesFromRequest(req, ADMIN_PANEL_SESSION_COOKIE_NAME);
|
|
1727
|
+
if (direct.length > 0) return direct[0];
|
|
1577
1728
|
const cookies = parseCookies(req);
|
|
1578
1729
|
return String(cookies[ADMIN_PANEL_SESSION_COOKIE_NAME] || '').trim();
|
|
1579
1730
|
};
|
|
@@ -1585,6 +1736,14 @@ const clearAdminPanelSessionCookie = (req, res) => {
|
|
|
1585
1736
|
maxAgeSeconds: 0,
|
|
1586
1737
|
}),
|
|
1587
1738
|
);
|
|
1739
|
+
// Also clear host-only variant (legacy cookie written without Domain).
|
|
1740
|
+
appendSetCookie(
|
|
1741
|
+
res,
|
|
1742
|
+
buildCookieString(ADMIN_PANEL_SESSION_COOKIE_NAME, '', req, {
|
|
1743
|
+
maxAgeSeconds: 0,
|
|
1744
|
+
domain: false,
|
|
1745
|
+
}),
|
|
1746
|
+
);
|
|
1588
1747
|
};
|
|
1589
1748
|
|
|
1590
1749
|
const createAdminPanelSession = (googleSession, { role = 'owner' } = {}) => {
|
|
@@ -1732,61 +1891,67 @@ const activateGoogleWebSession = (session) => {
|
|
|
1732
1891
|
|
|
1733
1892
|
const resolveGoogleWebSessionFromRequest = async (req) => {
|
|
1734
1893
|
pruneExpiredGoogleSessions();
|
|
1735
|
-
const
|
|
1736
|
-
if (!
|
|
1737
|
-
|
|
1738
|
-
|
|
1894
|
+
const sessionTokens = getGoogleWebSessionTokensFromRequest(req);
|
|
1895
|
+
if (!sessionTokens.length) return null;
|
|
1896
|
+
|
|
1897
|
+
for (const sessionToken of sessionTokens) {
|
|
1898
|
+
const session = webGoogleSessionMap.get(sessionToken);
|
|
1899
|
+
if (!session) continue;
|
|
1739
1900
|
if (Number(session.expiresAt || 0) <= Date.now()) {
|
|
1740
1901
|
webGoogleSessionMap.delete(sessionToken);
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
void touchGoogleWebUserSeenInDb(session.sub).catch(() => {});
|
|
1753
|
-
}
|
|
1754
|
-
try {
|
|
1755
|
-
await assertGoogleIdentityNotBanned({
|
|
1756
|
-
sub: session.sub,
|
|
1757
|
-
email: session.email,
|
|
1758
|
-
ownerJid: session.ownerJid,
|
|
1902
|
+
continue;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
const now = Date.now();
|
|
1906
|
+
session.lastSeenAt = now;
|
|
1907
|
+
if (now - Number(session.lastDbTouchAt || 0) >= GOOGLE_WEB_SESSION_DB_TOUCH_INTERVAL_MS) {
|
|
1908
|
+
session.lastDbTouchAt = now;
|
|
1909
|
+
void touchGoogleWebSessionSeenInDb(sessionToken).catch((error) => {
|
|
1910
|
+
logger.warn('Falha ao atualizar last_seen da sessão Google web.', {
|
|
1911
|
+
action: 'sticker_pack_google_web_session_touch_failed',
|
|
1912
|
+
error: error?.message,
|
|
1759
1913
|
});
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
void deleteGoogleWebSessionFromDb(sessionToken).catch(() => {});
|
|
1763
|
-
return null;
|
|
1764
|
-
}
|
|
1765
|
-
return session;
|
|
1914
|
+
});
|
|
1915
|
+
void touchGoogleWebUserSeenInDb(session.sub).catch(() => {});
|
|
1766
1916
|
}
|
|
1767
|
-
}
|
|
1768
|
-
try {
|
|
1769
|
-
const persistedSession = await findGoogleWebSessionInDbByToken(sessionToken);
|
|
1770
|
-
if (!persistedSession) return null;
|
|
1771
1917
|
try {
|
|
1772
1918
|
await assertGoogleIdentityNotBanned({
|
|
1773
|
-
sub:
|
|
1774
|
-
email:
|
|
1775
|
-
ownerJid:
|
|
1919
|
+
sub: session.sub,
|
|
1920
|
+
email: session.email,
|
|
1921
|
+
ownerJid: session.ownerJid,
|
|
1776
1922
|
});
|
|
1923
|
+
return session;
|
|
1777
1924
|
} catch {
|
|
1778
|
-
|
|
1779
|
-
|
|
1925
|
+
webGoogleSessionMap.delete(sessionToken);
|
|
1926
|
+
void deleteGoogleWebSessionFromDb(sessionToken).catch(() => {});
|
|
1780
1927
|
}
|
|
1781
|
-
webGoogleSessionMap.set(sessionToken, persistedSession);
|
|
1782
|
-
return persistedSession;
|
|
1783
|
-
} catch (error) {
|
|
1784
|
-
logger.warn('Falha ao resolver sessão Google web no banco.', {
|
|
1785
|
-
action: 'sticker_pack_google_web_session_db_resolve_failed',
|
|
1786
|
-
error: error?.message,
|
|
1787
|
-
});
|
|
1788
|
-
return null;
|
|
1789
1928
|
}
|
|
1929
|
+
|
|
1930
|
+
for (const sessionToken of sessionTokens) {
|
|
1931
|
+
try {
|
|
1932
|
+
const persistedSession = await findGoogleWebSessionInDbByToken(sessionToken);
|
|
1933
|
+
if (!persistedSession) continue;
|
|
1934
|
+
try {
|
|
1935
|
+
await assertGoogleIdentityNotBanned({
|
|
1936
|
+
sub: persistedSession.sub,
|
|
1937
|
+
email: persistedSession.email,
|
|
1938
|
+
ownerJid: persistedSession.ownerJid,
|
|
1939
|
+
});
|
|
1940
|
+
} catch {
|
|
1941
|
+
await deleteGoogleWebSessionFromDb(sessionToken).catch(() => {});
|
|
1942
|
+
continue;
|
|
1943
|
+
}
|
|
1944
|
+
webGoogleSessionMap.set(sessionToken, persistedSession);
|
|
1945
|
+
return persistedSession;
|
|
1946
|
+
} catch (error) {
|
|
1947
|
+
logger.warn('Falha ao resolver sessão Google web no banco.', {
|
|
1948
|
+
action: 'sticker_pack_google_web_session_db_resolve_failed',
|
|
1949
|
+
error: error?.message,
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
return null;
|
|
1790
1955
|
};
|
|
1791
1956
|
|
|
1792
1957
|
const clearGoogleWebSessionCookie = (req, res) => {
|
|
@@ -1796,6 +1961,14 @@ const clearGoogleWebSessionCookie = (req, res) => {
|
|
|
1796
1961
|
maxAgeSeconds: 0,
|
|
1797
1962
|
}),
|
|
1798
1963
|
);
|
|
1964
|
+
// Also clear host-only variant (legacy cookie written without Domain).
|
|
1965
|
+
appendSetCookie(
|
|
1966
|
+
res,
|
|
1967
|
+
buildCookieString(GOOGLE_WEB_SESSION_COOKIE_NAME, '', req, {
|
|
1968
|
+
maxAgeSeconds: 0,
|
|
1969
|
+
domain: false,
|
|
1970
|
+
}),
|
|
1971
|
+
);
|
|
1799
1972
|
};
|
|
1800
1973
|
|
|
1801
1974
|
const sendAsset = (req, res, buffer, mimetype = 'image/webp') => {
|
|
@@ -2146,7 +2319,9 @@ const resolveWebCreateOwnerJid = async (explicitOwner = '') => {
|
|
|
2146
2319
|
const resolvedAdminJid = await resolveAdminJid();
|
|
2147
2320
|
const fromAdmin = toOwnerJid(resolvedAdminJid);
|
|
2148
2321
|
if (fromAdmin) return fromAdmin;
|
|
2149
|
-
} catch {
|
|
2322
|
+
} catch {
|
|
2323
|
+
// Ignore fallback errors while resolving owner identity.
|
|
2324
|
+
}
|
|
2150
2325
|
|
|
2151
2326
|
const adminCandidates = [
|
|
2152
2327
|
getAdminRawValue(),
|
|
@@ -2316,14 +2491,18 @@ const resolveSupportAdminPhone = async () => {
|
|
|
2316
2491
|
const resolvedFromLidMap = await resolveUserId(extractUserIdInfo(adminRaw));
|
|
2317
2492
|
const resolvedPhoneFromLidMap = isPlausibleWhatsAppPhone(getJidUser(resolvedFromLidMap || ''));
|
|
2318
2493
|
if (resolvedPhoneFromLidMap) return resolvedPhoneFromLidMap;
|
|
2319
|
-
} catch {
|
|
2494
|
+
} catch {
|
|
2495
|
+
// Ignore and fallback to other admin sources.
|
|
2496
|
+
}
|
|
2320
2497
|
}
|
|
2321
2498
|
|
|
2322
2499
|
try {
|
|
2323
2500
|
const resolvedAdminJid = await resolveAdminJid();
|
|
2324
2501
|
const resolvedPhone = isPlausibleWhatsAppPhone(getJidUser(resolvedAdminJid || ''));
|
|
2325
2502
|
if (resolvedPhone) return resolvedPhone;
|
|
2326
|
-
} catch {
|
|
2503
|
+
} catch {
|
|
2504
|
+
// Ignore and fallback to static admin phone sources.
|
|
2505
|
+
}
|
|
2327
2506
|
|
|
2328
2507
|
const rawPhone = isPlausibleWhatsAppPhone(getJidUser(adminRaw) || adminRaw);
|
|
2329
2508
|
if (rawPhone) return rawPhone;
|
|
@@ -2725,6 +2904,10 @@ const hydrateMarketplaceEntries = async (packs, { includeItems = true, driftSnap
|
|
|
2725
2904
|
const packIds = packs.map((pack) => pack.id);
|
|
2726
2905
|
const engagementByPackId = await listStickerPackEngagementByPackIds(packIds);
|
|
2727
2906
|
const interactionStatsByPackId = await listStickerPackInteractionStatsByPackIds(packIds);
|
|
2907
|
+
const useSnapshot = await canUseRankingSnapshotRead(`hydrate:${packIds.length}:${includeItems ? 1 : 0}`);
|
|
2908
|
+
const snapshotByPackId = useSnapshot
|
|
2909
|
+
? await listStickerPackScoreSnapshotsByPackIds(packIds).catch(() => new Map())
|
|
2910
|
+
: new Map();
|
|
2728
2911
|
|
|
2729
2912
|
const entries = [];
|
|
2730
2913
|
const packClassificationById = new Map();
|
|
@@ -2743,14 +2926,24 @@ const hydrateMarketplaceEntries = async (packs, { includeItems = true, driftSnap
|
|
|
2743
2926
|
const packMetadata = parsePackDescriptionMetadata(pack.description);
|
|
2744
2927
|
const decoratedClassification = decoratePackClassificationSummary(packClassification);
|
|
2745
2928
|
const mergedPackTags = mergeUniqueTags(decoratedClassification?.tags || [], packMetadata.tags);
|
|
2746
|
-
const
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2929
|
+
const snapshot = snapshotByPackId.get(pack.id);
|
|
2930
|
+
const signals = snapshot?.signals
|
|
2931
|
+
? {
|
|
2932
|
+
...snapshot.signals,
|
|
2933
|
+
ranking_score: Number(snapshot?.signals?.ranking_score || 0),
|
|
2934
|
+
pack_score: Number(snapshot?.signals?.pack_score || 0),
|
|
2935
|
+
trend_score: Number(snapshot?.signals?.trend_score || 0),
|
|
2936
|
+
nsfw_level: String(snapshot?.signals?.nsfw_level || 'safe'),
|
|
2937
|
+
sensitive_content: Boolean(snapshot?.signals?.sensitive_content),
|
|
2938
|
+
}
|
|
2939
|
+
: computePackSignals({
|
|
2940
|
+
pack: { ...pack, items },
|
|
2941
|
+
engagement,
|
|
2942
|
+
packClassification,
|
|
2943
|
+
itemClassifications: orderedClassifications,
|
|
2944
|
+
interactionStats,
|
|
2945
|
+
scoringWeights: driftSnapshot?.weights || null,
|
|
2946
|
+
});
|
|
2754
2947
|
|
|
2755
2948
|
const entry = {
|
|
2756
2949
|
pack,
|
|
@@ -3321,95 +3514,118 @@ const handleListRequest = async (req, res, url) => {
|
|
|
3321
3514
|
const limit = clampInt(url.searchParams.get('limit'), DEFAULT_LIST_LIMIT, 1, MAX_LIST_LIMIT);
|
|
3322
3515
|
const offset = clampInt(url.searchParams.get('offset'), 0, 0, 100000);
|
|
3323
3516
|
const normalizedIntent = normalizeCategoryToken(intent).replace(/-/g, '_');
|
|
3324
|
-
const batchLimit = Math.max(limit, Math.min(MAX_LIST_LIMIT, 24));
|
|
3325
|
-
const maxPagesToScan = 8;
|
|
3326
3517
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
3327
3518
|
const hasNsfwAccess = Boolean(googleSession?.sub && googleSession?.ownerJid);
|
|
3328
|
-
const
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3519
|
+
const cacheKey = buildCacheKey([
|
|
3520
|
+
'list',
|
|
3521
|
+
q,
|
|
3522
|
+
visibility,
|
|
3523
|
+
sort,
|
|
3524
|
+
categories.join(','),
|
|
3525
|
+
normalizedIntent,
|
|
3526
|
+
includeSensitive ? 1 : 0,
|
|
3527
|
+
limit,
|
|
3528
|
+
offset,
|
|
3529
|
+
hasNsfwAccess ? 1 : 0,
|
|
3530
|
+
]);
|
|
3531
|
+
const payload = await getCachedSnapshot({
|
|
3532
|
+
cacheMap: CATALOG_LIST_CACHE,
|
|
3533
|
+
key: cacheKey,
|
|
3534
|
+
ttlSeconds: CATALOG_LIST_CACHE_SECONDS,
|
|
3535
|
+
staleWhileRefresh: true,
|
|
3536
|
+
staleOnError: true,
|
|
3537
|
+
load: async () => {
|
|
3538
|
+
const batchLimit = Math.max(limit, Math.min(MAX_LIST_LIMIT, 24));
|
|
3539
|
+
const maxPagesToScan = 8;
|
|
3540
|
+
const seenPackIds = new Set();
|
|
3541
|
+
const collectedEntries = [];
|
|
3542
|
+
const driftSnapshot = await getMarketplaceDriftSnapshot();
|
|
3543
|
+
let sourceHasMore = true;
|
|
3544
|
+
let cursorOffset = offset;
|
|
3545
|
+
let pagesScanned = 0;
|
|
3546
|
+
|
|
3547
|
+
while (collectedEntries.length < limit && sourceHasMore && pagesScanned < maxPagesToScan) {
|
|
3548
|
+
pagesScanned += 1;
|
|
3549
|
+
const { packs, hasMore } = await listStickerPacksForCatalog({
|
|
3550
|
+
visibility,
|
|
3551
|
+
search: q,
|
|
3552
|
+
limit: batchLimit,
|
|
3553
|
+
offset: cursorOffset,
|
|
3554
|
+
});
|
|
3555
|
+
sourceHasMore = hasMore;
|
|
3556
|
+
cursorOffset += batchLimit;
|
|
3557
|
+
if (!packs.length) break;
|
|
3558
|
+
|
|
3559
|
+
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
3560
|
+
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
3561
|
+
? entries.filter((entry) => isPackClassified(entry.packClassification))
|
|
3562
|
+
: entries;
|
|
3563
|
+
const entriesByCategory = categories.length
|
|
3564
|
+
? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories))
|
|
3565
|
+
: entriesClassified;
|
|
3566
|
+
const entriesBySensitivity = includeSensitive
|
|
3567
|
+
? entriesByCategory
|
|
3568
|
+
: entriesByCategory.filter((entry) => entry.signals?.nsfw_level === 'safe');
|
|
3569
|
+
const entriesByIntent = intent
|
|
3570
|
+
? entriesBySensitivity.filter((entry) => classifyPackIntent(entry) === normalizedIntent)
|
|
3571
|
+
: entriesBySensitivity;
|
|
3572
|
+
const sortedEntries = [...entriesByIntent].sort((left, right) => {
|
|
3573
|
+
const completenessDelta = compareEntriesByPackCompleteness(left, right);
|
|
3574
|
+
if (completenessDelta !== 0) return completenessDelta;
|
|
3575
|
+
if (sort === 'recent') {
|
|
3576
|
+
return Date.parse(right?.pack?.created_at || right?.pack?.updated_at || 0) - Date.parse(left?.pack?.created_at || left?.pack?.updated_at || 0);
|
|
3577
|
+
}
|
|
3578
|
+
if (sort === 'likes') {
|
|
3579
|
+
return Number(right?.engagement?.like_count || 0) - Number(left?.engagement?.like_count || 0);
|
|
3580
|
+
}
|
|
3581
|
+
if (sort === 'downloads') {
|
|
3582
|
+
return Number(right?.engagement?.open_count || 0) - Number(left?.engagement?.open_count || 0);
|
|
3583
|
+
}
|
|
3584
|
+
if (sort === 'comments') {
|
|
3585
|
+
const commentDelta = Number(right?.engagement?.comment_count || 0) - Number(left?.engagement?.comment_count || 0);
|
|
3586
|
+
if (commentDelta !== 0) return commentDelta;
|
|
3587
|
+
return Number(right?.engagement?.like_count || 0) - Number(left?.engagement?.like_count || 0);
|
|
3588
|
+
}
|
|
3589
|
+
if (sort === 'trending') {
|
|
3590
|
+
const trendDelta = Number(right?.signals?.trend_score || 0) - Number(left?.signals?.trend_score || 0);
|
|
3591
|
+
if (trendDelta !== 0) return trendDelta;
|
|
3592
|
+
}
|
|
3593
|
+
const leftScore = Number(left?.signals?.ranking_score || 0);
|
|
3594
|
+
const rightScore = Number(right?.signals?.ranking_score || 0);
|
|
3595
|
+
if (rightScore !== leftScore) return rightScore - leftScore;
|
|
3596
|
+
return Date.parse(right?.pack?.updated_at || 0) - Date.parse(left?.pack?.updated_at || 0);
|
|
3597
|
+
});
|
|
3334
3598
|
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
});
|
|
3343
|
-
sourceHasMore = hasMore;
|
|
3344
|
-
cursorOffset += batchLimit;
|
|
3345
|
-
if (!packs.length) break;
|
|
3346
|
-
|
|
3347
|
-
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
3348
|
-
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
3349
|
-
? entries.filter((entry) => isPackClassified(entry.packClassification))
|
|
3350
|
-
: entries;
|
|
3351
|
-
const entriesByCategory = categories.length
|
|
3352
|
-
? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories))
|
|
3353
|
-
: entriesClassified;
|
|
3354
|
-
const entriesBySensitivity = includeSensitive
|
|
3355
|
-
? entriesByCategory
|
|
3356
|
-
: entriesByCategory.filter((entry) => entry.signals?.nsfw_level === 'safe');
|
|
3357
|
-
const entriesByIntent = intent
|
|
3358
|
-
? entriesBySensitivity.filter((entry) => classifyPackIntent(entry) === normalizedIntent)
|
|
3359
|
-
: entriesBySensitivity;
|
|
3360
|
-
const sortedEntries = [...entriesByIntent].sort((left, right) => {
|
|
3361
|
-
const completenessDelta = compareEntriesByPackCompleteness(left, right);
|
|
3362
|
-
if (completenessDelta !== 0) return completenessDelta;
|
|
3363
|
-
if (sort === 'recent') {
|
|
3364
|
-
return Date.parse(right?.pack?.created_at || right?.pack?.updated_at || 0) - Date.parse(left?.pack?.created_at || left?.pack?.updated_at || 0);
|
|
3365
|
-
}
|
|
3366
|
-
if (sort === 'likes') {
|
|
3367
|
-
return Number(right?.engagement?.like_count || 0) - Number(left?.engagement?.like_count || 0);
|
|
3368
|
-
}
|
|
3369
|
-
if (sort === 'downloads') {
|
|
3370
|
-
return Number(right?.engagement?.open_count || 0) - Number(left?.engagement?.open_count || 0);
|
|
3371
|
-
}
|
|
3372
|
-
if (sort === 'comments') {
|
|
3373
|
-
const commentDelta = Number(right?.engagement?.comment_count || 0) - Number(left?.engagement?.comment_count || 0);
|
|
3374
|
-
if (commentDelta !== 0) return commentDelta;
|
|
3375
|
-
return Number(right?.engagement?.like_count || 0) - Number(left?.engagement?.like_count || 0);
|
|
3376
|
-
}
|
|
3377
|
-
if (sort === 'trending') {
|
|
3378
|
-
const trendDelta = Number(right?.signals?.trend_score || 0) - Number(left?.signals?.trend_score || 0);
|
|
3379
|
-
if (trendDelta !== 0) return trendDelta;
|
|
3599
|
+
for (const entry of sortedEntries) {
|
|
3600
|
+
if (!entry?.pack?.id) continue;
|
|
3601
|
+
if (seenPackIds.has(entry.pack.id)) continue;
|
|
3602
|
+
seenPackIds.add(entry.pack.id);
|
|
3603
|
+
collectedEntries.push(entry);
|
|
3604
|
+
if (collectedEntries.length >= limit) break;
|
|
3605
|
+
}
|
|
3380
3606
|
}
|
|
3381
|
-
const leftScore = Number(left?.signals?.ranking_score || 0);
|
|
3382
|
-
const rightScore = Number(right?.signals?.ranking_score || 0);
|
|
3383
|
-
if (rightScore !== leftScore) return rightScore - leftScore;
|
|
3384
|
-
return Date.parse(right?.pack?.updated_at || 0) - Date.parse(left?.pack?.updated_at || 0);
|
|
3385
|
-
});
|
|
3386
3607
|
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
filters: {
|
|
3405
|
-
q,
|
|
3406
|
-
visibility,
|
|
3407
|
-
sort,
|
|
3408
|
-
categories,
|
|
3409
|
-
intent: intent || null,
|
|
3410
|
-
include_sensitive: includeSensitive,
|
|
3608
|
+
return {
|
|
3609
|
+
data: collectedEntries.map((entry) => toSummaryEntry(entry, { hideSensitiveCover: !hasNsfwAccess })),
|
|
3610
|
+
pagination: {
|
|
3611
|
+
limit,
|
|
3612
|
+
offset,
|
|
3613
|
+
has_more: sourceHasMore,
|
|
3614
|
+
next_offset: sourceHasMore ? cursorOffset : null,
|
|
3615
|
+
},
|
|
3616
|
+
filters: {
|
|
3617
|
+
q,
|
|
3618
|
+
visibility,
|
|
3619
|
+
sort,
|
|
3620
|
+
categories,
|
|
3621
|
+
intent: intent || null,
|
|
3622
|
+
include_sensitive: includeSensitive,
|
|
3623
|
+
},
|
|
3624
|
+
};
|
|
3411
3625
|
},
|
|
3412
3626
|
});
|
|
3627
|
+
|
|
3628
|
+
sendJson(req, res, 200, payload);
|
|
3413
3629
|
};
|
|
3414
3630
|
|
|
3415
3631
|
const handleIntentCollectionsRequest = async (req, res, url) => {
|
|
@@ -3647,9 +3863,9 @@ const handleGoogleAuthSessionRequest = async (req, res) => {
|
|
|
3647
3863
|
}
|
|
3648
3864
|
|
|
3649
3865
|
if (req.method === 'DELETE') {
|
|
3650
|
-
const
|
|
3651
|
-
|
|
3652
|
-
|
|
3866
|
+
const tokens = getGoogleWebSessionTokensFromRequest(req);
|
|
3867
|
+
for (const token of tokens) {
|
|
3868
|
+
webGoogleSessionMap.delete(token);
|
|
3653
3869
|
await deleteGoogleWebSessionFromDb(token).catch((error) => {
|
|
3654
3870
|
logger.warn('Falha ao remover sessão Google web do banco.', {
|
|
3655
3871
|
action: 'sticker_pack_google_web_session_db_delete_failed',
|
|
@@ -4090,6 +4306,9 @@ const invalidateStickerCatalogDerivedCaches = () => {
|
|
|
4090
4306
|
GLOBAL_RANK_CACHE.value = null;
|
|
4091
4307
|
GLOBAL_RANK_CACHE.pending = null;
|
|
4092
4308
|
HOME_MARKETPLACE_STATS_CACHE.clear();
|
|
4309
|
+
CATALOG_LIST_CACHE.clear();
|
|
4310
|
+
CATALOG_CREATOR_RANKING_CACHE.clear();
|
|
4311
|
+
CATALOG_PACK_PAYLOAD_CACHE.clear();
|
|
4093
4312
|
SYSTEM_SUMMARY_CACHE.expiresAt = 0;
|
|
4094
4313
|
SYSTEM_SUMMARY_CACHE.value = null;
|
|
4095
4314
|
SYSTEM_SUMMARY_CACHE.pending = null;
|
|
@@ -5753,46 +5972,63 @@ const handleCreatorRankingRequest = async (req, res, url) => {
|
|
|
5753
5972
|
const limit = clampInt(url.searchParams.get('limit'), 50, 5, 200);
|
|
5754
5973
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
5755
5974
|
const hasNsfwAccess = Boolean(googleSession?.sub && googleSession?.ownerJid);
|
|
5756
|
-
|
|
5757
|
-
|
|
5975
|
+
const cacheKey = buildCacheKey([
|
|
5976
|
+
'creator_ranking',
|
|
5758
5977
|
visibility,
|
|
5759
|
-
|
|
5760
|
-
limit
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
const
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5978
|
+
q,
|
|
5979
|
+
limit,
|
|
5980
|
+
hasNsfwAccess ? 1 : 0,
|
|
5981
|
+
]);
|
|
5982
|
+
const payload = await getCachedSnapshot({
|
|
5983
|
+
cacheMap: CATALOG_CREATOR_RANKING_CACHE,
|
|
5984
|
+
key: cacheKey,
|
|
5985
|
+
ttlSeconds: CATALOG_CREATOR_RANKING_CACHE_SECONDS,
|
|
5986
|
+
staleWhileRefresh: true,
|
|
5987
|
+
staleOnError: true,
|
|
5988
|
+
load: async () => {
|
|
5989
|
+
const { packs } = await listStickerPacksForCatalog({
|
|
5990
|
+
visibility,
|
|
5991
|
+
search: q,
|
|
5992
|
+
limit: 120,
|
|
5993
|
+
offset: 0,
|
|
5994
|
+
});
|
|
5995
|
+
const driftSnapshot = await getMarketplaceDriftSnapshot();
|
|
5996
|
+
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
5997
|
+
const ranking = buildCreatorRanking(
|
|
5998
|
+
STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries,
|
|
5999
|
+
{ limit },
|
|
6000
|
+
);
|
|
5769
6001
|
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
6002
|
+
return {
|
|
6003
|
+
data: ranking.map((creator) => ({
|
|
6004
|
+
creator_score: Number(
|
|
6005
|
+
(
|
|
6006
|
+
Number(creator.avg_pack_score || 0) * 0.45 +
|
|
6007
|
+
Number(creator.total_likes || 0) * 0.0008 +
|
|
6008
|
+
Number(creator.total_opens || 0) * 0.00015
|
|
6009
|
+
).toFixed(6),
|
|
6010
|
+
),
|
|
6011
|
+
publisher: creator.publisher,
|
|
6012
|
+
verified: Boolean(creator.verified),
|
|
6013
|
+
badges: creator.verified ? ['verified_creator'] : [],
|
|
6014
|
+
stats: {
|
|
6015
|
+
packs_count: Number(creator.packs_count || 0),
|
|
6016
|
+
total_likes: Number(creator.total_likes || 0),
|
|
6017
|
+
total_opens: Number(creator.total_opens || 0),
|
|
6018
|
+
avg_pack_score: Number(creator.avg_pack_score || 0),
|
|
6019
|
+
},
|
|
6020
|
+
top_pack: creator.top_pack ? toSummaryEntry(creator.top_pack, { hideSensitiveCover: !hasNsfwAccess }) : null,
|
|
6021
|
+
})),
|
|
6022
|
+
filters: {
|
|
6023
|
+
visibility,
|
|
6024
|
+
q,
|
|
6025
|
+
limit,
|
|
6026
|
+
},
|
|
6027
|
+
};
|
|
5794
6028
|
},
|
|
5795
6029
|
});
|
|
6030
|
+
|
|
6031
|
+
sendJson(req, res, 200, payload);
|
|
5796
6032
|
};
|
|
5797
6033
|
|
|
5798
6034
|
const handleRecommendationsRequest = async (req, res, url) => {
|
|
@@ -6934,42 +7170,60 @@ const handlePublicDataAssetRequest = async (req, res, pathname) => {
|
|
|
6934
7170
|
};
|
|
6935
7171
|
|
|
6936
7172
|
const fetchPublicPackPayload = async (normalizedPackKey) => {
|
|
6937
|
-
const
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
7173
|
+
const cacheKey = buildCacheKey(['pack_payload', normalizedPackKey]);
|
|
7174
|
+
return getCachedSnapshot({
|
|
7175
|
+
cacheMap: CATALOG_PACK_PAYLOAD_CACHE,
|
|
7176
|
+
key: cacheKey,
|
|
7177
|
+
ttlSeconds: CATALOG_PACK_PAYLOAD_CACHE_SECONDS,
|
|
7178
|
+
staleWhileRefresh: true,
|
|
7179
|
+
staleOnError: true,
|
|
7180
|
+
load: async () => {
|
|
7181
|
+
const pack = await findStickerPackByPackKey(normalizedPackKey);
|
|
7182
|
+
if (!pack || !isPackPubliclyVisible(pack)) return null;
|
|
7183
|
+
|
|
7184
|
+
const items = await listStickerPackItems(pack.id);
|
|
7185
|
+
const stickerIds = items.map((item) => item.sticker_id);
|
|
7186
|
+
const [classifications, packClassification, engagement] = await Promise.all([
|
|
7187
|
+
listStickerClassificationsByAssetIds(stickerIds),
|
|
7188
|
+
getPackClassificationSummaryByAssetIds(stickerIds),
|
|
7189
|
+
getStickerPackEngagementByPackId(pack.id),
|
|
7190
|
+
]);
|
|
7191
|
+
|
|
7192
|
+
if (STICKER_CATALOG_ONLY_CLASSIFIED && !isPackClassified(packClassification)) {
|
|
7193
|
+
return null;
|
|
7194
|
+
}
|
|
6947
7195
|
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
7196
|
+
const [interactionStatsByPack, driftSnapshot, snapshotByPackId] = await Promise.all([
|
|
7197
|
+
listStickerPackInteractionStatsByPackIds([pack.id]),
|
|
7198
|
+
getMarketplaceDriftSnapshot(),
|
|
7199
|
+
canUseRankingSnapshotRead(`pack_payload:${pack.id}`)
|
|
7200
|
+
.then((enabled) => (enabled ? listStickerPackScoreSnapshotsByPackIds([pack.id]) : new Map()))
|
|
7201
|
+
.catch(() => new Map()),
|
|
7202
|
+
]);
|
|
7203
|
+
const byAssetClassification = new Map(classifications.map((entry) => [entry.asset_id, entry]));
|
|
7204
|
+
const orderedClassifications = stickerIds.map((stickerId) => byAssetClassification.get(stickerId)).filter(Boolean);
|
|
7205
|
+
const snapshot = snapshotByPackId.get(pack.id);
|
|
7206
|
+
const signals = snapshot?.signals
|
|
7207
|
+
? snapshot.signals
|
|
7208
|
+
: computePackSignals({
|
|
7209
|
+
pack: { ...pack, items },
|
|
7210
|
+
engagement,
|
|
7211
|
+
packClassification,
|
|
7212
|
+
itemClassifications: orderedClassifications,
|
|
7213
|
+
interactionStats: interactionStatsByPack.get(pack.id) || null,
|
|
7214
|
+
scoringWeights: driftSnapshot.weights,
|
|
7215
|
+
});
|
|
6951
7216
|
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
interactionStats: interactionStatsByPack.get(pack.id) || null,
|
|
6962
|
-
scoringWeights: driftSnapshot.weights,
|
|
7217
|
+
return {
|
|
7218
|
+
pack,
|
|
7219
|
+
items,
|
|
7220
|
+
byAssetClassification,
|
|
7221
|
+
packClassification,
|
|
7222
|
+
engagement,
|
|
7223
|
+
signals,
|
|
7224
|
+
};
|
|
7225
|
+
},
|
|
6963
7226
|
});
|
|
6964
|
-
|
|
6965
|
-
return {
|
|
6966
|
-
pack,
|
|
6967
|
-
items,
|
|
6968
|
-
byAssetClassification,
|
|
6969
|
-
packClassification,
|
|
6970
|
-
engagement,
|
|
6971
|
-
signals,
|
|
6972
|
-
};
|
|
6973
7227
|
};
|
|
6974
7228
|
|
|
6975
7229
|
const handleDetailsRequest = async (req, res, packKey, url) => {
|
|
@@ -7060,6 +7314,23 @@ const handleAssetRequest = async (req, res, packKey, stickerToken) => {
|
|
|
7060
7314
|
return;
|
|
7061
7315
|
}
|
|
7062
7316
|
}
|
|
7317
|
+
|
|
7318
|
+
const externalAssetUrl = await getStickerAssetExternalUrl(item.asset, {
|
|
7319
|
+
secure: true,
|
|
7320
|
+
expiresInSeconds: Math.max(60, Math.min(3600, Number(process.env.STICKER_OBJECT_STORAGE_SIGNED_URL_TTL_SECONDS) || 300)),
|
|
7321
|
+
}).catch(() => null);
|
|
7322
|
+
if (externalAssetUrl) {
|
|
7323
|
+
res.statusCode = 302;
|
|
7324
|
+
res.setHeader('Location', externalAssetUrl);
|
|
7325
|
+
res.setHeader('Cache-Control', 'private, max-age=45');
|
|
7326
|
+
if (req.method === 'HEAD') {
|
|
7327
|
+
res.end();
|
|
7328
|
+
return;
|
|
7329
|
+
}
|
|
7330
|
+
res.end();
|
|
7331
|
+
return;
|
|
7332
|
+
}
|
|
7333
|
+
|
|
7063
7334
|
if (decorated) {
|
|
7064
7335
|
res.setHeader('X-Sticker-Category', String(decorated?.category || 'unknown'));
|
|
7065
7336
|
res.setHeader('X-Sticker-NSFW', decorated?.is_nsfw ? '1' : '0');
|
|
@@ -7667,323 +7938,60 @@ const handleAdminBanRevokeRequest = async (req, res, banId) => {
|
|
|
7667
7938
|
}
|
|
7668
7939
|
};
|
|
7669
7940
|
|
|
7670
|
-
const
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
if (pathname === `${STICKER_API_BASE_PATH}/recommendations`) {
|
|
7723
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7724
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7725
|
-
return true;
|
|
7726
|
-
}
|
|
7727
|
-
await handleRecommendationsRequest(req, res, url);
|
|
7728
|
-
return true;
|
|
7729
|
-
}
|
|
7730
|
-
|
|
7731
|
-
if (pathname === `${STICKER_API_BASE_PATH}/stats`) {
|
|
7732
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7733
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7734
|
-
return true;
|
|
7735
|
-
}
|
|
7736
|
-
await handleMarketplaceStatsRequest(req, res, url);
|
|
7737
|
-
return true;
|
|
7738
|
-
}
|
|
7739
|
-
|
|
7740
|
-
if (pathname === `${STICKER_API_BASE_PATH}/create-config`) {
|
|
7741
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7742
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7743
|
-
return true;
|
|
7744
|
-
}
|
|
7745
|
-
await handleCreatePackConfigRequest(req, res);
|
|
7746
|
-
return true;
|
|
7747
|
-
}
|
|
7748
|
-
|
|
7749
|
-
if (pathname === STICKER_ORPHAN_API_PATH) {
|
|
7750
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7751
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7752
|
-
return true;
|
|
7753
|
-
}
|
|
7754
|
-
await handleOrphanStickerListRequest(req, res, url);
|
|
7755
|
-
return true;
|
|
7756
|
-
}
|
|
7757
|
-
|
|
7758
|
-
const suffix = pathname.slice(STICKER_API_BASE_PATH.length).replace(/^\/+/, '');
|
|
7759
|
-
if (!suffix) return false;
|
|
7760
|
-
|
|
7761
|
-
const segments = suffix.split('/').filter(Boolean).map((segment) => {
|
|
7762
|
-
try {
|
|
7763
|
-
return decodeURIComponent(segment);
|
|
7764
|
-
} catch {
|
|
7765
|
-
return segment;
|
|
7766
|
-
}
|
|
7767
|
-
});
|
|
7768
|
-
|
|
7769
|
-
if (segments.length === 1 && segments[0] === 'data-files') {
|
|
7770
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7771
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7772
|
-
return true;
|
|
7773
|
-
}
|
|
7774
|
-
await handleDataFileListRequest(req, res, url);
|
|
7775
|
-
return true;
|
|
7776
|
-
}
|
|
7777
|
-
|
|
7778
|
-
if (segments.length === 1 && segments[0] === 'system-summary') {
|
|
7779
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7780
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7781
|
-
return true;
|
|
7782
|
-
}
|
|
7783
|
-
await handleSystemSummaryRequest(req, res);
|
|
7784
|
-
return true;
|
|
7785
|
-
}
|
|
7786
|
-
|
|
7787
|
-
if (segments.length === 1 && segments[0] === 'project-summary') {
|
|
7788
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7789
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7790
|
-
return true;
|
|
7791
|
-
}
|
|
7792
|
-
await handleGitHubProjectSummaryRequest(req, res);
|
|
7793
|
-
return true;
|
|
7794
|
-
}
|
|
7795
|
-
|
|
7796
|
-
if (segments.length === 1 && segments[0] === 'global-ranking-summary') {
|
|
7797
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7798
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7799
|
-
return true;
|
|
7800
|
-
}
|
|
7801
|
-
await handleGlobalRankingSummaryRequest(req, res);
|
|
7802
|
-
return true;
|
|
7803
|
-
}
|
|
7804
|
-
|
|
7805
|
-
if (segments.length === 1 && segments[0] === 'readme-summary') {
|
|
7806
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7807
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7808
|
-
return true;
|
|
7809
|
-
}
|
|
7810
|
-
await handleReadmeSummaryRequest(req, res);
|
|
7811
|
-
return true;
|
|
7812
|
-
}
|
|
7813
|
-
|
|
7814
|
-
if (segments.length === 1 && segments[0] === 'readme-markdown') {
|
|
7815
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7816
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7817
|
-
return true;
|
|
7818
|
-
}
|
|
7819
|
-
await handleReadmeMarkdownRequest(req, res);
|
|
7820
|
-
return true;
|
|
7821
|
-
}
|
|
7822
|
-
|
|
7823
|
-
if (segments.length === 1 && segments[0] === 'support') {
|
|
7824
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7825
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7826
|
-
return true;
|
|
7827
|
-
}
|
|
7828
|
-
await handleSupportInfoRequest(req, res);
|
|
7829
|
-
return true;
|
|
7830
|
-
}
|
|
7831
|
-
|
|
7832
|
-
if (segments.length === 1 && segments[0] === 'bot-contact') {
|
|
7833
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7834
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7835
|
-
return true;
|
|
7836
|
-
}
|
|
7837
|
-
await handleBotContactInfoRequest(req, res);
|
|
7838
|
-
return true;
|
|
7839
|
-
}
|
|
7840
|
-
|
|
7841
|
-
if (segments[0] === 'admin') {
|
|
7842
|
-
if (segments.length === 2 && segments[1] === 'overview') {
|
|
7843
|
-
await handleAdminOverviewRequest(req, res);
|
|
7844
|
-
return true;
|
|
7845
|
-
}
|
|
7846
|
-
if (segments.length === 2 && segments[1] === 'users') {
|
|
7847
|
-
await handleAdminUsersRequest(req, res, url);
|
|
7848
|
-
return true;
|
|
7849
|
-
}
|
|
7850
|
-
if (segments.length === 2 && segments[1] === 'moderators') {
|
|
7851
|
-
await handleAdminModeratorsRequest(req, res);
|
|
7852
|
-
return true;
|
|
7853
|
-
}
|
|
7854
|
-
if (segments.length === 3 && segments[1] === 'moderators') {
|
|
7855
|
-
await handleAdminModeratorDeleteRequest(req, res, segments[2]);
|
|
7856
|
-
return true;
|
|
7857
|
-
}
|
|
7858
|
-
if (segments.length === 2 && segments[1] === 'packs') {
|
|
7859
|
-
await handleAdminPacksRequest(req, res, url);
|
|
7860
|
-
return true;
|
|
7861
|
-
}
|
|
7862
|
-
if (segments.length === 3 && segments[1] === 'packs') {
|
|
7863
|
-
await handleAdminPackDetailsRequest(req, res, segments[2]);
|
|
7864
|
-
return true;
|
|
7865
|
-
}
|
|
7866
|
-
if (segments.length === 4 && segments[1] === 'packs' && segments[3] === 'delete') {
|
|
7867
|
-
await handleAdminPackDeleteRequest(req, res, segments[2]);
|
|
7868
|
-
return true;
|
|
7869
|
-
}
|
|
7870
|
-
if (segments.length === 6 && segments[1] === 'packs' && segments[3] === 'stickers' && segments[5] === 'delete') {
|
|
7871
|
-
await handleAdminPackStickerDeleteRequest(req, res, segments[2], segments[4]);
|
|
7872
|
-
return true;
|
|
7873
|
-
}
|
|
7874
|
-
if (segments.length === 4 && segments[1] === 'stickers' && segments[3] === 'delete') {
|
|
7875
|
-
await handleAdminGlobalStickerDeleteRequest(req, res, segments[2]);
|
|
7876
|
-
return true;
|
|
7877
|
-
}
|
|
7878
|
-
if (segments.length === 2 && segments[1] === 'bans') {
|
|
7879
|
-
await handleAdminBansRequest(req, res);
|
|
7880
|
-
return true;
|
|
7881
|
-
}
|
|
7882
|
-
if (segments.length === 4 && segments[1] === 'bans' && segments[3] === 'revoke') {
|
|
7883
|
-
await handleAdminBanRevokeRequest(req, res, segments[2]);
|
|
7884
|
-
return true;
|
|
7885
|
-
}
|
|
7886
|
-
sendJson(req, res, 404, { error: 'Rota admin nao encontrada.' });
|
|
7887
|
-
return true;
|
|
7888
|
-
}
|
|
7889
|
-
|
|
7890
|
-
if (segments.length === 1) {
|
|
7891
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7892
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7893
|
-
return true;
|
|
7894
|
-
}
|
|
7895
|
-
await handleDetailsRequest(req, res, segments[0], url);
|
|
7896
|
-
return true;
|
|
7897
|
-
}
|
|
7898
|
-
|
|
7899
|
-
if (segments.length === 2 && ['open', 'like', 'dislike'].includes(segments[1])) {
|
|
7900
|
-
if (req.method !== 'POST') {
|
|
7901
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7902
|
-
return true;
|
|
7903
|
-
}
|
|
7904
|
-
await handlePackInteractionRequest(req, res, segments[0], segments[1], url);
|
|
7905
|
-
return true;
|
|
7906
|
-
}
|
|
7907
|
-
|
|
7908
|
-
if (segments.length === 2 && segments[1] === 'manage') {
|
|
7909
|
-
await handleManagedPackRequest(req, res, segments[0]);
|
|
7910
|
-
return true;
|
|
7911
|
-
}
|
|
7912
|
-
|
|
7913
|
-
if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'clone') {
|
|
7914
|
-
await handleManagedPackCloneRequest(req, res, segments[0]);
|
|
7915
|
-
return true;
|
|
7916
|
-
}
|
|
7917
|
-
|
|
7918
|
-
if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'cover') {
|
|
7919
|
-
await handleManagedPackCoverRequest(req, res, segments[0]);
|
|
7920
|
-
return true;
|
|
7921
|
-
}
|
|
7922
|
-
|
|
7923
|
-
if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'reorder') {
|
|
7924
|
-
await handleManagedPackReorderRequest(req, res, segments[0]);
|
|
7925
|
-
return true;
|
|
7926
|
-
}
|
|
7927
|
-
|
|
7928
|
-
if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'analytics') {
|
|
7929
|
-
await handleManagedPackAnalyticsRequest(req, res, segments[0]);
|
|
7930
|
-
return true;
|
|
7931
|
-
}
|
|
7932
|
-
|
|
7933
|
-
if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'stickers') {
|
|
7934
|
-
await handleManagedPackStickerCreateRequest(req, res, segments[0]);
|
|
7935
|
-
return true;
|
|
7936
|
-
}
|
|
7937
|
-
|
|
7938
|
-
if (segments.length === 4 && segments[1] === 'manage' && segments[2] === 'stickers') {
|
|
7939
|
-
await handleManagedPackStickerDeleteRequest(req, res, segments[0], segments[3]);
|
|
7940
|
-
return true;
|
|
7941
|
-
}
|
|
7942
|
-
|
|
7943
|
-
if (segments.length === 5 && segments[1] === 'manage' && segments[2] === 'stickers' && segments[4] === 'replace') {
|
|
7944
|
-
await handleManagedPackStickerReplaceRequest(req, res, segments[0], segments[3]);
|
|
7945
|
-
return true;
|
|
7946
|
-
}
|
|
7947
|
-
|
|
7948
|
-
if (segments.length === 2 && segments[1] === 'publish-state') {
|
|
7949
|
-
if (!['GET', 'HEAD', 'POST'].includes(req.method || '')) {
|
|
7950
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7951
|
-
return true;
|
|
7952
|
-
}
|
|
7953
|
-
await handlePackPublishStateRequest(req, res, segments[0], url);
|
|
7954
|
-
return true;
|
|
7955
|
-
}
|
|
7956
|
-
|
|
7957
|
-
if (segments.length === 2 && segments[1] === 'finalize') {
|
|
7958
|
-
if (req.method !== 'POST') {
|
|
7959
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7960
|
-
return true;
|
|
7961
|
-
}
|
|
7962
|
-
await handleFinalizePackRequest(req, res, segments[0]);
|
|
7963
|
-
return true;
|
|
7964
|
-
}
|
|
7965
|
-
|
|
7966
|
-
if (segments.length === 2 && segments[1] === 'stickers-upload') {
|
|
7967
|
-
if (req.method !== 'POST') {
|
|
7968
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7969
|
-
return true;
|
|
7970
|
-
}
|
|
7971
|
-
await handleUploadStickerToPackRequest(req, res, segments[0]);
|
|
7972
|
-
return true;
|
|
7973
|
-
}
|
|
7974
|
-
|
|
7975
|
-
if (segments.length === 3 && segments[1] === 'stickers') {
|
|
7976
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7977
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7978
|
-
return true;
|
|
7979
|
-
}
|
|
7980
|
-
await handleAssetRequest(req, res, segments[0], segments[2]);
|
|
7981
|
-
return true;
|
|
7982
|
-
}
|
|
7941
|
+
const catalogApiRouter = createCatalogApiRouter({
|
|
7942
|
+
apiBasePath: STICKER_API_BASE_PATH,
|
|
7943
|
+
orphanApiPath: STICKER_ORPHAN_API_PATH,
|
|
7944
|
+
sendJson,
|
|
7945
|
+
handlers: {
|
|
7946
|
+
handleCreatePackRequest,
|
|
7947
|
+
handleGoogleAuthSessionRequest,
|
|
7948
|
+
handleMyProfileRequest,
|
|
7949
|
+
handleAdminPanelSessionRequest,
|
|
7950
|
+
handleListRequest,
|
|
7951
|
+
handleIntentCollectionsRequest,
|
|
7952
|
+
handleCreatorRankingRequest,
|
|
7953
|
+
handleRecommendationsRequest,
|
|
7954
|
+
handleMarketplaceStatsRequest,
|
|
7955
|
+
handleCreatePackConfigRequest,
|
|
7956
|
+
handleOrphanStickerListRequest,
|
|
7957
|
+
handleDataFileListRequest,
|
|
7958
|
+
handleSystemSummaryRequest,
|
|
7959
|
+
handleGitHubProjectSummaryRequest,
|
|
7960
|
+
handleGlobalRankingSummaryRequest,
|
|
7961
|
+
handleReadmeSummaryRequest,
|
|
7962
|
+
handleReadmeMarkdownRequest,
|
|
7963
|
+
handleSupportInfoRequest,
|
|
7964
|
+
handleBotContactInfoRequest,
|
|
7965
|
+
handleAdminOverviewRequest,
|
|
7966
|
+
handleAdminUsersRequest,
|
|
7967
|
+
handleAdminModeratorsRequest,
|
|
7968
|
+
handleAdminModeratorDeleteRequest,
|
|
7969
|
+
handleAdminPacksRequest,
|
|
7970
|
+
handleAdminPackDetailsRequest,
|
|
7971
|
+
handleAdminPackDeleteRequest,
|
|
7972
|
+
handleAdminPackStickerDeleteRequest,
|
|
7973
|
+
handleAdminGlobalStickerDeleteRequest,
|
|
7974
|
+
handleAdminBansRequest,
|
|
7975
|
+
handleAdminBanRevokeRequest,
|
|
7976
|
+
handleDetailsRequest,
|
|
7977
|
+
handlePackInteractionRequest,
|
|
7978
|
+
handleManagedPackRequest,
|
|
7979
|
+
handleManagedPackCloneRequest,
|
|
7980
|
+
handleManagedPackCoverRequest,
|
|
7981
|
+
handleManagedPackReorderRequest,
|
|
7982
|
+
handleManagedPackAnalyticsRequest,
|
|
7983
|
+
handleManagedPackStickerCreateRequest,
|
|
7984
|
+
handleManagedPackStickerDeleteRequest,
|
|
7985
|
+
handleManagedPackStickerReplaceRequest,
|
|
7986
|
+
handlePackPublishStateRequest,
|
|
7987
|
+
handleFinalizePackRequest,
|
|
7988
|
+
handleUploadStickerToPackRequest,
|
|
7989
|
+
handleAssetRequest,
|
|
7990
|
+
},
|
|
7991
|
+
});
|
|
7983
7992
|
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
};
|
|
7993
|
+
const handleCatalogApiRequest = async (req, res, pathname, url) =>
|
|
7994
|
+
catalogApiRouter({ req, res, pathname, url });
|
|
7987
7995
|
|
|
7988
7996
|
const handleCatalogPageRequest = async (req, res, pathname) => {
|
|
7989
7997
|
const normalizedPath = pathname.length > 1 ? pathname.replace(/\/+$/, '') : pathname;
|