@oondemand/oon-core-back 0.3.53 → 0.3.54

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.53",
3
+ "version": "0.3.54",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/oondemand/oon-platform.git",
@@ -1,4 +1,5 @@
1
1
  const mongoose = require("mongoose");
2
+ const registry = require("../core/registry");
2
3
 
3
4
  /**
4
5
  * Conexão com o MongoDB a partir das envs padrão da Central.
@@ -51,9 +52,42 @@ function databaseLabel(uri) {
51
52
  }
52
53
  }
53
54
 
55
+ function shouldSyncDeclaredIndexes() {
56
+ return String(process.env.OON_SYNC_INDEXES ?? "true").toLowerCase() !== "false";
57
+ }
58
+
59
+ /**
60
+ * Mantém os índices físicos do MongoDB alinhados às models registradas.
61
+ *
62
+ * `createIndexes()` apenas cria índices ausentes e deixa índices removidos do
63
+ * domínio ativos indefinidamente. `syncIndexes()` também elimina esses índices
64
+ * obsoletos, evitando restrições antigas como um `codigo_1` único continuar
65
+ * bloqueando novos registros depois que o campo saiu do manifesto.
66
+ */
67
+ async function syncDeclaredIndexes(entries = registry.listModels()) {
68
+ if (!shouldSyncDeclaredIndexes()) return [];
69
+
70
+ const results = [];
71
+ for (const entry of entries) {
72
+ const model = entry?.mongooseModel;
73
+ if (!model || typeof model.syncIndexes !== "function") continue;
74
+
75
+ const dropped = await model.syncIndexes();
76
+ const normalized = Array.isArray(dropped) ? dropped : [];
77
+ results.push({ model: entry.name, dropped: normalized });
78
+
79
+ if (normalized.length) {
80
+ console.log(`[OonCore] Índices obsoletos removidos de ${entry.name}: ${normalized.join(", ")}`);
81
+ }
82
+ }
83
+
84
+ return results;
85
+ }
86
+
54
87
  async function connectDB() {
55
88
  const uri = buildMongoUri();
56
89
  await mongoose.connect(uri, connectionOptions());
90
+ await syncDeclaredIndexes();
57
91
  console.log(`Conectado ao MongoDB (${databaseLabel(uri)})`);
58
92
  return mongoose.connection;
59
93
  }
@@ -63,4 +97,6 @@ module.exports = {
63
97
  buildMongoUri,
64
98
  connectionOptions,
65
99
  databaseLabel,
100
+ shouldSyncDeclaredIndexes,
101
+ syncDeclaredIndexes,
66
102
  };
@@ -1,27 +1,94 @@
1
1
  const helpers = require("../utils/helpers");
2
2
  const GenericError = require("../errors/GenericError");
3
3
 
4
+ function firstDuplicateField(error) {
5
+ const source = error?.keyPattern || error?.keyValue;
6
+ const direct = source && typeof source === "object" ? Object.keys(source)[0] : undefined;
7
+ if (direct) return direct;
8
+
9
+ const index = String(error?.message || "").match(/index:\s+([^\s]+)\s+dup key/i)?.[1];
10
+ return index?.replace(/_\d+$/, "");
11
+ }
12
+
13
+ function duplicateIndexName(error) {
14
+ return String(error?.message || "").match(/index:\s+([^\s]+)\s+dup key/i)?.[1];
15
+ }
16
+
17
+ function fieldLabel(field) {
18
+ if (!field) return "o campo informado";
19
+ return field
20
+ .replace(/Id$/, "")
21
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
22
+ .replace(/[_-]+/g, " ")
23
+ .replace(/^./, (letter) => letter.toUpperCase());
24
+ }
25
+
26
+ function duplicateKeyDetails(error, requestId) {
27
+ const field = firstDuplicateField(error);
28
+ const index = duplicateIndexName(error);
29
+ const value = field && error?.keyValue && typeof error.keyValue === "object"
30
+ ? error.keyValue[field]
31
+ : undefined;
32
+ const staleNullIndex = field && value == null;
33
+ const message = staleNullIndex
34
+ ? `A estrutura do banco ainda possui uma restrição antiga para ${fieldLabel(field)}. Os índices precisam ser sincronizados antes de tentar novamente.`
35
+ : field
36
+ ? `Já existe um registro com o mesmo valor em ${fieldLabel(field)}.`
37
+ : "Já existe um registro com os mesmos dados únicos.";
38
+
39
+ return {
40
+ code: staleNullIndex ? "STALE_UNIQUE_INDEX" : "DUPLICATE_KEY",
41
+ message,
42
+ ...(field ? { field } : {}),
43
+ ...(index ? { index } : {}),
44
+ ...(requestId ? { requestId } : {}),
45
+ };
46
+ }
47
+
4
48
  /**
5
- * Error handler central. Trata GenericError (domínio), erros de validação
6
- * do Mongoose e cai num 500 genérico para o resto.
49
+ * Error handler central. Trata GenericError (domínio), erros conhecidos do
50
+ * Mongo/Mongoose e cai num 500 genérico para o restante.
7
51
  */
