@hed-hog/core 0.0.361 → 0.0.362

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.
Files changed (103) hide show
  1. package/dist/access-log/access-log.controller.d.ts +1 -1
  2. package/dist/access-log/access-log.service.d.ts +1 -1
  3. package/dist/access-log/access-log.service.d.ts.map +1 -1
  4. package/dist/access-log/access-log.service.js +43 -1
  5. package/dist/access-log/access-log.service.js.map +1 -1
  6. package/dist/auth/auth.controller.d.ts +1 -0
  7. package/dist/auth/auth.controller.d.ts.map +1 -1
  8. package/dist/auth/auth.controller.js +6 -2
  9. package/dist/auth/auth.controller.js.map +1 -1
  10. package/dist/auth/auth.service.d.ts +1 -0
  11. package/dist/auth/auth.service.d.ts.map +1 -1
  12. package/dist/auth/auth.service.js +15 -5
  13. package/dist/auth/auth.service.js.map +1 -1
  14. package/dist/auth/guards/auth.guard.d.ts +1 -0
  15. package/dist/auth/guards/auth.guard.d.ts.map +1 -1
  16. package/dist/auth/guards/auth.guard.js +11 -4
  17. package/dist/auth/guards/auth.guard.js.map +1 -1
  18. package/dist/file/file.module.d.ts.map +1 -1
  19. package/dist/file/file.module.js +3 -2
  20. package/dist/file/file.module.js.map +1 -1
  21. package/dist/file/file.service.d.ts +71 -1
  22. package/dist/file/file.service.d.ts.map +1 -1
  23. package/dist/file/file.service.js +180 -32
  24. package/dist/file/file.service.js.map +1 -1
  25. package/dist/file/image-optimization.service.d.ts +20 -0
  26. package/dist/file/image-optimization.service.d.ts.map +1 -0
  27. package/dist/file/image-optimization.service.js +126 -0
  28. package/dist/file/image-optimization.service.js.map +1 -0
  29. package/dist/file/provider/local.provider.d.ts +1 -1
  30. package/dist/file/provider/local.provider.d.ts.map +1 -1
  31. package/dist/file/provider/local.provider.js +2 -9
  32. package/dist/file/provider/local.provider.js.map +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/mcp/tools/core-users.mcp-tools.d.ts +1 -1
  38. package/dist/mcp/tools/core-users.mcp-tools.d.ts.map +1 -1
  39. package/dist/mcp/tools/core-users.mcp-tools.js +2 -2
  40. package/dist/mcp/tools/core-users.mcp-tools.js.map +1 -1
  41. package/dist/profile/profile.service.d.ts.map +1 -1
  42. package/dist/profile/profile.service.js +44 -23
  43. package/dist/profile/profile.service.js.map +1 -1
  44. package/dist/session/session.service.d.ts.map +1 -1
  45. package/dist/session/session.service.js +7 -2
  46. package/dist/session/session.service.js.map +1 -1
  47. package/dist/token/token.service.d.ts +1 -0
  48. package/dist/token/token.service.d.ts.map +1 -1
  49. package/dist/token/token.service.js +23 -6
  50. package/dist/token/token.service.js.map +1 -1
  51. package/dist/user/user.controller.d.ts +1 -1
  52. package/dist/user/user.controller.d.ts.map +1 -1
  53. package/dist/user/user.controller.js +4 -3
  54. package/dist/user/user.controller.js.map +1 -1
  55. package/dist/user/user.service.d.ts +1 -1
  56. package/dist/user/user.service.d.ts.map +1 -1
  57. package/dist/user/user.service.js +6 -6
  58. package/dist/user/user.service.js.map +1 -1
  59. package/hedhog/data/route.yaml +2 -0
  60. package/hedhog/data/setting_group.yaml +117 -0
  61. package/hedhog/data/setting_subgroup.yaml +8 -0
  62. package/hedhog/frontend/app/access-log/page.tsx.ejs +170 -11
  63. package/hedhog/frontend/app/account/components/remove-mfa-dialog.tsx.ejs +1 -1
  64. package/hedhog/frontend/app/account/components/verify-before-add-dialog.tsx.ejs +1 -1
  65. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +112 -5
  66. package/hedhog/frontend/app/users/page.tsx.ejs +7 -7
  67. package/hedhog/frontend/app/users/permissions.tsx.ejs +137 -83
  68. package/hedhog/frontend/app/users/user-access-sheet.tsx.ejs +28 -26
  69. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +207 -75
  70. package/hedhog/frontend/messages/en.json +9 -2
  71. package/hedhog/frontend/messages/pt.json +9 -2
  72. package/package.json +11 -6
  73. package/src/access-log/access-log.service.ts +19 -1
  74. package/src/auth/auth.controller.ts +5 -0
  75. package/src/auth/auth.service.ts +14 -4
  76. package/src/auth/guards/auth.guard.ts +10 -3
  77. package/src/file/file.module.ts +3 -2
  78. package/src/file/file.service.ts +361 -47
  79. package/src/file/image-optimization.service.ts +123 -0
  80. package/src/file/provider/local.provider.ts +2 -9
  81. package/src/index.ts +1 -0
  82. package/src/mcp/tools/core-users.mcp-tools.ts +267 -267
  83. package/src/profile/profile.service.ts +40 -22
  84. package/src/session/session.service.ts +10 -2
  85. package/src/token/token.service.ts +26 -6
  86. package/src/user/user.controller.ts +5 -2
  87. package/src/user/user.service.ts +11 -8
  88. package/dist/menu/menu.service.spec.d.ts +0 -1
  89. package/dist/menu/menu.service.spec.d.ts.map +0 -1
  90. package/dist/menu/menu.service.spec.js +0 -208
  91. package/dist/menu/menu.service.spec.js.map +0 -1
  92. package/dist/route/route.service.spec.d.ts +0 -1
  93. package/dist/route/route.service.spec.d.ts.map +0 -1
  94. package/dist/route/route.service.spec.js +0 -262
  95. package/dist/route/route.service.spec.js.map +0 -1
  96. package/dist/screen/screen.service.spec.d.ts +0 -1
  97. package/dist/screen/screen.service.spec.d.ts.map +0 -1
  98. package/dist/screen/screen.service.spec.js +0 -259
  99. package/dist/screen/screen.service.spec.js.map +0 -1
  100. package/dist/user/user.service.spec.d.ts +0 -1
  101. package/dist/user/user.service.spec.d.ts.map +0 -1
  102. package/dist/user/user.service.spec.js +0 -261
  103. package/dist/user/user.service.spec.js.map +0 -1
