@kaikybrofc/omnizap-system 2.1.8

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 (166) hide show
  1. package/.env.example +534 -0
  2. package/LICENSE +21 -0
  3. package/README.md +431 -0
  4. package/RELEASE-v2.1.2.md +83 -0
  5. package/app/config/adminIdentity.js +87 -0
  6. package/app/config/baileysConfig.js +693 -0
  7. package/app/config/groupUtils.js +388 -0
  8. package/app/connection/socketController.js +992 -0
  9. package/app/controllers/messageController.js +354 -0
  10. package/app/modules/adminModule/groupCommandHandlers.js +1294 -0
  11. package/app/modules/adminModule/groupEventHandlers.js +355 -0
  12. package/app/modules/aiModule/catCommand.js +1006 -0
  13. package/app/modules/broadcastModule/noticeCommand.js +416 -0
  14. package/app/modules/gameModule/diceCommand.js +67 -0
  15. package/app/modules/menuModule/common.js +311 -0
  16. package/app/modules/menuModule/menus.js +59 -0
  17. package/app/modules/playModule/playCommand.js +1615 -0
  18. package/app/modules/quoteModule/quoteCommand.js +851 -0
  19. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +786 -0
  20. package/app/modules/rpgPokemonModule/rpgBattleService.js +2082 -0
  21. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +760 -0
  22. package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
  23. package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +172 -0
  24. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
  25. package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
  26. package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
  27. package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
  28. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1859 -0
  29. package/app/modules/rpgPokemonModule/rpgPokemonService.js +6738 -0
  30. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
  31. package/app/modules/statsModule/globalRankingCommand.js +65 -0
  32. package/app/modules/statsModule/noMessageCommand.js +288 -0
  33. package/app/modules/statsModule/rankingCommand.js +60 -0
  34. package/app/modules/statsModule/rankingCommon.js +889 -0
  35. package/app/modules/stickerModule/addStickerMetadata.js +239 -0
  36. package/app/modules/stickerModule/convertToWebp.js +390 -0
  37. package/app/modules/stickerModule/stickerCommand.js +454 -0
  38. package/app/modules/stickerModule/stickerConvertCommand.js +156 -0
  39. package/app/modules/stickerModule/stickerTextCommand.js +657 -0
  40. package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
  41. package/app/modules/stickerPackModule/autoPackCollectorService.js +284 -0
  42. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +466 -0
  43. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +88 -0
  44. package/app/modules/stickerPackModule/semanticThemeClusterService.js +571 -0
  45. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +449 -0
  46. package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
  47. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +180 -0
  48. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +4078 -0
  49. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +598 -0
  50. package/app/modules/stickerPackModule/stickerClassificationService.js +588 -0
  51. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +102 -0
  52. package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +7506 -0
  53. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1095 -0
  54. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +108 -0
  55. package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
  56. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +110 -0
  57. package/app/modules/stickerPackModule/stickerPackItemRepository.js +440 -0
  58. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +337 -0
  59. package/app/modules/stickerPackModule/stickerPackMessageService.js +296 -0
  60. package/app/modules/stickerPackModule/stickerPackRepository.js +442 -0
  61. package/app/modules/stickerPackModule/stickerPackService.js +788 -0
  62. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +51 -0
  63. package/app/modules/stickerPackModule/stickerPackUtils.js +97 -0
  64. package/app/modules/stickerPackModule/stickerStorageService.js +507 -0
  65. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +233 -0
  66. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +205 -0
  67. package/app/modules/systemMetricsModule/pingCommand.js +421 -0
  68. package/app/modules/tiktokModule/tiktokCommand.js +798 -0
  69. package/app/modules/userModule/userCommand.js +1217 -0
  70. package/app/modules/waifuPicsModule/waifuPicsCommand.js +177 -0
  71. package/app/observability/metrics.js +734 -0
  72. package/app/services/captchaService.js +492 -0
  73. package/app/services/dbWriteQueue.js +572 -0
  74. package/app/services/groupMetadataService.js +279 -0
  75. package/app/services/lidMapService.js +663 -0
  76. package/app/services/messagePersistenceService.js +56 -0
  77. package/app/services/newsBroadcastService.js +351 -0
  78. package/app/services/pokeApiService.js +398 -0
  79. package/app/services/queueUtils.js +57 -0
  80. package/app/services/socketState.js +7 -0
  81. package/app/store/aiPromptStore.js +38 -0
  82. package/app/store/groupConfigStore.js +58 -0
  83. package/app/store/premiumUserStore.js +36 -0
  84. package/app/utils/antiLink/antiLinkModule.js +804 -0
  85. package/app/utils/http/getImageBufferModule.js +18 -0
  86. package/app/utils/json/jsonSanitizer.js +113 -0
  87. package/app/utils/json/jsonSanitizer.test.js +40 -0
  88. package/app/utils/logger/loggerModule.js +262 -0
  89. package/app/utils/systemMetrics/systemMetricsModule.js +91 -0
  90. package/database/index.js +2052 -0
  91. package/database/init.js +516 -0
  92. package/database/migrations/20260203_0001_sticker_packs.sql +54 -0
  93. package/database/migrations/20260210_0003_rpg_pokemon.sql +58 -0
  94. package/database/migrations/20260210_0004_rpg_shiny_biome.sql +9 -0
  95. package/database/migrations/20260210_0005_rpg_missions.sql +14 -0
  96. package/database/migrations/20260210_0006_rpg_world_pokedex_traits.sql +27 -0
  97. package/database/migrations/20260210_0007_rpg_raid_pvp.sql +56 -0
  98. package/database/migrations/20260210_0008_rpg_social_system.sql +195 -0
  99. package/database/migrations/20260211_0009_rpg_social_xp.sql +36 -0
  100. package/database/migrations/20260222_0010_remove_message_xp.sql +2 -0
  101. package/database/migrations/20260226_0011_sticker_asset_classification.sql +17 -0
  102. package/database/migrations/20260226_0012_sticker_pack_engagement.sql +16 -0
  103. package/database/migrations/20260226_0013_sticker_marketplace_intelligence.sql +19 -0
  104. package/database/migrations/20260226_0014_sticker_pack_publish_flow.sql +30 -0
  105. package/database/migrations/20260226_0014_sticker_worker_queues.sql +42 -0
  106. package/database/migrations/20260226_0015_sticker_auto_pack_curation_integrity.sql +18 -0
  107. package/database/migrations/20260226_0016_sticker_web_google_auth_persistence.sql +34 -0
  108. package/database/migrations/20260226_0017_sticker_web_admin_ban.sql +22 -0
  109. package/database/migrations/20260226_0018_sticker_web_admin_moderator.sql +18 -0
  110. package/database/migrations/20260227_0019_sticker_classification_v2_signals.sql +12 -0
  111. package/database/migrations/20260227_0020_semantic_theme_clusters.sql +35 -0
  112. package/docker-compose.yml +103 -0
  113. package/ecosystem.prod.config.cjs +35 -0
  114. package/eslint.config.js +61 -0
  115. package/index.js +437 -0
  116. package/ml/clip_classifier/Dockerfile +16 -0
  117. package/ml/clip_classifier/README.md +120 -0
  118. package/ml/clip_classifier/adaptive_scoring.py +40 -0
  119. package/ml/clip_classifier/classifier.py +654 -0
  120. package/ml/clip_classifier/embedding_store.py +481 -0
  121. package/ml/clip_classifier/env_loader.py +15 -0
  122. package/ml/clip_classifier/llm_label_expander.py +144 -0
  123. package/ml/clip_classifier/main.py +213 -0
  124. package/ml/clip_classifier/requirements.txt +10 -0
  125. package/ml/clip_classifier/similarity_engine.py +74 -0
  126. package/observability/alert-rules.yml +60 -0
  127. package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
  128. package/observability/grafana/dashboards/omnizap-overview.json +170 -0
  129. package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
  130. package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
  131. package/observability/loki-config.yml +38 -0
  132. package/observability/mysql-exporter.cnf +5 -0
  133. package/observability/mysql-setup.sql +46 -0
  134. package/observability/prometheus.yml +32 -0
  135. package/observability/promtail-config.yml +84 -0
  136. package/package.json +109 -0
  137. package/public/api-docs/index.html +144 -0
  138. package/public/css/github-project-panel.css +297 -0
  139. package/public/css/stickers-admin.css +1272 -0
  140. package/public/css/styles.css +671 -0
  141. package/public/index.html +1311 -0
  142. package/public/js/apps/apiDocsApp.js +310 -0
  143. package/public/js/apps/createPackApp.js +2069 -0
  144. package/public/js/apps/homeApp.js +396 -0
  145. package/public/js/apps/stickersAdminApp.js +1744 -0
  146. package/public/js/apps/stickersApp.js +4830 -0
  147. package/public/js/catalog.js +1019 -0
  148. package/public/js/github-panel/components/CommitList.js +34 -0
  149. package/public/js/github-panel/components/ErrorState.js +16 -0
  150. package/public/js/github-panel/components/GithubProjectPanel.js +106 -0
  151. package/public/js/github-panel/components/ReleaseList.js +38 -0
  152. package/public/js/github-panel/components/SkeletonPanel.js +22 -0
  153. package/public/js/github-panel/components/StatCard.js +15 -0
  154. package/public/js/github-panel/index.js +15 -0
  155. package/public/js/github-panel/useGithubRepoData.js +154 -0
  156. package/public/js/github-panel/vendor/react.js +11 -0
  157. package/public/js/runtime/react-runtime.js +19 -0
  158. package/public/licenca/index.html +106 -0
  159. package/public/stickers/admin/index.html +23 -0
  160. package/public/stickers/create/index.html +47 -0
  161. package/public/stickers/index.html +48 -0
  162. package/public/termos-de-uso/index.html +125 -0
  163. package/scripts/cache-bust.mjs +107 -0
  164. package/scripts/deploy.sh +458 -0
  165. package/scripts/github-deploy-notify.mjs +174 -0
  166. package/scripts/release.sh +129 -0
