@oondemand/oon-core-back 0.3.64 → 0.3.66

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oondemand/oon-core-back",
3
- "version": "0.3.64",
3
+ "version": "0.3.66",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/oondemand/oon-platform.git",
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
 
3
3
  const express = require("express");
4
+ const mongoose = require("mongoose");
4
5
  const GenericError = require("../../core/errors/GenericError");
5
6
  const auth = require("../../core/middlewares/auth");
6
7
  const { rbac } = require("../../core/middlewares/rbac");
7
8
  const { asyncHandler } = require("../../core/utils/helpers");
8
- const { getIntegrationModels } = require("./models");
9
+ const { getIntegrationModels, OUTBOX_STATUSES } = require("./models");
9
10
  const { getIntegrationProvider, listIntegrationProviders } = require("./registry");
10
11
  const {
11
12
  archiveIntegrationTicket,
@@ -33,6 +34,48 @@ function pagination(query = {}) {
33
34
  return { limit, page, skip: (page - 1) * limit };
34
35
  }
35
36
 
37
+ function escapeRegExp(value) {
38
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
39
+ }
40
+
41
+ function buildQueueFilter(query = {}, options = {}) {
42
+ const includeStatus = options.includeStatus !== false;
43
+ const filter = {};
44
+ for (const field of ["provider", "resource", "operation", "aggregateType", "aggregateId"]) {
45
+ if (query[field]) filter[field] = String(query[field]);
46
+ }
47
+ if (includeStatus && query.status) filter.status = String(query.status);
48
+
49
+ const search = String(query.search || "").trim().slice(0, 160);
50
+ if (search) {
51
+ const pattern = new RegExp(escapeRegExp(search), "i");
52
+ const clauses = [
53
+ { handler: pattern },
54
+ { resource: pattern },
55
+ { operation: pattern },
56
+ { aggregateType: pattern },
57
+ { aggregateId: pattern },
58
+ { idempotencyKey: pattern },
59
+ ];
60
+ if (mongoose.isValidObjectId(search)) clauses.unshift({ _id: new mongoose.Types.ObjectId(search) });
61
+ filter.$or = clauses;
62
+ }
63
+ return filter;
64
+ }
65
+
66
+ async function queueStatusSummary(Outbox, filter = {}) {
67
+ const rows = await Outbox.aggregate([
68
+ { $match: filter },
69
+ { $group: { _id: "$status", count: { $sum: 1 } } },
70
+ ]);
71
+ const statuses = Object.fromEntries(OUTBOX_STATUSES.map((status) => [status, 0]));
72
+ for (const row of rows) statuses[String(row._id)] = Number(row.count || 0);
73
+ return {
74
+ total: Object.values(statuses).reduce((sum, count) => sum + Number(count || 0), 0),
75
+ statuses,
76
+ };
77
+ }
78
+
36
79
  function requireProviderExtension(provider, method, message) {
37
80
  if (typeof provider[method] !== "function") {
38
81
  throw new GenericError(message || `O provedor ${provider.label} não oferece ${method}.`, {
@@ -144,16 +187,16 @@ function buildIntegrationRouter() {
144
187
  router.get("/queue", rbac(READ_ROLES), asyncHandler(async (req, res) => {
145
188
  const { Outbox } = getIntegrationModels();
146
189
  const { limit, page, skip } = pagination(req.query);
147
- const filter = {};
148
- for (const field of ["provider", "resource", "operation", "status", "aggregateType", "aggregateId"]) {
149
- if (req.query[field]) filter[field] = String(req.query[field]);
150
- }
151
- const [results, totalItems] = await Promise.all([
190
+ const filter = buildQueueFilter(req.query);
191
+ const summaryFilter = buildQueueFilter(req.query, { includeStatus: false });
192
+ const [results, totalItems, summary] = await Promise.all([
152
193
  Outbox.find(filter).sort({ createdAt: -1 }).skip(skip).limit(limit).lean(),
153
194
  Outbox.countDocuments(filter),
195
+ queueStatusSummary(Outbox, summaryFilter),
154
196
  ]);
155
197
  res.json({
156
198
  results: sanitizeIntegrationValue(results),
199
+ summary,
157
200
  pagination: {
158
201
  currentPage: page,
159
202
  totalItems,
@@ -222,4 +265,4 @@ function buildIntegrationRouter() {
222
265
  return router;
223
266
  }
224
267
 
225
- module.exports = { buildIntegrationRouter };
268
+ module.exports = { buildIntegrationRouter, buildQueueFilter, queueStatusSummary };
@@ -95,6 +95,22 @@ function responseError(body, statusCode) {
95
95
  });
96
96
  }
97
97
 
98
+ function normalizedStringList(value) {
99
+ const values = Array.isArray(value) ? value : value === undefined || value === null ? [] : [value];
100
+ return values.map((item) => String(item).trim()).filter(Boolean);
101
+ }
102
+
103
+ function isConfiguredEmptyResult(callDefinition = {}, body = {}) {
104
+ const code = String(body?.faultcode ?? body?.code ?? body?.codigo_status ?? "").trim();
105
+ const description = String(
106
+ body?.faultstring ?? body?.description ?? body?.descricao_status ?? "",
107
+ ).trim();
108
+ const codes = normalizedStringList(callDefinition.emptyResultFaultCodes);
109
+ const messages = normalizedStringList(callDefinition.emptyResultMessages);
110
+ return (code && codes.includes(code))
111
+ || (description && messages.some((message) => description.includes(message)));
112
+ }
113
+
98
114
  function retryableTransportError(error) {
99
115
  const statusCode = Number(error?.response?.status || error?.statusCode || 0);
100
116
  return RETRYABLE_HTTP.has(statusCode)
@@ -166,9 +182,10 @@ function createOmieClient(options = {}) {
166
182
  app_secret: appSecret,
167
183
  param: Array.isArray(resolvedParam) ? resolvedParam : [resolvedParam],
168
184
  };
185
+ const callMaxAttempts = Math.max(1, Number(callDefinition.maxAttempts ?? maxAttempts));
169
186
  let lastError;
170
187
 
171
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
188
+ for (let attempt = 1; attempt <= callMaxAttempts; attempt += 1) {
172
189
  const startedAt = Date.now();
173
190
  const trace = {
174
191
  endpoint: callDefinition.endpoint,
@@ -185,14 +202,18 @@ function createOmieClient(options = {}) {
185
202
  });
186
203
  const status = Number(response?.status || 200);
187
204
  const data = response?.data ?? {};
205
+ const emptyResult = isConfiguredEmptyResult(callDefinition, data);
188
206
  trace.httpStatus = status;
189
207
  trace.response = sanitizeIntegrationValue(data);
190
208
  trace.durationMs = Date.now() - startedAt;
191
- const normalized = responseError(data, status);
192
- if (normalized) throw normalized;
209
+ trace.emptyResult = emptyResult;
210
+ if (!emptyResult) {
211
+ const normalized = responseError(data, status);
212
+ if (normalized) throw normalized;
213
+ }
193
214
  trace.status = "success";
194
215
  context.recordRequest?.(trace);
195
- return { data, trace };
216
+ return { data, trace, emptyResult };
196
217
  } catch (error) {
197
218
  const normalized = error instanceof OmieApiError
198
219
  ? error
@@ -209,7 +230,7 @@ function createOmieClient(options = {}) {
209
230
  context.recordRequest?.(trace);
210
231
  normalized.trace = trace;
211
232
  lastError = normalized;
212
- if (!normalized.retryable || attempt >= maxAttempts) throw normalized;
233
+ if (!normalized.retryable || attempt >= callMaxAttempts) throw normalized;
213
234
  const delay = Math.min(5_000, retryInitialMs * (2 ** (attempt - 1)));
214
235
  await wait(delay);
215
236
  }
@@ -253,8 +274,9 @@ module.exports = {
253
274
  OmieApiError,
254
275
  createOmieClient,
255
276
  getPath,
277
+ isConfiguredEmptyResult,
256
278
  normalizeEndpoint,
257
279
  resolveCallParam,
258
280
  resolveTemplate,
259
281
  responseError,
260
- };
282
+ };