dcos-core-monalisav2-latam 1.0.0
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/README.md +130 -0
- package/index.js +350 -0
- package/package.json +52 -0
- package/src/auth/handler.js +3 -0
- package/src/common/MondelezCastOrder.js +449 -0
- package/src/common/utils/AuthSecurity.js +46 -0
- package/src/common/utils/account-error-handler.js +279 -0
- package/src/common/utils/account-error-helper.js +231 -0
- package/src/common/utils/account-properties-handler.js +355 -0
- package/src/common/utils/api-response.js +62 -0
- package/src/common/utils/aws-services.js +186 -0
- package/src/common/utils/constants/account-error-codes.json +801 -0
- package/src/common/utils/constants.js +37 -0
- package/src/common/utils/convert/MondelezClientsItemsCast.js +52 -0
- package/src/common/utils/convert/MondelezInventoryItemsCast.js +15 -0
- package/src/common/utils/convert/MondelezOrderStatusCast.js +34 -0
- package/src/common/utils/convert/MondelezPricesItemsCast.js +37 -0
- package/src/common/utils/cron-ftp-get.js +143 -0
- package/src/common/utils/data-tables-helper.js +213 -0
- package/src/common/utils/date-range-calculator.js +113 -0
- package/src/common/utils/delay.js +17 -0
- package/src/common/utils/ftp-sftp.js +320 -0
- package/src/common/utils/logger.js +126 -0
- package/src/common/utils/nodemailerLib.js +61 -0
- package/src/common/utils/product-unit-converter.js +168 -0
- package/src/common/utils/schemas-utils.js +101 -0
- package/src/common/utils/seller-email-sharing-service.js +441 -0
- package/src/common/utils/sftp-utils.js +202 -0
- package/src/common/utils/status.js +15 -0
- package/src/common/utils/util.js +236 -0
- package/src/common/utils/validate-state-order.js +35 -0
- package/src/common/utils/validateProviders.js +67 -0
- package/src/common/utils/validation-data.js +45 -0
- package/src/common/utils/vtex/save-hooks.js +65 -0
- package/src/common/utils/vtex/save-schemas.js +65 -0
- package/src/common/utils/vtex-hook-handler.js +71 -0
- package/src/common/validation/AccountCoordinatesValidation.js +350 -0
- package/src/common/validation/GeneralErrorValidation.js +11 -0
- package/src/common/validation/MainErrorValidation.js +8 -0
- package/src/entities/account.js +639 -0
- package/src/entities/clients.js +104 -0
- package/src/entities/controlprice.js +196 -0
- package/src/entities/controlstock.js +206 -0
- package/src/entities/cron.js +77 -0
- package/src/entities/cronjob.js +71 -0
- package/src/entities/orders.js +195 -0
- package/src/entities/sftp-inbound.js +88 -0
- package/src/entities/sku.js +220 -0
- package/src/entities/taxpromotion.js +249 -0
- package/src/functions/account/account-get.js +262 -0
- package/src/functions/account/account-handler.js +299 -0
- package/src/functions/account/clients.js +10 -0
- package/src/functions/account/index.js +208 -0
- package/src/functions/actions/save-promotions-order-history.js +324 -0
- package/src/functions/affiliates/affiliates-hook-consumer.js +87 -0
- package/src/functions/affiliates/affiliates-hook-producer.js +45 -0
- package/src/functions/clients/clients-audience.js +62 -0
- package/src/functions/clients/clients-consumer.js +648 -0
- package/src/functions/clients/clients-producer.js +362 -0
- package/src/functions/clients/clients-suggested-product-consumer.js +166 -0
- package/src/functions/clients/helpers/suggested-product-mdlz.js +233 -0
- package/src/functions/clients_peru/email.html +129 -0
- package/src/functions/clients_peru/splitfile.js +357 -0
- package/src/functions/clients_peru/updateClients.js +1334 -0
- package/src/functions/clients_peru/utils.js +243 -0
- package/src/functions/cronjobs/cron-jobs-manager.js +40 -0
- package/src/functions/cronjobs/cron-jobs.js +171 -0
- package/src/functions/crons/cron.js +39 -0
- package/src/functions/distributors/distributor-handler.js +81 -0
- package/src/functions/distributors/distributor.js +535 -0
- package/src/functions/distributors/index.js +60 -0
- package/src/functions/financialpolicy/assign-financialpolicy.js +111 -0
- package/src/functions/financialpolicy/get-financialpolicy.js +91 -0
- package/src/functions/financialpolicy/index.js +28 -0
- package/src/functions/inventory/catalog-sync-consumer.js +17 -0
- package/src/functions/inventory/catalog-sync-handler.js +311 -0
- package/src/functions/inventory/inventory-consumer.js +119 -0
- package/src/functions/inventory/inventory-producer.js +197 -0
- package/src/functions/multiPresentation/multipre-queue.js +155 -0
- package/src/functions/multiPresentation/multipres.js +459 -0
- package/src/functions/nodeflow/index.js +83 -0
- package/src/functions/nodeflow/nodeflow-cron.js +200 -0
- package/src/functions/nodeflow/nodeflow-pub.js +203 -0
- package/src/functions/nodeflow/nodeflow-pvt.js +266 -0
- package/src/functions/notifications/download-leads-handler.js +67 -0
- package/src/functions/notifications/new-leads-notification-consumer.js +17 -0
- package/src/functions/notifications/new-leads-notification-handler.js +359 -0
- package/src/functions/notifications/order-status-notification-handler.js +482 -0
- package/src/functions/notifications/promotion-notification-handler.js +193 -0
- package/src/functions/orders/index.js +32 -0
- package/src/functions/orders/orders-cancel-handler.js +74 -0
- package/src/functions/orders/orders-handler.js +280 -0
- package/src/functions/orders/orders-hook-consumer.js +137 -0
- package/src/functions/orders/orders-hook-producer.js +170 -0
- package/src/functions/orders/orders-notifications-handler.js +137 -0
- package/src/functions/orders/orders-status-consumer.js +461 -0
- package/src/functions/orders/orders-status-producer.js +443 -0
- package/src/functions/prices/index.js +75 -0
- package/src/functions/prices/prices-consumer.js +236 -0
- package/src/functions/prices/prices-producer.js +323 -0
- package/src/functions/prices/promotion-and-tax.js +1284 -0
- package/src/functions/routesflow/assign-routeflow-queue.js +77 -0
- package/src/functions/schemas/vtex/handle-schemas.js +102 -0
- package/src/functions/security/process_gas.js +221 -0
- package/src/functions/security/security-handler.js +950 -0
- package/src/functions/sftp/sftp-consumer.js +453 -0
- package/src/functions/sftpIntegrations/processes/redirectServices.js +184 -0
- package/src/functions/sftpIntegrations/processes/validateFileSchema.js +226 -0
- package/src/functions/sftpIntegrations/schemas/credential-schema.js +123 -0
- package/src/functions/sftpIntegrations/schemas/record-schema.js +131 -0
- package/src/functions/sftpIntegrations/schemas/sftp_required_fields.json +3 -0
- package/src/functions/sftpIntegrations/sftp-config-producer.js +112 -0
- package/src/functions/sftpIntegrations/sftp-consumer.js +700 -0
- package/src/functions/sftpIntegrations/test/validateFile.test.js +122 -0
- package/src/functions/sftpIntegrations/utils/connect-dynamo.js +29 -0
- package/src/functions/sftpIntegrations/utils/split-data.js +25 -0
- package/src/functions/utils/index.js +130 -0
- package/src/functions/vtex/vtex-helpers.js +694 -0
- package/src/integrations/accountErrors/AccountErrorManager.js +437 -0
- package/src/integrations/audience/Audience.js +70 -0
- package/src/integrations/financialPolicy/FinancialPolicyApi.js +377 -0
- package/src/integrations/index.js +0 -0
- package/src/integrations/mobilvendor/MobilvendorApi.js +405 -0
- package/src/integrations/productmultipresentation/ProductMultiPresentation.js +200 -0
- package/src/mdlz/auth/SecretManagerApi.js +77 -0
- package/src/mdlz/client/MdlzApi.js +70 -0
- package/src/vtex/clients/ProvidersApi.js +51 -0
- package/src/vtex/clients/VtexApi.js +511 -0
- package/src/vtex/models/VtexOrder.js +87 -0
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
const { parse } = require('aws-multipart-parser');
|
|
2
|
+
const validate = require('validate.js');
|
|
3
|
+
const AWSServices = require('../../common/utils/aws-services');
|
|
4
|
+
const ApiResponse = require('../../common/utils/api-response');
|
|
5
|
+
const Util = require('../../common/utils/util.js');
|
|
6
|
+
const SecretManagerApi = require('../../mdlz/auth/SecretManagerApi.js');
|
|
7
|
+
const VtexApi = require('../../vtex/clients/VtexApi');
|
|
8
|
+
const Logger = require("../../common/utils/logger");
|
|
9
|
+
|
|
10
|
+
const MANAGER_STORE_KEYS = process.env.AUTHORIZATION_MANAGER_KEY;
|
|
11
|
+
const REMOVE_USER = true;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Función principal que maneja las solicitudes entrantes.
|
|
15
|
+
* @param {Object} event - Evento de AWS Lambda.
|
|
16
|
+
* @returns {Object} - Respuesta API con resultado del proceso.
|
|
17
|
+
*/
|
|
18
|
+
const producer = async (event) => {
|
|
19
|
+
// Datos iniciales de la respuesta a la petición
|
|
20
|
+
let statusCode = 200, responseMessage = { message: '' };
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Determinar el tipo de operación basado en la ruta
|
|
24
|
+
const operationType = getOperationType(event);
|
|
25
|
+
|
|
26
|
+
// Conexión a la API de Secret Manager
|
|
27
|
+
const configAuth = await AWSServices.getSecretValue(MANAGER_STORE_KEYS);
|
|
28
|
+
const auth = JSON.parse(configAuth);
|
|
29
|
+
|
|
30
|
+
// Rama de ejecución según tipo de operación
|
|
31
|
+
switch (operationType) {
|
|
32
|
+
case 'accounts':
|
|
33
|
+
return await processAccountsRequest(event, auth);
|
|
34
|
+
case 'users':
|
|
35
|
+
return await processUsersRequest(event, auth);
|
|
36
|
+
default:
|
|
37
|
+
return await processUsersRequest(event, auth);
|
|
38
|
+
}
|
|
39
|
+
} catch (ex) {
|
|
40
|
+
Logger.error('Error process request: ', ex);
|
|
41
|
+
|
|
42
|
+
statusCode = 400;
|
|
43
|
+
const { message } = ex;
|
|
44
|
+
try {
|
|
45
|
+
responseMessage = JSON.parse(message);
|
|
46
|
+
} catch (parseEx) {
|
|
47
|
+
responseMessage.message = `Process went wrong: ${message}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return ApiResponse.response(statusCode, responseMessage);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Determina el tipo de operación basado en la ruta de la petición
|
|
56
|
+
* @param {Object} event - Evento de AWS Lambda
|
|
57
|
+
* @returns {String} - Tipo de operación: 'users' o 'accounts'
|
|
58
|
+
*/
|
|
59
|
+
const getOperationType = (event) => {
|
|
60
|
+
const path = event.resource || '';
|
|
61
|
+
switch (path) {
|
|
62
|
+
case '/batch/users':
|
|
63
|
+
return 'users';
|
|
64
|
+
case '/batch/accounts':
|
|
65
|
+
return 'accounts';
|
|
66
|
+
default:
|
|
67
|
+
return 'users';
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Procesa la solicitud para gestionar cuentas
|
|
73
|
+
* @param {Object} event - Evento de AWS Lambda
|
|
74
|
+
* @param {Object} auth - Configuración de autenticación
|
|
75
|
+
* @returns {Object} - Respuesta API con resultado del proceso
|
|
76
|
+
*/
|
|
77
|
+
async function processAccountsRequest(event, auth) {
|
|
78
|
+
let statusCode = 200;
|
|
79
|
+
let responseMessage = { message: '', dataSuccess: [], dataErrors: [] };
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Consulta de los valores almacenados en el Secret Manager
|
|
83
|
+
const { accountsData, isAvailable, authorizedUsers, availableExtensions } = await getSecretDataAccount(auth);
|
|
84
|
+
|
|
85
|
+
// Extraer datos del formulario
|
|
86
|
+
const formData = getFormData(event, availableExtensions);
|
|
87
|
+
const { email: formDataEmail, file: formDataFile } = formData;
|
|
88
|
+
|
|
89
|
+
// Validar autorización del remitente
|
|
90
|
+
if (!validateSenderAuthorizationAdmin(formDataEmail, authorizedUsers, isAvailable)) {
|
|
91
|
+
return ApiResponse.response(403, {
|
|
92
|
+
message: `El usuario '${formDataEmail}' no está autorizado para ejecutar este proceso`
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Procesar el archivo CSV para accounts
|
|
97
|
+
const result = await processAccountsCsv(formDataFile, accountsData, auth);
|
|
98
|
+
|
|
99
|
+
responseMessage.message = "Proceso de cuentas finalizado";
|
|
100
|
+
responseMessage.dataSuccess = result.dataSuccess;
|
|
101
|
+
responseMessage.dataErrors = result.dataErrors;
|
|
102
|
+
|
|
103
|
+
return ApiResponse.response(statusCode, responseMessage);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
Logger.error('Error procesando cuentas:', error);
|
|
106
|
+
return ApiResponse.response(400, {
|
|
107
|
+
message: `Error procesando cuentas: ${error.message}`
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Procesa la solicitud para gestionar usuarios
|
|
114
|
+
* @param {Object} event - Evento de AWS Lambda
|
|
115
|
+
* @param {Object} auth - Configuración de autenticación
|
|
116
|
+
* @returns {Object} - Respuesta API con resultado del proceso
|
|
117
|
+
*/
|
|
118
|
+
async function processUsersRequest(event, auth) {
|
|
119
|
+
let statusCode = 200;
|
|
120
|
+
let responseMessage = { message: '', dataSuccess: [], dataErrors: [] };
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Consulta de los valores almacenados en el Secret Manager
|
|
124
|
+
const { accountsData, isAvailable, authorizedUsers, availableExtensions } = await getSecretData(auth);
|
|
125
|
+
const { limitUsersProcess = 50 } = auth;
|
|
126
|
+
|
|
127
|
+
// Extraer datos del formulario
|
|
128
|
+
const formData = getFormData(event, availableExtensions);
|
|
129
|
+
const { email: formDataEmail, file: formDataFile } = formData;
|
|
130
|
+
|
|
131
|
+
// Validar permisos del usuario
|
|
132
|
+
let permissions = { '*': isAvailable };
|
|
133
|
+
if (Object.keys(authorizedUsers).length && formDataEmail && authorizedUsers.hasOwnProperty(formDataEmail)) {
|
|
134
|
+
permissions = { ...permissions, ...authorizedUsers[formDataEmail] };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Validar si el usuario tiene permisos para ejecutar el proceso
|
|
138
|
+
const { '*': executeProcess, ...remainPermissions } = permissions;
|
|
139
|
+
if (!executeProcess && Object.keys(remainPermissions).length == 0) {
|
|
140
|
+
throw new Error(`The user '${formDataEmail}' isn't authorized for execute the process`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Obtener propiedades de validación del CSV
|
|
144
|
+
const { separatorColumn, constraints } = getCsvValidationProperties(accountsData, executeProcess, remainPermissions);
|
|
145
|
+
|
|
146
|
+
// Obtener filas del CSV
|
|
147
|
+
const { csvRows, headerRow } = getCsvRowsData(formDataFile, limitUsersProcess);
|
|
148
|
+
|
|
149
|
+
// Validar encabezado del CSV
|
|
150
|
+
const csvColumns = Object.keys(constraints);
|
|
151
|
+
if (!new RegExp(`^\\w+(?:${separatorColumn}\\w+)*$`).test(headerRow)) {
|
|
152
|
+
throw new Error(`CSV header '${headerRow}' must contain the column separator '${separatorColumn}'`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Validar columnas del CSV
|
|
156
|
+
const csvHeaderKeys = arrayToJson(headerRow.split(separatorColumn), []);
|
|
157
|
+
for (let key of csvColumns) {
|
|
158
|
+
if (csvHeaderKeys.hasOwnProperty(key)) {
|
|
159
|
+
delete csvHeaderKeys[key];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Validar columnas inválidas
|
|
164
|
+
const invalidColumns = Object.keys(csvHeaderKeys);
|
|
165
|
+
if (invalidColumns.length) {
|
|
166
|
+
throw new Error(`CSV header contains invalid columns: '${invalidColumns.join(separatorColumn)}'`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Validar filas del CSV
|
|
170
|
+
let promises = [], checkUsers = {}, accountsIssue = {};
|
|
171
|
+
const dataSuccess = [], dataErrors = [];
|
|
172
|
+
validateRowsData(promises, checkUsers, csvRows, csvColumns, separatorColumn, constraints, dataErrors);
|
|
173
|
+
|
|
174
|
+
// Consultar usuarios en VTEX si es necesario
|
|
175
|
+
await findUsersInVtex(accountsData, checkUsers, accountsIssue);
|
|
176
|
+
|
|
177
|
+
if (promises.length) {
|
|
178
|
+
let result = await Promise.allSettled(promises.map(userRow => {
|
|
179
|
+
return new Promise(async (resolve) => {
|
|
180
|
+
const { jsonData } = userRow;
|
|
181
|
+
let { roles } = jsonData;
|
|
182
|
+
if (!roles && REMOVE_USER) {
|
|
183
|
+
await removeUser(accountsData, resolve, userRow, auth, checkUsers, accountsIssue);
|
|
184
|
+
} else {
|
|
185
|
+
await updateUserRoles(accountsData, resolve, userRow, auth);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
})).catch(Logger.error);
|
|
189
|
+
|
|
190
|
+
for (let res of result) {
|
|
191
|
+
const { index, error, message } = res.value;
|
|
192
|
+
if (error) {
|
|
193
|
+
dataErrors[index] = `Line: ${index + 1}. Error: ${error}`;
|
|
194
|
+
} else {
|
|
195
|
+
dataSuccess[index] = `Line: ${index + 1}. Message: ${message}`;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
responseMessage.message = 'Process finished';
|
|
201
|
+
responseMessage.dataSuccess = dataSuccess.filter(message => message);
|
|
202
|
+
responseMessage.dataErrors = dataErrors.filter(message => message);
|
|
203
|
+
|
|
204
|
+
return ApiResponse.response(statusCode, responseMessage);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
Logger.error('Error procesando usuarios:', error);
|
|
207
|
+
return ApiResponse.response(400, {
|
|
208
|
+
message: `Error procesando usuarios: ${error.message}`
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Valida si el remitente está autorizado para ejecutar el proceso
|
|
215
|
+
* @param {string} email - Correo del remitente
|
|
216
|
+
* @param {object} authorizedUsers - Objeto con usuarios autorizados
|
|
217
|
+
* @param {boolean} isAvailable - Indica si el sistema está disponible globalmente
|
|
218
|
+
* @returns {boolean} - Indica si el remitente está autorizado
|
|
219
|
+
*/
|
|
220
|
+
function validateSenderAuthorization(email, authorizedUsers, isAvailable) {
|
|
221
|
+
if (!email) return false;
|
|
222
|
+
if (isAvailable) return true;
|
|
223
|
+
if (authorizedUsers.hasOwnProperty(email)) {
|
|
224
|
+
const permissions = authorizedUsers[email];
|
|
225
|
+
if (permissions.hasOwnProperty('*') && permissions['*'] === true) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
for (const account in permissions) {
|
|
229
|
+
if (permissions[account] === true) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Valida si el remitente está autorizado para ejecutar el proceso (solo admin con permiso global)
|
|
239
|
+
* @param {string} email - Correo del remitente
|
|
240
|
+
* @param {object} authorizedUsers - Objeto con usuarios autorizados
|
|
241
|
+
* @param {boolean} isAvailable - Indica si el sistema está disponible globalmente
|
|
242
|
+
* @returns {boolean} - Indica si el remitente está autorizado
|
|
243
|
+
*/
|
|
244
|
+
function validateSenderAuthorizationAdmin(email, authorizedUsers, isAvailable) {
|
|
245
|
+
// Si no hay email, no está autorizado
|
|
246
|
+
if (!email) return false;
|
|
247
|
+
// Si el sistema está disponible globalmente, todos están autorizados
|
|
248
|
+
if (isAvailable) return true;
|
|
249
|
+
// Verificar si el usuario existe en la lista de autorizados
|
|
250
|
+
if (authorizedUsers.hasOwnProperty(email)) {
|
|
251
|
+
const permissions = authorizedUsers[email];
|
|
252
|
+
// Solo está autorizado si tiene el permiso global (*) explícitamente en true
|
|
253
|
+
return permissions['*'] === true;
|
|
254
|
+
}
|
|
255
|
+
// No está autorizado por defecto
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Obtiene los datos enviados al cuerpo de la petición.
|
|
261
|
+
* @param {Object} event - Objeto con datos de la petición a la función lambda.
|
|
262
|
+
* @param {Array} availableExtensions - Lista de extensiones permitidas.
|
|
263
|
+
* @returns {Object} - Objeto con los datos del form-data.
|
|
264
|
+
*/
|
|
265
|
+
function getFormData(event, availableExtensions) {
|
|
266
|
+
const formData = parse(event, true);
|
|
267
|
+
|
|
268
|
+
let errors = validate(formData, {
|
|
269
|
+
'file': { presence: { allowEmpty: false } },
|
|
270
|
+
'file.type': { presence: { allowEmpty: false } },
|
|
271
|
+
'file.filename': { presence: { allowEmpty: false } },
|
|
272
|
+
'file.contentType': {
|
|
273
|
+
presence: { allowEmpty: false },
|
|
274
|
+
inclusion: {
|
|
275
|
+
within: availableExtensions,
|
|
276
|
+
message: `^El archivo debe tener una extensión .csv o .txt. Verifique el formato de su archivo e inténtelo nuevamente.`
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
'file.content': { presence: { allowEmpty: false } },
|
|
280
|
+
'email': { presence: { allowEmpty: true }, email: true }
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (errors) {
|
|
284
|
+
let errorsData = {};
|
|
285
|
+
for (let key in errors) {
|
|
286
|
+
const [message = ''] = errors[key];
|
|
287
|
+
errorsData[key] = message;
|
|
288
|
+
}
|
|
289
|
+
throw new Error(JSON.stringify(errorsData));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return formData;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Consulta los valores configuradas en Secret Manager.
|
|
297
|
+
* @param {Object} auth - Configuración de autenticación
|
|
298
|
+
* @returns {Object} - Datos de configuración obtenidos
|
|
299
|
+
*/
|
|
300
|
+
async function getSecretDataAccount(auth) {
|
|
301
|
+
const { tokenSecretManagerAPI, urlSecretManagerAPI, secretName } = auth;
|
|
302
|
+
const secretManagerApi = new SecretManagerApi(`Bearer ${tokenSecretManagerAPI}`, urlSecretManagerAPI);
|
|
303
|
+
const responseSecret = await secretManagerApi.fetch(`${secretName}`, { method: 'GET' }, true);
|
|
304
|
+
const { status: smStatus, statusText: smStatusText, data: smData } = responseSecret;
|
|
305
|
+
|
|
306
|
+
// Validar respuesta del Secret Manager
|
|
307
|
+
if (!(smStatus >= 200 && smStatus < 300)) {
|
|
308
|
+
throw new Error(`Can't get information from SM API`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (!smData?.[0]?.value) {
|
|
312
|
+
throw new Error(`Information from SM API not defined`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const {
|
|
316
|
+
isAvailable = false,
|
|
317
|
+
authorizedUsers = {},
|
|
318
|
+
availableExtensions = ['text/csv', 'text/plain'],
|
|
319
|
+
accounts = []
|
|
320
|
+
} = smData[0].value;
|
|
321
|
+
|
|
322
|
+
// 1. Procesar cuentas: Mantener como ARRAY para usar .some()
|
|
323
|
+
const accountsData = accounts
|
|
324
|
+
.filter(account =>
|
|
325
|
+
account.accountName &&
|
|
326
|
+
account.vtexAppkey &&
|
|
327
|
+
account.vtexAppToken
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// 2. Limpiar permisos redundantes
|
|
331
|
+
Object.keys(authorizedUsers).forEach(email => {
|
|
332
|
+
const hasWildcard = authorizedUsers[email].hasOwnProperty('*');
|
|
333
|
+
const { '*': wildcardUser, ...remainPerms } = authorizedUsers[email];
|
|
334
|
+
|
|
335
|
+
Object.keys(remainPerms).forEach(account => {
|
|
336
|
+
if (remainPerms[account] === (hasWildcard ? wildcardUser : isAvailable)) {
|
|
337
|
+
delete authorizedUsers[email][account];
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
accountsData, // Ahora es un array filtrado y mapeado
|
|
344
|
+
isAvailable,
|
|
345
|
+
authorizedUsers,
|
|
346
|
+
availableExtensions
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Consulta los valores configuradas en Secret Manager.
|
|
352
|
+
* @param {*} auth
|
|
353
|
+
*/
|
|
354
|
+
async function getSecretData(auth) {
|
|
355
|
+
const { tokenSecretManagerAPI, urlSecretManagerAPI, secretName } = auth;
|
|
356
|
+
const secretManagerApi = new SecretManagerApi(`Bearer ${tokenSecretManagerAPI}`, urlSecretManagerAPI);
|
|
357
|
+
const responseSecret = await secretManagerApi.fetch(`${secretName}`, { method: 'GET' }, true);
|
|
358
|
+
const { status: smStatus, statusText: smStatusText, data: smData } = responseSecret;
|
|
359
|
+
|
|
360
|
+
// Se valida si la consulta de los secret fue exitosa
|
|
361
|
+
if (!(smStatus >= 200 && smStatus < 300)) {
|
|
362
|
+
throw new Error(`Can't get information from SM API`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!smData?.[0]?.value) {
|
|
366
|
+
throw new Error(`Information from SM API not defined`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const accountsData = {};
|
|
370
|
+
const { isAvailable = false, authorizedUsers = {}, availableExtensions = ['text/csv', 'text/plain'], accounts = [] } = smData[0].value;
|
|
371
|
+
|
|
372
|
+
// Se agrupa los datos de las cuentas por el nombre (accountName)
|
|
373
|
+
for (let account of accounts) {
|
|
374
|
+
const { accountName, vtexAppkey, vtexAppToken } = account;
|
|
375
|
+
if (accountName && !accountsData[accountName]) {
|
|
376
|
+
// Instancia de conexión a la API de VTEX para el account 'accountName'
|
|
377
|
+
accountsData[accountName] = new VtexApi(accountName, vtexAppkey, vtexAppToken);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Se eliminan las redundancias que puede tener la parametrización de permisos
|
|
382
|
+
for (let email in authorizedUsers) {
|
|
383
|
+
const hasWildcard = authorizedUsers[email].hasOwnProperty('*');
|
|
384
|
+
const { '*': wildcardUser, ...remainPerms } = authorizedUsers[email];
|
|
385
|
+
for (let account in remainPerms) {
|
|
386
|
+
if (remainPerms[account] == hasWildcard ? wildcardUser : isAvailable) {
|
|
387
|
+
delete authorizedUsers[email][account];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return { accountsData, isAvailable, authorizedUsers, availableExtensions };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Procesa el archivo CSV para configurar accounts
|
|
397
|
+
* @param {object} fileData - Objeto con datos del archivo subido
|
|
398
|
+
* @param {object} accountsData - Datos de las cuentas existentes
|
|
399
|
+
* @param {object} auth - Configuración de autenticación
|
|
400
|
+
* @returns {object} - Resultados del procesamiento
|
|
401
|
+
*/
|
|
402
|
+
async function processAccountsCsv(fileData, accountsData, auth) {
|
|
403
|
+
const result = {
|
|
404
|
+
dataSuccess: [],
|
|
405
|
+
dataErrors: []
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
// 1. Validación básica del contenido del archivo
|
|
410
|
+
let csvDataString = Buffer.from(fileData.content).toString();
|
|
411
|
+
if (!csvDataString || !csvDataString.trim()) {
|
|
412
|
+
throw new Error("El contenido del CSV está vacío o no es válido");
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const csvRows = csvDataString.trim().split(/\r?\n/);
|
|
416
|
+
|
|
417
|
+
// 2. Validación de headers
|
|
418
|
+
if (csvRows.length === 0) {
|
|
419
|
+
throw new Error("El archivo no contiene datos");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const headerRow = csvRows.shift();
|
|
423
|
+
|
|
424
|
+
// 3. Validación de existencia de headers
|
|
425
|
+
if (!headerRow) {
|
|
426
|
+
throw new Error("El archivo no contiene encabezados. Debe incluir la primera línea con los nombres de columna.");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// 4. Validación del delimitador
|
|
430
|
+
if (!headerRow.includes(';')) {
|
|
431
|
+
throw new Error("El archivo CSV debe estar delimitado por ';'. Verifique el formato e inténtelo nuevamente.");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// 5. Validación de headers esperados
|
|
435
|
+
const headers = headerRow.split(';').map(h => h.trim().toLowerCase());
|
|
436
|
+
const expectedHeaders = ['action', 'accountname', 'appkey', 'apptoken'];
|
|
437
|
+
const missingHeaders = expectedHeaders.filter(h => !headers.includes(h));
|
|
438
|
+
|
|
439
|
+
if (missingHeaders.length > 0) {
|
|
440
|
+
throw new Error(`Encabezados incorrectos. Faltan: ${missingHeaders.join(', ')}. Formato esperado: ACTION;ACCOUNTNAME;APPKEY;APPTOKEN`);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Mapeo de índices de columnas
|
|
444
|
+
const columnIndices = {};
|
|
445
|
+
headers.forEach((header, index) => {
|
|
446
|
+
columnIndices[header] = index;
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Obtener configuración actual del Secret Manager
|
|
450
|
+
const { currentConfig, secretManagerApi } = await getCurrentSecretManagerConfig(auth);
|
|
451
|
+
|
|
452
|
+
if (!Array.isArray(currentConfig.value.accounts)) {
|
|
453
|
+
currentConfig.value.accounts = [];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Procesar cada fila del CSV
|
|
457
|
+
for (let [index, row] of csvRows.entries()) {
|
|
458
|
+
try {
|
|
459
|
+
row = row.trim();
|
|
460
|
+
if (!row) {
|
|
461
|
+
result.dataErrors.push(`Línea ${index + 2}: Fila vacía (se esperaban datos)`);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const rowData = row.split(';');
|
|
466
|
+
|
|
467
|
+
// 6. Validación de campos vacíos
|
|
468
|
+
const action = (rowData[columnIndices.action]?.trim() || 'add').toLowerCase();
|
|
469
|
+
const accountName = rowData[columnIndices.accountname]?.trim();
|
|
470
|
+
const appKey = rowData[columnIndices.appkey]?.trim();
|
|
471
|
+
const appToken = rowData[columnIndices.apptoken]?.trim();
|
|
472
|
+
|
|
473
|
+
// Validar campos obligatorios
|
|
474
|
+
const errors = [];
|
|
475
|
+
if (!accountName) errors.push('ACCOUNTNAME no puede estar vacío');
|
|
476
|
+
if (!appKey) errors.push('APPKEY no puede estar vacío');
|
|
477
|
+
if (!appToken) errors.push('APPTOKEN no puede estar vacío');
|
|
478
|
+
if (!['add', 'delete'].includes(action)) {
|
|
479
|
+
errors.push(`ACTION '${action}' no válida (solo ADD/DELETE)`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (errors.length > 0) {
|
|
483
|
+
result.dataErrors.push(`Línea ${index + 2}: ${errors.join('; ')}`);
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Procesar acción
|
|
488
|
+
const accountData = {
|
|
489
|
+
accountName: accountName.toLowerCase(),
|
|
490
|
+
vtexAppkey: appKey,
|
|
491
|
+
vtexAppToken: appToken
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
if (action === 'add') {
|
|
495
|
+
// Eliminar cuenta existente si ya existe
|
|
496
|
+
currentConfig.value.accounts = currentConfig.value.accounts.filter(
|
|
497
|
+
acc => acc.accountName.toLowerCase() !== accountData.accountName
|
|
498
|
+
);
|
|
499
|
+
currentConfig.value.accounts.push(accountData);
|
|
500
|
+
result.dataSuccess.push(`Línea ${index + 2}: Cuenta '${accountData.accountName}' agregada/actualizada correctamente`);
|
|
501
|
+
} else if (action === 'delete') {
|
|
502
|
+
const initialCount = currentConfig.value.accounts.length;
|
|
503
|
+
currentConfig.value.accounts = currentConfig.value.accounts.filter(
|
|
504
|
+
acc => acc.accountName.toLowerCase() !== accountData.accountName
|
|
505
|
+
);
|
|
506
|
+
if (initialCount === currentConfig.value.accounts.length) {
|
|
507
|
+
result.dataErrors.push(`Línea ${index + 2}: Cuenta '${accountData.accountName}' no encontrada para eliminar`);
|
|
508
|
+
} else {
|
|
509
|
+
result.dataSuccess.push(`Línea ${index + 2}: Cuenta '${accountData.accountName}' eliminada correctamente`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
} catch (error) {
|
|
513
|
+
result.dataErrors.push(`Línea ${index + 2}: Error procesando - ${error.message}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Actualizar Secret Manager solo si no hay errores críticos
|
|
518
|
+
if (result.dataErrors.length === 0 || result.dataSuccess.length > 0) {
|
|
519
|
+
const updateResponse = await secretManagerApi.fetch(`${currentConfig.id}/${currentConfig.name}`, {
|
|
520
|
+
method: 'PUT',
|
|
521
|
+
data: {
|
|
522
|
+
id: currentConfig.id,
|
|
523
|
+
name: currentConfig.name,
|
|
524
|
+
value: currentConfig.value
|
|
525
|
+
}
|
|
526
|
+
}, true);
|
|
527
|
+
|
|
528
|
+
if (updateResponse.status < 200 || updateResponse.status >= 300) {
|
|
529
|
+
throw new Error(`Error al actualizar Secret Manager: ${updateResponse.statusText}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
} catch (error) {
|
|
534
|
+
// Capturar errores de validación globales
|
|
535
|
+
throw new Error(`Error procesando CSV: ${error.message}`);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Obtiene la configuración actual del Secret Manager
|
|
543
|
+
* @param {object} auth - Configuración de autenticación
|
|
544
|
+
* @returns {object} - Configuración actual y instancia de SecretManagerApi
|
|
545
|
+
*/
|
|
546
|
+
async function getCurrentSecretManagerConfig(auth) {
|
|
547
|
+
const { tokenSecretManagerAPI, urlSecretManagerAPI, secretName } = auth;
|
|
548
|
+
const secretManagerApi = new SecretManagerApi(`Bearer ${tokenSecretManagerAPI}`, urlSecretManagerAPI);
|
|
549
|
+
const response = await secretManagerApi.fetch(`${secretName}`, { method: 'GET' }, true);
|
|
550
|
+
|
|
551
|
+
if (response.status < 200 || response.status >= 300) {
|
|
552
|
+
throw new Error(`Error al obtener información del Secret Manager: ${response.statusText}`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (!response.data?.[0]?.value) {
|
|
556
|
+
throw new Error("No se encontró configuración en Secret Manager");
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Manejar tanto string JSON como objeto
|
|
560
|
+
let secretValue = response.data[0].value;
|
|
561
|
+
if (typeof secretValue === 'string') {
|
|
562
|
+
try {
|
|
563
|
+
secretValue = JSON.parse(secretValue);
|
|
564
|
+
} catch (e) {
|
|
565
|
+
throw new Error("El valor del secret no es un JSON válido");
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Asegurar que accounts es un array
|
|
570
|
+
if (!Array.isArray(secretValue.accounts)) {
|
|
571
|
+
secretValue.accounts = [];
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
currentConfig: {
|
|
576
|
+
id: response.data[0].id || secretName,
|
|
577
|
+
name: response.data[0].name || secretName,
|
|
578
|
+
value: secretValue
|
|
579
|
+
},
|
|
580
|
+
secretManagerApi
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Obtiene el contenido del archivo CSV enviado en la petición.
|
|
586
|
+
* @param {object} fileData - Objeto con los datos del archivo subido.
|
|
587
|
+
* @param {Number} limitUsersProcess - Número máximo de filas que debe tener el archivo.
|
|
588
|
+
* @returns {Object} - Fila de encabezado, y filas restantes del archivo leido.
|
|
589
|
+
*/
|
|
590
|
+
function getCsvRowsData(fileData, limitUsersProcess) {
|
|
591
|
+
// Se obtiene el contenido del archivo subido
|
|
592
|
+
let csvDataString = Buffer.from(fileData.content).toString();
|
|
593
|
+
if (csvDataString) {
|
|
594
|
+
csvDataString = csvDataString.trim();
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (!csvDataString) {
|
|
598
|
+
throw new Error(`CSV content is empty or is invalid`);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Se obtiene las filas del archivo
|
|
602
|
+
const csvRows = csvDataString.split(/\r?\n/), headerRow = csvRows.shift();
|
|
603
|
+
|
|
604
|
+
// Se valida que existan filas en el contenido
|
|
605
|
+
if (csvRows.length == 0) {
|
|
606
|
+
throw new Error(`CSV content is required`);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Se valida que el N° de filas no supere el valor máximo establecido
|
|
610
|
+
if (csvRows.length > limitUsersProcess) {
|
|
611
|
+
throw new Error(`Limit process exceed to upload and update users. Maximum users: ${limitUsersProcess}`);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return { csvRows, headerRow };
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Obtiene las propiedades a usar en la validación del contenido del archivo csv.
|
|
619
|
+
* @param {object} accountsData - Lista de accounts registradas en SecretManager.
|
|
620
|
+
* @param {Boolean} hasPermission - Valor que indica si se tiene permisos para ejecutar la actualización con todas los accounts.
|
|
621
|
+
* @param {object} remainPermissions - Objeto con lista de accounts exentos de la condicional 'hasPermission'.
|
|
622
|
+
* @returns {Object} - Caracter * del separador de columnas (separatorColumn) y reglas de validación (constraints).
|
|
623
|
+
*/
|
|
624
|
+
function getCsvValidationProperties(accountsData, hasPermission, remainPermissions) {
|
|
625
|
+
let exclusion = [];
|
|
626
|
+
for (let account in accountsData) {
|
|
627
|
+
if (remainPermissions.hasOwnProperty(account)) {
|
|
628
|
+
if (!remainPermissions[account]) {
|
|
629
|
+
exclusion.push(account);
|
|
630
|
+
}
|
|
631
|
+
} else if (!hasPermission) {
|
|
632
|
+
exclusion.push(account);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const separatorColumn = ';', constraints = {
|
|
637
|
+
'accountname': {
|
|
638
|
+
'presence': { allowEmpty: false },
|
|
639
|
+
'inclusion': {
|
|
640
|
+
'within': Object.keys(accountsData),
|
|
641
|
+
'message': `^'%{value}' isn't in the accounts configuration list`
|
|
642
|
+
},
|
|
643
|
+
'exclusion': {
|
|
644
|
+
'within': exclusion,
|
|
645
|
+
'message': `^User isn't authorized for process roles in account '%{value}'`
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
'email': {
|
|
649
|
+
'presence': { allowEmpty: false },
|
|
650
|
+
'email': {
|
|
651
|
+
'message': `'%{value}' is not valid`
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
'roles': {
|
|
655
|
+
'presence': { allowEmpty: true },
|
|
656
|
+
'format': {
|
|
657
|
+
'pattern': /^\d*(,\d+)*$/,
|
|
658
|
+
'message': `'%{value}' is not a valid comma separated id values`
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
return { separatorColumn, constraints };
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Realiza la validación de datos para cada una de las filas del archivo
|
|
668
|
+
* @param {array} promises - Array donde se inserta los datos válidos.
|
|
669
|
+
* @param {object} checkUsers - Objeto donde se consolida los usuarios a eliminar.
|
|
670
|
+
* @param {array} csvRows - Filas del archivo csv leido.
|
|
671
|
+
* @param {array} csvColumns - Lista de columnas del archivo.
|
|
672
|
+
* @param {string} separatorColumn - Separador de columnas.
|
|
673
|
+
* @param {object} constraints - Reglas de validación del archivo.
|
|
674
|
+
* @param {array} dataErrors - Lista donde se concatena el mensaje de error para cada línea del archivo.
|
|
675
|
+
*/
|
|
676
|
+
function validateRowsData(promises, checkUsers, csvRows, csvColumns, separatorColumn, constraints, dataErrors) {
|
|
677
|
+
for (let [index, row] of csvRows.entries()) {
|
|
678
|
+
row = row.trim();
|
|
679
|
+
if (row) {
|
|
680
|
+
if (new RegExp(`^.*(?:${separatorColumn}.*){${csvColumns.length - 1}}$`).test(row)) {
|
|
681
|
+
let rowData = row.split(separatorColumn);
|
|
682
|
+
const jsonData = arrayToJson(csvColumns, rowData);
|
|
683
|
+
let errors = validate(jsonData, constraints);
|
|
684
|
+
if (errors) {
|
|
685
|
+
let reportErrors = [];
|
|
686
|
+
for (let err in errors) {
|
|
687
|
+
const [message = ''] = errors[err];
|
|
688
|
+
if (message) {
|
|
689
|
+
reportErrors.push(message);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
dataErrors[index] = `Line: ${index + 1}. Error: ${reportErrors.join(';')}`;
|
|
693
|
+
} else {
|
|
694
|
+
promises.push({ index, jsonData });
|
|
695
|
+
|
|
696
|
+
let { accountname, email, roles } = jsonData;
|
|
697
|
+
if (!roles && REMOVE_USER) {
|
|
698
|
+
if (accountname && !checkUsers.hasOwnProperty(accountname)) {
|
|
699
|
+
checkUsers[accountname] = {};
|
|
700
|
+
}
|
|
701
|
+
if (email && !checkUsers[accountname]?.hasOwnProperty(email)) {
|
|
702
|
+
checkUsers[accountname][email] = null;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
} else {
|
|
707
|
+
dataErrors[index] = `Line: ${index + 1}. Error: row data '${row}' does not have the columns separator (${separatorColumn}) and/or does not have the columns number required (${csvColumns.length})`;
|
|
708
|
+
}
|
|
709
|
+
} else {
|
|
710
|
+
dataErrors[index] = `Line: ${index + 1}. Error: Line is empty`;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Convierte un array a un objeto JSON usando las cabeceras como claves.
|
|
717
|
+
* @param {Array} headers - Cabeceras que serán usadas como claves
|
|
718
|
+
* @param {Array} items - Valores a asignar a cada clave
|
|
719
|
+
* @returns {Object} - Objeto JSON con los datos
|
|
720
|
+
*/
|
|
721
|
+
function arrayToJson(headers, items) {
|
|
722
|
+
let objectData = {};
|
|
723
|
+
headers.forEach((key, index) => {
|
|
724
|
+
objectData[key] = items?.[index]?.trim() ?? null;
|
|
725
|
+
});
|
|
726
|
+
return objectData;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Actualiza los roles asociados a un determinado usuario.
|
|
731
|
+
* @param {object} accountsData - Datos de cuentas
|
|
732
|
+
* @param {function} resolve - Función para resolver la promesa
|
|
733
|
+
* @param {object} userRow - Objeto de datos de la fila
|
|
734
|
+
* @param {object} auth - Configuración de autenticación
|
|
735
|
+
*/
|
|
736
|
+
async function updateUserRoles(accountsData, resolve, userRow, auth) {
|
|
737
|
+
const { index, jsonData } = userRow;
|
|
738
|
+
let { accountname, email, roles } = jsonData;
|
|
739
|
+
|
|
740
|
+
const responseCreateUser = await accountsData[accountname].fetch(auth?.pathCreateUser, {
|
|
741
|
+
method: 'POST',
|
|
742
|
+
data: { name: '', email }
|
|
743
|
+
}, false).catch(ex => {
|
|
744
|
+
const errReq = Util.getErrorData(ex);
|
|
745
|
+
Logger.error(errReq);
|
|
746
|
+
const { status, message } = errReq;
|
|
747
|
+
switch (status) {
|
|
748
|
+
case 400:
|
|
749
|
+
resolve({ index, error: `Invalid VTEX request for create user with email '${email}'` });
|
|
750
|
+
break;
|
|
751
|
+
case 403:
|
|
752
|
+
resolve({ index, error: `Denied access or invalid/inactived credentials for create user in account '${accountname}'` });
|
|
753
|
+
break;
|
|
754
|
+
default:
|
|
755
|
+
resolve({ index, error: message });
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
if (responseCreateUser?.data?.id) {
|
|
761
|
+
const { id: userId } = responseCreateUser.data;
|
|
762
|
+
|
|
763
|
+
let rolesKeys = {};
|
|
764
|
+
for (let roleId of roles.split(',')) {
|
|
765
|
+
roleId = String(roleId);
|
|
766
|
+
if (roleId) {
|
|
767
|
+
rolesKeys[roleId] = true;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const relativeGetAddRoleByUser = auth?.pathGetAddRoleByUser.replace('{userId}', userId);
|
|
772
|
+
const responseGetRoles = await accountsData[accountname].fetch(`${relativeGetAddRoleByUser}`, { method: 'GET' }, false).catch(ex => {
|
|
773
|
+
const errReq = Util.getErrorData(ex);
|
|
774
|
+
Logger.error(errReq);
|
|
775
|
+
const { status, message } = errReq;
|
|
776
|
+
switch (status) {
|
|
777
|
+
case 400:
|
|
778
|
+
resolve({ index, error: `Invalid VTEX request for get user roles with email '${email}'. ${message}` });
|
|
779
|
+
break;
|
|
780
|
+
case 403:
|
|
781
|
+
resolve({ index, error: `Denied access or invalid/inactived credentials for get user roles in account '${accountname}'. ${message}` });
|
|
782
|
+
break;
|
|
783
|
+
default:
|
|
784
|
+
resolve({ index, error: message });
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
if (responseGetRoles?.data) {
|
|
790
|
+
for (let roleItem of responseGetRoles.data) {
|
|
791
|
+
if (roleItem?.id) {
|
|
792
|
+
let userRoleId = String(roleItem.id);
|
|
793
|
+
if (rolesKeys.hasOwnProperty(userRoleId)) {
|
|
794
|
+
delete rolesKeys[userRoleId];
|
|
795
|
+
} else {
|
|
796
|
+
const relativeDeleteRoleByUser = auth?.pathRemoveRoleByUser.replace('{userId}', userId).replace('{roleId}', userRoleId);
|
|
797
|
+
const responseDelRoles = await accountsData[accountname].fetch(relativeDeleteRoleByUser, { method: 'DELETE' }, false).catch(ex => {
|
|
798
|
+
const errReq = Util.getErrorData(ex);
|
|
799
|
+
Logger.error(errReq);
|
|
800
|
+
});
|
|
801
|
+
if (responseDelRoles) {
|
|
802
|
+
Logger.log(`Role ${userRoleId} was deleted successfully for user ${userId}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const newRolesId = Object.keys(rolesKeys);
|
|
809
|
+
if (newRolesId.length) {
|
|
810
|
+
const responseSetUserRoles = await accountsData[accountname].fetch(relativeGetAddRoleByUser, {
|
|
811
|
+
method: 'PUT', data: newRolesId
|
|
812
|
+
}, false).catch(ex => {
|
|
813
|
+
const errReq = Util.getErrorData(ex);
|
|
814
|
+
Logger.error(errReq);
|
|
815
|
+
const { status, message } = errReq;
|
|
816
|
+
switch (status) {
|
|
817
|
+
case 400:
|
|
818
|
+
resolve({ index, error: `${message}. Roles list: '${newRolesId.join(',')}'. Email: '${email}'. Account: ${accountname}` });
|
|
819
|
+
break;
|
|
820
|
+
case 403:
|
|
821
|
+
resolve({ index, error: `Denied access or invalid/inactived credentials for put user roles '${newRolesId.join(',')}' in account '${accountname}'. ${message}` });
|
|
822
|
+
break;
|
|
823
|
+
default:
|
|
824
|
+
resolve({ index, error: message });
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
if (responseSetUserRoles) {
|
|
830
|
+
resolve({ index, message: `User '${email}' created/updated successfully` });
|
|
831
|
+
}
|
|
832
|
+
} else {
|
|
833
|
+
resolve({ index, message: `User '${email}' not modified` });
|
|
834
|
+
}
|
|
835
|
+
} else {
|
|
836
|
+
resolve({ index, error: `Error white get user roles for email '${email}'` });
|
|
837
|
+
}
|
|
838
|
+
} else {
|
|
839
|
+
resolve({ index, error: `User '${email}' not created/updated` });
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Realiza la eliminación del usuario de VTEX.
|
|
845
|
+
* @param {object} accountsData - Datos de cuentas
|
|
846
|
+
* @param {function} resolve - Función para resolver la promesa
|
|
847
|
+
* @param {object} userRow - Objeto de datos del usuario a eliminar
|
|
848
|
+
* @param {object} auth - Configuración de autenticación
|
|
849
|
+
* @param {object} checkUsers - Lista de usuarios con opción de ser eliminados
|
|
850
|
+
* @param {object} accountsIssue - Lista de accounts que haya presentado algún incidente
|
|
851
|
+
*/
|
|
852
|
+
async function removeUser(accountsData, resolve, userRow, auth, checkUsers, accountsIssue) {
|
|
853
|
+
const { index, jsonData } = userRow;
|
|
854
|
+
let { accountname, email } = jsonData;
|
|
855
|
+
|
|
856
|
+
if (checkUsers?.[accountname]?.[email]) {
|
|
857
|
+
const deleteUserEndpoint = auth?.pathCreateUser + '/' + checkUsers[accountname][email];
|
|
858
|
+
|
|
859
|
+
const responseDeleteUser = await accountsData[accountname].fetch(deleteUserEndpoint, {
|
|
860
|
+
method: 'DELETE'
|
|
861
|
+
}, false).catch(ex => {
|
|
862
|
+
const errReq = Util.getErrorData(ex);
|
|
863
|
+
const { status, message } = errReq;
|
|
864
|
+
switch (status) {
|
|
865
|
+
case 400:
|
|
866
|
+
resolve({ index, error: `Invalid VTEX request for delete user with email '${email}'` });
|
|
867
|
+
break;
|
|
868
|
+
case 403:
|
|
869
|
+
resolve({ index, error: `Denied access or invalid/inactived credentials for delete user in account '${accountname}'` });
|
|
870
|
+
break;
|
|
871
|
+
default:
|
|
872
|
+
resolve({ index, error: message });
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
if (responseDeleteUser) {
|
|
878
|
+
resolve({ index, message: `User '${email}' delete success in account '${accountname}'` });
|
|
879
|
+
}
|
|
880
|
+
} else if (accountsIssue.hasOwnProperty(accountname)) {
|
|
881
|
+
resolve({ index, error: accountsIssue[accountname] })
|
|
882
|
+
} else {
|
|
883
|
+
resolve({ index, message: `User '${email}' not found in account '${accountname}'` });
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Consulta en cada account la lista de usuarios.
|
|
889
|
+
* @param {object} accountsData - Instancias de conexión a la API de VTEX por accounts.
|
|
890
|
+
* @param {object} checkUsers - Lista de usuarios a eliminar.
|
|
891
|
+
* @param {object} accountsIssue - Lista de accounts que haya presentado algún incidente.
|
|
892
|
+
*/
|
|
893
|
+
async function findUsersInVtex(accountsData, checkUsers, accountsIssue) {
|
|
894
|
+
if (Object.keys(checkUsers).length) {
|
|
895
|
+
await Promise.allSettled(Object.entries(checkUsers).map(accountData => {
|
|
896
|
+
return new Promise(async (resolve, reject) => {
|
|
897
|
+
const [accountName, users] = accountData;
|
|
898
|
+
|
|
899
|
+
let pageNumber = 1, numItems = 100;
|
|
900
|
+
do {
|
|
901
|
+
const responseListUsers = await accountsData[accountName].fetch(Util.setQueryParams('/license-manager/site/pvt/logins/list/paged', {
|
|
902
|
+
pageNumber, numItems
|
|
903
|
+
}), {
|
|
904
|
+
method: 'GET'
|
|
905
|
+
}, false).catch(ex => {
|
|
906
|
+
const errReq = Util.getErrorData(ex);
|
|
907
|
+
Logger.error(errReq);
|
|
908
|
+
|
|
909
|
+
let message;
|
|
910
|
+
switch (errReq.status) {
|
|
911
|
+
case 400:
|
|
912
|
+
message = `Invalid VTEX request for find users in account '${accountName}'`;
|
|
913
|
+
break;
|
|
914
|
+
case 401:
|
|
915
|
+
message = `Credential key is inactived in account '${accountName}'`;
|
|
916
|
+
break;
|
|
917
|
+
case 403:
|
|
918
|
+
message = `Denied access or invalid/inactived credentials for find users in account '${accountName}'`;
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (!accountsIssue.hasOwnProperty(accountName) && message) {
|
|
923
|
+
accountsIssue[accountName] = message;
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
if (responseListUsers?.data?.items?.length) {
|
|
927
|
+
const { items } = responseListUsers.data;
|
|
928
|
+
for (let userData of items) {
|
|
929
|
+
const { id, email } = userData;
|
|
930
|
+
if (id && email) {
|
|
931
|
+
if (users.hasOwnProperty(email)) {
|
|
932
|
+
users[email] = id;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
pageNumber++;
|
|
938
|
+
} else {
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
941
|
+
} while (true);
|
|
942
|
+
resolve(accountName);
|
|
943
|
+
});
|
|
944
|
+
})).catch(Logger.error);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
module.exports = {
|
|
949
|
+
producer,
|
|
950
|
+
};
|