8
- const errorMiddleware = (error, _req, res, next) => {
52
+ const errorMiddleware = (error, req, res, next) => {
9
53
  if (!error) return next();
10
54
 
55
+ const requestId = req?.correlationId;
56
+
11
57
  if (error instanceof GenericError) {
58
+ const details = error.details && typeof error.details === "object" && !Array.isArray(error.details)
59
+ ? { ...error.details, ...(requestId ? { requestId } : {}) }
60
+ : error.details;
12
61
  return helpers.sendErrorResponse({
13
62
  res,
14
63
  statusCode: error.statusCode,
15
- error: error.details,
64
+ error: details,
16
65
  message: error.message,
17
66
  });
18
67
  }
19
68
 
69
+ if (error?.code === 11000 || error?.name === "MongoServerError" && error?.code === 11000) {
70
+ const details = duplicateKeyDetails(error, requestId);
71
+ return helpers.sendErrorResponse({
72
+ res,
73
+ statusCode: 409,
74
+ error: details,
75
+ message: details.message,
76
+ });
77
+ }
78
+
20
79
  if (error.name === "ValidationError") {
21
80
  return helpers.sendErrorResponse({
22
81
  res,
23
82
  statusCode: 422,
24
- error: Object.values(error.errors || {}).map((e) => e.message),
83
+ error: {
84
+ code: "VALIDATION_ERROR",
85
+ message: "Erro de validação.",
86
+ details: Object.entries(error.errors || {}).map(([field, item]) => ({
87
+ field,
88
+ message: item.message,
89
+ })),
90
+ ...(requestId ? { requestId } : {}),
91
+ },
25
92
  message: "Erro de validação.",
26
93
  });
27
94
  }
@@ -30,17 +97,33 @@ const errorMiddleware = (error, _req, res, next) => {
30
97
  return helpers.sendErrorResponse({
31
98
  res,
32
99
  statusCode: 400,
100
+ error: {
101
+ code: "INVALID_IDENTIFIER",
102
+ message: "Identificador inválido.",
103
+ ...(error.path ? { field: error.path } : {}),
104
+ ...(requestId ? { requestId } : {}),
105
+ },
33
106
  message: "Identificador inválido.",
34
107
  });
35
108
  }
36
109
 
37
110
  console.log("🆘 [UNHANDLED ERROR]:", error?.message || error);
111
+ const technicalMessage = process.env.NODE_ENV === "production"
112
+ ? undefined
113
+ : String(error?.message || error || "");
38
114
  return helpers.sendErrorResponse({
39
115
  res,
40
116
  statusCode: 500,
41
- error: error.message,
117
+ error: {
118
+ code: "INTERNAL_ERROR",
119
+ message: "Houve um erro inesperado!",
120
+ ...(technicalMessage ? { technicalMessage } : {}),
121
+ ...(requestId ? { requestId } : {}),
122
+ },
42
123
  message: "Houve um erro inesperado!",
43
124
  });
44
125
  };
45
126
 
46
127
  module.exports = errorMiddleware;
128
+ module.exports.firstDuplicateField = firstDuplicateField;
129
+ module.exports.duplicateKeyDetails = duplicateKeyDetails;