enterprise-logging-system 1.0.55 → 1.1.1

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.
@@ -12,320 +12,182 @@ class AccessLogRepository extends BaseRepository_1.BaseRepository {
12
12
  /**
13
13
  * Ensure unique index exists for userId + tenantId + date
14
14
  * This prevents race conditions from creating duplicate entries
15
- * NOTE: This index is only for aggregated daily summaries, not individual activities
16
15
  */
17
16
  async ensureUniqueIndex() {
18
17
  try {
19
- // We DON'T want a unique index on individual activities
20
- // Individual activities should allow multiple entries per day
21
- // Only aggregated summaries should be unique
22
- // Drop the unique index if it exists
23
- try {
24
- await this.collection.dropIndex('userId_1_tenantId_1_indexes.session_date_1');
25
- console.log('✅ Dropped unique index - allowing multiple activities per day');
26
- }
27
- catch (error) {
28
- // Index might not exist, that's fine
29
- }
30
- // Create a non-unique index for better query performance
31
18
  await this.collection.createIndex({
32
19
  userId: 1,
33
20
  tenantId: 1,
34
- timestamp: -1
21
+ 'indexes.session_date': 1
35
22
  }, {
36
- background: true,
37
- name: 'userId_tenantId_timestamp_idx'
23
+ unique: true,
24
+ partialFilterExpression: {
25
+ 'indexes.session_date': { $exists: true }
26
+ },
27
+ background: true
38
28
  });
39
- console.log('✅ Created non-unique index for queries');
40
29
  }
41
30
  catch (error) {
42
- console.log('Index creation info:', error.message);
31
+ // Index might already exist, ignore error
32
+ console.log('Unique index creation skipped (may already exist)');
43
33
  }
44
34
  }
45
35
  /**
46
- * Get grouped access logs - one entry per user per day with aggregated data
36
+ * Log a page view. Implements upsert logic: if an access log entry already exists
37
+ * for this userId on the same day, it updates that record instead of creating
38
+ * a new one. This ensures one access log entry per user per day.
39
+ * Uses atomic operations to prevent race conditions.
47
40
  */
48
- async listGroupedByUser(query) {
49
- const { page = 1, limit = 20, search, tenantId, userId, startDate, endDate, sortBy = 'date', sortOrder = 'desc' } = query;
50
- const skip = (page - 1) * limit;
51
- const matchStage = {};
52
- // Apply filters
53
- if (tenantId)
54
- matchStage.tenantId = tenantId;
55
- if (userId)
56
- matchStage.userId = userId;
57
- if (startDate || endDate) {
58
- matchStage.timestamp = {};
59
- if (startDate)
60
- matchStage.timestamp.$gte = new Date(startDate);
61
- if (endDate)
62
- matchStage.timestamp.$lte = new Date(endDate);
63
- }
64
- if (search) {
65
- matchStage.$or = [
66
- { username: { $regex: search, $options: 'i' } },
67
- { employeeId: { $regex: search, $options: 'i' } },
68
- { pageTitle: { $regex: search, $options: 'i' } },
69
- { activityName: { $regex: search, $options: 'i' } }
70
- ];
71
- }
72
- console.log('🔍 Match Stage:', JSON.stringify(matchStage, null, 2));
73
- // First, let's check if we have any documents at all
74
- const totalDocs = await this.collection.countDocuments({});
75
- console.log('📊 Total documents in collection:', totalDocs);
76
- // Check documents matching our filter
77
- const matchingDocs = await this.collection.countDocuments(matchStage);
78
- console.log('🎯 Documents matching filter:', matchingDocs);
79
- const pipeline = [
80
- { $match: matchStage },
81
- {
82
- $group: {
83
- _id: {
84
- userId: '$userId',
85
- date: {
86
- $dateToString: {
87
- format: '%Y-%m-%d',
88
- date: '$timestamp'
89
- }
90
- }
91
- },
92
- // User info (take first occurrence)
93
- username: { $first: '$username' },
94
- employeeId: { $first: '$employeeId' },
95
- userRole: { $first: '$userRole' },
96
- tenantId: { $first: '$tenantId' },
97
- // Session info
98
- sessionId: { $first: '$sessionId' },
99
- ipAddress: { $first: '$ipAddress' },
100
- browser: { $first: '$browser' },
101
- // Time tracking
102
- startTime: { $min: '$timestamp' },
103
- endTime: { $max: '$timestamp' },
104
- // Activity counts
105
- totalActivities: { $sum: 1 },
106
- totalPageViews: {
107
- $sum: { $cond: [{ $eq: ['$type', 'PAGE_OPEN'] }, 1, 0] }
108
- },
109
- totalActions: {
110
- $sum: { $cond: [{ $eq: ['$type', 'ACTION_PERFORMED'] }, 1, 0] }
111
- },
112
- // Collect all activities for detailed view
113
- activities: {
114
- $push: {
115
- _id: '$_id',
116
- timestamp: '$timestamp',
117
- type: '$type',
118
- activityType: '$activityType',
119
- activityName: '$activityName',
120
- pageTitle: '$pageTitle',
121
- pageUrl: '$pageUrl',
122
- actionType: '$actionType',
123
- actionTarget: '$actionTarget',
124
- actionData: '$actionData',
125
- activeDuration: '$activeDuration'
126
- }
127
- }
41
+ async logPageView(log) {
42
+ // Get start and end of the day for the current timestamp
43
+ const currentDate = new Date(log.timestamp);
44
+ // Create a unique daily key for this user (user_date format)
45
+ const dateKey = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-${String(currentDate.getDate()).padStart(2, '0')}`;
46
+ const dailyKey = `${log.userId}_${dateKey}`;
47
+ // Use atomic findOneAndUpdate with upsert to prevent race conditions
48
+ // Filter by user_date index to ensure one entry per user per day
49
+ const filter = {
50
+ userId: log.userId,
51
+ tenantId: log.tenantId,
52
+ 'indexes.user_date': dailyKey
53
+ };
54
+ const update = {
55
+ $set: {
56
+ // Always update these fields to latest values
57
+ timestamp: log.timestamp,
58
+ sessionId: log.sessionId,
59
+ pageId: log.pageId,
60
+ pageTitle: log.pageTitle,
61
+ pageUrl: log.pageUrl,
62
+ pageRoute: log.pageRoute,
63
+ previousPage: log.previousPage,
64
+ endTime: log.timestamp,
65
+ browser: log.browser,
66
+ geoLocation: log.geoLocation,
67
+ network: log.network,
68
+ ipAddress: log.ipAddress,
69
+ userAgent: log.userAgent,
70
+ activityType: log.activityType,
71
+ activityName: log.activityName,
72
+ ...(log.metadata && { metadata: log.metadata }),
73
+ indexes: {
74
+ tenant_user_date: `${log.tenantId}_${log.userId}_${dateKey}`,
75
+ session_time: `${log.sessionId}_${log.activityType}`,
76
+ page_time: `${log.pageId}_${log.timestamp.getTime()}`,
77
+ session_date: dailyKey
128
78
  }
129
79
  },
130
- {
131
- $project: {
132
- _id: { $concat: ['$_id.userId', '_', '$_id.date'] },
133
- userId: '$_id.userId',
134
- username: 1,
135
- employeeId: 1,
136
- userRole: 1,
137
- tenantId: 1,
138
- sessionId: 1,
139
- ipAddress: 1,
140
- browser: 1,
141
- startTime: 1,
142
- endTime: 1,
143
- duration: {
144
- $subtract: ['$endTime', '$startTime']
145
- },
146
- totalActivities: 1,
147
- totalPageViews: 1,
148
- totalActions: 1,
149
- activities: 1,
150
- // For display compatibility
151
- timestamp: '$startTime',
152
- activityType: 'DAILY_SUMMARY',
153
- activityName: {
154
- $concat: [
155
- { $toString: '$totalActivities' },
156
- ' activities (',
157
- { $toString: '$totalPageViews' },
158
- ' page views, ',
159
- { $toString: '$totalActions' },
160
- ' actions)'
161
- ]
162
- }
163
- }
164
- }
165
- ];
166
- // Add sorting
167
- const sortField = sortBy === 'date' ? 'startTime' : sortBy;
168
- const sortDirection = sortOrder === 'asc' ? 1 : -1;
169
- pipeline.push({ $sort: { [sortField]: sortDirection } });
170
- console.log('🔧 Aggregation Pipeline:', JSON.stringify(pipeline, null, 2));
171
- // Get total count
172
- const countPipeline = [...pipeline, { $count: 'total' }];
173
- const countResult = await this.collection.aggregate(countPipeline).toArray();
174
- const total = countResult[0]?.total || 0;
175
- console.log('📈 Total after aggregation:', total);
176
- // Add pagination
177
- pipeline.push({ $skip: skip }, { $limit: limit });
178
- const data = await this.collection.aggregate(pipeline).toArray();
179
- console.log('📋 Final result count:', data.length);
180
- console.log('📋 Sample data:', data.slice(0, 2));
181
- return {
182
- data,
183
- total,
184
- page,
185
- limit,
186
- totalPages: Math.ceil(total / limit),
187
- hasNext: page < Math.ceil(total / limit),
188
- hasPrev: page > 1
189
- };
190
- }
191
- /**
192
- * Get detailed activities for a specific user and date
193
- */
194
- async getUserDayActivities(userId, date, tenantId) {
195
- const startOfDay = new Date(date);
196
- startOfDay.setHours(0, 0, 0, 0);
197
- const endOfDay = new Date(date);
198
- endOfDay.setHours(23, 59, 59, 999);
199
- const filter = {
200
- userId,
201
- timestamp: {
202
- $gte: startOfDay,
203
- $lte: endOfDay
80
+ $setOnInsert: {
81
+ // Only set these on first insert
82
+ _id: this.generateId(),
83
+ type: 'PAGE_OPEN',
84
+ tenantId: log.tenantId,
85
+ userId: log.userId,
86
+ userRole: log.userRole,
87
+ username: log.username,
88
+ employeeId: log.employeeId,
89
+ eventTime: log.eventTime,
90
+ startTime: log.startTime || log.timestamp
91
+ },
92
+ $inc: {
93
+ // Increment activity count atomically (starts at 1 on first insert)
94
+ activityCount: 1
204
95
  }
205
96
  };
206
- if (tenantId) {
207
- filter.tenantId = tenantId;
208
- }
209
- return this.collection
210
- .find(filter)
211
- .sort({ timestamp: 1 })
212
- .toArray();
213
- }
214
- /**
215
- * Get ALL individual activities for a user (every click, every page view)
216
- * This returns detailed activity log for comprehensive user tracking
217
- */
218
- async getUserAllActivities(userId, startDate, endDate, tenantId) {
219
- const filter = { userId };
220
- if (startDate || endDate) {
221
- filter.timestamp = {};
222
- if (startDate) {
223
- const start = new Date(startDate);
224
- start.setHours(0, 0, 0, 0);
225
- filter.timestamp.$gte = start;
226
- }
227
- if (endDate) {
228
- const end = new Date(endDate);
229
- end.setHours(23, 59, 59, 999);
230
- filter.timestamp.$lte = end;
231
- }
97
+ const result = await this.collection.findOneAndUpdate(filter, update, {
98
+ upsert: true,
99
+ returnDocument: 'after'
100
+ });
101
+ if (result.value) {
102
+ // Calculate duration
103
+ const startTime = new Date(result.value.startTime || result.value.timestamp);
104
+ result.value.activeDuration = log.timestamp.getTime() - startTime.getTime();
105
+ return result.value;
232
106
  }
233
- if (tenantId) {
234
- filter.tenantId = tenantId;
107
+ // Fallback: if upsert somehow failed, try to find the existing record
108
+ const existingLog = await this.findOne(filter);
109
+ if (existingLog) {
110
+ return existingLog;
235
111
  }
236
- // Get all individual activities (not aggregated ones)
237
- return this.collection
238
- .find(filter)
239
- .sort({ timestamp: -1 }) // Most recent first
240
- .toArray();
112
+ // Last resort: This should never happen with upsert, but handle it
113
+ throw new Error('Failed to create or update access log entry');
241
114
  }
242
115
  /**
243
- * Log a page view as individual entry (not aggregated)
244
- * This creates a separate log entry for each page view to track detailed user activity
116
+ * Log a user action. Implements upsert logic: if an access log entry already exists
117
+ * for this userId on the same day, it updates that record instead of creating
118
+ * a new one. This ensures one access log entry per user per day.
119
+ * Uses atomic operations to prevent race conditions.
245
120
  */
246
- async logIndividualPageView(log) {
121
+ async logAction(log) {
122
+ // Get start and end of the day for the current timestamp
247
123
  const currentDate = new Date(log.timestamp);
124
+ // Create a unique daily key for this user
248
125
  const dateKey = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-${String(currentDate.getDate()).padStart(2, '0')}`;
249
- // Create individual entry with unique ID - NO UPSERT, just insert
250
- const individualLog = {
251
- _id: this.generateId(),
252
- type: 'PAGE_OPEN',
253
- tenantId: log.tenantId,
126
+ const dailyKey = `${log.userId}_${dateKey}`;
127
+ // Use atomic findOneAndUpdate with upsert - same filter as logPageView
128
+ const filter = {
254
129
  userId: log.userId,
255
- userRole: log.userRole,
256
- username: log.username,
257
- employeeId: log.employeeId,
258
- sessionId: log.sessionId,
259
- ipAddress: log.ipAddress,
260
- userAgent: log.userAgent,
261
- timestamp: log.timestamp,
262
- eventTime: log.eventTime,
263
- startTime: log.startTime || log.timestamp,
264
- activityType: log.activityType,
265
- activityName: log.activityName,
266
- pageId: log.pageId,
267
- pageTitle: log.pageTitle,
268
- pageUrl: log.pageUrl,
269
- pageRoute: log.pageRoute,
270
- previousPage: log.previousPage,
271
- browser: log.browser,
272
- geoLocation: log.geoLocation,
273
- network: log.network,
274
- metadata: log.metadata,
275
- indexes: {
276
- tenant_user_date: `${log.tenantId}_${log.userId}_${dateKey}`,
277
- session_time: `${log.sessionId}_${log.activityType}`,
278
- page_time: `${log.pageId}_${log.timestamp.getTime()}`,
279
- session_date: `${log.userId}_${dateKey}`
280
- }
281
- };
282
- await this.collection.insertOne(individualLog);
283
- return individualLog;
284
- }
285
- /**
286
- * Log a user action as individual entry (not aggregated)
287
- * This creates a separate log entry for each action to track detailed user activity
288
- */
289
- async logIndividualAction(log) {
290
- const currentDate = new Date(log.timestamp);
291
- const dateKey = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-${String(currentDate.getDate()).padStart(2, '0')}`;
292
- // Create individual entry with unique ID - NO UPSERT, just insert
293
- const individualLog = {
294
- _id: this.generateId(),
295
- type: 'ACTION_PERFORMED',
296
130
  tenantId: log.tenantId,
297
- userId: log.userId,
298
- userRole: log.userRole,
299
- username: log.username,
300
- employeeId: log.employeeId,
301
- sessionId: log.sessionId,
302
- ipAddress: log.ipAddress,
303
- userAgent: log.userAgent,
304
- timestamp: log.timestamp,
305
- eventTime: log.eventTime,
306
- startTime: log.startTime || log.timestamp,
307
- activityType: log.activityType,
308
- activityName: log.activityName,
309
- actionType: log.actionType,
310
- actionTarget: log.actionTarget,
311
- actionData: log.actionData,
312
- pageId: log.pageId,
313
- browser: log.browser,
314
- metadata: log.metadata,
315
- indexes: {
316
- tenant_user_date: `${log.tenantId}_${log.userId}_${dateKey}`,
317
- session_time: `${log.sessionId}_${log.activityType}`,
318
- page_time: `${log.pageId}_${log.timestamp.getTime()}`,
319
- session_date: `${log.userId}_${dateKey}`
131
+ 'indexes.session_date': dailyKey
132
+ };
133
+ const update = {
134
+ $set: {
135
+ // Always update these fields to latest values
136
+ timestamp: log.timestamp,
137
+ sessionId: log.sessionId,
138
+ activityType: log.activityType,
139
+ activityName: log.activityName,
140
+ actionType: log.actionType,
141
+ actionTarget: log.actionTarget,
142
+ actionData: log.actionData,
143
+ endTime: log.timestamp,
144
+ browser: log.browser,
145
+ ipAddress: log.ipAddress,
146
+ userAgent: log.userAgent,
147
+ ...(log.pageId && { pageId: log.pageId }),
148
+ ...(log.metadata && { metadata: log.metadata }),
149
+ indexes: {
150
+ tenant_user_date: `${log.tenantId}_${log.userId}_${dateKey}`,
151
+ session_time: `${log.sessionId}_${log.activityType}`,
152
+ page_time: `${log.pageId}_${log.timestamp.getTime()}`,
153
+ session_date: dailyKey
154
+ }
155
+ },
156
+ $setOnInsert: {
157
+ // Only set these on first insert
158
+ _id: this.generateId(),
159
+ type: 'ACTION_PERFORMED',
160
+ tenantId: log.tenantId,
161
+ userId: log.userId,
162
+ userRole: log.userRole,
163
+ username: log.username,
164
+ employeeId: log.employeeId,
165
+ eventTime: log.eventTime,
166
+ startTime: log.startTime || log.timestamp
167
+ },
168
+ $inc: {
169
+ // Track how many actions this user performed today (starts at 1 on first insert)
170
+ actionCount: 1
320
171
  }
321
172
  };
322
- await this.collection.insertOne(individualLog);
323
- return individualLog;
173
+ const result = await this.collection.findOneAndUpdate(filter, update, {
174
+ upsert: true,
175
+ returnDocument: 'after'
176
+ });
177
+ if (result.value) {
178
+ // Calculate duration for existing entries
179
+ const startTime = new Date(result.value.startTime || result.value.timestamp);
180
+ result.value.activeDuration = log.timestamp.getTime() - startTime.getTime();
181
+ return result.value;
182
+ }
183
+ // Fallback: if upsert failed, try to find the existing record
184
+ const existingLog = await this.findOne(filter);
185
+ if (existingLog) {
186
+ return existingLog;
187
+ }
188
+ // Last resort: This should never happen with upsert, but handle it
189
+ throw new Error('Failed to create or update access log entry');
324
190
  }
325
- // OLD METHODS REMOVED - Use logIndividualPageView and logIndividualAction instead
326
- // These old methods used upsert logic with unique indexes which caused E11000 errors
327
- // when users revisited pages on the same day. The new approach stores each activity
328
- // as a separate document and uses aggregation to show grouped views.
329
191
  async getUserTimeline(userId, dateRange) {
330
192
  return this.find({
331
193
  userId,
@@ -338,6 +200,7 @@ class AccessLogRepository extends BaseRepository_1.BaseRepository {
338
200
  /**
339
201
  * List access logs with filters, search, and pagination
340
202
  * Supports grouping by user per day to show one entry per user per day
203
+ * Excludes heavy fields like images, large metadata, etc. from listing
341
204
  */
342
205
  async list(query) {
343
206
  const filter = {};
@@ -369,9 +232,9 @@ class AccessLogRepository extends BaseRepository_1.BaseRepository {
369
232
  { pageRoute: searchRegex }
370
233
  ];
371
234
  }
372
- // If groupByUser is true, use the public listGroupedByUser method
235
+ // If groupByUser is true, use aggregation to get one entry per user
373
236
  if (query.groupByUser) {
374
- return this.listGroupedByUser(query);
237
+ return this.listGroupedByUser(filter, query);
375
238
  }
376
239
  // Default behavior: list all entries
377
240
  const page = Math.max(1, query.page ?? 1);
@@ -381,7 +244,22 @@ class AccessLogRepository extends BaseRepository_1.BaseRepository {
381
244
  : 'timestamp';
382
245
  const sortOrder = query.sortOrder === 'asc' ? 1 : -1;
383
246
  const sort = { [sortKey]: sortOrder };
384
- const result = await this.paginate(filter, page, limit, sort);
247
+ // Projection to include only essential fields for listing (more efficient than exclusion)
248
+ const projection = {
249
+ _id: 1,
250
+ username: 1,
251
+ employeeId: 1,
252
+ ipAddress: 1,
253
+ browser: 1,
254
+ startTime: 1,
255
+ endTime: 1,
256
+ timestamp: 1,
257
+ activeDuration: 1,
258
+ type: 1,
259
+ activityType: 1,
260
+ activityName: 1
261
+ };
262
+ const result = await this.paginate(filter, page, limit, sort, projection);
385
263
  return {
386
264
  data: result.data,
387
265
  total: result.total,
@@ -392,6 +270,107 @@ class AccessLogRepository extends BaseRepository_1.BaseRepository {
392
270
  hasPrev: result.page > 1
393
271
  };
394
272
  }
273
+ /**
274
+ * List access logs grouped by user per day - shows one entry per user per day with aggregated data
275
+ */
276
+ async listGroupedByUser(filter, query) {
277
+ const page = Math.max(1, query.page ?? 1);
278
+ const limit = Math.min(100, Math.max(1, query.limit ?? 20));
279
+ const skip = (page - 1) * limit;
280
+ // Build aggregation pipeline
281
+ const pipeline = [
282
+ { $match: filter },
283
+ {
284
+ $addFields: {
285
+ // Extract date from timestamp for grouping
286
+ dateOnly: {
287
+ $dateToString: { format: '%Y-%m-%d', date: '$timestamp' }
288
+ }
289
+ }
290
+ },
291
+ {
292
+ $sort: { timestamp: -1 } // Sort by latest timestamp first
293
+ },
294
+ {
295
+ $group: {
296
+ _id: {
297
+ userId: '$userId',
298
+ date: '$dateOnly' // Group by both userId AND date
299
+ },
300
+ // Take the latest entry for each user per day
301
+ latestLog: { $first: '$$ROOT' },
302
+ // Aggregate statistics for this user on this day
303
+ totalActivities: { $sum: '$activityCount' },
304
+ totalActions: { $sum: '$actionCount' },
305
+ firstActivity: { $min: '$startTime' },
306
+ lastActivity: { $max: '$timestamp' },
307
+ totalSessions: { $addToSet: '$sessionId' }
308
+ }
309
+ },
310
+ {
311
+ $replaceRoot: {
312
+ newRoot: {
313
+ $mergeObjects: [
314
+ '$latestLog',
315
+ {
316
+ // Add aggregated fields for this day
317
+ totalActivities: '$totalActivities',
318
+ totalActions: '$totalActions',
319
+ firstActivity: '$firstActivity',
320
+ lastActivity: '$lastActivity',
321
+ sessionCount: { $size: '$totalSessions' }
322
+ }
323
+ ]
324
+ }
325
+ }
326
+ },
327
+ // Include only essential fields for listing
328
+ {
329
+ $project: {
330
+ _id: 1,
331
+ username: 1,
332
+ employeeId: 1,
333
+ ipAddress: 1,
334
+ browser: 1,
335
+ startTime: 1,
336
+ endTime: 1,
337
+ timestamp: 1,
338
+ activeDuration: 1,
339
+ type: 1,
340
+ activityType: 1,
341
+ activityName: 1,
342
+ // Include aggregated fields
343
+ totalActivities: 1,
344
+ totalActions: 1,
345
+ firstActivity: 1,
346
+ lastActivity: 1,
347
+ sessionCount: 1
348
+ }
349
+ },
350
+ {
351
+ $sort: { timestamp: -1 } // Sort final results by latest activity
352
+ }
353
+ ];
354
+ // Get total count of unique user-day combinations (before projection)
355
+ const countPipeline = [...pipeline.slice(0, 4), { $count: 'total' }];
356
+ const countResult = await this.collection.aggregate(countPipeline).toArray();
357
+ const total = countResult.length > 0 ? countResult[0].total : 0;
358
+ // Add pagination
359
+ pipeline.push({ $skip: skip });
360
+ pipeline.push({ $limit: limit });
361
+ // Execute aggregation
362
+ const data = await this.collection.aggregate(pipeline).toArray();
363
+ const totalPages = Math.ceil(total / limit);
364
+ return {
365
+ data,
366
+ total,
367
+ page,
368
+ limit,
369
+ totalPages,
370
+ hasNext: page < totalPages,
371
+ hasPrev: page > 1
372
+ };
373
+ }
395
374
  async updateActivityDuration(logId, endTime) {
396
375
  const log = await this.findById(logId);
397
376
  if (!log)
@@ -403,7 +382,7 @@ class AccessLogRepository extends BaseRepository_1.BaseRepository {
403
382
  });
404
383
  }
405
384
  generateId() {
406
- return `acc_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
385
+ return `acc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
407
386
  }
408
387
  }
409
388
  exports.AccessLogRepository = AccessLogRepository;