aloux-iam 0.0.141 → 0.0.142

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.
@@ -9,9 +9,11 @@ self.create = async (req, res) => {
9
9
  req.header("Company") !== "undefined" ? req.header("Company") : null;
10
10
 
11
11
  const log = new Log(req.body);
12
+ const businessId = req.header("business") !== "undefined" ? req.header("business") : null;
12
13
  log.createdAt = new Date().getTime();
13
14
  log._user = req.user._id;
14
15
  log._company = companyId;
16
+ log._business = businessId;
15
17
 
16
18
  log.label = req.body.label;
17
19
  await log.save();
@@ -54,110 +56,36 @@ self.retrieve = async (req, res) => {
54
56
  const companyId =
55
57
  req.header("Company") !== "undefined" ? req.header("Company") : null;
56
58
 
57
- const matchStage = { _company: companyId };
59
+ const query = { _company: companyId };
58
60
 
59
61
  if (req.body.users?.length) {
60
- matchStage._user = {
62
+ query._user = {
61
63
  $in: req.body.users.map((id) => new mongoose.Types.ObjectId(id)),
62
64
  };
63
65
  }
64
66
 
65
67
  if (req.body.dateStart || req.body.dateEnd) {
66
- matchStage.createdAt = {};
67
- if (req.body.dateStart)
68
- matchStage.createdAt.$gte = Number(req.body.dateStart);
69
- if (req.body.dateEnd)
70
- matchStage.createdAt.$lte = Number(req.body.dateEnd);
68
+ query.createdAt = {};
69
+ if (req.body.dateStart) query.createdAt.$gte = Number(req.body.dateStart);
70
+ if (req.body.dateEnd) query.createdAt.$lte = Number(req.body.dateEnd);
71
71
  }
72
72
 
73
- const [totalCount, byLabel, byDate, byUser] = await Promise.all([
74
- Log.countDocuments(matchStage),
75
-
76
- Log.aggregate([
77
- { $match: matchStage },
78
- { $group: { _id: "$label", count: { $sum: 1 } } },
79
- { $sort: { count: -1 } },
80
- ]),
81
-
82
- Log.aggregate([
83
- { $match: matchStage },
84
- {
85
- $group: {
86
- _id: {
87
- $dateToString: {
88
- format: "%Y-%m-%d",
89
- date: { $toDate: "$createdAt" },
90
- },
91
- },
92
- count: { $sum: 1 },
93
- },
94
- },
95
- { $sort: { _id: 1 } },
96
- ]),
97
-
98
- Log.aggregate([
99
- { $match: matchStage },
100
- { $group: { _id: "$_user", count: { $sum: 1 } } },
101
- {
102
- $lookup: {
103
- from: "users",
104
- localField: "_id",
105
- foreignField: "_id",
106
- as: "user",
107
- },
108
- },
109
- { $unwind: "$user" },
110
- {
111
- $project: {
112
- name: { $concat: ["$user.name", " ", "$user.lastName"] },
113
- count: 1,
114
- },
115
- },
116
- { $sort: { count: -1 } },
117
- ]),
118
- ]);
119
-
120
- const topUsers = byUser.slice(0, 10);
121
- const leastUsers = [...byUser]
122
- .sort((a, b) => a.count - b.count)
123
- .slice(0, 10);
124
-
125
- // for (let i in consulta) {
126
- // consulta[i].label = consulta[i]._label.label;
127
- // await consulta[i].save();
128
- // }
73
+ if (req.body.business?.length) {
74
+ query._business = { $in: req.body.business };
75
+ }
76
+
77
+ const consulta = await Log.find(query).populate("_user", "name lastName email");
129
78
 
130
79
  const response = {
131
- dataset0: { field: "Visualizaciones totales", count: totalCount },
132
- dataset1: [],
133
- dataset2: {
134
- field: "Distribución de acciones",
135
- counts: byLabel.map((i) => i.count),
136
- operations: byLabel.map((i) => i._id),
137
- },
138
- dataset3: {
139
- field: "Distribución de acciones",
140
- items: byLabel.map((i) => ({
141
- addGroup: i._id,
142
- totalResponse: i.count,
143
- })),
144
- },
145
- dataset4: {
146
- field: "Actividad en la plataforma",
147
- counts: byDate.map((i) => i.count),
148
- actionsName: byDate.map((i) => formatDate(i._id)),
149
- },
150
- dataset5: [],
151
- dataset6: {
152
- field: "Usuarios con mas actividad en la plataforma",
153
- counts: topUsers.map((i) => i.count),
154
- actionsName: topUsers.map((i) => i.name.split(" ")),
155
- },
156
- dataset7: {
157
- field: "Usuarios con menos actividad en la plataforma",
158
- counts: leastUsers.map((i) => i.count),
159
- actionsName: leastUsers.map((i) => i.name.split(" ")),
160
- },
80
+ dataset0: { field: "Visualizaciones totales", count: consulta.length },
81
+ dataset1: processDataset1(consulta),
82
+ dataset2: processDataset2(consulta),
83
+ dataset3: processDataset3(consulta),
84
+ dataset4: processDataset4(consulta),
85
+ dataset5: processDataset5(consulta),
86
+ dataset6: processDataset6(consulta),
87
+ dataset7: processDataset7(consulta),
88
+ dataset8: processDataset8(consulta, req.body.search, req.body.page, req.body.limit),
161
89
  };
162
90
 
163
91
  res.status(200).send(response);
@@ -170,31 +98,20 @@ self.retrieve = async (req, res) => {
170
98
  function formatDate(isoDate) {
171
99
  const [year, month, day] = isoDate.split("-");
172
100
  const monthNames = [
173
- "Ene",
174
- "Feb",
175
- "Mar",
176
- "Abr",
177
- "May",
178
- "Jun",
179
- "Jul",
180
- "Ago",
181
- "Sep",
182
- "Oct",
183
- "Nov",
184
- "Dic",
101
+ "Ene", "Feb", "Mar", "Abr", "May", "Jun",
102
+ "Jul", "Ago", "Sep", "Oct", "Nov", "Dic",
185
103
  ];
186
104
  return `${day} ${monthNames[parseInt(month) - 1]} ${year}`;
187
105
  }
188
106
 
189
107
  function processDataset1(consulta) {
190
- return consulta.map((item) => {
191
- return {
192
- _id: item._id,
193
- labelDescription: item.label,
194
- userName: item._user.name + " " + item._user.lastName,
195
- createdAt: item.createdAt,
196
- };
197
- });
108
+ return consulta.map((item) => ({
109
+ _id: item._id,
110
+ labelDescription: item.label,
111
+ userName: item._user.name + " " + item._user.lastName,
112
+ userEmail: item._user.email,
113
+ createdAt: item.createdAt,
114
+ }));
198
115
  }
199
116
 
200
117
  function processDataset2(consulta) {
@@ -229,7 +146,8 @@ function processDataset3(consulta) {
229
146
 
230
147
  function processDataset4(consulta) {
231
148
  const dateCounts = consulta.reduce((acc, item) => {
232
- const date = formatDate(new Date(item.createdAt));
149
+ const isoDate = new Date(item.createdAt).toISOString().split("T")[0];
150
+ const date = formatDate(isoDate);
233
151
  acc[date] = (acc[date] || 0) + 1;
234
152
  return acc;
235
153
  }, {});
@@ -246,7 +164,8 @@ function processDataset5(consulta) {
246
164
  const categories = new Set();
247
165
 
248
166
  consulta.forEach((item) => {
249
- const date = formatDate(new Date(item.createdAt));
167
+ const isoDate = new Date(item.createdAt).toISOString().split("T")[0];
168
+ const date = formatDate(isoDate);
250
169
  const label = item.label;
251
170
  categories.add(date);
252
171
 
@@ -276,55 +195,80 @@ function processDataset5(consulta) {
276
195
  function processDataset6(data) {
277
196
  const userActivity = {};
278
197
 
279
- // Count activity per user. Assuming data contains _user with name and lastName
280
198
  data.forEach((item) => {
281
199
  const fullName = `${item._user.name} ${item._user.lastName}`;
282
200
  userActivity[fullName] = (userActivity[fullName] || 0) + 1;
283
201
  });
284
202
 
285
- // Sort users by activity count in descending order
286
- const sortedUsers = Object.entries(userActivity).sort(
287
- ([, countA], [, countB]) => countB - countA
288
- );
289
-
290
- // Extract top 10 users
291
- const topUsers = sortedUsers.slice(0, 10);
203
+ const topUsers = Object.entries(userActivity)
204
+ .sort(([, a], [, b]) => b - a)
205
+ .slice(0, 10);
292
206
 
293
- // Build the result object
294
- const result = {
207
+ return {
208
+ field: "Usuarios con mas actividad en la plataforma",
295
209
  counts: topUsers.map(([, count]) => count),
296
210
  actionsName: topUsers.map(([fullName]) => fullName.split(" ")),
297
- field: "Usuarios con mas actividad en la plataforma",
298
211
  };
299
-
300
- return result;
301
212
  }
302
213
 
303
214
  function processDataset7(data) {
304
215
  const userActivity = {};
305
216
 
306
- // Count activity per user. Assuming data contains _user with name and lastName
307
217
  data.forEach((item) => {
308
218
  const fullName = `${item._user.name} ${item._user.lastName}`;
309
219
  userActivity[fullName] = (userActivity[fullName] || 0) + 1;
310
220
  });
311
221
 
312
- // Sort users by activity count in ascending order
313
- const sortedUsers = Object.entries(userActivity).sort(
314
- ([, countA], [, countB]) => countA - countB
315
- );
222
+ const leastUsers = Object.entries(userActivity)
223
+ .sort(([, a], [, b]) => a - b)
224
+ .slice(0, 10);
316
225
 
317
- // Extract top 10 least active users
318
- const leastActiveUsers = sortedUsers.slice(0, 10);
319
-
320
- // Build the result object
321
- const result = {
322
- counts: leastActiveUsers.map(([, count]) => count),
323
- actionsName: leastActiveUsers.map(([fullName]) => fullName.split(" ")),
226
+ return {
324
227
  field: "Usuarios con menos actividad en la plataforma",
228
+ counts: leastUsers.map(([, count]) => count),
229
+ actionsName: leastUsers.map(([fullName]) => fullName.split(" ")),
325
230
  };
231
+ }
326
232
 
327
- return result;
233
+ function processDataset8(data, search = "", page = 1, limit = 10) {
234
+ const userActivity = {};
235
+
236
+ data.forEach((item) => {
237
+ const fullName = `${item._user.name} ${item._user.lastName}`;
238
+ if (!userActivity[fullName]) {
239
+ userActivity[fullName] = {
240
+ name: item._user.name,
241
+ lastName: item._user.lastName,
242
+ email: item._user.email,
243
+ count: 0,
244
+ };
245
+ }
246
+ userActivity[fullName].count++;
247
+ });
248
+
249
+ let users = Object.values(userActivity).sort((a, b) => b.count - a.count);
250
+
251
+ if (search) {
252
+ const s = search.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
253
+ users = users.filter((u) => {
254
+ const full = `${u.name} ${u.lastName} ${u.email}`.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
255
+ return full.includes(s);
256
+ });
257
+ }
258
+
259
+ const total = users.length;
260
+ const totalPages = Math.ceil(total / limit);
261
+ const start = (page - 1) * limit;
262
+ const items = users.slice(start, start + limit);
263
+
264
+ return {
265
+ field: "Actividad de usuarios",
266
+ total,
267
+ page,
268
+ limit,
269
+ totalPages,
270
+ items,
271
+ };
328
272
  }
329
273
 
330
274
  self.get = async (req, res) => {
@@ -14,29 +14,29 @@ const self = module.exports;
14
14
 
15
15
  self.create = async (req, res) => {
16
16
  try {
17
- let user = await serviceUser.create(req.body);
18
- if (process.env.SEND_EMAIL_USER === "true") {
19
- let file = _brand.template("TEMPLATE_ACCOUNT");
20
- file = file.replace("{{user}}", user.name);
21
- file = file.replace("{{email}}", req.body.email);
22
- file = file.replace("{{password}}", req.body.pwd);
23
- const app = process.env.APP;
24
- const urlEmail = process.env[`URL_EMAIL_${app}`];
25
-
26
- if (urlEmail) {
27
- file = file.replaceAll("{{urlToken}}", urlEmail);
28
- }
17
+ let user = await serviceUser.create(req.body)
18
+
19
+ if (process.env.SEND_EMAIL_USER === "true" && user.email) {
20
+ let file = _brand.template("TEMPLATE_ACCOUNT")
21
+ file = file.replace("{{user}}", user.name)
22
+ file = file.replace("{{email}}", req.body.email)
23
+ file = file.replace("{{password}}", req.body.pwd)
24
+ const app = process.env.APP
25
+ const urlEmail = process.env[`URL_EMAIL_${app}`]
26
+ if (urlEmail) file = file.replaceAll("{{urlToken}}", urlEmail)
27
+
29
28
  await AWS_SES.sendCustom(
30
29
  user.email,
31
30
  file,
32
31
  process.env.SUBJECT_EMAIL || "bienvenido"
33
- );
32
+ )
34
33
  }
35
- res.status(201).send(user);
34
+
35
+ res.status(201).send(user)
36
36
  } catch (error) {
37
- utils.responseError(res, error, 400, "Error al crear usuario", "Revisa el detalle del error");
37
+ utils.responseError(res, error, 400, "Error al crear usuario", "Revisa el detalle del error")
38
38
  }
39
- };
39
+ }
40
40
 
41
41
  self.update = async (req, resp) => {
42
42
  try {
@@ -952,3 +952,12 @@ self.addTimeToken = async (req, res) => {
952
952
  res.status(400).send({ error: error.message });
953
953
  }
954
954
  };
955
+
956
+ self.checkUsername = async (req, res) => {
957
+ try {
958
+ const result = await serviceUser.checkUsername(req.body.name)
959
+ res.status(200).send(result)
960
+ } catch (error) {
961
+ utils.responseError(res, error, 400, "Error al verificar username", "Revisa el detalle del error")
962
+ }
963
+ }
package/lib/models/Log.js CHANGED
@@ -5,6 +5,7 @@ const menuSchema = mongoose.Schema({
5
5
  label: { type: String, required: true },
6
6
  _user: { type: ObjectId, required: true, ref: "User" },
7
7
  _company: { type: ObjectId, required: false, ref: "Company" },
8
+ _business: { type: ObjectId, required: false, ref: "Business" },
8
9
  createdAt: { type: Number },
9
10
  });
10
11
 
@@ -8,9 +8,17 @@ const adminSchema = mongoose.Schema({
8
8
  lastName: { type: String, required: false, trim: true },
9
9
  email: {
10
10
  type: String,
11
- required: true,
11
+ required: false,
12
+ trim: true,
13
+ unique: true,
14
+ sparse: true,
15
+ lowercase: true,
16
+ },
17
+ username: {
18
+ type: String,
12
19
  trim: true,
13
20
  unique: true,
21
+ sparse: true,
14
22
  lowercase: true,
15
23
  },
16
24
  pwd: { type: String, trim: true, minLength: 8 },
@@ -78,6 +86,7 @@ const adminSchema = mongoose.Schema({
78
86
  token: { type: String, required: true },
79
87
  date: { type: Number },
80
88
  dateEnd: { type: Number },
89
+ type: { type: String, enum: ["session", "api"], default: "session" },
81
90
  },
82
91
  ],
83
92
 
package/lib/router.js CHANGED
@@ -46,6 +46,7 @@ router.get("/iam/user", middleware, user.retrieve);
46
46
  router.post("/iam/user/pages", middleware, user.retrievePages);
47
47
  router.get("/iam/business/user", middleware, user.retrieveByBusiness);
48
48
  router.get("/iam/user/by/my/companies", middleware, user.retrieveByMyCompanies);
49
+ router.post('/iam/user/check/username', middleware, user.checkUsername);
49
50
  router.get("/iam/user/:USER_ID", middleware, user.get);
50
51
  router.patch("/iam/user/:USER_ID", middleware, user.update);
51
52
  router.put("/iam/user/:USER_ID/status", middleware, user.status);
@@ -1,99 +1,179 @@
1
+ const jwt = require("jsonwebtoken")
1
2
  const User = require('../models/User')
2
3
  const self = module.exports
3
4
 
5
+ const generateUniqueUsername = async (name) => {
6
+ const base = name
7
+ .toLowerCase()
8
+ .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
9
+ .replace(/\s+/g, '-')
10
+ .replace(/[^a-z0-9-]/g, '')
11
+
12
+ let username = base
13
+ let count = 1
14
+
15
+ while (await User.findOne({ username }).lean()) {
16
+ username = `${base}${count}`
17
+ count++
18
+ }
19
+
20
+ return username
21
+ }
22
+
4
23
  self.create = async (body) => {
5
- let user
6
- user = await User.findOne({email: body.email}).lean()
24
+ let user
25
+ const isServiceAccount = !body.email && !body.pwd
26
+
27
+ // Limpieza de campos únicos que pueden llegar como null
28
+ const nullableUnique = ['username', 'phone', 'email']
29
+ nullableUnique.forEach(field => {
30
+ if (!body[field]) delete body[field]
31
+ })
7
32
 
8
- if(user){
33
+ if (isServiceAccount) {
34
+ user = new User(body)
35
+ user.username = await generateUniqueUsername(body.name)
36
+ user.createdAt = new Date().getTime()
37
+ user.status = body?.status ?? 'Activo'
38
+ user.data = { changePwd: false }
39
+
40
+ const token = jwt.sign({ _id: user._id }, process.env.AUTH_SECRET)
41
+ user.tokens = [{
42
+ token,
43
+ date: new Date().getTime(),
44
+ dateEnd: Number.MAX_SAFE_INTEGER,
45
+ type: 'api'
46
+ }]
47
+
48
+ try {
49
+ await user.save()
50
+ } catch (error) {
51
+ if (error.code === 11000) {
9
52
  throw {
10
- code: 404,
11
- title: 'Upss!',
12
- detail: '',
13
- suggestion: 'El correo ya se encuentra resgitrado',
14
- error: new Error()
53
+ code: 409,
54
+ title: 'Upss!',
55
+ detail: 'Clave duplicada',
56
+ suggestion: 'El username ya se encuentra registrado',
57
+ error
15
58
  }
59
+ }
60
+ throw error
16
61
  }
17
-
18
- user = new User(body)
19
- user.createdAt = (new Date()).getTime()
20
- user.status = body?.status ? body?.status : 'Activo'
21
-
22
- delete user.pwd
23
- user.data.changePwd = false
24
- await user.save()
25
62
 
26
63
  return user
27
- }
64
+ }
65
+
66
+ // Flujo normal
67
+ if (body.email) {
68
+ const exists = await User.findOne({ email: body.email }).lean()
69
+ if (exists) {
70
+ throw {
71
+ code: 409,
72
+ title: 'Upss!',
73
+ detail: '',
74
+ suggestion: 'El correo ya se encuentra registrado',
75
+ error: new Error()
76
+ }
77
+ }
78
+ }
28
79
 
29
- self.update = async (USER_ID, body) => {
30
- const _id = USER_ID
31
- const user = await User.findOne({ _id }).countDocuments().lean()
80
+ user = new User(body)
81
+ user.createdAt = new Date().getTime()
82
+ user.status = body?.status ?? 'Activo'
83
+ user.data = { changePwd: false }
32
84
 
33
- if (!user) {
34
- throw {
35
- code: 404,
36
- title: 'Upss!',
37
- detail: 'No se encontró el elemento',
38
- suggestion: 'Verifica que el usuario aun este activo en la plataforma',
39
- error: new Error()
40
- }
85
+ try {
86
+ await user.save()
87
+ } catch (error) {
88
+ if (error.code === 11000) {
89
+ throw {
90
+ code: 409,
91
+ title: 'Upss!',
92
+ detail: 'Clave duplicada',
93
+ suggestion: 'El correo o username ya se encuentra registrado',
94
+ error
95
+ }
41
96
  }
97
+ throw error
98
+ }
42
99
 
43
- if (body.phone) {
44
- await User.updateOne({ _id }, { 'validateKey.validatePhone.validCodePhone': false })
100
+ return user
101
+ }
102
+
103
+ self.update = async (USER_ID, body) => {
104
+ const _id = USER_ID
105
+ const user = await User.findOne({ _id }).countDocuments().lean()
106
+
107
+ if (!user) {
108
+ throw {
109
+ code: 404,
110
+ title: 'Upss!',
111
+ detail: 'No se encontró el elemento',
112
+ suggestion: 'Verifica que el usuario aun este activo en la plataforma',
113
+ error: new Error()
45
114
  }
115
+ }
116
+
117
+ if (body.phone) {
118
+ await User.updateOne({ _id }, { 'validateKey.validatePhone.validCodePhone': false })
119
+ }
46
120
 
47
- body.lastUpdate = (new Date()).getTime()
48
- const result = await User.updateOne({ _id }, { $set: body })
121
+ body.lastUpdate = new Date().getTime()
122
+ const result = await User.updateOne({ _id }, { $set: body })
49
123
 
50
- return result
124
+ return result
51
125
  }
52
126
 
53
127
  self.status = async (USER_ID, body) => {
54
- const _id = USER_ID
55
- const user = await User.findOne({ _id })
56
-
57
- if (!user) {
58
- throw {
59
- code: 404,
60
- title: 'Upss!',
61
- detail: 'No se encontró el elemento',
62
- suggestion: 'Verifica que el usuario aun este activo en la plataforma',
63
- error: new Error()
64
- }
128
+ const _id = USER_ID
129
+ const user = await User.findOne({ _id })
130
+
131
+ if (!user) {
132
+ throw {
133
+ code: 404,
134
+ title: 'Upss!',
135
+ detail: 'No se encontró el elemento',
136
+ suggestion: 'Verifica que el usuario aun este activo en la plataforma',
137
+ error: new Error()
65
138
  }
139
+ }
66
140
 
67
- user.status = body.status
68
- user.lastUpdate = (new Date()).getTime()
141
+ user.status = body.status
142
+ user.lastUpdate = new Date().getTime()
69
143
 
70
- const result = await user.save()
71
-
72
- return result
144
+ return await user.save()
73
145
  }
74
146
 
75
147
  self.updatepassword = async (body, USER_ID) => {
76
- let user
77
- user = User(body)
78
- const _id = USER_ID
79
-
80
- user = await User.findOne({ _id })
81
-
82
- if (!user) {
83
- throw {
84
- code: 404,
85
- title: 'Upss!',
86
- detail: 'No se encontró el elemento',
87
- suggestion: 'Verifica que el usuario aun este activo en la plataforma',
88
- error: new Error()
89
- }
148
+ const _id = USER_ID
149
+ const user = await User.findOne({ _id })
150
+
151
+ if (!user) {
152
+ throw {
153
+ code: 404,
154
+ title: 'Upss!',
155
+ detail: 'No se encontró el elemento',
156
+ suggestion: 'Verifica que el usuario aun este activo en la plataforma',
157
+ error: new Error()
90
158
  }
159
+ }
91
160
 
92
- user.pwd = body.pwd
93
- user.lastUpdate = (new Date()).getTime()
94
-
95
- const result = await user.save()
161
+ user.pwd = body.pwd
162
+ user.lastUpdate = new Date().getTime()
96
163
 
97
- return result
164
+ return await user.save()
98
165
  }
99
166
 
167
+ self.checkUsername = async (name) => {
168
+ if (!name) {
169
+ throw {
170
+ code: 400,
171
+ title: 'El nombre es requerido',
172
+ detail: '',
173
+ suggestion: 'Envía un nombre para generar el username',
174
+ error: new Error()
175
+ }
176
+ }
177
+ const username = await generateUniqueUsername(name)
178
+ return { username }
179
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aloux-iam",
3
- "version": "0.0.141",
3
+ "version": "0.0.142",
4
4
  "description": "Aloux IAM for APIs ",
5
5
  "main": "index.js",
6
6
  "scripts": {