@kaikybrofc/omnizap-system 2.2.4 → 2.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.env.example +5 -0
  2. package/README.md +13 -13
  3. package/app/modules/stickerPackModule/catalogHandlers/catalogAdminHttp.js +68 -0
  4. package/app/modules/stickerPackModule/catalogHandlers/catalogAuthHttp.js +34 -0
  5. package/app/modules/stickerPackModule/catalogHandlers/catalogPublicHttp.js +179 -0
  6. package/app/modules/stickerPackModule/catalogHandlers/catalogUploadHttp.js +92 -0
  7. package/app/modules/stickerPackModule/catalogRouter.js +79 -0
  8. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +243 -0
  9. package/app/modules/stickerPackModule/domainEvents.js +61 -0
  10. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +21 -0
  11. package/app/modules/stickerPackModule/stickerAssetRepository.js +19 -0
  12. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +55 -15
  13. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +238 -0
  14. package/app/modules/stickerPackModule/stickerDomainEventBus.js +71 -0
  15. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +198 -0
  16. package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
  17. package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +537 -529
  18. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +44 -0
  19. package/app/modules/stickerPackModule/stickerPackItemRepository.js +18 -0
  20. package/app/modules/stickerPackModule/stickerPackRepository.js +51 -0
  21. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +191 -0
  22. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +301 -0
  23. package/app/modules/stickerPackModule/stickerStorageService.js +111 -10
  24. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +21 -0
  25. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +59 -7
  26. package/app/observability/metrics.js +169 -0
  27. package/app/services/featureFlagService.js +137 -0
  28. package/database/index.js +5 -0
  29. package/database/migrations/20260228_0022_sticker_scale_indexes.sql +16 -0
  30. package/database/migrations/20260228_0023_sticker_pack_score_snapshot.sql +25 -0
  31. package/database/migrations/20260228_0024_domain_event_outbox.sql +42 -0
  32. package/database/migrations/20260228_0025_sticker_worker_task_idempotency_dlq.sql +23 -0
  33. package/database/migrations/20260228_0026_feature_flags.sql +21 -0
  34. package/ecosystem.prod.config.cjs +70 -9
  35. package/index.js +26 -0
  36. package/package.json +5 -1
  37. package/public/index.html +30 -3
  38. package/scripts/sticker-catalog-loadtest.mjs +208 -0
  39. package/scripts/sticker-worker-task.mjs +122 -0
