@kaikybrofc/omnizap-system 2.2.4 → 2.2.6

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 (55) hide show
  1. package/.env.example +5 -0
  2. package/.prettierrc +16 -0
  3. package/README.md +13 -13
  4. package/app/modules/stickerPackModule/autoPackCollectorService.js +63 -8
  5. package/app/modules/stickerPackModule/catalogHandlers/catalogAdminHttp.js +68 -0
  6. package/app/modules/stickerPackModule/catalogHandlers/catalogAuthHttp.js +34 -0
  7. package/app/modules/stickerPackModule/catalogHandlers/catalogPublicHttp.js +179 -0
  8. package/app/modules/stickerPackModule/catalogHandlers/catalogUploadHttp.js +92 -0
  9. package/app/modules/stickerPackModule/catalogRouter.js +79 -0
  10. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +243 -0
  11. package/app/modules/stickerPackModule/domainEvents.js +61 -0
  12. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +21 -0
  13. package/app/modules/stickerPackModule/stickerAssetRepository.js +19 -0
  14. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +55 -15
  15. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +238 -0
  16. package/app/modules/stickerPackModule/stickerDomainEventBus.js +71 -0
  17. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +198 -0
  18. package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
  19. package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +570 -536
  20. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +44 -0
  21. package/app/modules/stickerPackModule/stickerPackItemRepository.js +18 -0
  22. package/app/modules/stickerPackModule/stickerPackRepository.js +51 -0
  23. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +191 -0
  24. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +301 -0
  25. package/app/modules/stickerPackModule/stickerStorageService.js +111 -10
  26. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +21 -0
  27. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +59 -7
  28. package/app/observability/metrics.js +169 -0
  29. package/app/services/featureFlagService.js +137 -0
  30. package/database/index.js +5 -0
  31. package/database/migrations/20260228_0022_sticker_scale_indexes.sql +16 -0
  32. package/database/migrations/20260228_0023_sticker_pack_score_snapshot.sql +25 -0
  33. package/database/migrations/20260228_0024_domain_event_outbox.sql +42 -0
  34. package/database/migrations/20260228_0025_sticker_worker_task_idempotency_dlq.sql +23 -0
  35. package/database/migrations/20260228_0026_feature_flags.sql +21 -0
  36. package/ecosystem.prod.config.cjs +70 -9
  37. package/index.js +26 -0
  38. package/kaikybrofc-omnizap-system-2.2.6.tgz +0 -0
  39. package/observability/sticker-catalog-slo.md +83 -0
  40. package/observability/sticker-scale-hardening-rollout.md +128 -0
  41. package/package.json +7 -35
  42. package/public/assets/images/brand-icon-192.png +0 -0
  43. package/public/assets/images/brand-logo-128.webp +0 -0
  44. package/public/assets/images/hero-banner-1280.avif +0 -0
  45. package/public/assets/images/hero-banner-1280.jpg +0 -0
  46. package/public/assets/images/hero-banner-1280.webp +0 -0
  47. package/public/assets/images/hero-banner-720.avif +0 -0
  48. package/public/assets/images/hero-banner-720.webp +0 -0
  49. package/public/index.html +120 -18
  50. package/public/js/apps/homeApp.js +469 -353
  51. package/public/robots.txt +9 -0
  52. package/public/sitemap.xml +28 -0
  53. package/scripts/sticker-catalog-loadtest.mjs +208 -0
  54. package/scripts/sticker-worker-task.mjs +122 -0
  55. package/observability/mysql-exporter.cnf +0 -5
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/.prettierrc ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "printWidth": 12000,
3
+ "tabWidth": 2,
4
+ "useTabs": false,
5
+ "semi": true,
6
+ "singleQuote": true,
7
+ "trailingComma": "all",
8
+ "bracketSpacing": true,
9
+ "arrowParens": "always",
10
+ "endOfLine": "lf",
11
+ "bracketSameLine": false,
12
+ "proseWrap": "preserve",
13
+ "htmlWhitespaceSensitivity": "css",
14
+ "quoteProps": "as-needed",
15
+ "embeddedLanguageFormatting": "auto"
16
+ }
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-28T06:26:02.459Z` | 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 | 293 |
63
+ | Stickers | 6.775 |
64
+ | Mensagens registradas | 440.765 |
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.279 |
70
+ | `figurinha` | 4.721 |
71
+ | `reacao` | 1.504 |
72
+ | `imagem` | 1.290 |
73
+ | `outros` | 757 |
74
+ | `video` | 228 |
75
+ | `audio` | 216 |
76
+ | `documento` | 5 |
77
77
 
78
78
  <details><summary>Comandos disponíveis (62)</summary>
79
79
 
@@ -8,6 +8,8 @@ const DEFAULT_AUTO_PACK_NAME = process.env.STICKER_PACK_AUTO_PACK_NAME || 'pack'
8
8
  const AUTO_PACK_TARGET_VISIBILITY = 'unlisted';
9
9
  const AUTO_COLLECT_ENABLED = process.env.STICKER_PACK_AUTO_COLLECT_ENABLED !== 'false';
10
10
  const AUTO_PACK_NAME_MAX_LENGTH = 120;
11
+ const AUTO_PACK_DESCRIPTION_MARKER = '[auto-pack:collector]';
12
+ const AUTO_PACK_DESCRIPTION_TEXT = 'Coleção automática de figurinhas criadas pelo usuário.';
11
13
  const normalizeVisibility = (value) => String(value || '').trim().toLowerCase();
12
14
 
13
15
  /**
@@ -43,6 +45,42 @@ const normalizeAutoPackName = (value, { fallback = 'pack', maxLength = AUTO_PACK
43
45
  return normalized || fallback;
44
46
  };
45
47
 
48
+ const buildAutoPackDescription = () => `${AUTO_PACK_DESCRIPTION_TEXT} ${AUTO_PACK_DESCRIPTION_MARKER}`.trim();
49
+
50
+ const isThemeCurationAutoPack = (pack) => {
51
+ if (!pack || typeof pack !== 'object') return false;
52
+ const description = String(pack.description || '').toLowerCase();
53
+ if (description.includes('[auto-theme:') || description.includes('[auto-tag:')) return true;
54
+ if (String(pack.name || '').trim().toLowerCase().startsWith('[auto]')) return true;
55
+ return Boolean(String(pack.pack_theme_key || '').trim());
56
+ };
57
+
58
+ const isAutoCollectorPack = (pack) => {
59
+ if (!pack || typeof pack !== 'object') return false;
60
+ if (isThemeCurationAutoPack(pack)) return false;
61
+
62
+ const description = String(pack.description || '').toLowerCase();
63
+ if (description.includes(AUTO_PACK_DESCRIPTION_MARKER)) return true;
64
+ if (description.includes('coleção automática de figurinhas criadas pelo usuário.')) return true;
65
+
66
+ const normalizedName = normalizeAutoPackName(pack.name, { fallback: '', maxLength: AUTO_PACK_NAME_MAX_LENGTH });
67
+ if (!normalizedName) return false;
68
+
69
+ const base = normalizeAutoPackName(DEFAULT_AUTO_PACK_NAME, { fallback: 'pack', maxLength: AUTO_PACK_NAME_MAX_LENGTH });
70
+ const matcher = new RegExp(`^${escapeRegex(base.toLowerCase())}\\d+$`, 'i');
71
+ const looksLikeLegacyCollector = matcher.test(normalizedName);
72
+ if (looksLikeLegacyCollector) return true;
73
+
74
+ return pack.is_auto_pack === true || Number(pack.is_auto_pack || 0) === 1;
75
+ };
76
+
77
+ const isUserManagedPackCandidate = (pack) => {
78
+ if (!pack || typeof pack !== 'object') return false;
79
+ if (isThemeCurationAutoPack(pack)) return false;
80
+ if (isAutoCollectorPack(pack)) return false;
81
+ return Number(pack.is_auto_pack || 0) !== 1;
82
+ };
83
+
46
84
  /**
47
85
  * Monta candidato incremental para nome de pack automático.
48
86
  *
@@ -177,21 +215,37 @@ export function createAutoPackCollector(options = {}) {
177
215
  const ensureTargetPack = async ({ ownerJid, senderName }) => {
178
216
  const packs = await deps.stickerPackService.listPacks({ ownerJid, limit: 30 });
179
217
  if (packs.length > 0) {
218
+ // Prioriza pack gerenciado pelo usuário (pack atual/manual).
219
+ const userManagedPacks = packs.filter((entry) => isUserManagedPackCandidate(entry));
220
+ if (userManagedPacks.length > 0) {
221
+ return {
222
+ pack: userManagedPacks[0],
223
+ packs,
224
+ };
225
+ }
226
+
227
+ const managedAutoPacks = packs.filter((entry) => isAutoCollectorPack(entry));
180
228
  const preferredPack =
181
- packs.find((entry) => normalizeVisibility(entry?.visibility) === AUTO_PACK_TARGET_VISIBILITY) || packs[0];
182
- const ensuredPack = await ensureAutoPackVisibility({ ownerJid, pack: preferredPack });
183
- return {
184
- pack: ensuredPack,
185
- packs,
186
- };
229
+ managedAutoPacks.find((entry) => normalizeVisibility(entry?.visibility) === AUTO_PACK_TARGET_VISIBILITY)
230
+ || managedAutoPacks[0]
231
+ || null;
232
+
233
+ if (preferredPack) {
234
+ const ensuredPack = await ensureAutoPackVisibility({ ownerJid, pack: preferredPack });
235
+ return {
236
+ pack: ensuredPack,
237
+ packs,
238
+ };
239
+ }
187
240
  }
188
241
 
189
242
  const created = await deps.stickerPackService.createPack({
190
243
  ownerJid,
191
244
  name: makeAutoPackName([]),
192
245
  publisher: sanitizeText(senderName, 120, { allowEmpty: true }) || 'OmniZap',
193
- description: 'Coleção automática de figurinhas criadas pelo usuário.',
246
+ description: buildAutoPackDescription(),
194
247
  visibility: AUTO_PACK_TARGET_VISIBILITY,
248
+ isAutoPack: true,
195
249
  });
196
250
 
197
251
  return {
@@ -250,8 +304,9 @@ export function createAutoPackCollector(options = {}) {
250
304
  ownerJid,
251
305
  name: makeAutoPackName(packs),
252
306
  publisher: sanitizeText(senderName, 120, { allowEmpty: true }) || targetPack.publisher || 'OmniZap',
253
- description: 'Coleção automática de figurinhas criadas pelo usuário.',
307
+ description: buildAutoPackDescription(),
254
308
  visibility: AUTO_PACK_TARGET_VISIBILITY,
309
+ isAutoPack: true,
255
310
  });
256
311
 
257
312
  const updated = await deps.stickerPackService.addStickerToPack({
@@ -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
+ };