@@ -0,0 +1,18 @@
1
+ import https from 'node:https';
2
+
3
+ const getImageBuffer = (url) =>
4
+ new Promise((resolve, reject) => {
5
+ https
6
+ .get(url, (response) => {
7
+ if (response.statusCode !== 200) {
8
+ reject(new Error(`Failed to get image, status code: ${response.statusCode}`));
9
+ return;
10
+ }
11
+ const chunks = [];
12
+ response.on('data', (chunk) => chunks.push(chunk));
13
+ response.on('end', () => resolve(Buffer.concat(chunks)));
14
+ })
15
+ .on('error', (err) => reject(err));
16
+ });
17
+
18
+ export default getImageBuffer;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Sanitiza caracteres Unicode invalidos para JSON.
3
+ * Substitui surrogates "soltos" (sem par valido) por U+FFFD.
4
+ *
5
+ * @param {string} value
6
+ * @returns {string}
7
+ */
8
+ export const sanitizeUnicodeString = (value) => {
9
+ if (typeof value !== 'string' || value.length === 0) {
10
+ return value;
11
+ }
12
+
13
+ if (!/[\uD800-\uDFFF]/.test(value)) {
14
+ return value;
15
+ }
16
+
17
+ let normalized = '';
18
+ for (let i = 0; i < value.length; i += 1) {
19
+ const code = value.charCodeAt(i);
20
+
21
+ if (code >= 0xd800 && code <= 0xdbff) {
22
+ const nextCode = value.charCodeAt(i + 1);
23
+ if (nextCode >= 0xdc00 && nextCode <= 0xdfff) {
24
+ normalized += value[i] + value[i + 1];
25
+ i += 1;
26
+ } else {
27
+ normalized += '\uFFFD';
28
+ }
29
+ continue;
30
+ }
31
+
32
+ if (code >= 0xdc00 && code <= 0xdfff) {
33
+ normalized += '\uFFFD';
34
+ continue;
35
+ }
36
+
37
+ normalized += value[i];
38
+ }
39
+
40
+ return normalized;
41
+ };
42
+
43
+ /**
44
+ * Cria replacer para JSON.stringify com suporte a:
45
+ * - strings com surrogate invalido
46
+ * - referencias circulares
47
+ * - bigint
48
+ * - numeros nao finitos
49
+ *
50
+ * @returns {(key:string, value:any)=>any}
51
+ */
52
+ const createSafeJsonReplacer = () => {
53
+ const seen = new WeakSet();
54
+
55
+ return (_, value) => {
56
+ if (typeof value === 'string') {
57
+ return sanitizeUnicodeString(value);
58
+ }
59
+
60
+ if (typeof value === 'bigint') {
61
+ return value.toString();
62
+ }
63
+
64
+ if (typeof value === 'number' && !Number.isFinite(value)) {
65
+ return null;
66
+ }
67
+
68
+ if (value instanceof Date) {
69
+ return Number.isNaN(value.getTime()) ? null : value.toISOString();
70
+ }
71
+
72
+ if (value && typeof value === 'object') {
73
+ if (seen.has(value)) {
74
+ return '[Circular]';
75
+ }
76
+ seen.add(value);
77
+ }
78
+
79
+ return value;
80
+ };
81
+ };
82
+
83
+ /**
84
+ * Converte qualquer valor para texto JSON seguro para colunas JSON do MySQL.
85
+ * - Objetos/arrays sao serializados com saneamento
86
+ * - Strings sao saneadas; se nao forem JSON valido, viram JSON string
87
+ *
88
+ * @param {*} value
89
+ * @returns {string|null}
90
+ */
91
+ export const toSafeJsonColumnValue = (value) => {
92
+ if (value === undefined || value === null) {
93
+ return null;
94
+ }
95
+
96
+ if (typeof value === 'string') {
97
+ const sanitized = sanitizeUnicodeString(value);
98
+ try {
99
+ const parsed = JSON.parse(sanitized);
100
+ const normalizedJson = JSON.stringify(parsed, createSafeJsonReplacer());
101
+ return normalizedJson === undefined ? null : normalizedJson;
102
+ } catch {
103
+ return JSON.stringify(sanitized);
104
+ }
105
+ }
106
+
107
+ try {
108
+ const json = JSON.stringify(value, createSafeJsonReplacer());
109
+ return json === undefined ? null : json;
110
+ } catch {
111
+ return JSON.stringify('[Unserializable]');
112
+ }
113
+ };
@@ -0,0 +1,40 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { sanitizeUnicodeString, toSafeJsonColumnValue } from './jsonSanitizer.js';
4
+
5
+ test('sanitizeUnicodeString mantem pares surrogate validos', () => {
6
+ const input = 'emoji \uD83D\uDE00 ok';
7
+ assert.equal(sanitizeUnicodeString(input), input);
8
+ });
9
+
10
+ test('sanitizeUnicodeString substitui surrogates invalidos por U+FFFD', () => {
11
+ const input = `invalido \ud800 meio \udc00 fim`;
12
+ const output = sanitizeUnicodeString(input);
13
+ assert.equal(output, 'invalido \uFFFD meio \uFFFD fim');
14
+ });
15
+
16
+ test('toSafeJsonColumnValue sanitiza objeto com surrogate invalido', () => {
17
+ const raw = {
18
+ text: `abc\ud800def`,
19
+ nested: { note: `x\udc00y` },
20
+ };
21
+
22
+ const json = toSafeJsonColumnValue(raw);
23
+ assert.equal(typeof json, 'string');
24
+
25
+ const parsed = JSON.parse(json);
26
+ assert.equal(parsed.text, 'abc\uFFFDdef');
27
+ assert.equal(parsed.nested.note, 'x\uFFFDy');
28
+ });
29
+
30
+ test('toSafeJsonColumnValue sanitiza JSON string com escape de surrogate invalido', () => {
31
+ const raw = '{"text":"\\ud800"}';
32
+ const json = toSafeJsonColumnValue(raw);
33
+ const parsed = JSON.parse(json);
34
+ assert.equal(parsed.text, '\uFFFD');
35
+ });
36
+
37
+ test('toSafeJsonColumnValue transforma texto simples em JSON string valido', () => {
38
+ const json = toSafeJsonColumnValue('texto simples');
39
+ assert.equal(json, '"texto simples"');
40
+ });
@@ -0,0 +1,262 @@
1
+ import 'dotenv/config';
2
+
3
+ import winston from 'winston';
4
+ import DailyRotateFile from 'winston-daily-rotate-file';
5
+ import { cleanEnv, str } from 'envalid';
6
+
7
+ import util from 'node:util';
8
+ import fs from 'node:fs';
9
+
10
+ import { fileURLToPath } from 'node:url';
11
+ import path from 'node:path';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ const LOG_LEVELS = { error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6 };
17
+ const LOG_LEVEL_NAMES = Object.keys(LOG_LEVELS);
18
+
19
+ const env = cleanEnv(process.env, {
20
+ NODE_ENV: str({
21
+ choices: ['development', 'production', 'test'],
22
+ default: 'development',
23
+ desc: 'Node environment',
24
+ }),
25
+ LOG_LEVEL: str({
26
+ choices: LOG_LEVEL_NAMES,
27
+ default: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
28
+ desc: 'Logging level',
29
+ }),
30
+ ECOSYSTEM_NAME: str({ default: 'system', desc: 'Service name for logs' }),
31
+ name: str({ default: undefined, desc: 'PM2 application name (standard)' }),
32
+ PM2_INSTANCE_ID: str({ default: undefined, desc: 'PM2 instance ID (standard)' }),
33
+ NODE_APP_INSTANCE: str({ default: undefined, desc: 'PM2 instance ID (alternative)' }),
34
+ pm_id: str({ default: undefined, desc: 'PM2 instance ID (legacy)' }),
35
+ });
36
+
37
+ const _IS_PRODUCTION = env.NODE_ENV === 'production';
38
+ const DEFAULT_LOG_LEVEL = env.LOG_LEVEL;
39
+ const INSTANCE_ID = env.PM2_INSTANCE_ID ?? env.NODE_APP_INSTANCE ?? env.pm_id ?? 'local';
40
+ const ECOSYSTEM_NAME = env.name ?? env.ECOSYSTEM_NAME ?? 'system';
41
+ const NODE_ENV = env.NODE_ENV;
42
+
43
+ const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');
44
+ const LOG_DIR_PATH = path.join(PROJECT_ROOT, 'logs');
45
+
46
+ const LOG_DEFAULTS = {
47
+ LOG_DIR: LOG_DIR_PATH,
48
+ APP_LOG_FILENAME: 'application-%DATE%.log',
49
+ ERROR_LOG_FILENAME: 'error-%DATE%.log',
50
+ WARN_LOG_FILENAME: 'warn-%DATE%.log',
51
+ DATE_PATTERN: 'YYYY-MM-DD',
52
+ ZIPPED_ARCHIVE: true,
53
+ MAX_SIZE_APP: '20m',
54
+ MAX_FILES_APP: '14d',
55
+ MAX_SIZE_ERROR: '10m',
56
+ MAX_FILES_ERROR: '30d',
57
+ MAX_SIZE_WARN: '10m',
58
+ MAX_FILES_WARN: '14d',
59
+ FILE_PERMISSIONS: 0o777,
60
+ DIR_PERMISSIONS: 0o777,
61
+ };
62
+
63
+ const LOG_COLORS = {
64
+ error: 'red',
65
+ warn: 'yellow',
66
+ info: 'green',
67
+ http: 'magenta',
68
+ verbose: 'cyan',
69
+ debug: 'blue',
70
+ silly: 'grey',
71
+ };
72
+ winston.addColors(LOG_COLORS);
73
+
74
+ const consoleFormat = winston.format.combine(
75
+ winston.format.timestamp({ format: () => new Date().toISOString() }),
76
+ winston.format.colorize({ all: true }),
77
+ winston.format.splat(),
78
+ winston.format.metadata({
79
+ fillExcept: [
80
+ 'message',
81
+ 'level',
82
+ 'timestamp',
83
+ 'label',
84
+ 'service',
85
+ 'instanceId',
86
+ 'environment',
87
+ 'stack',
88
+ ],
89
+ }),
90
+ winston.format.printf((info) => {
91
+ const { timestamp, level, message, metadata, stack } = info;
92
+ const label = metadata?.label;
93
+ const labelPart = label ? ` [${label}]` : '';
94
+ const metaToPrint = { ...metadata };
95
+ if (label) {
96
+ delete metaToPrint.label;
97
+ }
98
+
99
+ const metaPart =
100
+ Object.keys(metaToPrint).length > 0
101
+ ? ` ${util.inspect(metaToPrint, { colors: true, depth: 2 })}`
102
+ : '';
103
+
104
+ const servicePart = info.service ? ` [${info.service}]` : '';
105
+ const instancePart = info.instanceId ? ` [${info.instanceId}]` : '';
106
+
107
+ const stackPart = stack ? `\n${stack}` : '';
108
+
109
+ return `[${timestamp}] [${level}]${servicePart}${instancePart}${labelPart} - ${message}${metaPart}${stackPart}`;
110
+ }),
111
+ );
112
+
113
+ const fileFormat = winston.format.combine(
114
+ winston.format.timestamp(),
115
+ winston.format.splat(),
116
+ winston.format.errors({ stack: true }),
117
+ winston.format.metadata({
118
+ fillExcept: ['message', 'level', 'timestamp', 'service', 'instanceId', 'environment', 'stack'],
119
+ }),
120
+ winston.format.json(),
121
+ );
122
+
123
+ const getDefaultTransportDefinitions = (level) => [
124
+ {
125
+ type: 'console',
126
+ options: {
127
+ level: level,
128
+ format: consoleFormat,
129
+ handleExceptions: true,
130
+ handleRejections: true,
131
+ },
132
+ },
133
+ {
134
+ type: 'dailyRotateFile',
135
+ options: {
136
+ filename: path.join(LOG_DEFAULTS.LOG_DIR, LOG_DEFAULTS.APP_LOG_FILENAME),
137
+ datePattern: LOG_DEFAULTS.DATE_PATTERN,
138
+ zippedArchive: LOG_DEFAULTS.ZIPPED_ARCHIVE,
139
+ maxSize: LOG_DEFAULTS.MAX_SIZE_APP,
140
+ maxFiles: LOG_DEFAULTS.MAX_FILES_APP,
141
+ level: level,
142
+ format: fileFormat,
143
+ mode: LOG_DEFAULTS.FILE_PERMISSIONS,
144
+ },
145
+ },
146
+ {
147
+ type: 'dailyRotateFile',
148
+ options: {
149
+ filename: path.join(LOG_DEFAULTS.LOG_DIR, LOG_DEFAULTS.ERROR_LOG_FILENAME),
150
+ level: 'error',
151
+ datePattern: LOG_DEFAULTS.DATE_PATTERN,
152
+ zippedArchive: LOG_DEFAULTS.ZIPPED_ARCHIVE,
153
+ maxSize: LOG_DEFAULTS.MAX_SIZE_ERROR,
154
+ maxFiles: LOG_DEFAULTS.MAX_FILES_ERROR,
155
+ format: fileFormat,
156
+ mode: LOG_DEFAULTS.FILE_PERMISSIONS,
157
+ handleExceptions: true,
158
+ handleRejections: true,
159
+ },
160
+ },
161
+ {
162
+ type: 'dailyRotateFile',
163
+ options: {
164
+ filename: path.join(LOG_DEFAULTS.LOG_DIR, LOG_DEFAULTS.WARN_LOG_FILENAME),
165
+ level: 'warn',
166
+ datePattern: LOG_DEFAULTS.DATE_PATTERN,
167
+ zippedArchive: LOG_DEFAULTS.ZIPPED_ARCHIVE,
168
+ maxSize: LOG_DEFAULTS.MAX_SIZE_WARN,
169
+ maxFiles: LOG_DEFAULTS.MAX_FILES_WARN,
170
+ format: fileFormat,
171
+ mode: LOG_DEFAULTS.FILE_PERMISSIONS,
172
+ },
173
+ },
174
+ ];
175
+
176
+ function ensureLogDirectoryExists() {
177
+ const logDir = LOG_DEFAULTS.LOG_DIR;
178
+ const dirMode = LOG_DEFAULTS.DIR_PERMISSIONS;
179
+
180
+ try {
181
+ fs.mkdirSync(logDir, { recursive: true, mode: dirMode });
182
+
183
+ console.log(
184
+ `[ LoggerSetup ] Diretório de log garantido: '${logDir}' (modo ${dirMode.toString(8)})`,
185
+ );
186
+ } catch (error) {
187
+ throw new Error(
188
+ `Falha na configuração do Logger: Não foi possível acessar/criar o diretório de log '${logDir}'. Erro original: ${error.message}`,
189
+ );
190
+ }
191
+ }
192
+
193
+ const createLoggerInstance = (overrideOptions = {}) => {
194
+ ensureLogDirectoryExists();
195
+
196
+ const effectiveLevel = overrideOptions.level || DEFAULT_LOG_LEVEL;
197
+
198
+ let configuredTransports;
199
+ if (overrideOptions.transports) {
200
+ configuredTransports = overrideOptions.transports;
201
+ } else {
202
+ const transportDefinitions =
203
+ overrideOptions.transportDefinitions || getDefaultTransportDefinitions(effectiveLevel);
204
+ configuredTransports = transportDefinitions
205
+ .map((def) => {
206
+ try {
207
+ switch (def.type) {
208
+ case 'console':
209
+ return new winston.transports.Console(def.options);
210
+ case 'dailyRotateFile':
211
+ return new DailyRotateFile(def.options);
212
+ default:
213
+ console.warn(
214
+ `[ LoggerSetup ] Tipo de transporte desconhecido: ${def.type}. Pulando.`,
215
+ );
216
+ return null;
217
+ }
218
+ } catch (error) {
219
+ console.error(
220
+ `[ LoggerSetup ] Falha ao criar transporte tipo ${def.type}: ${error.message}`,
221
+ error,
222
+ );
223
+ return null;
224
+ }
225
+ })
226
+ .filter(Boolean);
227
+ }
228
+
229
+ const baseDefaultMeta = {
230
+ service: ECOSYSTEM_NAME,
231
+ instanceId: INSTANCE_ID,
232
+ environment: NODE_ENV,
233
+ };
234
+
235
+ const defaultMeta = { ...baseDefaultMeta, ...(overrideOptions.defaultMeta || {}) };
236
+
237
+ const loggerFormat =
238
+ overrideOptions.format || winston.format.combine(winston.format.errors({ stack: true }));
239
+
240
+ const loggerInstance = winston.createLogger({
241
+ level: effectiveLevel,
242
+ levels: LOG_LEVELS,
243
+ format: loggerFormat,
244
+ defaultMeta: defaultMeta,
245
+ transports: configuredTransports,
246
+ exitOnError: false,
247
+ });
248
+
249
+ loggerInstance.on('error', (error) => {
250
+ console.error('Erro ocorrido dentro do Winston Logger:', error);
251
+ });
252
+
253
+ console.log(
254
+ `[ LoggerSetup ] Instância do Logger criada. Nível: ${effectiveLevel}, Env: ${NODE_ENV}, Instância: ${INSTANCE_ID}, Serviço: ${ECOSYSTEM_NAME}`,
255
+ );
256
+
257
+ return loggerInstance;
258
+ };
259
+
260
+ const logger = createLoggerInstance();
261
+
262
+ export default logger;
@@ -0,0 +1,91 @@
1
+ import os from 'node:os';
2
+ import process from 'node:process';
3
+
4
+ const formatBytes = (bytes) => {
5
+ if (bytes === 0) return '0 Bytes';
6
+ const k = 1024;
7
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
8
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
9
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
10
+ };
11
+
12
+ const formatDuration = (totalSeconds) => {
13
+ const total = Math.max(0, Math.floor(totalSeconds));
14
+ const days = Math.floor(total / 86400);
15
+ const hours = Math.floor((total % 86400) / 3600);
16
+ const minutes = Math.floor((total % 3600) / 60);
17
+ const seconds = total % 60;
18
+
19
+ const time = [hours, minutes, seconds].map((value) => String(value).padStart(2, '0')).join(':');
20
+ return days > 0 ? `${days}d ${time}` : time;
21
+ };
22
+
23
+ export const getSystemMetrics = () => {
24
+ const totalMemory = os.totalmem();
25
+ const freeMemory = os.freemem();
26
+ const usedMemory = totalMemory - freeMemory;
27
+ const memoryUsagePercentage = (usedMemory / totalMemory) * 100;
28
+
29
+ const cpus = os.cpus();
30
+ const totalCpus = cpus.length;
31
+ let totalIdle = 0;
32
+ let totalTick = 0;
33
+
34
+ for (const cpu of cpus) {
35
+ for (const type in cpu.times) {
36
+ totalTick += cpu.times[type];
37
+ }
38
+ totalIdle += cpu.times.idle;
39
+ }
40
+
41
+ const cpuUsagePercentage = 100 - (totalIdle / totalTick) * 100;
42
+
43
+ const processUptimeSeconds = process.uptime();
44
+ const systemUptimeSeconds = os.uptime();
45
+ const uptime = formatDuration(processUptimeSeconds);
46
+ const systemUptime = formatDuration(systemUptimeSeconds);
47
+ const loadAverage = os.loadavg();
48
+ const cpuSpeedAverage =
49
+ totalCpus > 0
50
+ ? Math.round(cpus.reduce((sum, cpu) => sum + (cpu.speed || 0), 0) / totalCpus)
51
+ : 0;
52
+ const memoryUsage = process.memoryUsage();
53
+
54
+ return {
55
+ usoCpuPercentual: parseFloat(cpuUsagePercentage.toFixed(2)),
56
+ cargaMedia: loadAverage.map((value) => parseFloat(value.toFixed(2))),
57
+ totalCpus,
58
+ cpuModelo: cpus[0]?.model || 'Desconhecido',
59
+ cpuVelocidadeMediaMHz: cpuSpeedAverage,
60
+
61
+ usoMemoriaPercentual: parseFloat(memoryUsagePercentage.toFixed(2)),
62
+ memoriaTotal: formatBytes(totalMemory),
63
+ memoriaLivre: formatBytes(freeMemory),
64
+ memoriaUsada: formatBytes(usedMemory),
65
+ memoriaProcesso: {
66
+ rss: formatBytes(memoryUsage.rss),
67
+ heapTotal: formatBytes(memoryUsage.heapTotal),
68
+ heapUsado: formatBytes(memoryUsage.heapUsed),
69
+ external: formatBytes(memoryUsage.external),
70
+ arrayBuffers: formatBytes(memoryUsage.arrayBuffers || 0),
71
+ },
72
+
73
+ uptime,
74
+ uptimeSistema: systemUptime,
75
+ uptimeSegundos: Math.floor(processUptimeSeconds),
76
+ uptimeSistemaSegundos: Math.floor(systemUptimeSeconds),
77
+ plataforma: os.platform(),
78
+ arquitetura: os.arch(),
79
+ hostname: os.hostname(),
80
+ tipo: os.type(),
81
+ release: os.release(),
82
+ versaoNode: process.version,
83
+ nodeEnv: process.env.NODE_ENV || 'development',
84
+ pid: process.pid,
85
+ ppid: process.ppid,
86
+ cpus: cpus.map((cpu) => ({
87
+ modelo: cpu.model,
88
+ velocidade: cpu.speed,
89
+ })),
90
+ };
91
+ };