@@ -49,7 +49,7 @@ export class ProfileService {
49
49
  async changeEmail(locale: string, userId: number, { email, password, pin }: ChangeEmailDto) {
50
50
 
51
51
  const checkAlreadyExists = await this.prisma.user_identifier.findFirst({
52
- where: { type: 'email', value: email, enabled: true},
52
+ where: { type: 'email', value: email, enabled: true, verified_at: { not: null } },
53
53
  });
54
54
 
55
55
  if (checkAlreadyExists) {
@@ -98,25 +98,29 @@ export class ProfileService {
98
98
 
99
99
  await this.prisma.$transaction( async (tx) => {
100
100
 
101
- const userIdentifier = await tx.user_identifier.findFirst({
102
- where: { user_id: userId, type: 'email', enabled: true },
101
+ const currentVerifiedEmail = await tx.user_identifier.findFirst({
102
+ where: { user_id: userId, type: 'email', verified_at: { not: null }, enabled: true },
103
103
  });
104
104
 
105
- if (!userIdentifier) {
105
+ if (!currentVerifiedEmail) {
106
106
  throw new NotFoundException(getLocaleText('emailIdentifierNotFound', locale, 'Email identifier not found.'));
107
107
  }
108
108
 
109
- await tx.user_identifier.updateMany({
110
- where: { user_id: userId, type: 'email' },
111
- data: { value: email, verified_at: null, enabled: true},
112
- });
113
-
114
- await tx.user_identifier_challenge.deleteMany({
115
- where: {
116
- user_identifier_id: userIdentifier.id
109
+ // FIXED: Create new identifier for the new email instead of overwriting the current one
110
+ // This prevents the verified email from becoming orphaned
111
+ const newEmailIdentifier = await tx.user_identifier.create({
112
+ data: {
113
+ type: 'email',
114
+ value: email,
115
+ user_id: userId,
116
+ verified_at: null,
117
+ enabled: true,
117
118
  }
118
119
  });
119
120
 
121
+ // Create verification challenge for the new email
122
+ await this.challenge.verifyEmail(locale, userId, email);
123
+
120
124
  });
121
125
 
122
126
 
@@ -244,9 +248,14 @@ export class ProfileService {
244
248
 
245
249
  if (!userIdentifierId) {
246
250
  const userIdentifier = await tx.user_identifier.findFirst({
247
- where: { user_id: userId, type: 'email' },
251
+ where: { user_id: userId, type: 'email', verified_at: null },
248
252
  select: { id: true }
249
253
  });
254
+
255
+ if (!userIdentifier) {
256
+ throw new NotFoundException(getLocaleText('identifierNotFound', locale, 'Email identifier not found.'));
257
+ }
258
+
250
259
  userIdentifierId = userIdentifier.id;
251
260
  await tx.user_identifier_challenge.update({
252
261
  where: { id: challengeId },
@@ -254,6 +263,7 @@ export class ProfileService {
254
263
  });
255
264
  }
256
265
 
266
+ // Mark challenge and identifier as verified
257
267
  await tx.user_identifier_challenge.update({
258
268
  where: { id: challengeId },
259
269
  data: { verified_at: verifiedAt },
@@ -267,6 +277,8 @@ export class ProfileService {
267
277
  }
268
278
  });
269
279
 
280
+ // Delete ALL other email identifiers (both verified and unverified) to maintain single email principle
281
+ // This ensures user has only one verified primary email
270
282
  await tx.user_identifier.deleteMany({
271
283
  where: {
272
284
  user_id: userId,
@@ -1223,7 +1235,8 @@ export class ProfileService {
1223
1235
  ]);
1224
1236
 
1225
1237
  const rpName = settings['system-name'] || 'HedHog';
1226
- const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
1238
+ const primaryOrigin = (process.env.CORS_ALLOWED_ORIGINS ?? '').split(/[\n,;]+/).map(u => u.trim()).filter(Boolean)[0] ?? 'http://localhost:3200';
1239
+ const rpID = new URL(primaryOrigin).hostname;
1227
1240
  const userEmail = user.user_identifier.find(ui => ui.type === 'email')?.value || `user${userId}@example.com`;
1228
1241
  const existingAuthenticators = user.user_mfa
1229
1242
  .filter(mfa => mfa.user_mfa_webauthn && mfa.user_mfa_webauthn.length > 0)
@@ -1295,8 +1308,9 @@ export class ProfileService {
1295
1308
  );
1296
1309
  }
1297
1310
 
1298
- const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
1299
- const expectedOrigin = process.env.FRONTEND_URL || 'http://localhost:3200';
1311
+ const primaryOrigin = (process.env.CORS_ALLOWED_ORIGINS ?? '').split(/[\n,;]+/).map(u => u.trim()).filter(Boolean)[0] ?? 'http://localhost:3200';
1312
+ const rpID = new URL(primaryOrigin).hostname;
1313
+ const expectedOrigin = primaryOrigin;
1300
1314
  const verification: VerifiedRegistrationResponse = await verifyRegistrationResponse({
1301
1315
  response: attestationResponse,
1302
1316
  expectedChallenge: challengeRecord.hash,
@@ -1356,7 +1370,8 @@ export class ProfileService {
1356
1370
  );
1357
1371
  }
1358
1372
 
1359
- const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
1373
+ const primaryOrigin = (process.env.CORS_ALLOWED_ORIGINS ?? '').split(/[\n,;]+/).map(u => u.trim()).filter(Boolean)[0] ?? 'http://localhost:3200';
1374
+ const rpID = new URL(primaryOrigin).hostname;
1360
1375
  const allowCredentials = user.user_mfa
1361
1376
  .filter(mfa => mfa.user_mfa_webauthn && mfa.user_mfa_webauthn.length > 0)
1362
1377
  .map(mfa => ({
@@ -1409,8 +1424,9 @@ export class ProfileService {
1409
1424
  }
1410
1425
 
1411
1426
  const authenticator = challengeRecord.user_mfa.user_mfa_webauthn[0];
1412
- const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
1413
- const expectedOrigin = process.env.FRONTEND_URL || 'http://localhost:3200';
1427
+ const primaryOrigin = (process.env.CORS_ALLOWED_ORIGINS ?? '').split(/[\n,;]+/).map(u => u.trim()).filter(Boolean)[0] ?? 'http://localhost:3200';
1428
+ const rpID = new URL(primaryOrigin).hostname;
1429
+ const expectedOrigin = primaryOrigin;
1414
1430
  const verification: VerifiedAuthenticationResponse = await verifyAuthenticationResponse({
1415
1431
  response: assertionResponse,
1416
1432
  expectedChallenge: challengeRecord.hash,
@@ -1493,7 +1509,8 @@ export class ProfileService {
1493
1509
  throw new NotFoundException(getLocaleText('webauthnNotConfigured', locale, 'WebAuthn not configured.'));
1494
1510
  }
1495
1511
 
1496
- const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
1512
+ const primaryOrigin = (process.env.CORS_ALLOWED_ORIGINS ?? '').split(/[\n,;]+/).map(u => u.trim()).filter(Boolean)[0] ?? 'http://localhost:3200';
1513
+ const rpID = new URL(primaryOrigin).hostname;
1497
1514
  const allowCredentials = user.user_mfa
1498
1515
  .filter(mfa => mfa.user_mfa_webauthn && mfa.user_mfa_webauthn.length > 0)
1499
1516
  .map(mfa => ({
@@ -1552,8 +1569,9 @@ export class ProfileService {
1552
1569
  }
1553
1570
 
1554
1571
  const authenticator = challengeRecord.user_mfa.user_mfa_webauthn[0];
1555
- const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
1556
- const expectedOrigin = process.env.FRONTEND_URL || 'http://localhost:3200';
1572
+ const primaryOrigin = (process.env.CORS_ALLOWED_ORIGINS ?? '').split(/[\n,;]+/).map(u => u.trim()).filter(Boolean)[0] ?? 'http://localhost:3200';
1573
+ const rpID = new URL(primaryOrigin).hostname;
1574
+ const expectedOrigin = primaryOrigin;
1557
1575
  const verification: VerifiedAuthenticationResponse = await verifyAuthenticationResponse({
1558
1576
  response: assertionResponse,
1559
1577
  expectedChallenge: challengeRecord.hash,
@@ -101,7 +101,7 @@ export class SessionService {
101
101
  });
102
102
 
103
103
  if (!session) {
104
- this.logger.warn(`Refresh rejected: session not found or expired for token hash prefix ${hash.slice(0, 8)}`);
104
+ this.logger.warn(`[SessionRefresh] Refresh rejected: session not found or expired for token hash prefix ${hash.slice(0, 8)}`);
105
105
  throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
106
106
  }
107
107
 
@@ -121,6 +121,9 @@ export class SessionService {
121
121
  const nextIpAddress = ipAddress?.trim() || session.ip_address || 'unknown';
122
122
  const nextUserAgent = userAgent?.trim() || session.user_agent || 'unknown';
123
123
 
124
+ this.logger.debug(`[SessionRefresh] Starting rotation userId=${session.user_id} oldSessionId=${session.id} ip=${nextIpAddress}`);
125
+ const txStart = Date.now();
126
+
124
127
  const { updateResult, newSession } = await this.prisma.$transaction(async (tx) => {
125
128
  const updateResult = await tx.user_session.updateMany({
126
129
  where: {
@@ -146,10 +149,12 @@ export class SessionService {
146
149
  });
147
150
 
148
151
  if (updateResult.count === 0) {
149
- this.logger.warn(`Refresh rejected: session ${session.id} already rotated`);
152
+ this.logger.warn(`[SessionRefresh] Race condition detected: userId=${session.user_id} oldSessionId=${session.id} already rotated by another request (durationMs=${Date.now() - txStart})`);
150
153
  throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
151
154
  }
152
155
 
156
+ this.logger.debug(`[SessionRefresh] Rotation successful userId=${session.user_id} oldSessionId=${session.id} newSessionId=${newSession.id} durationMs=${Date.now() - txStart}`);
157
+
153
158
  return {
154
159
  token: nextRefreshToken,
155
160
  session: newSession,
@@ -390,11 +395,14 @@ export class SessionService {
390
395
  // During creation a new session is added, so we need one extra free slot (+1).
391
396
  const slotsNeeded = isRotation ? 0 : 1;
392
397
  const sessionsToExpire = activeSessions.length - maxSessions + slotsNeeded;
398
+ this.logger.debug(`[SessionLimit] userId=${userId} activeSessions=${activeSessions.length} maxSessions=${maxSessions} isRotation=${isRotation} toExpire=${sessionsToExpire}`);
393
399
  if (sessionsToExpire > 0) {
394
400
  const sessionIdsToExpire = activeSessions
395
401
  .slice(0, sessionsToExpire)
396
402
  .map(s => s.id);
397
403
 
404
+ this.logger.warn(`[SessionLimit] Expiring ${sessionsToExpire} oldest session(s) for userId=${userId}: [${sessionIdsToExpire.join(',')}]`);
405
+
398
406
  await this.prisma.user_session.updateMany({
399
407
  where: {
400
408
  id: {
@@ -1,12 +1,13 @@
1
1
  import { getLocaleText } from "@hed-hog/api-locale";
2
2
  import { PrismaService } from "@hed-hog/api-prisma";
3
- import { ForbiddenException, forwardRef, Inject, Injectable, UnauthorizedException } from "@nestjs/common";
3
+ import { ForbiddenException, forwardRef, Inject, Injectable, Logger, UnauthorizedException } from "@nestjs/common";
4
4
  import { JwtService } from "@nestjs/jwt";
5
5
  import { SecurityService } from "../security/security.service";
6
6
  import { SettingService } from "../setting/setting.service";
7
7
 
8
8
  @Injectable()
9
9
  export class TokenService {
10
+ private readonly logger = new Logger(TokenService.name);
10
11
 
11
12
  constructor(
12
13
  private readonly prisma: PrismaService,
@@ -33,7 +34,22 @@ export class TokenService {
33
34
  select: { revoked_at: true, expires_at: true }
34
35
  });
35
36
 
36
- if (!session || session.revoked_at !== null || session.expires_at <= new Date()) {
37
+ if (!session) {
38
+ this.logger.warn(`[TokenVerify] sessionId=${payload.sessionId} userId=${payload.sub} not found in DB`);
39
+ throw new UnauthorizedException(
40
+ getLocaleText('sessionRevoked', locale, 'Session has been revoked.')
41
+ );
42
+ }
43
+
44
+ if (session.revoked_at !== null) {
45
+ this.logger.warn(`[TokenVerify] sessionId=${payload.sessionId} userId=${payload.sub} revoked_at=${session.revoked_at.toISOString()}`);
46
+ throw new UnauthorizedException(
47
+ getLocaleText('sessionRevoked', locale, 'Session has been revoked.')
48
+ );
49
+ }
50
+
51
+ if (session.expires_at <= new Date()) {
52
+ this.logger.debug(`[TokenVerify] sessionId=${payload.sessionId} userId=${payload.sub} expired_at=${session.expires_at.toISOString()}`);
37
53
  throw new UnauthorizedException(
38
54
  getLocaleText('sessionRevoked', locale, 'Session has been revoked.')
39
55
  );
@@ -43,17 +59,21 @@ export class TokenService {
43
59
  if (sessionError instanceof UnauthorizedException) {
44
60
  throw sessionError;
45
61
  }
46
- // Otherwise, log the error but allow auth to continue
47
- console.error('Session validation error:', sessionError);
62
+ // DB error: log but allow auth to continue to avoid blocking on transient DB issues
63
+ this.logger.error(`[TokenVerify] DB error validating sessionId=${payload.sessionId} userId=${payload.sub}: ${(sessionError as any)?.message}`);
48
64
  }
49
65
  }
50
66
 
51
67
  return payload;
52
68
  } catch (error) {
53
-
69
+ const errName = (error as any)?.name ?? 'UnknownError';
70
+ const errMsg = (error as any)?.message ?? '';
71
+ if (!(error instanceof UnauthorizedException)) {
72
+ this.logger.debug(`[TokenVerify] JWT error=${errName}: ${errMsg}`);
73
+ }
54
74
  // Return 401 for JWT errors (expired, invalid, etc.)
55
75
  throw new UnauthorizedException(
56
- (error as any).message || getLocaleText('accessDenied', locale, 'Access denied.')
76
+ errMsg || getLocaleText('accessDenied', locale, 'Access denied.')
57
77
  );
58
78
  }
59
79
  }
@@ -112,8 +112,11 @@ export class UserController {
112
112
  }
113
113
 
114
114
  @Get(':userId/role')
115
- async getUserRoles(@Param('userId', ParseIntPipe) userId: number) {
116
- return this.userService.getUserRoles(userId);
115
+ async getUserRoles(
116
+ @Param('userId', ParseIntPipe) userId: number,
117
+ @Locale() locale: string,
118
+ ) {
119
+ return this.userService.getUserRoles(locale, userId);
117
120
  }
118
121
 
119
122
  @Get(':userId/menu')
@@ -1,4 +1,3 @@
1
- import { createHash } from 'crypto';
2
1
  import { getLocaleText } from '@hed-hog/api-locale';
3
2
  import { PaginationService } from '@hed-hog/api-pagination';
4
3
  import { PrismaService } from '@hed-hog/api-prisma';
@@ -9,6 +8,7 @@ import {
9
8
  NotFoundException,
10
9
  forwardRef,
11
10
  } from '@nestjs/common';
11
+ import { createHash } from 'crypto';
12
12
  import { ChallengeService } from '../challenge/challenge.service';
13
13
  import { DeleteDTO } from '../dto/delete.dto';
14
14
  import { FileService } from '../file/file.service';
@@ -31,7 +31,6 @@ const CREDENTIAL_TYPE = {
31
31
 
32
32
  const USER_AVATAR_CACHE_MAX_AGE_SETTING = 'user-avatar-cache-max-age';
33
33
  const DEFAULT_ROLE_SLUG = 'user';
34
- const DEFAULT_LOCALE = 'en';
35
34
  const DAYS_IN_MS = 24 * 60 * 60 * 1000;
36
35
  const NEW_USERS_PERIOD_DAYS = 7;
37
36
  const RANDOM_PASSWORD_LENGTH = 16;
@@ -417,13 +416,13 @@ export class UserService {
417
416
  // Role Management
418
417
  // ==========================================
419
418
 
420
- async getUserRoles(userId: number) {
419
+ async getUserRoles(locale: string, userId: number) {
421
420
  const [allRoles, userRoles] = await Promise.all([
422
421
  this.getAllRolesWithLocale(),
423
422
  this.getUserAssignedRoles(userId),
424
423
  ]);
425
424
 
426
- return this.mapRolesWithAssignment(allRoles, userRoles);
425
+ return this.mapRolesWithAssignment(locale, allRoles, userRoles);
427
426
  }
428
427
 
429
428
  async getUserMenus(locale: string, userId: number) {
@@ -730,15 +729,19 @@ export class UserService {
730
729
  });
731
730
  }
732
731
 
733
- private mapRolesWithAssignment(allRoles: any[], userRoles: any[]) {
732
+ private mapRolesWithAssignment(
733
+ locale: string,
734
+ allRoles: any[],
735
+ userRoles: any[],
736
+ ) {
734
737
  return allRoles.map((role) => {
735
738
  const isAssigned = userRoles.some(
736
739
  (userRole) => userRole.role_id === role.id
737
740
  );
738
741
 
739
- const localeData = role.role_locale.find(
740
- (rl) => rl.locale.code === DEFAULT_LOCALE
741
- );
742
+ const localeData =
743
+ role.role_locale.find((roleLocale) => roleLocale.locale.code === locale) ??
744
+ role.role_locale[0];
742
745
 
743
746
  return {
744
747
  id: role.id,
@@ -1 +0,0 @@
1
- //# sourceMappingURL=menu.service.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"menu.service.spec.d.ts","sourceRoot":"","sources":["../../src/menu/menu.service.spec.ts"],"names":[],"mappings":""}
@@ -1,208 +0,0 @@
1
- "use strict";
2
- // import {
3
- // PageOrderDirection,
4
- // PaginationDTO,
5
- // PaginationService,
6
- // } from '@hed-hog/api-pagination';
7
- // import { PrismaService } from '@hed-hog/api-prisma';
8
- // import { BadRequestException } from '@nestjs/common';
9
- // import { Test, TestingModule } from '@nestjs/testing';
10
- // import { MenuService } from './menu.service';
11
- // describe('MenuService', () => {
12
- // let menuService: MenuService;
13
- // let prismaService: PrismaService;
14
- // let paginationService: PaginationService;
15
- // const mockPrismaService = {
16
- // menu: {
17
- // create: jest.fn(),
18
- // update: jest.fn(),
19
- // deleteMany: jest.fn(),
20
- // findUnique: jest.fn(),
21
- // findMany: jest.fn(),
22
- // count: jest.fn(),
23
- // },
24
- // role_menu: {
25
- // deleteMany: jest.fn(),
26
- // createMany: jest.fn(),
27
- // },
28
- // menu_screen: {
29
- // deleteMany: jest.fn(),
30
- // createMany: jest.fn(),
31
- // },
32
- // };
33
- // const mockPaginationService = {
34
- // paginate: jest.fn(),
35
- // createInsensitiveSearch: jest.fn(),
36
- // };
37
- // beforeEach(async () => {
38
- // const module: TestingModule = await Test.createTestingModule({
39
- // providers: [
40
- // MenuService,
41
- // { provide: PrismaService, useValue: mockPrismaService },
42
- // { provide: PaginationService, useValue: mockPaginationService },
43
- // ],
44
- // }).compile();
45
- // menuService = module.get<MenuService>(MenuService);
46
- // prismaService = module.get<PrismaService>(PrismaService);
47
- // paginationService = module.get<PaginationService>(PaginationService);
48
- // });
49
- // afterEach(() => {
50
- // jest.clearAllMocks();
51
- // });
52
- // describe('create', () => {
53
- // it('should create a new menu', async () => {
54
- // const createMenuDto = {
55
- // name: 'Test Menu',
56
- // url: '/test',
57
- // icon: 'test-icon',
58
- // order: 1,
59
- // menu_id: undefined,
60
- // };
61
- // mockPrismaService.menu.create.mockResolvedValue(createMenuDto);
62
- // const result = await menuService.create(createMenuDto);
63
- // expect(result).toEqual(createMenuDto);
64
- // expect(prismaService.menu.create).toHaveBeenCalledWith({
65
- // data: createMenuDto,
66
- // });
67
- // });
68
- // });
69
- // describe('update', () => {
70
- // it('should update a menu', async () => {
71
- // const updateDto = { id: 1, data: { name: 'Updated Menu' } };
72
- // mockPrismaService.menu.update.mockResolvedValue(updateDto.data);
73
- // const result = await menuService.update(updateDto);
74
- // expect(result).toEqual(updateDto.data);
75
- // expect(prismaService.menu.update).toHaveBeenCalledWith({
76
- // where: { id: updateDto.id },
77
- // data: updateDto.data,
78
- // });
79
- // });
80
- // });
81
- // describe('delete', () => {
82
- // it('should delete menu', async () => {
83
- // const deleteDto = { ids: [1, 2, 3] };
84
- // mockPrismaService.menu.deleteMany.mockResolvedValue({ count: 3 });
85
- // const result = await menuService.delete(deleteDto);
86
- // expect(result).toEqual({ count: 3 });
87
- // expect(prismaService.menu.deleteMany).toHaveBeenCalledWith({
88
- // where: { id: { in: deleteDto.ids } },
89
- // });
90
- // });
91
- // it('should throw BadRequestException if no ids are provided', async () => {
92
- // await expect(menuService.delete({ ids: null })).rejects.toThrow(
93
- // BadRequestException,
94
- // );
95
- // });
96
- // });
97
- // describe('listScreens', () => {
98
- // it('should paginate screens', async () => {
99
- // const locale = 'en';
100
- // const menuId = 1;
101
- // const paginationParams: PaginationDTO = {
102
- // page: 1,
103
- // pageSize: 10,
104
- // search: '',
105
- // sortField: '',
106
- // sortOrder: PageOrderDirection.Asc,
107
- // fields: '',
108
- // };
109
- // const mockScreens = [{ id: 1, name: 'Screen 1' }];
110
- // mockPaginationService.paginate.mockResolvedValue(mockScreens);
111
- // const result = await menuService.listScreens(
112
- // locale,
113
- // menuId,
114
- // paginationParams,
115
- // );
116
- // expect(result).toEqual(mockScreens);
117
- // expect(paginationService.paginate).toHaveBeenCalledWith(
118
- // prismaService.screens,
119
- // paginationParams,
120
- // expect.anything(),
121
- // 'screen_locale',
122
- // );
123
- // });
124
- // });
125
- // describe('updateScreens', () => {
126
- // it('should update screens associated with a menu', async () => {
127
- // const menuId = 1;
128
- // const updateData = { ids: [1, 2, 3] };
129
- // jest
130
- // .spyOn(prismaService.menu_screen, 'deleteMany')
131
- // .mockResolvedValue(null);
132
- // jest
133
- // .spyOn(prismaService.menu_screen, 'createMany')
134
- // .mockResolvedValue(null);
135
- // await menuService.updateScreens(menuId, updateData);
136
- // expect(prismaService.menu_screen.deleteMany).toHaveBeenCalledWith({
137
- // where: { menu_id: menuId },
138
- // });
139
- // expect(prismaService.menu_screen.createMany).toHaveBeenCalledWith({
140
- // data: updateData.ids.map((screenId) => ({
141
- // menu_id: menuId,
142
- // screen_id: screenId,
143
- // })),
144
- // skipDuplicates: true,
145
- // });
146
- // });
147
- // });
148
- // describe('updateRoles', () => {
149
- // it('should update role associated with a menu', async () => {
150
- // const menuId = 1;
151
- // const updateData = { ids: [1, 2] };
152
- // jest.spyOn(prismaService.role_menu, 'deleteMany').mockResolvedValue(null);
153
- // jest.spyOn(prismaService.role_menu, 'createMany').mockResolvedValue(null);
154
- // await menuService.updateRoles(menuId, updateData);
155
- // expect(prismaService.role_menu.deleteMany).toHaveBeenCalledWith({
156
- // where: { menu_id: menuId },
157
- // });
158
- // expect(prismaService.role_menu.createMany).toHaveBeenCalledWith({
159
- // data: updateData.ids.map((roleId) => ({
160
- // menu_id: menuId,
161
- // role_id: roleId,
162
- // })),
163
- // skipDuplicates: true,
164
- // });
165
- // });
166
- // });
167
- // describe('updateOrder', () => {
168
- // it('should update the order of menu', async () => {
169
- // const orderData = { ids: [1, 2, 3] };
170
- // jest.spyOn(prismaService.menu, 'count').mockResolvedValue(3);
171
- // jest.spyOn(prismaService.menu, 'update').mockResolvedValue(null);
172
- // await menuService.updateOrder(orderData);
173
- // expect(prismaService.menu.count).toHaveBeenCalledWith({
174
- // where: { id: { in: orderData.ids } },
175
- // });
176
- // expect(prismaService.menu.update).toHaveBeenCalledTimes(
177
- // orderData.ids.length,
178
- // );
179
- // orderData.ids.forEach((id, index) => {
180
- // expect(prismaService.menu.update).toHaveBeenCalledWith({
181
- // where: { id },
182
- // data: { order: index + 1 },
183
- // });
184
- // });
185
- // });
186
- // it('should throw BadRequestException if IDs are invalid', async () => {
187
- // const orderData = { ids: [1, 2, 3] };
188
- // jest.spyOn(prismaService.menu, 'count').mockResolvedValue(2); // IDs não batem com o número esperado
189
- // await expect(menuService.updateOrder(orderData)).rejects.toThrow(
190
- // BadRequestException,
191
- // );
192
- // });
193
- // });
194
- // /*
195
- // describe('getMenus', () => {
196
- // it('should get menu for a user', async () => {
197
- // const locale = 'en';
198
- // const userId = 1;
199
- // const mockMenus = [{ id: 1, name: 'Menu 1' }];
200
- // mockPrismaService.menu.findMany.mockResolvedValue(mockMenus);
201
- // const result = await menuService.getMenus(locale, userId);
202
- // expect(result).toEqual(mockMenus);
203
- // expect(prismaService.menu.findMany).toHaveBeenCalled();
204
- // });
205
- // });
206
- // */
207
- // });
208
- //# sourceMappingURL=menu.service.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"menu.service.spec.js","sourceRoot":"","sources":["../../src/menu/menu.service.spec.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- //# sourceMappingURL=route.service.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"route.service.spec.d.ts","sourceRoot":"","sources":["../../src/route/route.service.spec.ts"],"names":[],"mappings":""}