@@ -0,0 +1,137 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ import logger from '../utils/logger/loggerModule.js';
4
+ import { executeQuery, TABLES } from '../../database/index.js';
5
+
6
+ const FEATURE_FLAG_CACHE_TTL_MS = Math.max(
7
+ 5_000,
8
+ Number(process.env.FEATURE_FLAG_CACHE_TTL_MS) || 30_000,
9
+ );
10
+
11
+ let cacheState = {
12
+ loadedAt: 0,
13
+ byName: new Map(),
14
+ tableAvailable: true,
15
+ };
16
+
17
+ const normalizeFlagName = (value) =>
18
+ String(value || '')
19
+ .trim()
20
+ .toLowerCase()
21
+ .replace(/[^a-z0-9_:-]/g, '')
22
+ .slice(0, 120);
23
+
24
+ const toPercent = (value) => {
25
+ const numeric = Number(value);
26
+ if (!Number.isFinite(numeric)) return 0;
27
+ return Math.max(0, Math.min(100, Math.floor(numeric)));
28
+ };
29
+
30
+ const toBool = (value, fallback = false) => {
31
+ if (value === true || value === 1) return true;
32
+ if (value === false || value === 0) return false;
33
+ if (value === undefined || value === null || value === '') return fallback;
34
+ const normalized = String(value).trim().toLowerCase();
35
+ if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
36
+ if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
37
+ return fallback;
38
+ };
39
+
40
+ const normalizeRow = (row) => ({
41
+ flag_name: normalizeFlagName(row?.flag_name),
42
+ is_enabled: toBool(row?.is_enabled, false),
43
+ rollout_percent: toPercent(row?.rollout_percent ?? 100),
44
+ });
45
+
46
+ const resolveCohortBucket = (subjectKey) => {
47
+ const normalized = String(subjectKey || '').trim();
48
+ if (!normalized) return 0;
49
+ const digest = createHash('sha1').update(normalized).digest();
50
+ const value = digest.readUInt32BE(0);
51
+ return value % 100;
52
+ };
53
+
54
+ const loadFlagsFromDatabase = async () => {
55
+ const rows = await executeQuery(
56
+ `SELECT flag_name, is_enabled, rollout_percent
57
+ FROM ${TABLES.FEATURE_FLAG}`,
58
+ [],
59
+ );
60
+
61
+ const byName = new Map();
62
+ (Array.isArray(rows) ? rows : []).forEach((row) => {
63
+ const normalized = normalizeRow(row);
64
+ if (!normalized.flag_name) return;
65
+ byName.set(normalized.flag_name, normalized);
66
+ });
67
+ return byName;
68
+ };
69
+
70
+ export const refreshFeatureFlags = async ({ force = false } = {}) => {
71
+ const now = Date.now();
72
+ const isFresh = now - cacheState.loadedAt < FEATURE_FLAG_CACHE_TTL_MS;
73
+ if (!force && isFresh) return cacheState.byName;
74
+
75
+ if (!cacheState.tableAvailable) return cacheState.byName;
76
+
77
+ try {
78
+ const byName = await loadFlagsFromDatabase();
79
+ cacheState = {
80
+ loadedAt: now,
81
+ byName,
82
+ tableAvailable: true,
83
+ };
84
+ } catch (error) {
85
+ if (error?.code === 'ER_NO_SUCH_TABLE') {
86
+ cacheState = {
87
+ ...cacheState,
88
+ tableAvailable: false,
89
+ };
90
+ logger.warn('Tabela de feature flags indisponível. Usando fallback por env/default.', {
91
+ action: 'feature_flag_table_unavailable',
92
+ });
93
+ return cacheState.byName;
94
+ }
95
+ logger.warn('Falha ao carregar feature flags. Mantendo cache anterior.', {
96
+ action: 'feature_flag_refresh_failed',
97
+ error: error?.message,
98
+ });
99
+ }
100
+
101
+ return cacheState.byName;
102
+ };
103
+
104
+ const resolveEnvFallback = (flagName, fallback) => {
105
+ const envKey = `FEATURE_${flagName.toUpperCase()}`;
106
+ return toBool(process.env[envKey], fallback);
107
+ };
108
+
109
+ export const isFeatureEnabled = async (
110
+ flagName,
111
+ { fallback = false, subjectKey = '' } = {},
112
+ ) => {
113
+ const normalizedFlagName = normalizeFlagName(flagName);
114
+ if (!normalizedFlagName) return Boolean(fallback);
115
+
116
+ const byName = await refreshFeatureFlags();
117
+ const entry = byName.get(normalizedFlagName);
118
+ if (!entry) {
119
+ return resolveEnvFallback(normalizedFlagName, fallback);
120
+ }
121
+
122
+ if (!entry.is_enabled) return false;
123
+ if (entry.rollout_percent >= 100) return true;
124
+ if (entry.rollout_percent <= 0) return false;
125
+
126
+ const bucket = resolveCohortBucket(subjectKey || normalizedFlagName);
127
+ return bucket < entry.rollout_percent;
128
+ };
129
+
130
+ export const getFeatureFlagsSnapshot = async () => {
131
+ const byName = await refreshFeatureFlags();
132
+ return Array.from(byName.values()).map((entry) => ({
133
+ flag_name: entry.flag_name,
134
+ is_enabled: Boolean(entry.is_enabled),
135
+ rollout_percent: Number(entry.rollout_percent || 0),
136
+ }));
137
+ };
package/database/index.js CHANGED
@@ -104,8 +104,13 @@ export const TABLES = {
104
104
  SEMANTIC_THEME_SUGGESTION_CACHE: 'semantic_theme_suggestion_cache',
105
105
  STICKER_PACK_ENGAGEMENT: 'sticker_pack_engagement',
106
106
  STICKER_PACK_INTERACTION_EVENT: 'sticker_pack_interaction_event',
107
+ STICKER_PACK_SCORE_SNAPSHOT: 'sticker_pack_score_snapshot',
107
108
  STICKER_ASSET_REPROCESS_QUEUE: 'sticker_asset_reprocess_queue',
108
109
  STICKER_WORKER_TASK_QUEUE: 'sticker_worker_task_queue',
110
+ STICKER_WORKER_TASK_DLQ: 'sticker_worker_task_dlq',
111
+ DOMAIN_EVENT_OUTBOX: 'domain_event_outbox',
112
+ DOMAIN_EVENT_OUTBOX_DLQ: 'domain_event_outbox_dlq',
113
+ FEATURE_FLAG: 'feature_flag',
109
114
  STICKER_WEB_GOOGLE_USER: 'sticker_web_google_user',
110
115
  STICKER_WEB_GOOGLE_SESSION: 'sticker_web_google_session',
111
116
  STICKER_WEB_ADMIN_BAN: 'sticker_web_admin_ban',
@@ -0,0 +1,16 @@
1
+ ALTER TABLE sticker_asset
2
+ ADD INDEX idx_sticker_asset_created_at (created_at);
3
+
4
+ ALTER TABLE sticker_asset_classification
5
+ ADD INDEX idx_sticker_asset_classification_confidence (confidence),
6
+ ADD INDEX idx_sticker_asset_classification_updated_at (updated_at),
7
+ ADD INDEX idx_sticker_asset_classification_version_updated (classification_version, updated_at);
8
+
9
+ ALTER TABLE sticker_pack_item
10
+ ADD INDEX idx_sticker_pack_item_sticker_id (sticker_id);
11
+
12
+ ALTER TABLE sticker_pack
13
+ ADD INDEX idx_sticker_pack_catalog_lookup (deleted_at, status, pack_status, visibility, updated_at);
14
+
15
+ ALTER TABLE sticker_pack_interaction_event
16
+ ADD INDEX idx_sticker_pack_interaction_created_at (created_at);
@@ -0,0 +1,25 @@
1
+ CREATE TABLE IF NOT EXISTS sticker_pack_score_snapshot (
2
+ pack_id CHAR(36) PRIMARY KEY,
3
+ ranking_score DECIMAL(10,6) NOT NULL DEFAULT 0,
4
+ pack_score DECIMAL(10,6) NOT NULL DEFAULT 0,
5
+ trend_score DECIMAL(10,6) NOT NULL DEFAULT 0,
6
+ quality_score DECIMAL(10,6) NOT NULL DEFAULT 0,
7
+ engagement_score DECIMAL(10,6) NOT NULL DEFAULT 0,
8
+ diversity_score DECIMAL(10,6) NOT NULL DEFAULT 0,
9
+ cohesion_score DECIMAL(10,6) NOT NULL DEFAULT 0,
10
+ sensitive_content TINYINT(1) NOT NULL DEFAULT 0,
11
+ nsfw_level ENUM('safe', 'suggestive', 'explicit') NOT NULL DEFAULT 'safe',
12
+ sticker_count INT UNSIGNED NOT NULL DEFAULT 0,
13
+ tags JSON NULL,
14
+ scores_json JSON NULL,
15
+ source_version VARCHAR(32) NOT NULL DEFAULT 'v1',
16
+ refreshed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
17
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
18
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
19
+ CONSTRAINT fk_sticker_pack_score_snapshot_pack
20
+ FOREIGN KEY (pack_id) REFERENCES sticker_pack(id)
21
+ ON DELETE CASCADE ON UPDATE CASCADE,
22
+ INDEX idx_sticker_pack_score_snapshot_ranking (ranking_score),
23
+ INDEX idx_sticker_pack_score_snapshot_trend (trend_score),
24
+ INDEX idx_sticker_pack_score_snapshot_refresh (refreshed_at)
25
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,42 @@
1
+ CREATE TABLE IF NOT EXISTS domain_event_outbox (
2
+ id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
3
+ event_type VARCHAR(96) NOT NULL,
4
+ aggregate_type VARCHAR(96) NOT NULL,
5
+ aggregate_id VARCHAR(128) NOT NULL,
6
+ payload JSON NULL,
7
+ status ENUM('pending', 'processing', 'completed', 'failed') NOT NULL DEFAULT 'pending',
8
+ priority TINYINT UNSIGNED NOT NULL DEFAULT 50,
9
+ idempotency_key VARCHAR(180) NULL,
10
+ available_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
11
+ attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
12
+ max_attempts TINYINT UNSIGNED NOT NULL DEFAULT 10,
13
+ worker_token CHAR(36) NULL,
14
+ last_error VARCHAR(255) NULL,
15
+ locked_at TIMESTAMP NULL DEFAULT NULL,
16
+ processed_at TIMESTAMP NULL DEFAULT NULL,
17
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
18
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
19
+ UNIQUE KEY uq_domain_event_outbox_idempotency_key (idempotency_key),
20
+ INDEX idx_domain_event_outbox_status_sched (status, available_at, priority),
21
+ INDEX idx_domain_event_outbox_event_type (event_type, status, available_at),
22
+ INDEX idx_domain_event_outbox_aggregate (aggregate_type, aggregate_id, created_at),
23
+ INDEX idx_domain_event_outbox_worker_token (worker_token)
24
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
25
+
26
+ CREATE TABLE IF NOT EXISTS domain_event_outbox_dlq (
27
+ id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
28
+ outbox_event_id BIGINT UNSIGNED NULL,
29
+ event_type VARCHAR(96) NOT NULL,
30
+ aggregate_type VARCHAR(96) NOT NULL,
31
+ aggregate_id VARCHAR(128) NOT NULL,
32
+ payload JSON NULL,
33
+ attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
34
+ max_attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
35
+ last_error VARCHAR(255) NULL,
36
+ failed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
37
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
38
+ UNIQUE KEY uq_domain_event_outbox_dlq_outbox_event (outbox_event_id),
39
+ INDEX idx_domain_event_outbox_dlq_event (event_type, failed_at),
40
+ INDEX idx_domain_event_outbox_dlq_aggregate (aggregate_type, aggregate_id, failed_at),
41
+ INDEX idx_domain_event_outbox_dlq_outbox_event_id (outbox_event_id)
42
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,23 @@
1
+ ALTER TABLE sticker_worker_task_queue
2
+ ADD COLUMN IF NOT EXISTS idempotency_key VARCHAR(180) NULL AFTER task_type;
3
+
4
+ CREATE UNIQUE INDEX uq_sticker_worker_task_idempotency_key
5
+ ON sticker_worker_task_queue (idempotency_key);
6
+
7
+ CREATE TABLE IF NOT EXISTS sticker_worker_task_dlq (
8
+ id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
9
+ task_id BIGINT UNSIGNED NULL,
10
+ task_type ENUM('classification_cycle', 'curation_cycle', 'rebuild_cycle') NOT NULL,
11
+ payload JSON NULL,
12
+ idempotency_key VARCHAR(180) NULL,
13
+ attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
14
+ max_attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
15
+ priority TINYINT UNSIGNED NOT NULL DEFAULT 0,
16
+ last_error VARCHAR(255) NULL,
17
+ failed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
18
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
19
+ UNIQUE KEY uq_sticker_worker_task_dlq_task_id (task_id),
20
+ INDEX idx_sticker_worker_task_dlq_type_failed_at (task_type, failed_at),
21
+ INDEX idx_sticker_worker_task_dlq_task_id (task_id),
22
+ INDEX idx_sticker_worker_task_dlq_idempotency_key (idempotency_key)
23
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,21 @@
1
+ CREATE TABLE IF NOT EXISTS feature_flag (
2
+ id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
3
+ flag_name VARCHAR(120) NOT NULL,
4
+ is_enabled TINYINT(1) NOT NULL DEFAULT 0,
5
+ rollout_percent TINYINT UNSIGNED NOT NULL DEFAULT 100,
6
+ description VARCHAR(255) NULL,
7
+ updated_by VARCHAR(120) NULL,
8
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
9
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
10
+ UNIQUE KEY uq_feature_flag_name (flag_name),
11
+ INDEX idx_feature_flag_enabled (is_enabled, rollout_percent)
12
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
13
+
14
+ INSERT INTO feature_flag (flag_name, is_enabled, rollout_percent, description)
15
+ VALUES
16
+ ('enable_ranking_snapshot_read', 1, 100, 'Leitura HTTP do ranking/sinais a partir de snapshot'),
17
+ ('enable_domain_event_outbox', 1, 100, 'Publicacao e consumo de eventos de dominio via outbox interno'),
18
+ ('enable_worker_dedicated_processes', 0, 100, 'Ativa workers dedicados por tipo de task'),
19
+ ('enable_object_storage_delivery', 0, 100, 'Entrega de assets via object storage/CDN com URL segura')
20
+ ON DUPLICATE KEY UPDATE
21
+ description = VALUES(description);
@@ -2,6 +2,21 @@ require('dotenv').config();
2
2
 
3
3
  const appName = process.env.PM2_APP_NAME || 'omnizap-system';
4
4
 
5
+ const baseEnv = {
6
+ NODE_ENV: 'production',
7
+ COMMAND_PREFIX: '/',
8
+ LOG_LEVEL: 'info',
9
+ DB_LOG_EVERY_QUERY: 'false',
10
+ DB_MONITOR_ENABLED: 'false',
11
+ LID_BACKFILL_ON_START: 'false',
12
+ STICKER_CLASSIFICATION_BACKGROUND_ENABLED: 'true',
13
+ STICKER_REPROCESS_QUEUE_ENABLED: 'true',
14
+ STICKER_AUTO_PACK_BY_TAGS_ENABLED: 'true',
15
+ STICKER_WORKER_PIPELINE_ENABLED: 'true',
16
+ STICKER_WORKER_PIPELINE_INLINE_POLLER_ENABLED: 'true',
17
+ STICKER_DEDICATED_WORKERS_ENABLED: 'true',
18
+ };
19
+
5
20
  module.exports = {
6
21
  apps: [
7
22
  {
@@ -17,19 +32,65 @@ module.exports = {
17
32
  out_file: `logs/${appName}-out.log`,
18
33
  error_file: `logs/${appName}-error.log`,
19
34
  env: {
20
- NODE_ENV: 'production',
21
- COMMAND_PREFIX: '/',
22
- LOG_LEVEL: 'info',
23
- DB_LOG_EVERY_QUERY: 'false',
24
- DB_MONITOR_ENABLED: 'false',
25
- LID_BACKFILL_ON_START: 'false',
26
- STICKER_CLASSIFICATION_BACKGROUND_ENABLED: 'true',
27
- STICKER_REPROCESS_QUEUE_ENABLED: 'true',
28
- STICKER_AUTO_PACK_BY_TAGS_ENABLED: 'true',
35
+ ...baseEnv,
29
36
  },
30
37
  wait_ready: true,
31
38
  listen_timeout: 10000,
32
39
  kill_timeout: 5000,
33
40
  },
41
+ {
42
+ name: `${appName}-worker-classification`,
43
+ script: './scripts/sticker-worker-task.mjs',
44
+ args: '--task-type classification_cycle',
45
+ cwd: __dirname,
46
+ exec_mode: 'fork',
47
+ instances: 1,
48
+ autorestart: true,
49
+ watch: false,
50
+ max_memory_restart: '2G',
51
+ log_date_format: 'YYYY-MM-DD HH:mm:ss',
52
+ out_file: `logs/${appName}-worker-classification-out.log`,
53
+ error_file: `logs/${appName}-worker-classification-error.log`,
54
+ env: {
55
+ ...baseEnv,
56
+ },
57
+ kill_timeout: 5000,
58
+ },
59
+ {
60
+ name: `${appName}-worker-curation`,
61
+ script: './scripts/sticker-worker-task.mjs',
62
+ args: '--task-type curation_cycle',
63
+ cwd: __dirname,
64
+ exec_mode: 'fork',
65
+ instances: 1,
66
+ autorestart: true,
67
+ watch: false,
68
+ max_memory_restart: '2G',
69
+ log_date_format: 'YYYY-MM-DD HH:mm:ss',
70
+ out_file: `logs/${appName}-worker-curation-out.log`,
71
+ error_file: `logs/${appName}-worker-curation-error.log`,
72
+ env: {
73
+ ...baseEnv,
74
+ },
75
+ kill_timeout: 5000,
76
+ },
77
+ {
78
+ name: `${appName}-worker-rebuild`,
79
+ script: './scripts/sticker-worker-task.mjs',
80
+ args: '--task-type rebuild_cycle',
81
+ cwd: __dirname,
82
+ exec_mode: 'fork',
83
+ instances: 1,
84
+ autorestart: true,
85
+ watch: false,
86
+ max_memory_restart: '2G',
87
+ log_date_format: 'YYYY-MM-DD HH:mm:ss',
88
+ out_file: `logs/${appName}-worker-rebuild-out.log`,
89
+ error_file: `logs/${appName}-worker-rebuild-error.log`,
90
+ env: {
91
+ ...baseEnv,
92
+ },
93
+ kill_timeout: 5000,
94
+ },
34
95
  ],
35
96
  };
package/index.js CHANGED
@@ -39,6 +39,14 @@ import {
39
39
  startStickerWorkerPipeline,
40
40
  stopStickerWorkerPipeline,
41
41
  } from './app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js';
42
+ import {
43
+ startStickerPackScoreSnapshotRuntime,
44
+ stopStickerPackScoreSnapshotRuntime,
45
+ } from './app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js';
46
+ import {
47
+ startStickerDomainEventConsumer,
48
+ stopStickerDomainEventConsumer,
49
+ } from './app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js';
42
50
 
43
51
  /**
44
52
  * Timeout máximo para inicialização do banco (criar/verificar DB + tabelas).
@@ -216,6 +224,8 @@ async function startApp() {
216
224
  startStickerClassificationBackground();
217
225
  startStickerAutoPackByTagsBackground();
218
226
  }
227
+ startStickerPackScoreSnapshotRuntime();
228
+ startStickerDomainEventConsumer();
219
229
 
220
230
  // Backfill é opcional, rodando em background.
221
231
  const shouldBackfill = process.env.LID_BACKFILL_ON_START !== 'false';
@@ -363,6 +373,22 @@ async function shutdown(signal, error) {
363
373
  });
364
374
  }
365
375
 
376
+ try {
377
+ stopStickerPackScoreSnapshotRuntime();
378
+ } catch (snapshotError) {
379
+ logger.warn('Falha ao encerrar runtime de snapshot de score dos packs.', {
380
+ error: snapshotError?.message,
381
+ });
382
+ }
383
+
384
+ try {
385
+ stopStickerDomainEventConsumer();
386
+ } catch (consumerError) {
387
+ logger.warn('Falha ao encerrar consumidor interno de eventos de domínio.', {
388
+ error: consumerError?.message,
389
+ });
390
+ }
391
+
366
392
  // 5) Encerrar MySQL pool
367
393
  await closeDatabasePool();
368
394
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaikybrofc/omnizap-system",
3
- "version": "2.2.4",
3
+ "version": "2.2.5",
4
4
  "description": "Sistema profissional de automação WhatsApp com tecnologia Baileys",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
@@ -59,6 +59,10 @@
59
59
  "deploy": "bash ./scripts/deploy.sh",
60
60
  "deploy:dry-run": "DEPLOY_DRY_RUN=1 bash ./scripts/deploy.sh",
61
61
  "readme:sync-snapshot": "node ./scripts/sync-readme-snapshot.mjs",
62
+ "loadtest:stickers": "node ./scripts/sticker-catalog-loadtest.mjs",
63
+ "worker:sticker:classification": "node ./scripts/sticker-worker-task.mjs --task-type classification_cycle",
64
+ "worker:sticker:curation": "node ./scripts/sticker-worker-task.mjs --task-type curation_cycle",
65
+ "worker:sticker:rebuild": "node ./scripts/sticker-worker-task.mjs --task-type rebuild_cycle",
62
66
  "release": "bash ./scripts/release.sh",
63
67
  "release:minor": "RELEASE_TYPE=minor bash ./scripts/release.sh",
64
68
  "release:major": "RELEASE_TYPE=major bash ./scripts/release.sh",
package/public/index.html CHANGED
@@ -276,6 +276,33 @@
276
276
  display: grid;
277
277
  }
278
278
 
279
+ body.home-authenticated .top-inner {
280
+ flex-direction: row;
281
+ align-items: center;
282
+ justify-content: space-between;
283
+ gap: 10px;
284
+ }
285
+
286
+ body.home-authenticated .top-head {
287
+ flex: 1 1 auto;
288
+ }
289
+
290
+ body.home-authenticated #nav-toggle {
291
+ display: none;
292
+ }
293
+
294
+ body.home-authenticated #main-nav {
295
+ display: flex;
296
+ width: auto;
297
+ grid-template-columns: none;
298
+ gap: 8px;
299
+ flex-wrap: nowrap;
300
+ }
301
+
302
+ body.home-authenticated #main-nav .btn:not(#nav-auth-link) {
303
+ display: none;
304
+ }
305
+
279
306
  .btn {
280
307
  width: 100%;
281
308
  text-align: center;
@@ -295,8 +322,8 @@
295
322
  padding: 4px;
296
323
  border-radius: 999px;
297
324
  justify-content: center;
298
- justify-self: center;
299
- align-self: center;
325
+ justify-self: auto;
326
+ align-self: auto;
300
327
  background: linear-gradient(120deg, #132544f0, #10203af0);
301
328
  }
302
329
 
@@ -1396,7 +1423,7 @@ curl -sS https://omnizap.shop/api/sticker-packs/system-summary | jq</code></pre>
1396
1423
  ><i class="fa-brands fa-whatsapp" aria-hidden="true"></i></a>
1397
1424
 
1398
1425
  <div id="home-react-root" hidden></div>
1399
- <script type="module" src="/js/apps/homeApp.js?v=20260228-mobile-user-bubble2"></script>
1426
+ <script type="module" src="/js/apps/homeApp.js?v=20260228-mobile-user-bubble3"></script>
1400
1427
  <script type="module" src="/js/github-panel/index.js?v=20260226a"></script>
1401
1428
  </body>
1402
1429
  </html>