@kaikybrofc/omnizap-system 2.2.4 → 2.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -0
- package/README.md +13 -13
- package/app/modules/stickerPackModule/catalogHandlers/catalogAdminHttp.js +68 -0
- package/app/modules/stickerPackModule/catalogHandlers/catalogAuthHttp.js +34 -0
- package/app/modules/stickerPackModule/catalogHandlers/catalogPublicHttp.js +179 -0
- package/app/modules/stickerPackModule/catalogHandlers/catalogUploadHttp.js +92 -0
- package/app/modules/stickerPackModule/catalogRouter.js +79 -0
- package/app/modules/stickerPackModule/domainEventOutboxRepository.js +243 -0
- package/app/modules/stickerPackModule/domainEvents.js +61 -0
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +21 -0
- package/app/modules/stickerPackModule/stickerAssetRepository.js +19 -0
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +55 -15
- package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +238 -0
- package/app/modules/stickerPackModule/stickerDomainEventBus.js +71 -0
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +198 -0
- package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
- package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +537 -529
- package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +44 -0
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +18 -0
- package/app/modules/stickerPackModule/stickerPackRepository.js +51 -0
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +191 -0
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +301 -0
- package/app/modules/stickerPackModule/stickerStorageService.js +111 -10
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +21 -0
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +59 -7
- package/app/observability/metrics.js +169 -0
- package/app/services/featureFlagService.js +137 -0
- package/database/index.js +5 -0
- package/database/migrations/20260228_0022_sticker_scale_indexes.sql +16 -0
- package/database/migrations/20260228_0023_sticker_pack_score_snapshot.sql +25 -0
- package/database/migrations/20260228_0024_domain_event_outbox.sql +42 -0
- package/database/migrations/20260228_0025_sticker_worker_task_idempotency_dlq.sql +23 -0
- package/database/migrations/20260228_0026_feature_flags.sql +21 -0
- package/ecosystem.prod.config.cjs +70 -9
- package/index.js +26 -0
- package/package.json +5 -1
- package/public/index.html +30 -3
- package/scripts/sticker-catalog-loadtest.mjs +208 -0
- package/scripts/sticker-worker-task.mjs +122 -0
|
@@ -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
|
-
|
|
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.
|
|
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:
|
|
299
|
-
align-self:
|
|
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-
|
|
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>
|