@kaikybrofc/omnizap-system 2.2.10 → 2.3.1
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/README.md +13 -13
- package/app/config/adminIdentity.js +1 -3
- package/app/connection/socketController.js +10 -20
- package/app/controllers/messageController.js +7 -28
- package/app/modules/aiModule/catCommand.js +29 -192
- package/app/modules/broadcastModule/noticeCommand.js +28 -97
- package/app/modules/gameModule/diceCommand.js +6 -32
- package/app/modules/playModule/playCommand.js +57 -258
- package/app/modules/quoteModule/quoteCommand.js +2 -4
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1 -13
- package/app/modules/statsModule/noMessageCommand.js +16 -84
- package/app/modules/statsModule/rankingCommand.js +5 -25
- package/app/modules/statsModule/rankingCommon.js +1 -9
- package/app/modules/stickerModule/convertToWebp.js +4 -27
- package/app/modules/stickerModule/stickerCommand.js +13 -24
- package/app/modules/stickerModule/stickerTextCommand.js +13 -25
- package/app/modules/stickerPackModule/autoPackCollectorService.js +16 -7
- package/app/modules/stickerPackModule/domainEventOutboxRepository.js +20 -36
- package/app/modules/stickerPackModule/domainEvents.js +2 -11
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +13 -50
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +2 -15
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +14 -41
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +25 -95
- package/app/modules/stickerPackModule/stickerAssetRepository.js +12 -31
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +13 -18
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +284 -709
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +27 -106
- package/app/modules/stickerPackModule/stickerClassificationService.js +46 -77
- package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +13 -53
- package/app/modules/stickerPackModule/stickerDomainEventBus.js +10 -16
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +40 -39
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +1 -4
- package/app/modules/stickerPackModule/stickerObjectStorageService.js +26 -26
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +32 -187
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +6 -15
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +6 -32
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +12 -36
- package/app/modules/stickerPackModule/stickerPackMessageService.js +12 -40
- package/app/modules/stickerPackModule/stickerPackRepository.js +23 -66
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +9 -21
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +10 -40
- package/app/modules/stickerPackModule/stickerPackService.js +50 -115
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +2 -21
- package/app/modules/stickerPackModule/stickerPackUtils.js +13 -3
- package/app/modules/stickerPackModule/stickerStorageService.js +16 -65
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +4 -22
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +14 -29
- package/app/modules/systemMetricsModule/pingCommand.js +9 -39
- package/app/modules/tiktokModule/tiktokCommand.js +17 -109
- package/app/modules/userModule/userCommand.js +2 -88
- package/app/observability/metrics.js +5 -16
- package/app/services/captchaService.js +1 -6
- package/app/services/dbWriteQueue.js +3 -18
- package/app/services/featureFlagService.js +2 -8
- package/app/services/newsBroadcastService.js +0 -1
- package/app/services/queueUtils.js +2 -4
- package/app/services/whatsappLoginLinkService.js +7 -9
- package/app/store/premiumUserStore.js +1 -2
- package/app/utils/antiLink/antiLinkModule.js +3 -233
- package/app/utils/logger/loggerModule.js +9 -34
- package/app/utils/systemMetrics/systemMetricsModule.js +1 -4
- package/database/index.js +1 -0
- package/database/init.js +1 -8
- package/database/migrations/20260228_0027_web_visit_event.sql +15 -0
- package/docker-compose.yml +27 -27
- package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +26 -0
- package/docs/seo/satellite-page-template.md +2 -0
- package/docs/seo/satellite-pages-phase1.json +40 -177
- package/eslint.config.js +2 -15
- package/index.js +8 -36
- package/ml/clip_classifier/README.md +4 -6
- package/observability/alert-rules.yml +12 -12
- package/observability/grafana/provisioning/dashboards/dashboards.yml +1 -1
- package/package.json +6 -3
- package/public/api-docs/index.html +220 -193
- package/public/bot-whatsapp-para-grupo/index.html +291 -261
- package/public/bot-whatsapp-sem-programar/index.html +291 -261
- package/public/comandos/index.html +421 -406
- package/public/como-automatizar-avisos-no-whatsapp/index.html +291 -261
- package/public/como-criar-comandos-whatsapp/index.html +291 -261
- package/public/como-evitar-spam-no-whatsapp/index.html +291 -261
- package/public/como-moderar-grupo-whatsapp/index.html +291 -261
- package/public/como-organizar-comunidade-whatsapp/index.html +291 -261
- package/public/css/github-project-panel.css +13 -8
- package/public/css/stickers-admin.css +25 -9
- package/public/css/styles.css +23 -16
- package/public/index.html +1106 -993
- package/public/js/apps/apiDocsApp.js +17 -167
- package/public/js/apps/createPackApp.js +69 -332
- package/public/js/apps/homeApp.js +274 -101
- package/public/js/apps/loginApp.js +3 -12
- package/public/js/apps/stickersAdminApp.js +190 -181
- package/public/js/apps/stickersApp.js +482 -1411
- package/public/js/apps/userApp.js +217 -1
- package/public/js/catalog.js +11 -74
- package/public/js/github-panel/components/ErrorState.js +1 -8
- package/public/js/github-panel/components/GithubProjectPanel.js +2 -9
- package/public/js/github-panel/components/SkeletonPanel.js +1 -11
- package/public/js/github-panel/components/StatCard.js +1 -7
- package/public/js/github-panel/vendor/react.js +1 -9
- package/public/js/runtime/react-runtime.js +1 -9
- package/public/licenca/index.html +200 -86
- package/public/login/index.html +315 -325
- package/public/melhor-bot-whatsapp-para-grupos/index.html +291 -261
- package/public/stickers/admin/index.html +14 -19
- package/public/stickers/create/index.html +39 -44
- package/public/stickers/index.html +96 -107
- package/public/termos-de-uso/index.html +369 -122
- package/public/user/index.html +527 -350
- package/scripts/cache-bust.mjs +5 -24
- package/scripts/generate-seo-satellite-pages.mjs +10 -13
- package/scripts/run-prettier-all.mjs +25 -0
- package/scripts/sticker-catalog-loadtest.mjs +13 -11
- package/scripts/sticker-worker-task.mjs +1 -4
- package/scripts/sync-readme-snapshot.mjs +3 -2
- package/server/auth/googleWebAuth/googleWebAuthService.js +614 -0
- package/server/controllers/stickerCatalogController.js +297 -632
- package/server/http/httpServer.js +2 -10
- package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +1 -8
- package/server/routes/stickerCatalog/catalogHandlers/catalogAuthHttp.js +1 -9
- package/server/routes/stickerCatalog/catalogHandlers/catalogPublicHttp.js +10 -11
- package/server/routes/stickerCatalog/catalogHandlers/catalogUploadHttp.js +1 -10
- package/server/routes/stickerCatalog/catalogRouter.js +11 -13
|
@@ -92,7 +92,10 @@ const includesToken = (parts, token) => {
|
|
|
92
92
|
|
|
93
93
|
const isAdminAuthenticated = () => Boolean(state.adminStatus?.session?.authenticated);
|
|
94
94
|
const canUnlockAdmin = () => Boolean(state.adminStatus?.eligible_google_login);
|
|
95
|
-
const getAdminRole = () =>
|
|
95
|
+
const getAdminRole = () =>
|
|
96
|
+
String(state.adminStatus?.session?.role || '')
|
|
97
|
+
.trim()
|
|
98
|
+
.toLowerCase();
|
|
96
99
|
const canManageModerators = () => Boolean(state.adminStatus?.session?.capabilities?.can_manage_moderators || getAdminRole() === 'owner');
|
|
97
100
|
|
|
98
101
|
function setToast(type, message, timeoutMs = 3200) {
|
|
@@ -250,7 +253,9 @@ function renderPagination(key, page) {
|
|
|
250
253
|
}
|
|
251
254
|
|
|
252
255
|
function toneClassForStatus(status) {
|
|
253
|
-
const normalized = String(status || '')
|
|
256
|
+
const normalized = String(status || '')
|
|
257
|
+
.trim()
|
|
258
|
+
.toLowerCase();
|
|
254
259
|
if (['published', 'ready', 'done', 'online', 'active'].includes(normalized)) return 'status-success';
|
|
255
260
|
if (['failed', 'error', 'deleted'].includes(normalized)) return 'status-danger';
|
|
256
261
|
if (['uploading', 'processing', 'pending', 'draft'].includes(normalized)) return 'status-warning';
|
|
@@ -373,9 +378,7 @@ function renderUsersTab() {
|
|
|
373
378
|
const { activeSessions, users } = getOverviewData();
|
|
374
379
|
const token = normalizeToken(state.usersQuery);
|
|
375
380
|
|
|
376
|
-
const filteredSessions = activeSessions.filter((row) =>
|
|
377
|
-
includesToken([row?.name, row?.email, row?.owner_jid, row?.google_sub], token),
|
|
378
|
-
);
|
|
381
|
+
const filteredSessions = activeSessions.filter((row) => includesToken([row?.name, row?.email, row?.owner_jid, row?.google_sub], token));
|
|
379
382
|
const filteredUsers = users.filter((row) => includesToken([row?.name, row?.email, row?.owner_jid, row?.google_sub], token));
|
|
380
383
|
|
|
381
384
|
const sessionsPage = paginate(filteredSessions, 'sessions');
|
|
@@ -383,13 +386,7 @@ function renderUsersTab() {
|
|
|
383
386
|
|
|
384
387
|
const sessionRowsDesktop = sessionsPage.items
|
|
385
388
|
.map((row) => {
|
|
386
|
-
const menu = renderMenuWrap(
|
|
387
|
-
'session',
|
|
388
|
-
row?.session_token || row?.google_sub || row?.email || Math.random(),
|
|
389
|
-
`<button class="row-menu-item danger" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(
|
|
390
|
-
row?.google_sub || '',
|
|
391
|
-
)}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir usuario</button>`,
|
|
392
|
-
);
|
|
389
|
+
const menu = renderMenuWrap('session', row?.session_token || row?.google_sub || row?.email || Math.random(), `<button class="row-menu-item danger" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(row?.google_sub || '')}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir usuario</button>`);
|
|
393
390
|
return `
|
|
394
391
|
<tr>
|
|
395
392
|
<td>
|
|
@@ -406,13 +403,7 @@ function renderUsersTab() {
|
|
|
406
403
|
|
|
407
404
|
const usersRowsDesktop = usersPage.items
|
|
408
405
|
.map((row) => {
|
|
409
|
-
const menu = renderMenuWrap(
|
|
410
|
-
'user',
|
|
411
|
-
row?.google_sub || row?.email || row?.owner_jid || Math.random(),
|
|
412
|
-
`<button class="row-menu-item danger" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(
|
|
413
|
-
row?.google_sub || '',
|
|
414
|
-
)}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir usuario</button>`,
|
|
415
|
-
);
|
|
406
|
+
const menu = renderMenuWrap('user', row?.google_sub || row?.email || row?.owner_jid || Math.random(), `<button class="row-menu-item danger" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(row?.google_sub || '')}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir usuario</button>`);
|
|
416
407
|
return `
|
|
417
408
|
<tr>
|
|
418
409
|
<td>
|
|
@@ -437,9 +428,7 @@ function renderUsersTab() {
|
|
|
437
428
|
<p class="row-meta break-all">${escapeHtml(row?.owner_jid || '')}</p>
|
|
438
429
|
<div class="mobile-card-foot">
|
|
439
430
|
<span class="muted">${escapeHtml(fmtDate(row?.last_seen_at || row?.created_at))}</span>
|
|
440
|
-
<button class="danger-btn" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(
|
|
441
|
-
row?.google_sub || '',
|
|
442
|
-
)}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir</button>
|
|
431
|
+
<button class="danger-btn" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(row?.google_sub || '')}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir</button>
|
|
443
432
|
</div>
|
|
444
433
|
</article>
|
|
445
434
|
`,
|
|
@@ -456,9 +445,7 @@ function renderUsersTab() {
|
|
|
456
445
|
<p class="row-meta break-all mono">${escapeHtml(row?.google_sub || '')}</p>
|
|
457
446
|
<div class="mobile-card-foot">
|
|
458
447
|
<span class="muted">${escapeHtml(fmtDate(row?.last_login_at || row?.last_seen_at || row?.updated_at))}</span>
|
|
459
|
-
<button class="danger-btn" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(
|
|
460
|
-
row?.google_sub || '',
|
|
461
|
-
)}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir</button>
|
|
448
|
+
<button class="danger-btn" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(row?.google_sub || '')}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir</button>
|
|
462
449
|
</div>
|
|
463
450
|
</article>
|
|
464
451
|
`,
|
|
@@ -529,11 +516,7 @@ function renderPacksTab() {
|
|
|
529
516
|
|
|
530
517
|
const packRowsDesktop = packsPage.items
|
|
531
518
|
.map((pack) => {
|
|
532
|
-
const statusBadges = [
|
|
533
|
-
renderStatusBadge(pack?.visibility || 'n/a'),
|
|
534
|
-
renderStatusBadge(pack?.status || 'n/a'),
|
|
535
|
-
renderStatusBadge(pack?.pack_status || 'ready'),
|
|
536
|
-
].join('');
|
|
519
|
+
const statusBadges = [renderStatusBadge(pack?.visibility || 'n/a'), renderStatusBadge(pack?.status || 'n/a'), renderStatusBadge(pack?.pack_status || 'ready')].join('');
|
|
537
520
|
|
|
538
521
|
const menu = renderMenuWrap(
|
|
539
522
|
'pack',
|
|
@@ -634,8 +617,9 @@ function renderPacksTab() {
|
|
|
634
617
|
<div class="panel-head slim">
|
|
635
618
|
<h4 class="panel-title">Pack selecionado</h4>
|
|
636
619
|
</div>
|
|
637
|
-
${
|
|
638
|
-
|
|
620
|
+
${
|
|
621
|
+
selectedPack
|
|
622
|
+
? `
|
|
639
623
|
<div class="selected-pack-head">
|
|
640
624
|
<div>
|
|
641
625
|
<p class="row-title break-words">${escapeHtml(selectedPack?.name || selectedPack?.pack_key || 'Pack')}</p>
|
|
@@ -655,7 +639,8 @@ function renderPacksTab() {
|
|
|
655
639
|
</div>
|
|
656
640
|
<div class="detail-list">${detailItems || '<div class="empty-box">Pack sem stickers.</div>'}</div>
|
|
657
641
|
`
|
|
658
|
-
|
|
642
|
+
: '<div class="empty-box">Selecione um pack na tabela para visualizar e moderar stickers.</div>'
|
|
643
|
+
}
|
|
659
644
|
</section>
|
|
660
645
|
</section>
|
|
661
646
|
`;
|
|
@@ -670,13 +655,7 @@ function renderLogsTab() {
|
|
|
670
655
|
const rowsDesktop = page.items
|
|
671
656
|
.map((ban) => {
|
|
672
657
|
const revoked = Boolean(ban?.revoked_at);
|
|
673
|
-
const menu = revoked
|
|
674
|
-
? ''
|
|
675
|
-
: renderMenuWrap(
|
|
676
|
-
'ban',
|
|
677
|
-
ban?.id || Math.random(),
|
|
678
|
-
`<button class="row-menu-item" data-action="revoke-ban" data-ban-id="${escapeHtml(ban?.id || '')}">Revogar banimento</button>`,
|
|
679
|
-
);
|
|
658
|
+
const menu = revoked ? '' : renderMenuWrap('ban', ban?.id || Math.random(), `<button class="row-menu-item" data-action="revoke-ban" data-ban-id="${escapeHtml(ban?.id || '')}">Revogar banimento</button>`);
|
|
680
659
|
|
|
681
660
|
return `
|
|
682
661
|
<tr>
|
|
@@ -702,9 +681,7 @@ function renderLogsTab() {
|
|
|
702
681
|
<p class="row-sub">${escapeHtml(ban?.reason || 'Sem motivo')}</p>
|
|
703
682
|
<p class="row-meta">${escapeHtml(fmtDate(ban?.created_at))}</p>
|
|
704
683
|
<div class="mobile-card-foot">
|
|
705
|
-
${Boolean(ban?.revoked_at)
|
|
706
|
-
? renderStatusBadge('revogado', 'status-neutral')
|
|
707
|
-
: `<button class="outline-btn" data-action="revoke-ban" data-ban-id="${escapeHtml(ban?.id || '')}">Revogar</button>`}
|
|
684
|
+
${Boolean(ban?.revoked_at) ? renderStatusBadge('revogado', 'status-neutral') : `<button class="outline-btn" data-action="revoke-ban" data-ban-id="${escapeHtml(ban?.id || '')}">Revogar</button>`}
|
|
708
685
|
</div>
|
|
709
686
|
</article>
|
|
710
687
|
`,
|
|
@@ -869,9 +846,7 @@ function renderSystemTab() {
|
|
|
869
846
|
<p class="row-meta break-all">${escapeHtml(row?.owner_jid || '')}</p>
|
|
870
847
|
<div class="mobile-card-foot">
|
|
871
848
|
${Boolean(row?.active && !row?.revoked_at) ? renderStatusBadge('ativo', 'status-success') : renderStatusBadge('revogado', 'status-neutral')}
|
|
872
|
-
${Boolean(row?.active && !row?.revoked_at)
|
|
873
|
-
? `<button class="danger-btn" data-action="revoke-moderator" data-google-sub="${escapeHtml(row?.google_sub || '')}">Remover</button>`
|
|
874
|
-
: ''}
|
|
849
|
+
${Boolean(row?.active && !row?.revoked_at) ? `<button class="danger-btn" data-action="revoke-moderator" data-google-sub="${escapeHtml(row?.google_sub || '')}">Remover</button>` : ''}
|
|
875
850
|
</div>
|
|
876
851
|
</article>
|
|
877
852
|
`,
|
|
@@ -930,8 +905,9 @@ function renderSystemTab() {
|
|
|
930
905
|
<h3 class="panel-title">Moderadores</h3>
|
|
931
906
|
<p class="panel-desc">Acesso secundario do painel com senha individual e sessao Google obrigatoria.</p>
|
|
932
907
|
</div>
|
|
933
|
-
${
|
|
934
|
-
|
|
908
|
+
${
|
|
909
|
+
ownerMode
|
|
910
|
+
? `
|
|
935
911
|
<form data-form="moderator-upsert" class="form-grid">
|
|
936
912
|
<input class="search-input" list="moderator-target-list" name="target" placeholder="email, google_sub ou owner_jid do usuario logado" />
|
|
937
913
|
<datalist id="moderator-target-list">${targetOptions}</datalist>
|
|
@@ -952,7 +928,8 @@ function renderSystemTab() {
|
|
|
952
928
|
</div>
|
|
953
929
|
<div class="mobile-list mobile-only">${moderatorRowsMobile || '<div class="empty-box">Nenhum moderador cadastrado.</div>'}</div>
|
|
954
930
|
`
|
|
955
|
-
|
|
931
|
+
: '<div class="empty-box">Sessao atual de moderador. Apenas o owner pode cadastrar/remover moderadores.</div>'
|
|
932
|
+
}
|
|
956
933
|
</article>
|
|
957
934
|
|
|
958
935
|
<article class="panel">
|
|
@@ -1044,12 +1021,8 @@ function renderHeader() {
|
|
|
1044
1021
|
</div>
|
|
1045
1022
|
<div class="topbar-actions">
|
|
1046
1023
|
<button class="ghost-btn" data-action="refresh-dashboard" ${state.busy ? 'disabled' : ''}>Atualizar</button>
|
|
1047
|
-
${isAdminAuthenticated()
|
|
1048
|
-
|
|
1049
|
-
: '<span class="user-chip muted">Nao autenticado</span>'}
|
|
1050
|
-
${isAdminAuthenticated()
|
|
1051
|
-
? `<button class="ghost-btn danger" data-action="logout-admin" ${state.busy ? 'disabled' : ''}>Sair</button>`
|
|
1052
|
-
: `<button class="ghost-btn" data-action="refresh-admin-status" ${state.busy ? 'disabled' : ''}>Revalidar</button>`}
|
|
1024
|
+
${isAdminAuthenticated() ? `<span class="user-chip">${escapeHtml(`${roleLabel ? `${roleLabel} • ` : ''}${adminUser?.email || adminUser?.name || 'Admin'}`)}</span>` : '<span class="user-chip muted">Nao autenticado</span>'}
|
|
1025
|
+
${isAdminAuthenticated() ? `<button class="ghost-btn danger" data-action="logout-admin" ${state.busy ? 'disabled' : ''}>Sair</button>` : `<button class="ghost-btn" data-action="refresh-admin-status" ${state.busy ? 'disabled' : ''}>Revalidar</button>`}
|
|
1053
1026
|
</div>
|
|
1054
1027
|
</div>
|
|
1055
1028
|
</header>
|
|
@@ -1108,31 +1081,33 @@ function renderUnlockView() {
|
|
|
1108
1081
|
<button class="subtle-btn" data-action="refresh-admin-status" ${state.busy ? 'disabled' : ''}>Revalidar</button>
|
|
1109
1082
|
</div>
|
|
1110
1083
|
|
|
1111
|
-
${
|
|
1112
|
-
|
|
1084
|
+
${
|
|
1085
|
+
googleSession?.authenticated
|
|
1086
|
+
? `
|
|
1113
1087
|
<div class="account-box">
|
|
1114
1088
|
<p class="row-title">${escapeHtml(googleSession.user?.name || 'Conta Google')}</p>
|
|
1115
1089
|
<p class="row-sub break-all">${escapeHtml(googleSession.user?.email || '')}</p>
|
|
1116
1090
|
<p class="row-meta">${canUnlockAdmin() ? 'Conta elegivel para admin.' : 'Conta Google logada nao bate com ADM_EMAIL.'}</p>
|
|
1117
1091
|
</div>
|
|
1118
1092
|
`
|
|
1119
|
-
|
|
1093
|
+
: `
|
|
1120
1094
|
<div class="account-box warning">
|
|
1121
1095
|
<p class="row-title">Nenhuma sessao Google ativa no servidor.</p>
|
|
1122
|
-
${hasLocalCache
|
|
1123
|
-
? `<p class="row-sub">Sessao local encontrada: ${escapeHtml(localGoogleCache.user?.email || localGoogleCache.user?.name || 'Conta Google')}.</p>`
|
|
1124
|
-
: '<p class="row-sub">Nao encontramos cache local de login Google.</p>'}
|
|
1096
|
+
${hasLocalCache ? `<p class="row-sub">Sessao local encontrada: ${escapeHtml(localGoogleCache.user?.email || localGoogleCache.user?.name || 'Conta Google')}.</p>` : '<p class="row-sub">Nao encontramos cache local de login Google.</p>'}
|
|
1125
1097
|
<p class="row-meta">Renove o login Google abaixo para continuar.</p>
|
|
1126
1098
|
</div>
|
|
1127
|
-
${
|
|
1128
|
-
|
|
1099
|
+
${
|
|
1100
|
+
googleConfigEnabled
|
|
1101
|
+
? `
|
|
1129
1102
|
<div class="google-login-box">
|
|
1130
1103
|
<div data-google-admin-login-button class="google-login-slot"></div>
|
|
1131
1104
|
${state.googleLoginUiReady ? '' : '<p class="row-meta">Carregando botao de login Google...</p>'}
|
|
1132
1105
|
</div>
|
|
1133
1106
|
`
|
|
1134
|
-
|
|
1135
|
-
|
|
1107
|
+
: `<p class="row-meta">Login Google indisponivel no painel (${escapeHtml(state.googleAuthConfigError || 'config nao encontrada')}).</p>`
|
|
1108
|
+
}
|
|
1109
|
+
`
|
|
1110
|
+
}
|
|
1136
1111
|
</article>
|
|
1137
1112
|
|
|
1138
1113
|
<article class="panel inner">
|
|
@@ -1142,9 +1117,7 @@ function renderUnlockView() {
|
|
|
1142
1117
|
<form data-form="admin-unlock" class="form-grid">
|
|
1143
1118
|
<input class="search-input" type="password" name="password" placeholder="Digite a senha do painel" autocomplete="current-password" />
|
|
1144
1119
|
<button class="primary-btn" type="submit" ${state.busy ? 'disabled' : ''}>${state.busy ? 'Validando...' : 'Desbloquear Painel'}</button>
|
|
1145
|
-
${!canUnlockAdmin()
|
|
1146
|
-
? `<p class="hint warning">A senha so desbloqueia apos sessao Google elegivel (${escapeHtml(adminEmail)} ou moderador autorizado).</p>`
|
|
1147
|
-
: '<p class="hint">Sessao Google elegivel detectada. Informe a senha correspondente.</p>'}
|
|
1120
|
+
${!canUnlockAdmin() ? `<p class="hint warning">A senha so desbloqueia apos sessao Google elegivel (${escapeHtml(adminEmail)} ou moderador autorizado).</p>` : '<p class="hint">Sessao Google elegivel detectada. Informe a senha correspondente.</p>'}
|
|
1148
1121
|
</form>
|
|
1149
1122
|
</article>
|
|
1150
1123
|
</div>
|
|
@@ -1296,81 +1269,96 @@ async function boot() {
|
|
|
1296
1269
|
}
|
|
1297
1270
|
|
|
1298
1271
|
async function refreshAdminStatusOnly() {
|
|
1299
|
-
await runTask(
|
|
1300
|
-
|
|
1301
|
-
|
|
1272
|
+
await runTask(
|
|
1273
|
+
async () => {
|
|
1274
|
+
await Promise.all([loadAdminStatus(), loadGoogleAuthConfig()]);
|
|
1275
|
+
},
|
|
1276
|
+
{ successMessage: 'Status de autenticacao atualizado.' },
|
|
1277
|
+
);
|
|
1302
1278
|
}
|
|
1303
1279
|
|
|
1304
1280
|
async function refreshDashboard() {
|
|
1305
|
-
await runTask(
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1281
|
+
await runTask(
|
|
1282
|
+
async () => {
|
|
1283
|
+
await Promise.all([loadAdminStatus(), loadGoogleAuthConfig()]);
|
|
1284
|
+
if (!isAdminAuthenticated()) {
|
|
1285
|
+
state.overview = null;
|
|
1286
|
+
state.packs = [];
|
|
1287
|
+
state.moderators = [];
|
|
1288
|
+
state.selectedPack = null;
|
|
1289
|
+
state.selectedPackKey = '';
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
await bootstrapDashboardData();
|
|
1293
|
+
},
|
|
1294
|
+
{ successMessage: 'Painel atualizado.' },
|
|
1295
|
+
);
|
|
1317
1296
|
}
|
|
1318
1297
|
|
|
1319
1298
|
async function loginGoogleForAdmin(credential) {
|
|
1320
|
-
await runTask(
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1299
|
+
await runTask(
|
|
1300
|
+
async () => {
|
|
1301
|
+
const payload = await fetchJson(googleSessionApiPath, {
|
|
1302
|
+
method: 'POST',
|
|
1303
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
1304
|
+
body: JSON.stringify({ google_id_token: credential }),
|
|
1305
|
+
});
|
|
1306
|
+
const data = payload?.data || {};
|
|
1307
|
+
if (!data?.authenticated || !data?.user?.sub) {
|
|
1308
|
+
throw new Error('Nao foi possivel criar sessao Google do site.');
|
|
1309
|
+
}
|
|
1310
|
+
await loadAdminStatus();
|
|
1311
|
+
},
|
|
1312
|
+
{ successMessage: 'Sessao Google renovada.' },
|
|
1313
|
+
);
|
|
1332
1314
|
}
|
|
1333
1315
|
|
|
1334
1316
|
async function unlockAdmin(password) {
|
|
1335
|
-
await runTask(
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1317
|
+
await runTask(
|
|
1318
|
+
async () => {
|
|
1319
|
+
if (!canUnlockAdmin()) {
|
|
1320
|
+
const google = state.adminStatus?.google || {};
|
|
1321
|
+
const adminEmail = String(state.adminStatus?.admin_email || '').trim();
|
|
1322
|
+
const loggedEmail = String(google?.user?.email || '').trim();
|
|
1323
|
+
if (!google?.authenticated) {
|
|
1324
|
+
const local = readLocalGoogleAuthCache();
|
|
1325
|
+
if (local?.user?.email) {
|
|
1326
|
+
throw new Error(`Sua sessao Google do servidor expirou. Renove o login Google (${local.user.email}) e tente novamente.`);
|
|
1327
|
+
}
|
|
1328
|
+
throw new Error('Faca login Google no site com o email admin antes de digitar a senha.');
|
|
1344
1329
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
throw new Error(
|
|
1330
|
+
if (loggedEmail && adminEmail && loggedEmail.toLowerCase() !== adminEmail.toLowerCase()) {
|
|
1331
|
+
throw new Error(`Email logado (${loggedEmail}) e diferente do ADM_EMAIL (${adminEmail}).`);
|
|
1332
|
+
}
|
|
1333
|
+
throw new Error('Conta Google atual nao esta elegivel para desbloquear o painel.');
|
|
1349
1334
|
}
|
|
1350
|
-
throw new Error('Conta Google atual nao esta elegivel para desbloquear o painel.');
|
|
1351
|
-
}
|
|
1352
1335
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1336
|
+
const payload = await fetchJson(`${adminApiBase}/session`, {
|
|
1337
|
+
method: 'POST',
|
|
1338
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
1339
|
+
body: JSON.stringify({ password }),
|
|
1340
|
+
});
|
|
1341
|
+
state.adminStatus = payload?.data || null;
|
|
1342
|
+
state.activeTab = 'users';
|
|
1343
|
+
await bootstrapDashboardData();
|
|
1344
|
+
},
|
|
1345
|
+
{ successMessage: 'Painel admin desbloqueado.' },
|
|
1346
|
+
);
|
|
1362
1347
|
}
|
|
1363
1348
|
|
|
1364
1349
|
async function logoutAdmin() {
|
|
1365
|
-
await runTask(
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1350
|
+
await runTask(
|
|
1351
|
+
async () => {
|
|
1352
|
+
await fetchJson(`${adminApiBase}/session`, { method: 'DELETE' });
|
|
1353
|
+
await loadAdminStatus();
|
|
1354
|
+
state.overview = null;
|
|
1355
|
+
state.packs = [];
|
|
1356
|
+
state.moderators = [];
|
|
1357
|
+
state.selectedPack = null;
|
|
1358
|
+
state.selectedPackKey = '';
|
|
1359
|
+
},
|
|
1360
|
+
{ successMessage: 'Sessao admin encerrada.' },
|
|
1361
|
+
);
|
|
1374
1362
|
}
|
|
1375
1363
|
|
|
1376
1364
|
async function searchPacks(query) {
|
|
@@ -1389,83 +1377,104 @@ async function openPackDetailsAdmin(packKey) {
|
|
|
1389
1377
|
|
|
1390
1378
|
async function deletePackAdmin(packKey) {
|
|
1391
1379
|
if (!window.confirm(`Apagar pack "${packKey}"?`)) return;
|
|
1392
|
-
await runTask(
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
state.
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1380
|
+
await runTask(
|
|
1381
|
+
async () => {
|
|
1382
|
+
await fetchJson(`${adminApiBase}/packs/${encodeURIComponent(packKey)}/delete`, { method: 'DELETE' });
|
|
1383
|
+
await loadPacks(state.packsQuery || '');
|
|
1384
|
+
if (state.selectedPackKey === packKey) {
|
|
1385
|
+
state.selectedPack = null;
|
|
1386
|
+
state.selectedPackKey = '';
|
|
1387
|
+
}
|
|
1388
|
+
if (state.overview) await loadOverview();
|
|
1389
|
+
},
|
|
1390
|
+
{ successMessage: 'Pack removido.' },
|
|
1391
|
+
);
|
|
1401
1392
|
}
|
|
1402
1393
|
|
|
1403
1394
|
async function removeStickerFromPackAdmin(packKey, stickerId) {
|
|
1404
1395
|
if (!window.confirm(`Remover sticker ${stickerId} do pack ${packKey}?`)) return;
|
|
1405
|
-
await runTask(
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1396
|
+
await runTask(
|
|
1397
|
+
async () => {
|
|
1398
|
+
await fetchJson(`${adminApiBase}/packs/${encodeURIComponent(packKey)}/stickers/${encodeURIComponent(stickerId)}/delete`, {
|
|
1399
|
+
method: 'DELETE',
|
|
1400
|
+
});
|
|
1401
|
+
await loadSelectedPack(packKey);
|
|
1402
|
+
await loadPacks(state.packsQuery || '');
|
|
1403
|
+
if (state.overview) await loadOverview();
|
|
1404
|
+
},
|
|
1405
|
+
{ successMessage: 'Sticker removido do pack.' },
|
|
1406
|
+
);
|
|
1413
1407
|
}
|
|
1414
1408
|
|
|
1415
1409
|
async function forceDeleteStickerAdmin(stickerId) {
|
|
1416
1410
|
if (!window.confirm(`Apagar sticker ${stickerId} globalmente (todas as referencias)?`)) return;
|
|
1417
|
-
await runTask(
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
state.
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1411
|
+
await runTask(
|
|
1412
|
+
async () => {
|
|
1413
|
+
await fetchJson(`${adminApiBase}/stickers/${encodeURIComponent(stickerId)}/delete`, { method: 'DELETE' });
|
|
1414
|
+
if (state.selectedPackKey) {
|
|
1415
|
+
await loadSelectedPack(state.selectedPackKey).catch(() => {
|
|
1416
|
+
state.selectedPack = null;
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
await loadPacks(state.packsQuery || '');
|
|
1420
|
+
if (state.overview) await loadOverview();
|
|
1421
|
+
},
|
|
1422
|
+
{ successMessage: 'Sticker removido globalmente.' },
|
|
1423
|
+
);
|
|
1427
1424
|
}
|
|
1428
1425
|
|
|
1429
1426
|
async function createBanAdmin(payload) {
|
|
1430
|
-
await runTask(
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1427
|
+
await runTask(
|
|
1428
|
+
async () => {
|
|
1429
|
+
await fetchJson(`${adminApiBase}/bans`, {
|
|
1430
|
+
method: 'POST',
|
|
1431
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
1432
|
+
body: JSON.stringify(payload),
|
|
1433
|
+
});
|
|
1434
|
+
await loadOverview();
|
|
1435
|
+
},
|
|
1436
|
+
{ successMessage: 'Usuario banido.' },
|
|
1437
|
+
);
|
|
1438
1438
|
}
|
|
1439
1439
|
|
|
1440
1440
|
async function revokeBanAdmin(banId) {
|
|
1441
1441
|
if (!window.confirm('Revogar este banimento?')) return;
|
|
1442
|
-
await runTask(
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1442
|
+
await runTask(
|
|
1443
|
+
async () => {
|
|
1444
|
+
await fetchJson(`${adminApiBase}/bans/${encodeURIComponent(banId)}/revoke`, { method: 'DELETE' });
|
|
1445
|
+
await loadOverview();
|
|
1446
|
+
},
|
|
1447
|
+
{ successMessage: 'Banimento revogado.' },
|
|
1448
|
+
);
|
|
1446
1449
|
}
|
|
1447
1450
|
|
|
1448
1451
|
async function upsertModeratorAdmin(payload) {
|
|
1449
|
-
await runTask(
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1452
|
+
await runTask(
|
|
1453
|
+
async () => {
|
|
1454
|
+
await fetchJson(`${adminApiBase}/moderators`, {
|
|
1455
|
+
method: 'POST',
|
|
1456
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
1457
|
+
body: JSON.stringify(payload),
|
|
1458
|
+
});
|
|
1459
|
+
await Promise.all([loadModerators(), loadOverview()]);
|
|
1460
|
+
},
|
|
1461
|
+
{ successMessage: 'Moderador salvo com sucesso.' },
|
|
1462
|
+
);
|
|
1457
1463
|
}
|
|
1458
1464
|
|
|
1459
1465
|
async function revokeModeratorAdmin(googleSub) {
|
|
1460
1466
|
const normalized = String(googleSub || '').trim();
|
|
1461
1467
|
if (!normalized) return;
|
|
1462
1468
|
if (!window.confirm('Remover acesso deste moderador?')) return;
|
|
1463
|
-
await runTask(
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
+
await runTask(
|
|
1470
|
+
async () => {
|
|
1471
|
+
await fetchJson(`${adminApiBase}/moderators/${encodeURIComponent(normalized)}`, {
|
|
1472
|
+
method: 'DELETE',
|
|
1473
|
+
});
|
|
1474
|
+
await Promise.all([loadModerators(), loadOverview()]);
|
|
1475
|
+
},
|
|
1476
|
+
{ successMessage: 'Moderador removido.' },
|
|
1477
|
+
);
|
|
1469
1478
|
}
|
|
1470
1479
|
|
|
1471
1480
|
async function ensureGoogleLoginButtonRendered() {
|