@kaikybrofc/omnizap-system 2.2.4 → 2.2.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 +5 -0
- package/.prettierrc +16 -0
- package/README.md +13 -13
- package/app/modules/stickerPackModule/autoPackCollectorService.js +63 -8
- 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 +570 -536
- 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/kaikybrofc-omnizap-system-2.2.6.tgz +0 -0
- package/observability/sticker-catalog-slo.md +83 -0
- package/observability/sticker-scale-hardening-rollout.md +128 -0
- package/package.json +7 -35
- package/public/assets/images/brand-icon-192.png +0 -0
- package/public/assets/images/brand-logo-128.webp +0 -0
- package/public/assets/images/hero-banner-1280.avif +0 -0
- package/public/assets/images/hero-banner-1280.jpg +0 -0
- package/public/assets/images/hero-banner-1280.webp +0 -0
- package/public/assets/images/hero-banner-720.avif +0 -0
- package/public/assets/images/hero-banner-720.webp +0 -0
- package/public/index.html +120 -18
- package/public/js/apps/homeApp.js +469 -353
- package/public/robots.txt +9 -0
- package/public/sitemap.xml +28 -0
- package/scripts/sticker-catalog-loadtest.mjs +208 -0
- package/scripts/sticker-worker-task.mjs +122 -0
- package/observability/mysql-exporter.cnf +0 -5
|
@@ -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;
|
|
@@ -148,6 +157,7 @@ const STICKER_LOGIN_WEB_PATH = normalizeBasePath(process.env.STICKER_LOGIN_WEB_P
|
|
|
148
157
|
const USER_PROFILE_WEB_PATH = normalizeBasePath(process.env.USER_PROFILE_WEB_PATH, '/user');
|
|
149
158
|
const STICKER_DATA_PUBLIC_PATH = normalizeBasePath(process.env.STICKER_DATA_PUBLIC_PATH, '/data');
|
|
150
159
|
const STICKER_DATA_PUBLIC_DIR = path.resolve(process.env.STICKER_DATA_PUBLIC_DIR || path.join(process.cwd(), 'data'));
|
|
160
|
+
const STICKER_WEB_ASSET_VERSION = sanitizeText(process.env.STICKER_WEB_ASSET_VERSION || '', 64, { allowEmpty: true }) || '';
|
|
151
161
|
const CATALOG_PUBLIC_DIR = path.resolve(process.cwd(), 'public');
|
|
152
162
|
const CATALOG_TEMPLATE_PATH = path.join(CATALOG_PUBLIC_DIR, 'stickers', 'index.html');
|
|
153
163
|
const CREATE_PACK_TEMPLATE_PATH = path.join(CATALOG_PUBLIC_DIR, 'stickers', 'create', 'index.html');
|
|
@@ -162,7 +172,19 @@ const MAX_ORPHAN_LIST_LIMIT = clampInt(process.env.STICKER_ORPHAN_LIST_MAX_LIMIT
|
|
|
162
172
|
const DEFAULT_DATA_LIST_LIMIT = clampInt(process.env.STICKER_DATA_LIST_LIMIT, 50, 1, 200);
|
|
163
173
|
const MAX_DATA_LIST_LIMIT = clampInt(process.env.STICKER_DATA_LIST_MAX_LIMIT, 200, 1, 500);
|
|
164
174
|
const MAX_DATA_SCAN_FILES = clampInt(process.env.STICKER_DATA_SCAN_MAX_FILES, 10000, 100, 50000);
|
|
165
|
-
const ASSET_CACHE_SECONDS = clampInt(
|
|
175
|
+
const ASSET_CACHE_SECONDS = clampInt(
|
|
176
|
+
process.env.STICKER_WEB_ASSET_CACHE_SECONDS,
|
|
177
|
+
60 * 60 * 24 * 30,
|
|
178
|
+
60 * 60,
|
|
179
|
+
60 * 60 * 24 * 365,
|
|
180
|
+
);
|
|
181
|
+
const STATIC_TEXT_CACHE_SECONDS = clampInt(process.env.STICKER_WEB_STATIC_TEXT_CACHE_SECONDS, 60 * 60, 60, 60 * 60 * 24 * 30);
|
|
182
|
+
const IMMUTABLE_ASSET_CACHE_SECONDS = clampInt(
|
|
183
|
+
process.env.STICKER_WEB_IMMUTABLE_ASSET_CACHE_SECONDS,
|
|
184
|
+
60 * 60 * 24 * 365,
|
|
185
|
+
60 * 60,
|
|
186
|
+
60 * 60 * 24 * 365,
|
|
187
|
+
);
|
|
166
188
|
const STICKER_WEB_WHATSAPP_MESSAGE_TEMPLATE =
|
|
167
189
|
String(process.env.STICKER_WEB_WHATSAPP_MESSAGE_TEMPLATE || '/pack send {{pack_key}}').trim() ||
|
|
168
190
|
'/pack send {{pack_key}}';
|
|
@@ -203,6 +225,19 @@ const GITHUB_REPOSITORY = String(process.env.GITHUB_REPOSITORY || 'Kaikygr/omniz
|
|
|
203
225
|
const GITHUB_TOKEN = String(process.env.GITHUB_TOKEN || '').trim();
|
|
204
226
|
const GITHUB_PROJECT_CACHE_SECONDS = clampInt(process.env.GITHUB_PROJECT_CACHE_SECONDS, 300, 30, 3600);
|
|
205
227
|
const GLOBAL_RANK_REFRESH_SECONDS = clampInt(process.env.GLOBAL_RANK_REFRESH_SECONDS, 600, 60, 3600);
|
|
228
|
+
const CATALOG_LIST_CACHE_SECONDS = clampInt(process.env.STICKER_CATALOG_LIST_CACHE_SECONDS, 90, 15, 900);
|
|
229
|
+
const CATALOG_CREATOR_RANKING_CACHE_SECONDS = clampInt(
|
|
230
|
+
process.env.STICKER_CATALOG_CREATOR_RANKING_CACHE_SECONDS,
|
|
231
|
+
120,
|
|
232
|
+
15,
|
|
233
|
+
900,
|
|
234
|
+
);
|
|
235
|
+
const CATALOG_PACK_PAYLOAD_CACHE_SECONDS = clampInt(
|
|
236
|
+
process.env.STICKER_CATALOG_PACK_PAYLOAD_CACHE_SECONDS,
|
|
237
|
+
300,
|
|
238
|
+
30,
|
|
239
|
+
1800,
|
|
240
|
+
);
|
|
206
241
|
const MARKETPLACE_GLOBAL_STATS_API_PATH = '/api/marketplace/stats';
|
|
207
242
|
const MARKETPLACE_GLOBAL_STATS_CACHE_SECONDS = clampInt(process.env.MARKETPLACE_GLOBAL_STATS_CACHE_SECONDS, 45, 30, 60);
|
|
208
243
|
const HOME_MARKETPLACE_STATS_CACHE_SECONDS = clampInt(process.env.HOME_MARKETPLACE_STATS_CACHE_SECONDS, 45, 10, 300);
|
|
@@ -218,6 +253,14 @@ const SITE_CANONICAL_REDIRECT_ENABLED = parseEnvBool(process.env.SITE_CANONICAL_
|
|
|
218
253
|
const SITE_ORIGIN = String(process.env.SITE_ORIGIN || `${SITE_CANONICAL_SCHEME}://${SITE_CANONICAL_HOST}`)
|
|
219
254
|
.trim()
|
|
220
255
|
.replace(/\/+$/, '');
|
|
256
|
+
const SITE_COOKIE_DOMAIN = String(process.env.SITE_COOKIE_DOMAIN || SITE_CANONICAL_HOST)
|
|
257
|
+
.trim()
|
|
258
|
+
.toLowerCase()
|
|
259
|
+
.replace(/^https?:\/\//, '')
|
|
260
|
+
.split('/')[0]
|
|
261
|
+
.split(':')[0]
|
|
262
|
+
.replace(/^\.+/, '')
|
|
263
|
+
.replace(/\.+$/, '');
|
|
221
264
|
const SITEMAP_MAX_PACKS = clampInt(process.env.STICKER_SITEMAP_MAX_PACKS, 45000, 100, 50000);
|
|
222
265
|
const SITEMAP_CACHE_SECONDS = clampInt(process.env.STICKER_SITEMAP_CACHE_SECONDS, 180, 30, 3600);
|
|
223
266
|
const SEO_DISCOVERY_LINK_LIMIT = clampInt(process.env.STICKER_SEO_DISCOVERY_LINK_LIMIT, 60, 10, 200);
|
|
@@ -295,6 +338,9 @@ const MARKETPLACE_GLOBAL_STATS_CACHE = {
|
|
|
295
338
|
pending: null,
|
|
296
339
|
};
|
|
297
340
|
const HOME_MARKETPLACE_STATS_CACHE = new Map();
|
|
341
|
+
const CATALOG_LIST_CACHE = new Map();
|
|
342
|
+
const CATALOG_CREATOR_RANKING_CACHE = new Map();
|
|
343
|
+
const CATALOG_PACK_PAYLOAD_CACHE = new Map();
|
|
298
344
|
const SYSTEM_SUMMARY_CACHE = {
|
|
299
345
|
expiresAt: 0,
|
|
300
346
|
value: null,
|
|
@@ -324,6 +370,69 @@ const formatDuration = (totalSeconds) => {
|
|
|
324
370
|
return days > 0 ? `${days}d ${hhmmss}` : hhmmss;
|
|
325
371
|
};
|
|
326
372
|
|
|
373
|
+
const buildCacheKey = (parts) => JSON.stringify(parts);
|
|
374
|
+
|
|
375
|
+
const getCacheBucket = (cacheMap, key) => {
|
|
376
|
+
let bucket = cacheMap.get(key);
|
|
377
|
+
if (!bucket) {
|
|
378
|
+
bucket = {
|
|
379
|
+
expiresAt: 0,
|
|
380
|
+
value: null,
|
|
381
|
+
pending: null,
|
|
382
|
+
};
|
|
383
|
+
cacheMap.set(key, bucket);
|
|
384
|
+
}
|
|
385
|
+
return bucket;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const getCachedSnapshot = async ({
|
|
389
|
+
cacheMap,
|
|
390
|
+
key,
|
|
391
|
+
ttlSeconds,
|
|
392
|
+
staleWhileRefresh = true,
|
|
393
|
+
staleOnError = true,
|
|
394
|
+
load,
|
|
395
|
+
}) => {
|
|
396
|
+
const bucket = getCacheBucket(cacheMap, key);
|
|
397
|
+
const now = Date.now();
|
|
398
|
+
const hasValue = bucket.value !== null;
|
|
399
|
+
const hasFreshValue = hasValue && now < bucket.expiresAt;
|
|
400
|
+
|
|
401
|
+
if (hasFreshValue) {
|
|
402
|
+
return bucket.value;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!bucket.pending) {
|
|
406
|
+
bucket.pending = Promise.resolve()
|
|
407
|
+
.then(load)
|
|
408
|
+
.then((value) => {
|
|
409
|
+
bucket.value = value;
|
|
410
|
+
bucket.expiresAt = Date.now() + ttlSeconds * 1000;
|
|
411
|
+
return value;
|
|
412
|
+
})
|
|
413
|
+
.finally(() => {
|
|
414
|
+
bucket.pending = null;
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (hasValue && staleWhileRefresh) {
|
|
419
|
+
return bucket.value;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
return await bucket.pending;
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (hasValue && staleOnError) return bucket.value;
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const canUseRankingSnapshotRead = async (subjectKey = 'catalog') =>
|
|
431
|
+
isFeatureEnabled('enable_ranking_snapshot_read', {
|
|
432
|
+
fallback: true,
|
|
433
|
+
subjectKey,
|
|
434
|
+
});
|
|
435
|
+
|
|
327
436
|
const sendJson = (req, res, statusCode, payload) => {
|
|
328
437
|
const body = JSON.stringify(payload);
|
|
329
438
|
res.statusCode = statusCode;
|
|
@@ -362,6 +471,22 @@ const toRequestHost = (req) =>
|
|
|
362
471
|
.replace(/\.$/, '')
|
|
363
472
|
.split(':')[0];
|
|
364
473
|
|
|
474
|
+
const isIpLiteralHost = (value) => {
|
|
475
|
+
const host = String(value || '').trim().toLowerCase();
|
|
476
|
+
if (!host) return false;
|
|
477
|
+
if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(host)) return true;
|
|
478
|
+
return host.includes(':');
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const resolveCookieDomainForRequest = (req) => {
|
|
482
|
+
if (!SITE_COOKIE_DOMAIN || isIpLiteralHost(SITE_COOKIE_DOMAIN)) return '';
|
|
483
|
+
const requestHost = toRequestHost(req);
|
|
484
|
+
if (!requestHost || isIpLiteralHost(requestHost) || requestHost === 'localhost') return '';
|
|
485
|
+
if (requestHost === SITE_COOKIE_DOMAIN) return SITE_COOKIE_DOMAIN;
|
|
486
|
+
if (requestHost.endsWith(`.${SITE_COOKIE_DOMAIN}`)) return SITE_COOKIE_DOMAIN;
|
|
487
|
+
return '';
|
|
488
|
+
};
|
|
489
|
+
|
|
365
490
|
const maybeRedirectToCanonicalHost = (req, res, url) => {
|
|
366
491
|
if (!SITE_CANONICAL_REDIRECT_ENABLED) return false;
|
|
367
492
|
if (!['GET', 'HEAD'].includes(req.method || '')) return false;
|
|
@@ -395,6 +520,33 @@ const parseCookies = (req) => {
|
|
|
395
520
|
}, {});
|
|
396
521
|
};
|
|
397
522
|
|
|
523
|
+
const getCookieValuesFromRequest = (req, cookieName) => {
|
|
524
|
+
const target = String(cookieName || '').trim();
|
|
525
|
+
if (!target) return [];
|
|
526
|
+
const raw = String(req?.headers?.cookie || '');
|
|
527
|
+
if (!raw) return [];
|
|
528
|
+
|
|
529
|
+
const values = [];
|
|
530
|
+
for (const chunk of raw.split(';')) {
|
|
531
|
+
const trimmed = String(chunk || '').trim();
|
|
532
|
+
if (!trimmed) continue;
|
|
533
|
+
const separatorIndex = trimmed.indexOf('=');
|
|
534
|
+
if (separatorIndex <= 0) continue;
|
|
535
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
536
|
+
if (key !== target) continue;
|
|
537
|
+
const encodedValue = trimmed.slice(separatorIndex + 1).trim();
|
|
538
|
+
if (!encodedValue) continue;
|
|
539
|
+
let decodedValue = encodedValue;
|
|
540
|
+
try {
|
|
541
|
+
decodedValue = decodeURIComponent(encodedValue);
|
|
542
|
+
} catch {}
|
|
543
|
+
const normalizedValue = String(decodedValue || '').trim();
|
|
544
|
+
if (!normalizedValue) continue;
|
|
545
|
+
if (!values.includes(normalizedValue)) values.push(normalizedValue);
|
|
546
|
+
}
|
|
547
|
+
return values;
|
|
548
|
+
};
|
|
549
|
+
|
|
398
550
|
const isRequestSecure = (req) => {
|
|
399
551
|
const proto = String(req?.headers?.['x-forwarded-proto'] || '').split(',')[0].trim().toLowerCase();
|
|
400
552
|
if (proto) return proto === 'https';
|
|
@@ -503,6 +655,11 @@ const runSqlTransaction = async (handler) => {
|
|
|
503
655
|
const buildCookieString = (name, value, req, options = {}) => {
|
|
504
656
|
const parts = [`${name}=${encodeURIComponent(String(value ?? ''))}`];
|
|
505
657
|
parts.push(`Path=${options.path || '/'}`);
|
|
658
|
+
const cookieDomain =
|
|
659
|
+
options.domain === false
|
|
660
|
+
? ''
|
|
661
|
+
: String(options.domain || resolveCookieDomainForRequest(req)).trim();
|
|
662
|
+
if (cookieDomain) parts.push(`Domain=${cookieDomain}`);
|
|
506
663
|
if (options.httpOnly !== false) parts.push('HttpOnly');
|
|
507
664
|
parts.push(`SameSite=${options.sameSite || 'Lax'}`);
|
|
508
665
|
if (isRequestSecure(req)) parts.push('Secure');
|
|
@@ -914,11 +1071,16 @@ const pruneExpiredGoogleSessions = () => {
|
|
|
914
1071
|
}
|
|
915
1072
|
};
|
|
916
1073
|
|
|
917
|
-
const
|
|
1074
|
+
const getGoogleWebSessionTokensFromRequest = (req) => {
|
|
1075
|
+
const direct = getCookieValuesFromRequest(req, GOOGLE_WEB_SESSION_COOKIE_NAME);
|
|
1076
|
+
if (direct.length > 0) return direct;
|
|
918
1077
|
const cookies = parseCookies(req);
|
|
919
|
-
|
|
1078
|
+
const fallback = String(cookies[GOOGLE_WEB_SESSION_COOKIE_NAME] || '').trim();
|
|
1079
|
+
return fallback ? [fallback] : [];
|
|
920
1080
|
};
|
|
921
1081
|
|
|
1082
|
+
const getGoogleWebSessionTokenFromRequest = (req) => getGoogleWebSessionTokensFromRequest(req)[0] || '';
|
|
1083
|
+
|
|
922
1084
|
const normalizeGoogleWebSessionRow = (row) => {
|
|
923
1085
|
if (!row || typeof row !== 'object') return null;
|
|
924
1086
|
const token = String(row.session_token || '').trim();
|
|
@@ -1574,6 +1736,8 @@ const pruneExpiredAdminPanelSessions = () => {
|
|
|
1574
1736
|
};
|
|
1575
1737
|
|
|
1576
1738
|
const getAdminPanelSessionTokenFromRequest = (req) => {
|
|
1739
|
+
const direct = getCookieValuesFromRequest(req, ADMIN_PANEL_SESSION_COOKIE_NAME);
|
|
1740
|
+
if (direct.length > 0) return direct[0];
|
|
1577
1741
|
const cookies = parseCookies(req);
|
|
1578
1742
|
return String(cookies[ADMIN_PANEL_SESSION_COOKIE_NAME] || '').trim();
|
|
1579
1743
|
};
|
|
@@ -1585,6 +1749,14 @@ const clearAdminPanelSessionCookie = (req, res) => {
|
|
|
1585
1749
|
maxAgeSeconds: 0,
|
|
1586
1750
|
}),
|
|
1587
1751
|
);
|
|
1752
|
+
// Also clear host-only variant (legacy cookie written without Domain).
|
|
1753
|
+
appendSetCookie(
|
|
1754
|
+
res,
|
|
1755
|
+
buildCookieString(ADMIN_PANEL_SESSION_COOKIE_NAME, '', req, {
|
|
1756
|
+
maxAgeSeconds: 0,
|
|
1757
|
+
domain: false,
|
|
1758
|
+
}),
|
|
1759
|
+
);
|
|
1588
1760
|
};
|
|
1589
1761
|
|
|
1590
1762
|
const createAdminPanelSession = (googleSession, { role = 'owner' } = {}) => {
|
|
@@ -1732,61 +1904,67 @@ const activateGoogleWebSession = (session) => {
|
|
|
1732
1904
|
|
|
1733
1905
|
const resolveGoogleWebSessionFromRequest = async (req) => {
|
|
1734
1906
|
pruneExpiredGoogleSessions();
|
|
1735
|
-
const
|
|
1736
|
-
if (!
|
|
1737
|
-
|
|
1738
|
-
|
|
1907
|
+
const sessionTokens = getGoogleWebSessionTokensFromRequest(req);
|
|
1908
|
+
if (!sessionTokens.length) return null;
|
|
1909
|
+
|
|
1910
|
+
for (const sessionToken of sessionTokens) {
|
|
1911
|
+
const session = webGoogleSessionMap.get(sessionToken);
|
|
1912
|
+
if (!session) continue;
|
|
1739
1913
|
if (Number(session.expiresAt || 0) <= Date.now()) {
|
|
1740
1914
|
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,
|
|
1915
|
+
continue;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
const now = Date.now();
|
|
1919
|
+
session.lastSeenAt = now;
|
|
1920
|
+
if (now - Number(session.lastDbTouchAt || 0) >= GOOGLE_WEB_SESSION_DB_TOUCH_INTERVAL_MS) {
|
|
1921
|
+
session.lastDbTouchAt = now;
|
|
1922
|
+
void touchGoogleWebSessionSeenInDb(sessionToken).catch((error) => {
|
|
1923
|
+
logger.warn('Falha ao atualizar last_seen da sessão Google web.', {
|
|
1924
|
+
action: 'sticker_pack_google_web_session_touch_failed',
|
|
1925
|
+
error: error?.message,
|
|
1759
1926
|
});
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
void deleteGoogleWebSessionFromDb(sessionToken).catch(() => {});
|
|
1763
|
-
return null;
|
|
1764
|
-
}
|
|
1765
|
-
return session;
|
|
1927
|
+
});
|
|
1928
|
+
void touchGoogleWebUserSeenInDb(session.sub).catch(() => {});
|
|
1766
1929
|
}
|
|
1767
|
-
}
|
|
1768
|
-
try {
|
|
1769
|
-
const persistedSession = await findGoogleWebSessionInDbByToken(sessionToken);
|
|
1770
|
-
if (!persistedSession) return null;
|
|
1771
1930
|
try {
|
|
1772
1931
|
await assertGoogleIdentityNotBanned({
|
|
1773
|
-
sub:
|
|
1774
|
-
email:
|
|
1775
|
-
ownerJid:
|
|
1932
|
+
sub: session.sub,
|
|
1933
|
+
email: session.email,
|
|
1934
|
+
ownerJid: session.ownerJid,
|
|
1776
1935
|
});
|
|
1936
|
+
return session;
|
|
1777
1937
|
} catch {
|
|
1778
|
-
|
|
1779
|
-
|
|
1938
|
+
webGoogleSessionMap.delete(sessionToken);
|
|
1939
|
+
void deleteGoogleWebSessionFromDb(sessionToken).catch(() => {});
|
|
1780
1940
|
}
|
|
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
1941
|
}
|
|
1942
|
+
|
|
1943
|
+
for (const sessionToken of sessionTokens) {
|
|
1944
|
+
try {
|
|
1945
|
+
const persistedSession = await findGoogleWebSessionInDbByToken(sessionToken);
|
|
1946
|
+
if (!persistedSession) continue;
|
|
1947
|
+
try {
|
|
1948
|
+
await assertGoogleIdentityNotBanned({
|
|
1949
|
+
sub: persistedSession.sub,
|
|
1950
|
+
email: persistedSession.email,
|
|
1951
|
+
ownerJid: persistedSession.ownerJid,
|
|
1952
|
+
});
|
|
1953
|
+
} catch {
|
|
1954
|
+
await deleteGoogleWebSessionFromDb(sessionToken).catch(() => {});
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
webGoogleSessionMap.set(sessionToken, persistedSession);
|
|
1958
|
+
return persistedSession;
|
|
1959
|
+
} catch (error) {
|
|
1960
|
+
logger.warn('Falha ao resolver sessão Google web no banco.', {
|
|
1961
|
+
action: 'sticker_pack_google_web_session_db_resolve_failed',
|
|
1962
|
+
error: error?.message,
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
return null;
|
|
1790
1968
|
};
|
|
1791
1969
|
|
|
1792
1970
|
const clearGoogleWebSessionCookie = (req, res) => {
|
|
@@ -1796,13 +1974,26 @@ const clearGoogleWebSessionCookie = (req, res) => {
|
|
|
1796
1974
|
maxAgeSeconds: 0,
|
|
1797
1975
|
}),
|
|
1798
1976
|
);
|
|
1977
|
+
// Also clear host-only variant (legacy cookie written without Domain).
|
|
1978
|
+
appendSetCookie(
|
|
1979
|
+
res,
|
|
1980
|
+
buildCookieString(GOOGLE_WEB_SESSION_COOKIE_NAME, '', req, {
|
|
1981
|
+
maxAgeSeconds: 0,
|
|
1982
|
+
domain: false,
|
|
1983
|
+
}),
|
|
1984
|
+
);
|
|
1799
1985
|
};
|
|
1800
1986
|
|
|
1801
1987
|
const sendAsset = (req, res, buffer, mimetype = 'image/webp') => {
|
|
1988
|
+
const maxAgeSeconds = Math.max(60 * 60 * 24, ASSET_CACHE_SECONDS);
|
|
1989
|
+
const staleWhileRevalidateSeconds = Math.min(60 * 60 * 24 * 7, Math.max(300, maxAgeSeconds));
|
|
1802
1990
|
res.statusCode = 200;
|
|
1803
1991
|
res.setHeader('Content-Type', mimetype);
|
|
1804
1992
|
res.setHeader('Content-Length', String(buffer.length));
|
|
1805
|
-
res.setHeader(
|
|
1993
|
+
res.setHeader(
|
|
1994
|
+
'Cache-Control',
|
|
1995
|
+
`public, max-age=${maxAgeSeconds}, stale-while-revalidate=${staleWhileRevalidateSeconds}`,
|
|
1996
|
+
);
|
|
1806
1997
|
if (req.method === 'HEAD') {
|
|
1807
1998
|
res.end();
|
|
1808
1999
|
return;
|
|
@@ -2041,8 +2232,12 @@ const buildStickerAssetUrl = (packKey, stickerId) =>
|
|
|
2041
2232
|
`${STICKER_API_BASE_PATH}/${encodeURIComponent(packKey)}/stickers/${encodeURIComponent(stickerId)}.webp`;
|
|
2042
2233
|
const buildOrphanStickersApiUrl = () => STICKER_ORPHAN_API_PATH;
|
|
2043
2234
|
const buildDataAssetApiBaseUrl = () => `${STICKER_API_BASE_PATH}/data-files`;
|
|
2044
|
-
const
|
|
2045
|
-
const
|
|
2235
|
+
const CATALOG_STYLES_WEB_PATH = `${STICKER_WEB_PATH}/assets/styles.css`;
|
|
2236
|
+
const CATALOG_SCRIPT_WEB_PATH = `${STICKER_WEB_PATH}/assets/catalog.js`;
|
|
2237
|
+
const appendAssetVersionQuery = (assetPath) =>
|
|
2238
|
+
STICKER_WEB_ASSET_VERSION ? `${assetPath}?v=${encodeURIComponent(STICKER_WEB_ASSET_VERSION)}` : assetPath;
|
|
2239
|
+
const buildCatalogStylesUrl = () => appendAssetVersionQuery(CATALOG_STYLES_WEB_PATH);
|
|
2240
|
+
const buildCatalogScriptUrl = () => appendAssetVersionQuery(CATALOG_SCRIPT_WEB_PATH);
|
|
2046
2241
|
const buildDataAssetUrl = (relativePath) =>
|
|
2047
2242
|
`${STICKER_DATA_PUBLIC_PATH}/${String(relativePath)
|
|
2048
2243
|
.split('/')
|
|
@@ -2146,7 +2341,9 @@ const resolveWebCreateOwnerJid = async (explicitOwner = '') => {
|
|
|
2146
2341
|
const resolvedAdminJid = await resolveAdminJid();
|
|
2147
2342
|
const fromAdmin = toOwnerJid(resolvedAdminJid);
|
|
2148
2343
|
if (fromAdmin) return fromAdmin;
|
|
2149
|
-
} catch {
|
|
2344
|
+
} catch {
|
|
2345
|
+
// Ignore fallback errors while resolving owner identity.
|
|
2346
|
+
}
|
|
2150
2347
|
|
|
2151
2348
|
const adminCandidates = [
|
|
2152
2349
|
getAdminRawValue(),
|
|
@@ -2316,14 +2513,18 @@ const resolveSupportAdminPhone = async () => {
|
|
|
2316
2513
|
const resolvedFromLidMap = await resolveUserId(extractUserIdInfo(adminRaw));
|
|
2317
2514
|
const resolvedPhoneFromLidMap = isPlausibleWhatsAppPhone(getJidUser(resolvedFromLidMap || ''));
|
|
2318
2515
|
if (resolvedPhoneFromLidMap) return resolvedPhoneFromLidMap;
|
|
2319
|
-
} catch {
|
|
2516
|
+
} catch {
|
|
2517
|
+
// Ignore and fallback to other admin sources.
|
|
2518
|
+
}
|
|
2320
2519
|
}
|
|
2321
2520
|
|
|
2322
2521
|
try {
|
|
2323
2522
|
const resolvedAdminJid = await resolveAdminJid();
|
|
2324
2523
|
const resolvedPhone = isPlausibleWhatsAppPhone(getJidUser(resolvedAdminJid || ''));
|
|
2325
2524
|
if (resolvedPhone) return resolvedPhone;
|
|
2326
|
-
} catch {
|
|
2525
|
+
} catch {
|
|
2526
|
+
// Ignore and fallback to static admin phone sources.
|
|
2527
|
+
}
|
|
2327
2528
|
|
|
2328
2529
|
const rawPhone = isPlausibleWhatsAppPhone(getJidUser(adminRaw) || adminRaw);
|
|
2329
2530
|
if (rawPhone) return rawPhone;
|
|
@@ -2725,6 +2926,10 @@ const hydrateMarketplaceEntries = async (packs, { includeItems = true, driftSnap
|
|
|
2725
2926
|
const packIds = packs.map((pack) => pack.id);
|
|
2726
2927
|
const engagementByPackId = await listStickerPackEngagementByPackIds(packIds);
|
|
2727
2928
|
const interactionStatsByPackId = await listStickerPackInteractionStatsByPackIds(packIds);
|
|
2929
|
+
const useSnapshot = await canUseRankingSnapshotRead(`hydrate:${packIds.length}:${includeItems ? 1 : 0}`);
|
|
2930
|
+
const snapshotByPackId = useSnapshot
|
|
2931
|
+
? await listStickerPackScoreSnapshotsByPackIds(packIds).catch(() => new Map())
|
|
2932
|
+
: new Map();
|
|
2728
2933
|
|
|
2729
2934
|
const entries = [];
|
|
2730
2935
|
const packClassificationById = new Map();
|
|
@@ -2743,14 +2948,24 @@ const hydrateMarketplaceEntries = async (packs, { includeItems = true, driftSnap
|
|
|
2743
2948
|
const packMetadata = parsePackDescriptionMetadata(pack.description);
|
|
2744
2949
|
const decoratedClassification = decoratePackClassificationSummary(packClassification);
|
|
2745
2950
|
const mergedPackTags = mergeUniqueTags(decoratedClassification?.tags || [], packMetadata.tags);
|
|
2746
|
-
const
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2951
|
+
const snapshot = snapshotByPackId.get(pack.id);
|
|
2952
|
+
const signals = snapshot?.signals
|
|
2953
|
+
? {
|
|
2954
|
+
...snapshot.signals,
|
|
2955
|
+
ranking_score: Number(snapshot?.signals?.ranking_score || 0),
|
|
2956
|
+
pack_score: Number(snapshot?.signals?.pack_score || 0),
|
|
2957
|
+
trend_score: Number(snapshot?.signals?.trend_score || 0),
|
|
2958
|
+
nsfw_level: String(snapshot?.signals?.nsfw_level || 'safe'),
|
|
2959
|
+
sensitive_content: Boolean(snapshot?.signals?.sensitive_content),
|
|
2960
|
+
}
|
|
2961
|
+
: computePackSignals({
|
|
2962
|
+
pack: { ...pack, items },
|
|
2963
|
+
engagement,
|
|
2964
|
+
packClassification,
|
|
2965
|
+
itemClassifications: orderedClassifications,
|
|
2966
|
+
interactionStats,
|
|
2967
|
+
scoringWeights: driftSnapshot?.weights || null,
|
|
2968
|
+
});
|
|
2754
2969
|
|
|
2755
2970
|
const entry = {
|
|
2756
2971
|
pack,
|
|
@@ -3274,9 +3489,13 @@ const handleSitemapRequest = async (req, res) => {
|
|
|
3274
3489
|
const sendStaticTextFile = async (req, res, filePath, contentType) => {
|
|
3275
3490
|
try {
|
|
3276
3491
|
const body = await fs.readFile(filePath, 'utf8');
|
|
3492
|
+
const hasVersionQuery = /(?:\?|&)v=/.test(String(req.url || ''));
|
|
3493
|
+
const cacheControl = hasVersionQuery
|
|
3494
|
+
? `public, max-age=${IMMUTABLE_ASSET_CACHE_SECONDS}, immutable`
|
|
3495
|
+
: `public, max-age=${STATIC_TEXT_CACHE_SECONDS}, stale-while-revalidate=${Math.min(86400, STATIC_TEXT_CACHE_SECONDS * 4)}`;
|
|
3277
3496
|
res.statusCode = 200;
|
|
3278
3497
|
res.setHeader('Content-Type', contentType);
|
|
3279
|
-
res.setHeader('Cache-Control',
|
|
3498
|
+
res.setHeader('Cache-Control', cacheControl);
|
|
3280
3499
|
if (req.method === 'HEAD') {
|
|
3281
3500
|
res.end();
|
|
3282
3501
|
return true;
|
|
@@ -3300,11 +3519,11 @@ const sendStaticTextFile = async (req, res, filePath, contentType) => {
|
|
|
3300
3519
|
};
|
|
3301
3520
|
|
|
3302
3521
|
const handleCatalogStaticAssetRequest = async (req, res, pathname) => {
|
|
3303
|
-
if (pathname ===
|
|
3522
|
+
if (pathname === CATALOG_STYLES_WEB_PATH) {
|
|
3304
3523
|
return sendStaticTextFile(req, res, CATALOG_STYLES_FILE_PATH, 'text/css; charset=utf-8');
|
|
3305
3524
|
}
|
|
3306
3525
|
|
|
3307
|
-
if (pathname ===
|
|
3526
|
+
if (pathname === CATALOG_SCRIPT_WEB_PATH) {
|
|
3308
3527
|
return sendStaticTextFile(req, res, CATALOG_SCRIPT_FILE_PATH, 'application/javascript; charset=utf-8');
|
|
3309
3528
|
}
|
|
3310
3529
|
|
|
@@ -3321,95 +3540,118 @@ const handleListRequest = async (req, res, url) => {
|
|
|
3321
3540
|
const limit = clampInt(url.searchParams.get('limit'), DEFAULT_LIST_LIMIT, 1, MAX_LIST_LIMIT);
|
|
3322
3541
|
const offset = clampInt(url.searchParams.get('offset'), 0, 0, 100000);
|
|
3323
3542
|
const normalizedIntent = normalizeCategoryToken(intent).replace(/-/g, '_');
|
|
3324
|
-
const batchLimit = Math.max(limit, Math.min(MAX_LIST_LIMIT, 24));
|
|
3325
|
-
const maxPagesToScan = 8;
|
|
3326
3543
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
3327
3544
|
const hasNsfwAccess = Boolean(googleSession?.sub && googleSession?.ownerJid);
|
|
3328
|
-
const
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3545
|
+
const cacheKey = buildCacheKey([
|
|
3546
|
+
'list',
|
|
3547
|
+
q,
|
|
3548
|
+
visibility,
|
|
3549
|
+
sort,
|
|
3550
|
+
categories.join(','),
|
|
3551
|
+
normalizedIntent,
|
|
3552
|
+
includeSensitive ? 1 : 0,
|
|
3553
|
+
limit,
|
|
3554
|
+
offset,
|
|
3555
|
+
hasNsfwAccess ? 1 : 0,
|
|
3556
|
+
]);
|
|
3557
|
+
const payload = await getCachedSnapshot({
|
|
3558
|
+
cacheMap: CATALOG_LIST_CACHE,
|
|
3559
|
+
key: cacheKey,
|
|
3560
|
+
ttlSeconds: CATALOG_LIST_CACHE_SECONDS,
|
|
3561
|
+
staleWhileRefresh: true,
|
|
3562
|
+
staleOnError: true,
|
|
3563
|
+
load: async () => {
|
|
3564
|
+
const batchLimit = Math.max(limit, Math.min(MAX_LIST_LIMIT, 24));
|
|
3565
|
+
const maxPagesToScan = 8;
|
|
3566
|
+
const seenPackIds = new Set();
|
|
3567
|
+
const collectedEntries = [];
|
|
3568
|
+
const driftSnapshot = await getMarketplaceDriftSnapshot();
|
|
3569
|
+
let sourceHasMore = true;
|
|
3570
|
+
let cursorOffset = offset;
|
|
3571
|
+
let pagesScanned = 0;
|
|
3572
|
+
|
|
3573
|
+
while (collectedEntries.length < limit && sourceHasMore && pagesScanned < maxPagesToScan) {
|
|
3574
|
+
pagesScanned += 1;
|
|
3575
|
+
const { packs, hasMore } = await listStickerPacksForCatalog({
|
|
3576
|
+
visibility,
|
|
3577
|
+
search: q,
|
|
3578
|
+
limit: batchLimit,
|
|
3579
|
+
offset: cursorOffset,
|
|
3580
|
+
});
|
|
3581
|
+
sourceHasMore = hasMore;
|
|
3582
|
+
cursorOffset += batchLimit;
|
|
3583
|
+
if (!packs.length) break;
|
|
3584
|
+
|
|
3585
|
+
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
3586
|
+
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
3587
|
+
? entries.filter((entry) => isPackClassified(entry.packClassification))
|
|
3588
|
+
: entries;
|
|
3589
|
+
const entriesByCategory = categories.length
|
|
3590
|
+
? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories))
|
|
3591
|
+
: entriesClassified;
|
|
3592
|
+
const entriesBySensitivity = includeSensitive
|
|
3593
|
+
? entriesByCategory
|
|
3594
|
+
: entriesByCategory.filter((entry) => entry.signals?.nsfw_level === 'safe');
|
|
3595
|
+
const entriesByIntent = intent
|
|
3596
|
+
? entriesBySensitivity.filter((entry) => classifyPackIntent(entry) === normalizedIntent)
|
|
3597
|
+
: entriesBySensitivity;
|
|
3598
|
+
const sortedEntries = [...entriesByIntent].sort((left, right) => {
|
|
3599
|
+
const completenessDelta = compareEntriesByPackCompleteness(left, right);
|
|
3600
|
+
if (completenessDelta !== 0) return completenessDelta;
|
|
3601
|
+
if (sort === 'recent') {
|
|
3602
|
+
return Date.parse(right?.pack?.created_at || right?.pack?.updated_at || 0) - Date.parse(left?.pack?.created_at || left?.pack?.updated_at || 0);
|
|
3603
|
+
}
|
|
3604
|
+
if (sort === 'likes') {
|
|
3605
|
+
return Number(right?.engagement?.like_count || 0) - Number(left?.engagement?.like_count || 0);
|
|
3606
|
+
}
|
|
3607
|
+
if (sort === 'downloads') {
|
|
3608
|
+
return Number(right?.engagement?.open_count || 0) - Number(left?.engagement?.open_count || 0);
|
|
3609
|
+
}
|
|
3610
|
+
if (sort === 'comments') {
|
|
3611
|
+
const commentDelta = Number(right?.engagement?.comment_count || 0) - Number(left?.engagement?.comment_count || 0);
|
|
3612
|
+
if (commentDelta !== 0) return commentDelta;
|
|
3613
|
+
return Number(right?.engagement?.like_count || 0) - Number(left?.engagement?.like_count || 0);
|
|
3614
|
+
}
|
|
3615
|
+
if (sort === 'trending') {
|
|
3616
|
+
const trendDelta = Number(right?.signals?.trend_score || 0) - Number(left?.signals?.trend_score || 0);
|
|
3617
|
+
if (trendDelta !== 0) return trendDelta;
|
|
3618
|
+
}
|
|
3619
|
+
const leftScore = Number(left?.signals?.ranking_score || 0);
|
|
3620
|
+
const rightScore = Number(right?.signals?.ranking_score || 0);
|
|
3621
|
+
if (rightScore !== leftScore) return rightScore - leftScore;
|
|
3622
|
+
return Date.parse(right?.pack?.updated_at || 0) - Date.parse(left?.pack?.updated_at || 0);
|
|
3623
|
+
});
|
|
3334
3624
|
|
|
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;
|
|
3625
|
+
for (const entry of sortedEntries) {
|
|
3626
|
+
if (!entry?.pack?.id) continue;
|
|
3627
|
+
if (seenPackIds.has(entry.pack.id)) continue;
|
|
3628
|
+
seenPackIds.add(entry.pack.id);
|
|
3629
|
+
collectedEntries.push(entry);
|
|
3630
|
+
if (collectedEntries.length >= limit) break;
|
|
3631
|
+
}
|
|
3380
3632
|
}
|
|
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
|
-
|
|
3387
|
-
for (const entry of sortedEntries) {
|
|
3388
|
-
if (!entry?.pack?.id) continue;
|
|
3389
|
-
if (seenPackIds.has(entry.pack.id)) continue;
|
|
3390
|
-
seenPackIds.add(entry.pack.id);
|
|
3391
|
-
collectedEntries.push(entry);
|
|
3392
|
-
if (collectedEntries.length >= limit) break;
|
|
3393
|
-
}
|
|
3394
|
-
}
|
|
3395
3633
|
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3634
|
+
return {
|
|
3635
|
+
data: collectedEntries.map((entry) => toSummaryEntry(entry, { hideSensitiveCover: !hasNsfwAccess })),
|
|
3636
|
+
pagination: {
|
|
3637
|
+
limit,
|
|
3638
|
+
offset,
|
|
3639
|
+
has_more: sourceHasMore,
|
|
3640
|
+
next_offset: sourceHasMore ? cursorOffset : null,
|
|
3641
|
+
},
|
|
3642
|
+
filters: {
|
|
3643
|
+
q,
|
|
3644
|
+
visibility,
|
|
3645
|
+
sort,
|
|
3646
|
+
categories,
|
|
3647
|
+
intent: intent || null,
|
|
3648
|
+
include_sensitive: includeSensitive,
|
|
3649
|
+
},
|
|
3650
|
+
};
|
|
3411
3651
|
},
|
|
3412
3652
|
});
|
|
3653
|
+
|
|
3654
|
+
sendJson(req, res, 200, payload);
|
|
3413
3655
|
};
|
|
3414
3656
|
|
|
3415
3657
|
const handleIntentCollectionsRequest = async (req, res, url) => {
|
|
@@ -3647,9 +3889,9 @@ const handleGoogleAuthSessionRequest = async (req, res) => {
|
|
|
3647
3889
|
}
|
|
3648
3890
|
|
|
3649
3891
|
if (req.method === 'DELETE') {
|
|
3650
|
-
const
|
|
3651
|
-
|
|
3652
|
-
|
|
3892
|
+
const tokens = getGoogleWebSessionTokensFromRequest(req);
|
|
3893
|
+
for (const token of tokens) {
|
|
3894
|
+
webGoogleSessionMap.delete(token);
|
|
3653
3895
|
await deleteGoogleWebSessionFromDb(token).catch((error) => {
|
|
3654
3896
|
logger.warn('Falha ao remover sessão Google web do banco.', {
|
|
3655
3897
|
action: 'sticker_pack_google_web_session_db_delete_failed',
|
|
@@ -4090,6 +4332,9 @@ const invalidateStickerCatalogDerivedCaches = () => {
|
|
|
4090
4332
|
GLOBAL_RANK_CACHE.value = null;
|
|
4091
4333
|
GLOBAL_RANK_CACHE.pending = null;
|
|
4092
4334
|
HOME_MARKETPLACE_STATS_CACHE.clear();
|
|
4335
|
+
CATALOG_LIST_CACHE.clear();
|
|
4336
|
+
CATALOG_CREATOR_RANKING_CACHE.clear();
|
|
4337
|
+
CATALOG_PACK_PAYLOAD_CACHE.clear();
|
|
4093
4338
|
SYSTEM_SUMMARY_CACHE.expiresAt = 0;
|
|
4094
4339
|
SYSTEM_SUMMARY_CACHE.value = null;
|
|
4095
4340
|
SYSTEM_SUMMARY_CACHE.pending = null;
|
|
@@ -5753,46 +5998,63 @@ const handleCreatorRankingRequest = async (req, res, url) => {
|
|
|
5753
5998
|
const limit = clampInt(url.searchParams.get('limit'), 50, 5, 200);
|
|
5754
5999
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
5755
6000
|
const hasNsfwAccess = Boolean(googleSession?.sub && googleSession?.ownerJid);
|
|
5756
|
-
|
|
5757
|
-
|
|
6001
|
+
const cacheKey = buildCacheKey([
|
|
6002
|
+
'creator_ranking',
|
|
5758
6003
|
visibility,
|
|
5759
|
-
|
|
5760
|
-
limit
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
const
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
6004
|
+
q,
|
|
6005
|
+
limit,
|
|
6006
|
+
hasNsfwAccess ? 1 : 0,
|
|
6007
|
+
]);
|
|
6008
|
+
const payload = await getCachedSnapshot({
|
|
6009
|
+
cacheMap: CATALOG_CREATOR_RANKING_CACHE,
|
|
6010
|
+
key: cacheKey,
|
|
6011
|
+
ttlSeconds: CATALOG_CREATOR_RANKING_CACHE_SECONDS,
|
|
6012
|
+
staleWhileRefresh: true,
|
|
6013
|
+
staleOnError: true,
|
|
6014
|
+
load: async () => {
|
|
6015
|
+
const { packs } = await listStickerPacksForCatalog({
|
|
6016
|
+
visibility,
|
|
6017
|
+
search: q,
|
|
6018
|
+
limit: 120,
|
|
6019
|
+
offset: 0,
|
|
6020
|
+
});
|
|
6021
|
+
const driftSnapshot = await getMarketplaceDriftSnapshot();
|
|
6022
|
+
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
6023
|
+
const ranking = buildCreatorRanking(
|
|
6024
|
+
STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries,
|
|
6025
|
+
{ limit },
|
|
6026
|
+
);
|
|
5769
6027
|
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
6028
|
+
return {
|
|
6029
|
+
data: ranking.map((creator) => ({
|
|
6030
|
+
creator_score: Number(
|
|
6031
|
+
(
|
|
6032
|
+
Number(creator.avg_pack_score || 0) * 0.45 +
|
|
6033
|
+
Number(creator.total_likes || 0) * 0.0008 +
|
|
6034
|
+
Number(creator.total_opens || 0) * 0.00015
|
|
6035
|
+
).toFixed(6),
|
|
6036
|
+
),
|
|
6037
|
+
publisher: creator.publisher,
|
|
6038
|
+
verified: Boolean(creator.verified),
|
|
6039
|
+
badges: creator.verified ? ['verified_creator'] : [],
|
|
6040
|
+
stats: {
|
|
6041
|
+
packs_count: Number(creator.packs_count || 0),
|
|
6042
|
+
total_likes: Number(creator.total_likes || 0),
|
|
6043
|
+
total_opens: Number(creator.total_opens || 0),
|
|
6044
|
+
avg_pack_score: Number(creator.avg_pack_score || 0),
|
|
6045
|
+
},
|
|
6046
|
+
top_pack: creator.top_pack ? toSummaryEntry(creator.top_pack, { hideSensitiveCover: !hasNsfwAccess }) : null,
|
|
6047
|
+
})),
|
|
6048
|
+
filters: {
|
|
6049
|
+
visibility,
|
|
6050
|
+
q,
|
|
6051
|
+
limit,
|
|
6052
|
+
},
|
|
6053
|
+
};
|
|
5794
6054
|
},
|
|
5795
6055
|
});
|
|
6056
|
+
|
|
6057
|
+
sendJson(req, res, 200, payload);
|
|
5796
6058
|
};
|
|
5797
6059
|
|
|
5798
6060
|
const handleRecommendationsRequest = async (req, res, url) => {
|
|
@@ -6934,42 +7196,60 @@ const handlePublicDataAssetRequest = async (req, res, pathname) => {
|
|
|
6934
7196
|
};
|
|
6935
7197
|
|
|
6936
7198
|
const fetchPublicPackPayload = async (normalizedPackKey) => {
|
|
6937
|
-
const
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
7199
|
+
const cacheKey = buildCacheKey(['pack_payload', normalizedPackKey]);
|
|
7200
|
+
return getCachedSnapshot({
|
|
7201
|
+
cacheMap: CATALOG_PACK_PAYLOAD_CACHE,
|
|
7202
|
+
key: cacheKey,
|
|
7203
|
+
ttlSeconds: CATALOG_PACK_PAYLOAD_CACHE_SECONDS,
|
|
7204
|
+
staleWhileRefresh: true,
|
|
7205
|
+
staleOnError: true,
|
|
7206
|
+
load: async () => {
|
|
7207
|
+
const pack = await findStickerPackByPackKey(normalizedPackKey);
|
|
7208
|
+
if (!pack || !isPackPubliclyVisible(pack)) return null;
|
|
7209
|
+
|
|
7210
|
+
const items = await listStickerPackItems(pack.id);
|
|
7211
|
+
const stickerIds = items.map((item) => item.sticker_id);
|
|
7212
|
+
const [classifications, packClassification, engagement] = await Promise.all([
|
|
7213
|
+
listStickerClassificationsByAssetIds(stickerIds),
|
|
7214
|
+
getPackClassificationSummaryByAssetIds(stickerIds),
|
|
7215
|
+
getStickerPackEngagementByPackId(pack.id),
|
|
7216
|
+
]);
|
|
7217
|
+
|
|
7218
|
+
if (STICKER_CATALOG_ONLY_CLASSIFIED && !isPackClassified(packClassification)) {
|
|
7219
|
+
return null;
|
|
7220
|
+
}
|
|
6947
7221
|
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
7222
|
+
const [interactionStatsByPack, driftSnapshot, snapshotByPackId] = await Promise.all([
|
|
7223
|
+
listStickerPackInteractionStatsByPackIds([pack.id]),
|
|
7224
|
+
getMarketplaceDriftSnapshot(),
|
|
7225
|
+
canUseRankingSnapshotRead(`pack_payload:${pack.id}`)
|
|
7226
|
+
.then((enabled) => (enabled ? listStickerPackScoreSnapshotsByPackIds([pack.id]) : new Map()))
|
|
7227
|
+
.catch(() => new Map()),
|
|
7228
|
+
]);
|
|
7229
|
+
const byAssetClassification = new Map(classifications.map((entry) => [entry.asset_id, entry]));
|
|
7230
|
+
const orderedClassifications = stickerIds.map((stickerId) => byAssetClassification.get(stickerId)).filter(Boolean);
|
|
7231
|
+
const snapshot = snapshotByPackId.get(pack.id);
|
|
7232
|
+
const signals = snapshot?.signals
|
|
7233
|
+
? snapshot.signals
|
|
7234
|
+
: computePackSignals({
|
|
7235
|
+
pack: { ...pack, items },
|
|
7236
|
+
engagement,
|
|
7237
|
+
packClassification,
|
|
7238
|
+
itemClassifications: orderedClassifications,
|
|
7239
|
+
interactionStats: interactionStatsByPack.get(pack.id) || null,
|
|
7240
|
+
scoringWeights: driftSnapshot.weights,
|
|
7241
|
+
});
|
|
6951
7242
|
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
interactionStats: interactionStatsByPack.get(pack.id) || null,
|
|
6962
|
-
scoringWeights: driftSnapshot.weights,
|
|
7243
|
+
return {
|
|
7244
|
+
pack,
|
|
7245
|
+
items,
|
|
7246
|
+
byAssetClassification,
|
|
7247
|
+
packClassification,
|
|
7248
|
+
engagement,
|
|
7249
|
+
signals,
|
|
7250
|
+
};
|
|
7251
|
+
},
|
|
6963
7252
|
});
|
|
6964
|
-
|
|
6965
|
-
return {
|
|
6966
|
-
pack,
|
|
6967
|
-
items,
|
|
6968
|
-
byAssetClassification,
|
|
6969
|
-
packClassification,
|
|
6970
|
-
engagement,
|
|
6971
|
-
signals,
|
|
6972
|
-
};
|
|
6973
7253
|
};
|
|
6974
7254
|
|
|
6975
7255
|
const handleDetailsRequest = async (req, res, packKey, url) => {
|
|
@@ -7060,6 +7340,23 @@ const handleAssetRequest = async (req, res, packKey, stickerToken) => {
|
|
|
7060
7340
|
return;
|
|
7061
7341
|
}
|
|
7062
7342
|
}
|
|
7343
|
+
|
|
7344
|
+
const externalAssetUrl = await getStickerAssetExternalUrl(item.asset, {
|
|
7345
|
+
secure: true,
|
|
7346
|
+
expiresInSeconds: Math.max(60, Math.min(3600, Number(process.env.STICKER_OBJECT_STORAGE_SIGNED_URL_TTL_SECONDS) || 300)),
|
|
7347
|
+
}).catch(() => null);
|
|
7348
|
+
if (externalAssetUrl) {
|
|
7349
|
+
res.statusCode = 302;
|
|
7350
|
+
res.setHeader('Location', externalAssetUrl);
|
|
7351
|
+
res.setHeader('Cache-Control', 'private, max-age=45');
|
|
7352
|
+
if (req.method === 'HEAD') {
|
|
7353
|
+
res.end();
|
|
7354
|
+
return;
|
|
7355
|
+
}
|
|
7356
|
+
res.end();
|
|
7357
|
+
return;
|
|
7358
|
+
}
|
|
7359
|
+
|
|
7063
7360
|
if (decorated) {
|
|
7064
7361
|
res.setHeader('X-Sticker-Category', String(decorated?.category || 'unknown'));
|
|
7065
7362
|
res.setHeader('X-Sticker-NSFW', decorated?.is_nsfw ? '1' : '0');
|
|
@@ -7667,323 +7964,60 @@ const handleAdminBanRevokeRequest = async (req, res, banId) => {
|
|
|
7667
7964
|
}
|
|
7668
7965
|
};
|
|
7669
7966
|
|
|
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
|
-
}
|
|
7967
|
+
const catalogApiRouter = createCatalogApiRouter({
|
|
7968
|
+
apiBasePath: STICKER_API_BASE_PATH,
|
|
7969
|
+
orphanApiPath: STICKER_ORPHAN_API_PATH,
|
|
7970
|
+
sendJson,
|
|
7971
|
+
handlers: {
|
|
7972
|
+
handleCreatePackRequest,
|
|
7973
|
+
handleGoogleAuthSessionRequest,
|
|
7974
|
+
handleMyProfileRequest,
|
|
7975
|
+
handleAdminPanelSessionRequest,
|
|
7976
|
+
handleListRequest,
|
|
7977
|
+
handleIntentCollectionsRequest,
|
|
7978
|
+
handleCreatorRankingRequest,
|
|
7979
|
+
handleRecommendationsRequest,
|
|
7980
|
+
handleMarketplaceStatsRequest,
|
|
7981
|
+
handleCreatePackConfigRequest,
|
|
7982
|
+
handleOrphanStickerListRequest,
|
|
7983
|
+
handleDataFileListRequest,
|
|
7984
|
+
handleSystemSummaryRequest,
|
|
7985
|
+
handleGitHubProjectSummaryRequest,
|
|
7986
|
+
handleGlobalRankingSummaryRequest,
|
|
7987
|
+
handleReadmeSummaryRequest,
|
|
7988
|
+
handleReadmeMarkdownRequest,
|
|
7989
|
+
handleSupportInfoRequest,
|
|
7990
|
+
handleBotContactInfoRequest,
|
|
7991
|
+
handleAdminOverviewRequest,
|
|
7992
|
+
handleAdminUsersRequest,
|
|
7993
|
+
handleAdminModeratorsRequest,
|
|
7994
|
+
handleAdminModeratorDeleteRequest,
|
|
7995
|
+
handleAdminPacksRequest,
|
|
7996
|
+
handleAdminPackDetailsRequest,
|
|
7997
|
+
handleAdminPackDeleteRequest,
|
|
7998
|
+
handleAdminPackStickerDeleteRequest,
|
|
7999
|
+
handleAdminGlobalStickerDeleteRequest,
|
|
8000
|
+
handleAdminBansRequest,
|
|
8001
|
+
handleAdminBanRevokeRequest,
|
|
8002
|
+
handleDetailsRequest,
|
|
8003
|
+
handlePackInteractionRequest,
|
|
8004
|
+
handleManagedPackRequest,
|
|
8005
|
+
handleManagedPackCloneRequest,
|
|
8006
|
+
handleManagedPackCoverRequest,
|
|
8007
|
+
handleManagedPackReorderRequest,
|
|
8008
|
+
handleManagedPackAnalyticsRequest,
|
|
8009
|
+
handleManagedPackStickerCreateRequest,
|
|
8010
|
+
handleManagedPackStickerDeleteRequest,
|
|
8011
|
+
handleManagedPackStickerReplaceRequest,
|
|
8012
|
+
handlePackPublishStateRequest,
|
|
8013
|
+
handleFinalizePackRequest,
|
|
8014
|
+
handleUploadStickerToPackRequest,
|
|
8015
|
+
handleAssetRequest,
|
|
8016
|
+
},
|
|
8017
|
+
});
|
|
7983
8018
|
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
};
|
|
8019
|
+
const handleCatalogApiRequest = async (req, res, pathname, url) =>
|
|
8020
|
+
catalogApiRouter({ req, res, pathname, url });
|
|
7987
8021
|
|
|
7988
8022
|
const handleCatalogPageRequest = async (req, res, pathname) => {
|
|
7989
8023
|
const normalizedPath = pathname.length > 1 ? pathname.replace(/\/+$/, '') : pathname;
|