@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
package/.env.example CHANGED
@@ -434,6 +434,11 @@ WHATSAPP_LOGIN_PATH=/login/
434
434
  WHATSAPP_LOGIN_LINK_SECRET=troque_por_um_segredo_forte
435
435
  WHATSAPP_LOGIN_LINK_TTL_SECONDS=900
436
436
  WHATSAPP_LOGIN_REQUIRE_SIGNATURE=true
437
+ # Canonical/cookies do catalogo web (necessario para compartilhar sessao entre www e dominio raiz)
438
+ SITE_CANONICAL_HOST=omnizap.shop
439
+ SITE_CANONICAL_SCHEME=https
440
+ SITE_CANONICAL_REDIRECT_ENABLED=true
441
+ SITE_COOKIE_DOMAIN=omnizap.shop
437
442
 
438
443
  # Paths locais (legado/uso auxiliar)
439
444
  STORE_PATH=./temp
package/README.md CHANGED
@@ -53,27 +53,27 @@ Atualização em cache: **30 minutos** por padrão (`README_SUMMARY_CACHE_SECOND
53
53
  <!-- README_SNAPSHOT:START -->
54
54
  ### Snapshot do Sistema
55
55
 
56
- > Atualizado em `2026-02-28T01:18:30.283Z` | cache `1800s`
56
+ > Atualizado em `2026-02-28T04:28:50.529Z` | cache `1800s`
57
57
 
58
58
  | Métrica | Valor |
59
59
  | --- | ---: |
60
- | Usuários (lid_map) | 5.502 |
60
+ | Usuários (lid_map) | 5.504 |
61
61
  | Grupos | 116 |
62
- | Packs | 295 |
63
- | Stickers | 6.314 |
64
- | Mensagens registradas | 438.975 |
62
+ | Packs | 294 |
63
+ | Stickers | 6.796 |
64
+ | Mensagens registradas | 440.582 |
65
65
 
66
66
  #### Tipos de mensagem mais usados (amostra: 25.000)
67
67
  | Tipo | Total |
68
68
  | --- | ---: |
69
- | `texto` | 16.526 |
70
- | `figurinha` | 4.263 |
71
- | `reacao` | 1.619 |
72
- | `imagem` | 1.326 |
73
- | `outros` | 800 |
74
- | `video` | 245 |
75
- | `audio` | 215 |
76
- | `documento` | 6 |
69
+ | `texto` | 16.293 |
70
+ | `figurinha` | 4.718 |
71
+ | `reacao` | 1.506 |
72
+ | `imagem` | 1.286 |
73
+ | `outros` | 744 |
74
+ | `video` | 232 |
75
+ | `audio` | 216 |
76
+ | `documento` | 5 |
77
77
 
78
78
  <details><summary>Comandos disponíveis (62)</summary>
79
79
 
@@ -0,0 +1,68 @@
1
+ export const handleCatalogAdminRoutes = async ({
2
+ req,
3
+ res,
4
+ url,
5
+ segments,
6
+ handlers,
7
+ sendJson,
8
+ }) => {
9
+ if (segments[0] !== 'admin') return false;
10
+
11
+ if (segments.length === 2 && segments[1] === 'overview') {
12
+ await handlers.handleAdminOverviewRequest(req, res);
13
+ return true;
14
+ }
15
+
16
+ if (segments.length === 2 && segments[1] === 'users') {
17
+ await handlers.handleAdminUsersRequest(req, res, url);
18
+ return true;
19
+ }
20
+
21
+ if (segments.length === 2 && segments[1] === 'moderators') {
22
+ await handlers.handleAdminModeratorsRequest(req, res);
23
+ return true;
24
+ }
25
+
26
+ if (segments.length === 3 && segments[1] === 'moderators') {
27
+ await handlers.handleAdminModeratorDeleteRequest(req, res, segments[2]);
28
+ return true;
29
+ }
30
+
31
+ if (segments.length === 2 && segments[1] === 'packs') {
32
+ await handlers.handleAdminPacksRequest(req, res, url);
33
+ return true;
34
+ }
35
+
36
+ if (segments.length === 3 && segments[1] === 'packs') {
37
+ await handlers.handleAdminPackDetailsRequest(req, res, segments[2]);
38
+ return true;
39
+ }
40
+
41
+ if (segments.length === 4 && segments[1] === 'packs' && segments[3] === 'delete') {
42
+ await handlers.handleAdminPackDeleteRequest(req, res, segments[2]);
43
+ return true;
44
+ }
45
+
46
+ if (segments.length === 6 && segments[1] === 'packs' && segments[3] === 'stickers' && segments[5] === 'delete') {
47
+ await handlers.handleAdminPackStickerDeleteRequest(req, res, segments[2], segments[4]);
48
+ return true;
49
+ }
50
+
51
+ if (segments.length === 4 && segments[1] === 'stickers' && segments[3] === 'delete') {
52
+ await handlers.handleAdminGlobalStickerDeleteRequest(req, res, segments[2]);
53
+ return true;
54
+ }
55
+
56
+ if (segments.length === 2 && segments[1] === 'bans') {
57
+ await handlers.handleAdminBansRequest(req, res);
58
+ return true;
59
+ }
60
+
61
+ if (segments.length === 4 && segments[1] === 'bans' && segments[3] === 'revoke') {
62
+ await handlers.handleAdminBanRevokeRequest(req, res, segments[2]);
63
+ return true;
64
+ }
65
+
66
+ sendJson(req, res, 404, { error: 'Rota admin nao encontrada.' });
67
+ return true;
68
+ };
@@ -0,0 +1,34 @@
1
+ const METHOD_NOT_ALLOWED_BODY = { error: 'Metodo nao permitido.' };
2
+
3
+ const isReadMethod = (method) => method === 'GET' || method === 'HEAD';
4
+
5
+ export const handleCatalogAuthRoutes = async ({
6
+ req,
7
+ res,
8
+ pathname,
9
+ url,
10
+ apiBasePath,
11
+ handlers,
12
+ sendJson,
13
+ }) => {
14
+ if (pathname === `${apiBasePath}/auth/google/session`) {
15
+ await handlers.handleGoogleAuthSessionRequest(req, res);
16
+ return true;
17
+ }
18
+
19
+ if (pathname === `${apiBasePath}/me`) {
20
+ if (!isReadMethod(req.method || '')) {
21
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
22
+ return true;
23
+ }
24
+ await handlers.handleMyProfileRequest(req, res, url);
25
+ return true;
26
+ }
27
+
28
+ if (pathname === `${apiBasePath}/admin/session`) {
29
+ await handlers.handleAdminPanelSessionRequest(req, res);
30
+ return true;
31
+ }
32
+
33
+ return false;
34
+ };
@@ -0,0 +1,179 @@
1
+ const METHOD_NOT_ALLOWED_BODY = { error: 'Metodo nao permitido.' };
2
+
3
+ const isReadMethod = (method) => method === 'GET' || method === 'HEAD';
4
+
5
+ export const handleCatalogPublicRoutes = async ({
6
+ req,
7
+ res,
8
+ pathname,
9
+ url,
10
+ segments,
11
+ apiBasePath,
12
+ orphanApiPath,
13
+ handlers,
14
+ sendJson,
15
+ }) => {
16
+ if (pathname === apiBasePath) {
17
+ if (!isReadMethod(req.method || '')) {
18
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
19
+ return true;
20
+ }
21
+ await handlers.handleListRequest(req, res, url);
22
+ return true;
23
+ }
24
+
25
+ if (pathname === `${apiBasePath}/intents`) {
26
+ if (!isReadMethod(req.method || '')) {
27
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
28
+ return true;
29
+ }
30
+ await handlers.handleIntentCollectionsRequest(req, res, url);
31
+ return true;
32
+ }
33
+
34
+ if (pathname === `${apiBasePath}/creators`) {
35
+ if (!isReadMethod(req.method || '')) {
36
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
37
+ return true;
38
+ }
39
+ await handlers.handleCreatorRankingRequest(req, res, url);
40
+ return true;
41
+ }
42
+
43
+ if (pathname === `${apiBasePath}/recommendations`) {
44
+ if (!isReadMethod(req.method || '')) {
45
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
46
+ return true;
47
+ }
48
+ await handlers.handleRecommendationsRequest(req, res, url);
49
+ return true;
50
+ }
51
+
52
+ if (pathname === `${apiBasePath}/stats`) {
53
+ if (!isReadMethod(req.method || '')) {
54
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
55
+ return true;
56
+ }
57
+ await handlers.handleMarketplaceStatsRequest(req, res, url);
58
+ return true;
59
+ }
60
+
61
+ if (pathname === `${apiBasePath}/create-config`) {
62
+ if (!isReadMethod(req.method || '')) {
63
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
64
+ return true;
65
+ }
66
+ await handlers.handleCreatePackConfigRequest(req, res);
67
+ return true;
68
+ }
69
+
70
+ if (pathname === orphanApiPath) {
71
+ if (!isReadMethod(req.method || '')) {
72
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
73
+ return true;
74
+ }
75
+ await handlers.handleOrphanStickerListRequest(req, res, url);
76
+ return true;
77
+ }
78
+
79
+ if (segments.length === 1 && segments[0] === 'data-files') {
80
+ if (!isReadMethod(req.method || '')) {
81
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
82
+ return true;
83
+ }
84
+ await handlers.handleDataFileListRequest(req, res, url);
85
+ return true;
86
+ }
87
+
88
+ if (segments.length === 1 && segments[0] === 'system-summary') {
89
+ if (!isReadMethod(req.method || '')) {
90
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
91
+ return true;
92
+ }
93
+ await handlers.handleSystemSummaryRequest(req, res);
94
+ return true;
95
+ }
96
+
97
+ if (segments.length === 1 && segments[0] === 'project-summary') {
98
+ if (!isReadMethod(req.method || '')) {
99
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
100
+ return true;
101
+ }
102
+ await handlers.handleGitHubProjectSummaryRequest(req, res);
103
+ return true;
104
+ }
105
+
106
+ if (segments.length === 1 && segments[0] === 'global-ranking-summary') {
107
+ if (!isReadMethod(req.method || '')) {
108
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
109
+ return true;
110
+ }
111
+ await handlers.handleGlobalRankingSummaryRequest(req, res);
112
+ return true;
113
+ }
114
+
115
+ if (segments.length === 1 && segments[0] === 'readme-summary') {
116
+ if (!isReadMethod(req.method || '')) {
117
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
118
+ return true;
119
+ }
120
+ await handlers.handleReadmeSummaryRequest(req, res);
121
+ return true;
122
+ }
123
+
124
+ if (segments.length === 1 && segments[0] === 'readme-markdown') {
125
+ if (!isReadMethod(req.method || '')) {
126
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
127
+ return true;
128
+ }
129
+ await handlers.handleReadmeMarkdownRequest(req, res);
130
+ return true;
131
+ }
132
+
133
+ if (segments.length === 1 && segments[0] === 'support') {
134
+ if (!isReadMethod(req.method || '')) {
135
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
136
+ return true;
137
+ }
138
+ await handlers.handleSupportInfoRequest(req, res);
139
+ return true;
140
+ }
141
+
142
+ if (segments.length === 1 && segments[0] === 'bot-contact') {
143
+ if (!isReadMethod(req.method || '')) {
144
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
145
+ return true;
146
+ }
147
+ await handlers.handleBotContactInfoRequest(req, res);
148
+ return true;
149
+ }
150
+
151
+ if (segments.length === 1) {
152
+ if (!isReadMethod(req.method || '')) {
153
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
154
+ return true;
155
+ }
156
+ await handlers.handleDetailsRequest(req, res, segments[0], url);
157
+ return true;
158
+ }
159
+
160
+ if (segments.length === 2 && ['open', 'like', 'dislike'].includes(segments[1])) {
161
+ if (req.method !== 'POST') {
162
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
163
+ return true;
164
+ }
165
+ await handlers.handlePackInteractionRequest(req, res, segments[0], segments[1], url);
166
+ return true;
167
+ }
168
+
169
+ if (segments.length === 3 && segments[1] === 'stickers') {
170
+ if (!isReadMethod(req.method || '')) {
171
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
172
+ return true;
173
+ }
174
+ await handlers.handleAssetRequest(req, res, segments[0], segments[2]);
175
+ return true;
176
+ }
177
+
178
+ return false;
179
+ };
@@ -0,0 +1,92 @@
1
+ const METHOD_NOT_ALLOWED_BODY = { error: 'Metodo nao permitido.' };
2
+
3
+ const isPublishStateMethod = (method) => method === 'GET' || method === 'HEAD' || method === 'POST';
4
+
5
+ export const handleCatalogUploadRoutes = async ({
6
+ req,
7
+ res,
8
+ pathname,
9
+ url,
10
+ segments,
11
+ apiBasePath,
12
+ handlers,
13
+ sendJson,
14
+ }) => {
15
+ if (pathname === `${apiBasePath}/create`) {
16
+ if (req.method !== 'POST') {
17
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
18
+ return true;
19
+ }
20
+ await handlers.handleCreatePackRequest(req, res);
21
+ return true;
22
+ }
23
+
24
+ if (segments.length === 2 && segments[1] === 'manage') {
25
+ await handlers.handleManagedPackRequest(req, res, segments[0]);
26
+ return true;
27
+ }
28
+
29
+ if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'clone') {
30
+ await handlers.handleManagedPackCloneRequest(req, res, segments[0]);
31
+ return true;
32
+ }
33
+
34
+ if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'cover') {
35
+ await handlers.handleManagedPackCoverRequest(req, res, segments[0]);
36
+ return true;
37
+ }
38
+
39
+ if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'reorder') {
40
+ await handlers.handleManagedPackReorderRequest(req, res, segments[0]);
41
+ return true;
42
+ }
43
+
44
+ if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'analytics') {
45
+ await handlers.handleManagedPackAnalyticsRequest(req, res, segments[0]);
46
+ return true;
47
+ }
48
+
49
+ if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'stickers') {
50
+ await handlers.handleManagedPackStickerCreateRequest(req, res, segments[0]);
51
+ return true;
52
+ }
53
+
54
+ if (segments.length === 4 && segments[1] === 'manage' && segments[2] === 'stickers') {
55
+ await handlers.handleManagedPackStickerDeleteRequest(req, res, segments[0], segments[3]);
56
+ return true;
57
+ }
58
+
59
+ if (segments.length === 5 && segments[1] === 'manage' && segments[2] === 'stickers' && segments[4] === 'replace') {
60
+ await handlers.handleManagedPackStickerReplaceRequest(req, res, segments[0], segments[3]);
61
+ return true;
62
+ }
63
+
64
+ if (segments.length === 2 && segments[1] === 'publish-state') {
65
+ if (!isPublishStateMethod(req.method || '')) {
66
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
67
+ return true;
68
+ }
69
+ await handlers.handlePackPublishStateRequest(req, res, segments[0], url);
70
+ return true;
71
+ }
72
+
73
+ if (segments.length === 2 && segments[1] === 'finalize') {
74
+ if (req.method !== 'POST') {
75
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
76
+ return true;
77
+ }
78
+ await handlers.handleFinalizePackRequest(req, res, segments[0]);
79
+ return true;
80
+ }
81
+
82
+ if (segments.length === 2 && segments[1] === 'stickers-upload') {
83
+ if (req.method !== 'POST') {
84
+ sendJson(req, res, 405, METHOD_NOT_ALLOWED_BODY);
85
+ return true;
86
+ }
87
+ await handlers.handleUploadStickerToPackRequest(req, res, segments[0]);
88
+ return true;
89
+ }
90
+
91
+ return false;
92
+ };
@@ -0,0 +1,79 @@
1
+ import { handleCatalogAuthRoutes } from './catalogHandlers/catalogAuthHttp.js';
2
+ import { handleCatalogAdminRoutes } from './catalogHandlers/catalogAdminHttp.js';
3
+ import { handleCatalogUploadRoutes } from './catalogHandlers/catalogUploadHttp.js';
4
+ import { handleCatalogPublicRoutes } from './catalogHandlers/catalogPublicHttp.js';
5
+
6
+ const decodePathSegments = (suffix) =>
7
+ suffix.split('/').filter(Boolean).map((segment) => {
8
+ try {
9
+ return decodeURIComponent(segment);
10
+ } catch {
11
+ return segment;
12
+ }
13
+ });
14
+
15
+ export const createCatalogApiRouter = ({
16
+ apiBasePath,
17
+ orphanApiPath,
18
+ handlers,
19
+ sendJson,
20
+ }) => {
21
+ if (!apiBasePath || typeof handlers !== 'object' || typeof sendJson !== 'function') {
22
+ throw new Error('catalog_api_router_config_invalid');
23
+ }
24
+
25
+ return async ({ req, res, pathname, url }) => {
26
+ const handledAuth = await handleCatalogAuthRoutes({
27
+ req,
28
+ res,
29
+ pathname,
30
+ url,
31
+ apiBasePath,
32
+ handlers,
33
+ sendJson,
34
+ });
35
+ if (handledAuth) return true;
36
+ if (!pathname.startsWith(apiBasePath)) return false;
37
+
38
+ const suffix = pathname.slice(apiBasePath.length).replace(/^\/+/, '');
39
+ const segments = decodePathSegments(suffix);
40
+
41
+ const handledAdmin = await handleCatalogAdminRoutes({
42
+ req,
43
+ res,
44
+ url,
45
+ segments,
46
+ handlers,
47
+ sendJson,
48
+ });
49
+ if (handledAdmin) return true;
50
+
51
+ const handledUpload = await handleCatalogUploadRoutes({
52
+ req,
53
+ res,
54
+ pathname,
55
+ url,
56
+ segments,
57
+ apiBasePath,
58
+ handlers,
59
+ sendJson,
60
+ });
61
+ if (handledUpload) return true;
62
+
63
+ const handledPublic = await handleCatalogPublicRoutes({
64
+ req,
65
+ res,
66
+ pathname,
67
+ url,
68
+ segments,
69
+ apiBasePath,
70
+ orphanApiPath,
71
+ handlers,
72
+ sendJson,
73
+ });
74
+ if (handledPublic) return true;
75
+
76
+ sendJson(req, res, 404, { error: 'Rota de sticker pack nao encontrada.' });
77
+ return true;
78
+ };
79
+ };