nestforge-generator 0.2.0 → 0.4.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.
Files changed (156) hide show
  1. package/README.md +24 -7
  2. package/README.pt-BR.md +24 -7
  3. package/dist/features/auth-strategy.js +7 -7
  4. package/dist/features/database.js +35 -0
  5. package/dist/features/database.js.map +1 -1
  6. package/dist/features/dependencies.js +3 -3
  7. package/dist/features/language.js +1 -1
  8. package/dist/features/language.js.map +1 -1
  9. package/dist/features/markers.js +11 -11
  10. package/dist/features/markers.js.map +1 -1
  11. package/dist/generator.js +14 -2
  12. package/dist/generator.js.map +1 -1
  13. package/dist/index.js +8 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/project-name.js +50 -0
  16. package/dist/project-name.js.map +1 -0
  17. package/dist/prompts.js +15 -11
  18. package/dist/prompts.js.map +1 -1
  19. package/package.json +2 -1
  20. package/templates/drizzle/README.md +2 -0
  21. package/templates/drizzle/README.pt-BR.md +2 -0
  22. package/templates/drizzle/drizzle.config.ts +2 -2
  23. package/templates/drizzle/src/auth/auth.controller.ts +35 -35
  24. package/templates/drizzle/src/auth/auth.service.spec.ts +4 -4
  25. package/templates/drizzle/src/auth/auth.service.ts +9 -9
  26. package/templates/drizzle/src/auth/dto/forgot-password.dto.ts +2 -2
  27. package/templates/drizzle/src/auth/dto/login.dto.ts +3 -3
  28. package/templates/drizzle/src/auth/dto/register.dto.ts +4 -4
  29. package/templates/drizzle/src/auth/dto/reset-password.dto.ts +3 -3
  30. package/templates/drizzle/src/auth/guards/github-auth.guard.ts +2 -2
  31. package/templates/drizzle/src/auth/guards/google-auth.guard.ts +2 -2
  32. package/templates/drizzle/src/auth/guards/session-auth.guard.spec.ts +4 -4
  33. package/templates/drizzle/src/auth/guards/session-auth.guard.ts +2 -2
  34. package/templates/drizzle/src/auth/session.service.spec.ts +5 -5
  35. package/templates/drizzle/src/auth/session.service.ts +5 -5
  36. package/templates/drizzle/src/auth/strategies/github.strategy.ts +3 -3
  37. package/templates/drizzle/src/auth/token.service.ts +3 -3
  38. package/templates/drizzle/src/common/filters/http-exception.filter.ts +1 -1
  39. package/templates/drizzle/src/common/guards/permissions.guard.spec.ts +7 -7
  40. package/templates/drizzle/src/common/guards/roles.guard.spec.ts +4 -4
  41. package/templates/drizzle/src/common/middleware/csrf.middleware.spec.ts +5 -5
  42. package/templates/drizzle/src/common/middleware/csrf.middleware.ts +2 -2
  43. package/templates/drizzle/src/common/utils/avatar-storage.util.ts +2 -2
  44. package/templates/drizzle/src/config/env.validation.ts +1 -1
  45. package/templates/drizzle/src/database/seed.ts +4 -4
  46. package/templates/drizzle/src/health/health.controller.ts +2 -2
  47. package/templates/drizzle/src/health/indicators/drizzle-health.indicator.spec.ts +5 -5
  48. package/templates/drizzle/src/health/indicators/drizzle-health.indicator.ts +2 -2
  49. package/templates/drizzle/src/health/indicators/redis-health.indicator.spec.ts +5 -5
  50. package/templates/drizzle/src/health/indicators/redis-health.indicator.ts +3 -3
  51. package/templates/drizzle/src/mail/mail.processor.ts +4 -4
  52. package/templates/drizzle/src/mail/templates/email-templates.ts +7 -7
  53. package/templates/drizzle/src/metrics/metrics.service.ts +3 -3
  54. package/templates/drizzle/src/users/dto/create-user.dto.ts +5 -5
  55. package/templates/drizzle/src/users/dto/find-users-query.dto.ts +3 -3
  56. package/templates/drizzle/src/users/users.controller.ts +21 -21
  57. package/templates/drizzle/src/users/users.service.spec.ts +12 -12
  58. package/templates/drizzle/src/users/users.service.ts +4 -4
  59. package/templates/drizzle/test/auth.e2e-spec.ts +8 -8
  60. package/templates/drizzle/test/session-auth.e2e-spec.ts +9 -9
  61. package/templates/drizzle/test/users.e2e-spec.ts +18 -18
  62. package/templates/prisma/.github/workflows/ci.yml +24 -1
  63. package/templates/prisma/ARCHITECTURE.md +7 -1
  64. package/templates/prisma/ARCHITECTURE.pt-BR.md +7 -1
  65. package/templates/prisma/README.md +11 -7
  66. package/templates/prisma/README.pt-BR.md +11 -7
  67. package/templates/prisma/ROADMAP.md +5 -1
  68. package/templates/prisma/ROADMAP.pt-BR.md +5 -1
  69. package/templates/prisma/TESTING.md +25 -3
  70. package/templates/prisma/TESTING.pt-BR.md +25 -3
  71. package/templates/prisma/docker-compose.yml +29 -1
  72. package/templates/prisma/docs/adding-a-module.md +246 -230
  73. package/templates/prisma/docs/features-markers.md +11 -1
  74. package/templates/prisma/prisma/schema.prisma +4 -0
  75. package/templates/prisma/prisma/seed.ts +46 -46
  76. package/templates/prisma/src/auth/auth.controller.ts +35 -35
  77. package/templates/prisma/src/auth/auth.service.spec.ts +3 -3
  78. package/templates/prisma/src/auth/auth.service.ts +11 -11
  79. package/templates/prisma/src/auth/dto/forgot-password.dto.ts +2 -2
  80. package/templates/prisma/src/auth/dto/login.dto.ts +3 -3
  81. package/templates/prisma/src/auth/dto/register.dto.ts +4 -4
  82. package/templates/prisma/src/auth/dto/reset-password.dto.ts +3 -3
  83. package/templates/prisma/src/auth/guards/github-auth.guard.ts +10 -10
  84. package/templates/prisma/src/auth/guards/google-auth.guard.ts +10 -10
  85. package/templates/prisma/src/auth/guards/session-auth.guard.spec.ts +4 -4
  86. package/templates/prisma/src/auth/guards/session-auth.guard.ts +2 -2
  87. package/templates/prisma/src/auth/session.service.spec.ts +5 -5
  88. package/templates/prisma/src/auth/session.service.ts +5 -5
  89. package/templates/prisma/src/auth/strategies/github.strategy.ts +3 -3
  90. package/templates/prisma/src/auth/token.service.ts +14 -5
  91. package/templates/prisma/src/common/filters/http-exception.filter.ts +34 -34
  92. package/templates/prisma/src/common/guards/permissions.guard.spec.ts +7 -7
  93. package/templates/prisma/src/common/guards/roles.guard.spec.ts +4 -4
  94. package/templates/prisma/src/common/middleware/csrf.middleware.spec.ts +5 -5
  95. package/templates/prisma/src/common/middleware/csrf.middleware.ts +2 -2
  96. package/templates/prisma/src/common/utils/avatar-storage.util.ts +32 -32
  97. package/templates/prisma/src/config/env.validation.ts +1 -1
  98. package/templates/prisma/src/health/health.controller.ts +2 -2
  99. package/templates/prisma/src/health/indicators/prisma-health.indicator.spec.ts +39 -22
  100. package/templates/prisma/src/health/indicators/prisma-health.indicator.ts +24 -19
  101. package/templates/prisma/src/health/indicators/redis-health.indicator.spec.ts +5 -5
  102. package/templates/prisma/src/health/indicators/redis-health.indicator.ts +3 -3
  103. package/templates/prisma/src/mail/mail.processor.ts +4 -4
  104. package/templates/prisma/src/mail/templates/email-templates.ts +7 -7
  105. package/templates/prisma/src/metrics/metrics.service.ts +34 -34
  106. package/templates/prisma/src/users/dto/create-user.dto.ts +12 -12
  107. package/templates/prisma/src/users/dto/find-users-query.dto.ts +12 -12
  108. package/templates/prisma/src/users/users.controller.ts +21 -21
  109. package/templates/prisma/src/users/users.service.spec.ts +11 -11
  110. package/templates/prisma/src/users/users.service.ts +4 -4
  111. package/templates/prisma/test/auth.e2e-spec.ts +8 -8
  112. package/templates/prisma/test/session-auth.e2e-spec.ts +9 -9
  113. package/templates/prisma/test/users.e2e-spec.ts +14 -14
  114. package/templates/typeorm/README.md +2 -0
  115. package/templates/typeorm/README.pt-BR.md +2 -0
  116. package/templates/typeorm/src/auth/auth.controller.ts +35 -35
  117. package/templates/typeorm/src/auth/auth.service.spec.ts +4 -4
  118. package/templates/typeorm/src/auth/auth.service.ts +9 -9
  119. package/templates/typeorm/src/auth/dto/forgot-password.dto.ts +2 -2
  120. package/templates/typeorm/src/auth/dto/login.dto.ts +3 -3
  121. package/templates/typeorm/src/auth/dto/register.dto.ts +4 -4
  122. package/templates/typeorm/src/auth/dto/reset-password.dto.ts +3 -3
  123. package/templates/typeorm/src/auth/guards/github-auth.guard.ts +2 -2
  124. package/templates/typeorm/src/auth/guards/google-auth.guard.ts +2 -2
  125. package/templates/typeorm/src/auth/guards/session-auth.guard.spec.ts +4 -4
  126. package/templates/typeorm/src/auth/guards/session-auth.guard.ts +2 -2
  127. package/templates/typeorm/src/auth/session.service.spec.ts +5 -5
  128. package/templates/typeorm/src/auth/session.service.ts +5 -5
  129. package/templates/typeorm/src/auth/strategies/github.strategy.ts +3 -3
  130. package/templates/typeorm/src/auth/token.service.ts +3 -3
  131. package/templates/typeorm/src/common/filters/http-exception.filter.ts +1 -1
  132. package/templates/typeorm/src/common/guards/permissions.guard.spec.ts +7 -7
  133. package/templates/typeorm/src/common/guards/roles.guard.spec.ts +4 -4
  134. package/templates/typeorm/src/common/middleware/csrf.middleware.spec.ts +5 -5
  135. package/templates/typeorm/src/common/middleware/csrf.middleware.ts +2 -2
  136. package/templates/typeorm/src/common/utils/avatar-storage.util.ts +2 -2
  137. package/templates/typeorm/src/config/env.validation.ts +1 -1
  138. package/templates/typeorm/src/database/data-source.ts +2 -2
  139. package/templates/typeorm/src/database/seed.ts +3 -3
  140. package/templates/typeorm/src/database/typeorm-options.ts +2 -2
  141. package/templates/typeorm/src/health/health.controller.ts +2 -2
  142. package/templates/typeorm/src/health/indicators/redis-health.indicator.spec.ts +5 -5
  143. package/templates/typeorm/src/health/indicators/redis-health.indicator.ts +3 -3
  144. package/templates/typeorm/src/health/indicators/typeorm-health.indicator.spec.ts +4 -4
  145. package/templates/typeorm/src/health/indicators/typeorm-health.indicator.ts +2 -2
  146. package/templates/typeorm/src/mail/mail.processor.ts +4 -4
  147. package/templates/typeorm/src/mail/templates/email-templates.ts +7 -7
  148. package/templates/typeorm/src/metrics/metrics.service.ts +3 -3
  149. package/templates/typeorm/src/users/dto/create-user.dto.ts +5 -5
  150. package/templates/typeorm/src/users/dto/find-users-query.dto.ts +3 -3
  151. package/templates/typeorm/src/users/users.controller.ts +21 -21
  152. package/templates/typeorm/src/users/users.service.spec.ts +12 -12
  153. package/templates/typeorm/src/users/users.service.ts +4 -4
  154. package/templates/typeorm/test/auth.e2e-spec.ts +8 -8
  155. package/templates/typeorm/test/session-auth.e2e-spec.ts +9 -9
  156. package/templates/typeorm/test/users.e2e-spec.ts +18 -18
@@ -15,14 +15,14 @@ function createContext(user?: { role: Role }): ExecutionContext {
15
15
  }
16
16
 
17
17
  describe('PermissionsGuard', () => {
18
- it('libera acesso quando a rota não exige nenhuma permissão', () => {
18
+ it('allows access when the route requires no permission', () => {
19
19
  const reflector = { getAllAndOverride: vi.fn().mockReturnValue(undefined) } as unknown as Reflector;
20
20
  const guard = new PermissionsGuard(reflector);
21
21
 
22
22
  expect(guard.canActivate(createContext({ role: Role.USER }))).toBe(true);
23
23
  });
24
24
 
25
- it('libera acesso quando a role do usuário tem a permissão exigida', () => {
25
+ it('allows access when the user role has the required permission', () => {
26
26
  const reflector = {
27
27
  getAllAndOverride: vi.fn().mockReturnValue([Permission.UserRead]),
28
28
  } as unknown as Reflector;
@@ -31,7 +31,7 @@ describe('PermissionsGuard', () => {
31
31
  expect(guard.canActivate(createContext({ role: Role.USER }))).toBe(true);
32
32
  });
33
33
 
34
- it('bloqueia acesso quando a role do usuário não tem a permissão exigida', () => {
34
+ it('blocks access when the user role lacks the required permission', () => {
35
35
  const reflector = {
36
36
  getAllAndOverride: vi.fn().mockReturnValue([Permission.UserDelete]),
37
37
  } as unknown as Reflector;
@@ -40,15 +40,15 @@ describe('PermissionsGuard', () => {
40
40
  expect(guard.canActivate(createContext({ role: Role.USER }))).toBe(false);
41
41
  });
42
42
 
43
- it('exige todas as permissões quando mais de uma é requisitada', () => {
43
+ it('requires all permissions when more than one is requested', () => {
44
44
  const reflector = {
45
45
  getAllAndOverride: vi.fn().mockReturnValue([Permission.UserRead, Permission.UserUpdate]),
46
46
  } as unknown as Reflector;
47
47
  const guard = new PermissionsGuard(reflector);
48
48
 
49
- // MANAGER tem user:read e user:update
49
+ // MANAGER has user:read and user:update
50
50
  expect(guard.canActivate(createContext({ role: Role.MANAGER }))).toBe(true);
51
- // USER só tem user:read
51
+ // USER only has user:read
52
52
  expect(guard.canActivate(createContext({ role: Role.USER }))).toBe(false);
53
53
  });
54
- });
54
+ });
@@ -14,24 +14,24 @@ function createContext(user?: { role: Role }): ExecutionContext {
14
14
  }
15
15
 
16
16
  describe('RolesGuard', () => {
17
- it('libera acesso quando a rota não exige nenhuma role', () => {
17
+ it('allows access when the route requires no role', () => {
18
18
  const reflector = { getAllAndOverride: vi.fn().mockReturnValue(undefined) } as unknown as Reflector;
19
19
  const guard = new RolesGuard(reflector);
20
20
 
21
21
  expect(guard.canActivate(createContext({ role: Role.USER }))).toBe(true);
22
22
  });
23
23
 
24
- it('libera acesso quando o usuário tem a role exigida', () => {
24
+ it('allows access when the user has the required role', () => {
25
25
  const reflector = { getAllAndOverride: vi.fn().mockReturnValue([Role.ADMIN]) } as unknown as Reflector;
26
26
  const guard = new RolesGuard(reflector);
27
27
 
28
28
  expect(guard.canActivate(createContext({ role: Role.ADMIN }))).toBe(true);
29
29
  });
30
30
 
31
- it('bloqueia acesso quando o usuário não tem a role exigida', () => {
31
+ it('blocks access when the user does not have the required role', () => {
32
32
  const reflector = { getAllAndOverride: vi.fn().mockReturnValue([Role.ADMIN]) } as unknown as Reflector;
33
33
  const guard = new RolesGuard(reflector);
34
34
 
35
35
  expect(guard.canActivate(createContext({ role: Role.USER }))).toBe(false);
36
36
  });
37
- });
37
+ });
@@ -36,7 +36,7 @@ describe('CsrfMiddleware', () => {
36
36
  }
37
37
 
38
38
  it.each(['GET', 'HEAD', 'OPTIONS'])(
39
- 'permite o método seguro %s sem token',
39
+ 'allows the safe %s method without a token',
40
40
  (method) => {
41
41
  const request = createRequest(method);
42
42
 
@@ -50,7 +50,7 @@ describe('CsrfMiddleware', () => {
50
50
  },
51
51
  );
52
52
 
53
- it('rejeita uma requisição sem token CSRF', () => {
53
+ it('rejects a request without a CSRF token', () => {
54
54
  const request = createRequest('POST');
55
55
 
56
56
  expect(() =>
@@ -64,7 +64,7 @@ describe('CsrfMiddleware', () => {
64
64
  expect(next).not.toHaveBeenCalled();
65
65
  });
66
66
 
67
- it('rejeita quando o token do header é diferente da sessão', () => {
67
+ it('rejects when the header token differs from the session token', () => {
68
68
  const request = createRequest(
69
69
  'PATCH',
70
70
  'session-token',
@@ -82,7 +82,7 @@ describe('CsrfMiddleware', () => {
82
82
  expect(next).not.toHaveBeenCalled();
83
83
  });
84
84
 
85
- it('permite quando o token corresponde ao valor da sessão', () => {
85
+ it('allows the request when the token matches the session value', () => {
86
86
  const token = 'a'.repeat(64);
87
87
  const request = createRequest('DELETE', token, token);
88
88
 
@@ -94,4 +94,4 @@ describe('CsrfMiddleware', () => {
94
94
 
95
95
  expect(next).toHaveBeenCalledOnce();
96
96
  });
97
- });
97
+ });
@@ -27,7 +27,7 @@ export class CsrfMiddleware implements NestMiddleware {
27
27
  !this.tokensMatch(sessionToken, headerToken)
28
28
  ) {
29
29
  throw new ForbiddenException(
30
- 'Token CSRF inválido ou ausente',
30
+ 'Invalid or missing CSRF token',
31
31
  );
32
32
  }
33
33
 
@@ -43,4 +43,4 @@ export class CsrfMiddleware implements NestMiddleware {
43
43
  timingSafeEqual(expectedBuffer, receivedBuffer)
44
44
  );
45
45
  }
46
- }
46
+ }
@@ -20,7 +20,7 @@ export function avatarFileFilter(
20
20
  ) {
21
21
  if (!ALLOWED_MIME_TYPES.includes(file.mimetype)) {
22
22
  callback(
23
- new BadRequestException('Formato de imagem não suportado (use PNG, JPEG ou WEBP)'),
23
+ new BadRequestException('Unsupported image format (use PNG, JPEG, or WEBP)'),
24
24
  false,
25
25
  );
26
26
  return;
@@ -29,4 +29,4 @@ export function avatarFileFilter(
29
29
  callback(null, true);
30
30
  }
31
31
 
32
- export const AVATAR_MAX_SIZE_BYTES = 2 * 1024 * 1024; // 2MB
32
+ export const AVATAR_MAX_SIZE_BYTES = 2 * 1024 * 1024; // 2MB
@@ -43,7 +43,7 @@ export function validateEnv(config: Record<string, unknown>): EnvConfig {
43
43
  const issues = parsed.error.issues
44
44
  .map((issue) => `${issue.path.join('.')}: ${issue.message}`)
45
45
  .join('\n');
46
- throw new Error(`Variáveis de ambiente inválidas:\n${issues}`);
46
+ throw new Error(`Invalid environment variables:\n${issues}`);
47
47
  }
48
48
 
49
49
  return parsed.data;
@@ -46,7 +46,7 @@ const SEED_USERS: SeedUser[] = [
46
46
  role: Role.MANAGER,
47
47
  },
48
48
  {
49
- name: 'Usuário',
49
+ name: 'User',
50
50
  email: 'user@nestforge.dev',
51
51
  password: 'user1234',
52
52
  role: Role.USER,
@@ -57,7 +57,7 @@ const databaseUrl = process.env.DATABASE_URL;
57
57
 
58
58
  if (!databaseUrl) {
59
59
  throw new Error(
60
- 'DATABASE_URL não foi definida',
60
+ 'DATABASE_URL is not defined',
61
61
  );
62
62
  }
63
63
 
@@ -144,9 +144,9 @@ async function seed(): Promise<void> {
144
144
 
145
145
  seed().catch((error: unknown) => {
146
146
  console.error(
147
- 'Erro ao executar o seed:',
147
+ 'Error while running the seed:',
148
148
  error,
149
149
  );
150
150
 
151
151
  process.exitCode = 1;
152
- });
152
+ });
@@ -33,7 +33,7 @@ export class HealthController {
33
33
  @Get()
34
34
  @HealthCheck()
35
35
  // nestforge:feature:swagger
36
- @ApiOperation({ summary: 'Verifica a saúde da API (banco, memória, disco e demais dependências configuradas)' })
36
+ @ApiOperation({ summary: 'Checks API health (database, memory, disk, and other configured dependencies)' })
37
37
  // nestforge:feature:swagger:end
38
38
  check() {
39
39
  return this.health.check([
@@ -46,4 +46,4 @@ export class HealthController {
46
46
  () => this.disk.checkStorage('disk', { path: '/', thresholdPercent: 0.9 }),
47
47
  ]);
48
48
  }
49
- }
49
+ }
@@ -9,7 +9,7 @@ import type { DatabaseClient } from '../../database/database.types';
9
9
  import { DrizzleHealthIndicator } from './drizzle-health.indicator';
10
10
 
11
11
  describe('DrizzleHealthIndicator', () => {
12
- it('retorna status up quando a consulta executa com sucesso', async () => {
12
+ it('returns an up status when the database query succeeds', async () => {
13
13
  const get = vi.fn().mockReturnValue({
14
14
  result: 1,
15
15
  });
@@ -41,16 +41,16 @@ describe('DrizzleHealthIndicator', () => {
41
41
  });
42
42
  });
43
43
 
44
- it('lança HealthCheckError quando a consulta falha', async () => {
44
+ it('throws HealthCheckError when the database query fails', async () => {
45
45
  const get = vi.fn(() => {
46
- throw new Error('conexão recusada');
46
+ throw new Error('connection refused');
47
47
  });
48
48
 
49
49
  const databaseClient = {
50
50
  query: vi
51
51
  .fn()
52
52
  .mockRejectedValue(
53
- new Error('conexão recusada'),
53
+ new Error('connection refused'),
54
54
  ),
55
55
  prepare: vi.fn(() => ({
56
56
  get,
@@ -68,4 +68,4 @@ describe('DrizzleHealthIndicator', () => {
68
68
  HealthCheckError,
69
69
  );
70
70
  });
71
- });
71
+ });
@@ -39,7 +39,7 @@ export class DrizzleHealthIndicator {
39
39
  };
40
40
  } catch (error) {
41
41
  throw new HealthCheckError(
42
- 'Banco de dados indisponível',
42
+ 'Database unavailable',
43
43
  {
44
44
  [key]: {
45
45
  status: 'down',
@@ -49,4 +49,4 @@ export class DrizzleHealthIndicator {
49
49
  );
50
50
  }
51
51
  }
52
- }
52
+ }
@@ -14,7 +14,7 @@ vi.mock('ioredis', () => ({
14
14
  })),
15
15
  }));
16
16
 
17
- // precisa ser importado depois do vi.mock, senão pega o ioredis real
17
+ // Must be imported after vi.mock; otherwise it loads the real ioredis package.
18
18
  const { RedisHealthIndicator } = await import('./redis-health.indicator');
19
19
 
20
20
  describe('RedisHealthIndicator', () => {
@@ -24,7 +24,7 @@ describe('RedisHealthIndicator', () => {
24
24
  disconnectMock.mockReset();
25
25
  });
26
26
 
27
- it('retorna status up quando o PING responde PONG', async () => {
27
+ it('returns an up status when PING returns PONG', async () => {
28
28
  connectMock.mockResolvedValue(undefined);
29
29
  pingMock.mockResolvedValue('PONG');
30
30
 
@@ -35,7 +35,7 @@ describe('RedisHealthIndicator', () => {
35
35
  expect(disconnectMock).toHaveBeenCalled();
36
36
  });
37
37
 
38
- it('lança HealthCheckError quando o PING não responde PONG', async () => {
38
+ it('throws HealthCheckError when PING does not return PONG', async () => {
39
39
  connectMock.mockResolvedValue(undefined);
40
40
  pingMock.mockResolvedValue('ALGO_INESPERADO');
41
41
 
@@ -44,11 +44,11 @@ describe('RedisHealthIndicator', () => {
44
44
  await expect(indicator.isHealthy('redis')).rejects.toBeInstanceOf(HealthCheckError);
45
45
  });
46
46
 
47
- it('lança HealthCheckError quando a conexão falha', async () => {
47
+ it('throws HealthCheckError when the connection fails', async () => {
48
48
  connectMock.mockRejectedValue(new Error('ECONNREFUSED'));
49
49
 
50
50
  const indicator = new RedisHealthIndicator();
51
51
 
52
52
  await expect(indicator.isHealthy('redis')).rejects.toBeInstanceOf(HealthCheckError);
53
53
  });
54
- });
54
+ });
@@ -18,16 +18,16 @@ export class RedisHealthIndicator {
18
18
  const response = await client.ping();
19
19
 
20
20
  if (response !== 'PONG') {
21
- throw new Error('Resposta inesperada do Redis');
21
+ throw new Error('Unexpected Redis response');
22
22
  }
23
23
 
24
24
  return { [key]: { status: 'up' } };
25
25
  } catch (error) {
26
- throw new HealthCheckError('Redis indisponível', {
26
+ throw new HealthCheckError('Redis unavailable', {
27
27
  [key]: { status: 'down', message: (error as Error).message },
28
28
  });
29
29
  } finally {
30
30
  client.disconnect();
31
31
  }
32
32
  }
33
- }
33
+ }
@@ -27,13 +27,13 @@ export class MailProcessor extends WorkerHost {
27
27
 
28
28
  switch (job.name) {
29
29
  case MailJob.VerifyEmail:
30
- await this.send(to, 'Confirme seu e-mail', verifyEmailTemplate(name, token));
30
+ await this.send(to, 'Confirm your email', verifyEmailTemplate(name, token));
31
31
  break;
32
32
  case MailJob.ResetPassword:
33
- await this.send(to, 'Redefinição de senha', resetPasswordTemplate(name, token));
33
+ await this.send(to, 'Password reset', resetPasswordTemplate(name, token));
34
34
  break;
35
35
  default:
36
- this.logger.warn(`Job de e-mail desconhecido: ${job.name}`);
36
+ this.logger.warn(`Unknown email job: ${job.name}`);
37
37
  }
38
38
  }
39
39
 
@@ -45,4 +45,4 @@ export class MailProcessor extends WorkerHost {
45
45
  html,
46
46
  });
47
47
  }
48
- }
48
+ }
@@ -6,10 +6,10 @@ export function verifyEmailTemplate(name: string, token: string): string {
6
6
 
7
7
  return `
8
8
  <div style="font-family: sans-serif; line-height: 1.5;">
9
- <h2>Olá, ${name}!</h2>
10
- <p>Confirme seu e-mail clicando no link abaixo:</p>
9
+ <h2>Hello, ${name}!</h2>
10
+ <p>Confirm your email by clicking the link below:</p>
11
11
  <p><a href="${link}">${link}</a></p>
12
- <p>Esse link expira em 24 horas.</p>
12
+ <p>This link expires in 24 hours.</p>
13
13
  </div>
14
14
  `;
15
15
  }
@@ -19,10 +19,10 @@ export function resetPasswordTemplate(name: string, token: string): string {
19
19
 
20
20
  return `
21
21
  <div style="font-family: sans-serif; line-height: 1.5;">
22
- <h2>Olá, ${name}!</h2>
23
- <p>Recebemos um pedido para redefinir sua senha. Clique no link abaixo para continuar:</p>
22
+ <h2>Hello, ${name}!</h2>
23
+ <p>We received a request to reset your password. Click the link below to continue:</p>
24
24
  <p><a href="${link}">${link}</a></p>
25
- <p>Se você não pediu isso, pode ignorar este e-mail. O link expira em 1 hora.</p>
25
+ <p>If you did not request this, you can ignore this email. The link expires in 1 hour.</p>
26
26
  </div>
27
27
  `;
28
- }
28
+ }
@@ -7,7 +7,7 @@ export class MetricsService {
7
7
 
8
8
  readonly httpRequestDuration = new client.Histogram({
9
9
  name: 'http_request_duration_seconds',
10
- help: 'Duração das requisições HTTP em segundos',
10
+ help: 'HTTP request duration in seconds',
11
11
  labelNames: ['method', 'route', 'status_code'],
12
12
  buckets: [0.05, 0.1, 0.3, 0.5, 1, 2, 5],
13
13
  registers: [this.registry],
@@ -15,7 +15,7 @@ export class MetricsService {
15
15
 
16
16
  readonly httpRequestsTotal = new client.Counter({
17
17
  name: 'http_requests_total',
18
- help: 'Total de requisições HTTP recebidas',
18
+ help: 'Total number of HTTP requests received',
19
19
  labelNames: ['method', 'route', 'status_code'],
20
20
  registers: [this.registry],
21
21
  });
@@ -31,4 +31,4 @@ export class MetricsService {
31
31
  getContentType(): string {
32
32
  return this.registry.contentType;
33
33
  }
34
- }
34
+ }
@@ -3,10 +3,10 @@ import { createZodDto } from 'nestjs-zod';
3
3
  import { Role } from '../../common/constants/role.enum';
4
4
 
5
5
  export const createUserSchema = z.object({
6
- name: z.string().min(2).describe('Nome completo'),
7
- email: z.string().email().describe('E-mail do usuário'),
8
- password: z.string().min(8).describe('Senha (mínimo 8 caracteres)'),
9
- role: z.nativeEnum(Role).optional().describe('Role do usuário (padrão: USER)'),
6
+ name: z.string().min(2).describe('Full name'),
7
+ email: z.string().email().describe('User email'),
8
+ password: z.string().min(8).describe('Password (minimum 8 characters)'),
9
+ role: z.nativeEnum(Role).optional().describe('User role (default: USER)'),
10
10
  });
11
11
 
12
- export class CreateUserDto extends createZodDto(createUserSchema) { }
12
+ export class CreateUserDto extends createZodDto(createUserSchema) { }
@@ -3,10 +3,10 @@ import { createZodDto } from 'nestjs-zod';
3
3
  import { Role } from '../../common/constants/role.enum';
4
4
 
5
5
  export const findUsersQuerySchema = z.object({
6
- page: z.coerce.number().int().min(1).default(1).describe('Página atual'),
7
- limit: z.coerce.number().int().min(1).max(100).default(10).describe('Itens por página (máx. 100)'),
6
+ page: z.coerce.number().int().min(1).default(1).describe('Current page'),
7
+ limit: z.coerce.number().int().min(1).max(100).default(10).describe('Items per page (maximum 100)'),
8
8
  search: z.string().optional().describe('Busca por nome ou e-mail'),
9
9
  role: z.nativeEnum(Role).optional().describe('Filtra por role'),
10
10
  });
11
11
 
12
- export class FindUsersQueryDto extends createZodDto(findUsersQuerySchema) { }
12
+ export class FindUsersQueryDto extends createZodDto(findUsersQuerySchema) { }
@@ -57,9 +57,9 @@ export class UsersController {
57
57
  @Permissions(Permission.UserCreate)
58
58
  // nestforge:feature:rbac:end
59
59
  // nestforge:feature:swagger
60
- @ApiOperation({ summary: 'Cria um usuário (requer permissão user:create)' })
61
- @ApiResponse({ status: 201, description: 'Usuário criado', schema: { example: USER_EXAMPLE } })
62
- @ApiResponse({ status: 403, description: 'Sem a permissão user:create', schema: { example: FORBIDDEN_EXAMPLE } })
60
+ @ApiOperation({ summary: 'Creates a user (requires the user:create permission)' })
61
+ @ApiResponse({ status: 201, description: 'User created', schema: { example: USER_EXAMPLE } })
62
+ @ApiResponse({ status: 403, description: 'Missing the user:create permission', schema: { example: FORBIDDEN_EXAMPLE } })
63
63
  // nestforge:feature:swagger:end
64
64
  create(@Body() dto: CreateUserDto) {
65
65
  return this.usersService.create(dto);
@@ -70,10 +70,10 @@ export class UsersController {
70
70
  @Permissions(Permission.UserRead)
71
71
  // nestforge:feature:rbac:end
72
72
  // nestforge:feature:swagger
73
- @ApiOperation({ summary: 'Lista usuários com paginação e filtros (requer permissão user:read)' })
73
+ @ApiOperation({ summary: 'Lists users with pagination and filters (requires the user:read permission)' })
74
74
  @ApiResponse({
75
75
  status: 200,
76
- description: 'Lista paginada',
76
+ description: 'Paginated list',
77
77
  schema: {
78
78
  example: {
79
79
  data: [USER_EXAMPLE],
@@ -88,8 +88,8 @@ export class UsersController {
88
88
 
89
89
  @Get('me')
90
90
  // nestforge:feature:swagger
91
- @ApiOperation({ summary: 'Retorna o usuário autenticado' })
92
- @ApiResponse({ status: 200, description: 'Usuário autenticado', schema: { example: USER_EXAMPLE } })
91
+ @ApiOperation({ summary: 'Returns the authenticated user' })
92
+ @ApiResponse({ status: 200, description: 'Authenticated user', schema: { example: USER_EXAMPLE } })
93
93
  // nestforge:feature:swagger:end
94
94
  findMe(@CurrentUser() user: { id: string }) {
95
95
  return this.usersService.findOne(user.id);
@@ -97,17 +97,17 @@ export class UsersController {
97
97
 
98
98
  @Post('me/avatar')
99
99
  // nestforge:feature:swagger
100
- @ApiOperation({ summary: 'Faz upload do avatar do usuário autenticado' })
100
+ @ApiOperation({ summary: 'Uploads an avatar for the authenticated user' })
101
101
  @ApiConsumes('multipart/form-data')
102
102
  @ApiResponse({
103
103
  status: 201,
104
- description: 'Avatar atualizado',
104
+ description: 'Avatar updated',
105
105
  schema: { example: { ...USER_EXAMPLE, avatarUrl: '/uploads/avatars/f47ac10b.png' } },
106
106
  })
107
107
  @ApiResponse({
108
108
  status: 400,
109
- description: 'Formato de imagem não suportado ou arquivo maior que 2MB',
110
- schema: { example: { statusCode: 400, message: 'Formato de imagem não suportado (use PNG, JPEG ou WEBP)' } },
109
+ description: 'Unsupported image format or file larger than 2 MB',
110
+ schema: { example: { statusCode: 400, message: 'Unsupported image format (use PNG, JPEG, or WEBP)' } },
111
111
  })
112
112
  // nestforge:feature:swagger:end
113
113
  @UseInterceptors(
@@ -129,9 +129,9 @@ export class UsersController {
129
129
  @Permissions(Permission.UserRead)
130
130
  // nestforge:feature:rbac:end
131
131
  // nestforge:feature:swagger
132
- @ApiOperation({ summary: 'Busca um usuário por id (requer permissão user:read)' })
133
- @ApiResponse({ status: 200, description: 'Usuário encontrado', schema: { example: USER_EXAMPLE } })
134
- @ApiResponse({ status: 404, description: 'Usuário não encontrado', schema: { example: { statusCode: 404, message: 'Usuário não encontrado' } } })
132
+ @ApiOperation({ summary: 'Finds a user by ID (requires the user:read permission)' })
133
+ @ApiResponse({ status: 200, description: 'User found', schema: { example: USER_EXAMPLE } })
134
+ @ApiResponse({ status: 404, description: 'User not found', schema: { example: { statusCode: 404, message: 'User not found' } } })
135
135
  // nestforge:feature:swagger:end
136
136
  findOne(@Param('id') id: string) {
137
137
  return this.usersService.findOne(id);
@@ -142,9 +142,9 @@ export class UsersController {
142
142
  @Permissions(Permission.UserUpdate)
143
143
  // nestforge:feature:rbac:end
144
144
  // nestforge:feature:swagger
145
- @ApiOperation({ summary: 'Atualiza um usuário (requer permissão user:update)' })
146
- @ApiResponse({ status: 200, description: 'Usuário atualizado', schema: { example: USER_EXAMPLE } })
147
- @ApiResponse({ status: 403, description: 'Sem a permissão user:update', schema: { example: FORBIDDEN_EXAMPLE } })
145
+ @ApiOperation({ summary: 'Updates a user (requires the user:update permission)' })
146
+ @ApiResponse({ status: 200, description: 'User updated', schema: { example: USER_EXAMPLE } })
147
+ @ApiResponse({ status: 403, description: 'Missing the user:update permission', schema: { example: FORBIDDEN_EXAMPLE } })
148
148
  // nestforge:feature:swagger:end
149
149
  update(@Param('id') id: string, @Body() dto: UpdateUserDto) {
150
150
  return this.usersService.update(id, dto);
@@ -155,11 +155,11 @@ export class UsersController {
155
155
  @Permissions(Permission.UserDelete)
156
156
  // nestforge:feature:rbac:end
157
157
  // nestforge:feature:swagger
158
- @ApiOperation({ summary: 'Remove um usuário (requer permissão user:delete)' })
159
- @ApiResponse({ status: 200, description: 'Usuário removido', schema: { example: { message: 'Usuário removido com sucesso' } } })
160
- @ApiResponse({ status: 403, description: 'Sem a permissão user:delete', schema: { example: FORBIDDEN_EXAMPLE } })
158
+ @ApiOperation({ summary: 'Deletes a user (requires the user:delete permission)' })
159
+ @ApiResponse({ status: 200, description: 'User deleted', schema: { example: { message: 'User deleted successfully' } } })
160
+ @ApiResponse({ status: 403, description: 'Missing the user:delete permission', schema: { example: FORBIDDEN_EXAMPLE } })
161
161
  // nestforge:feature:swagger:end
162
162
  remove(@Param('id') id: string) {
163
163
  return this.usersService.remove(id);
164
164
  }
165
- }
165
+ }
@@ -81,7 +81,7 @@ describe('UsersService', () => {
81
81
  );
82
82
  });
83
83
 
84
- it('deve lançar ConflictException se o e-mail já existir ao criar', async () => {
84
+ it('throws ConflictException when the email already exists during creation', async () => {
85
85
  database.select.mockReturnValueOnce(
86
86
  createSelectBuilder([
87
87
  {
@@ -94,14 +94,14 @@ describe('UsersService', () => {
94
94
  usersService.create({
95
95
  name: 'Jeiel',
96
96
  email: 'jeiel@example.com',
97
- password: 'senhaForte123',
97
+ password: 'strongPassword123',
98
98
  }),
99
99
  ).rejects.toBeInstanceOf(ConflictException);
100
100
 
101
101
  expect(database.insert).not.toHaveBeenCalled();
102
102
  });
103
103
 
104
- it('deve criar um usuário e esconder o passwordHash na serialização', async () => {
104
+ it('creates a user and hides passwordHash during serialization', async () => {
105
105
  database.select
106
106
  .mockReturnValueOnce(createSelectBuilder([]))
107
107
  .mockReturnValueOnce(
@@ -111,7 +111,7 @@ describe('UsersService', () => {
111
111
  const result = await usersService.create({
112
112
  name: 'Jeiel',
113
113
  email: 'jeiel@example.com',
114
- password: 'senhaForte123',
114
+ password: 'strongPassword123',
115
115
  });
116
116
 
117
117
  expect(result.email).toBe(
@@ -133,7 +133,7 @@ describe('UsersService', () => {
133
133
  );
134
134
  });
135
135
 
136
- it('deve lançar NotFoundException se o usuário não existir', async () => {
136
+ it('throws NotFoundException when the user does not exist', async () => {
137
137
  database.select.mockReturnValueOnce(
138
138
  createSelectBuilder([]),
139
139
  );
@@ -143,7 +143,7 @@ describe('UsersService', () => {
143
143
  ).rejects.toBeInstanceOf(NotFoundException);
144
144
  });
145
145
 
146
- it('deve retornar dados paginados em findAll', async () => {
146
+ it('returns paginated data from findAll', async () => {
147
147
  const usersBuilder = createSelectBuilder([
148
148
  mockUser,
149
149
  ]);
@@ -179,7 +179,7 @@ describe('UsersService', () => {
179
179
  );
180
180
  });
181
181
 
182
- it('deve aplicar busca e filtro por role em findAll', async () => {
182
+ it('applies search and role filters in findAll', async () => {
183
183
  const usersBuilder = createSelectBuilder([
184
184
  mockUser,
185
185
  ]);
@@ -214,7 +214,7 @@ describe('UsersService', () => {
214
214
  );
215
215
  });
216
216
 
217
- it('deve atualizar um usuário existente', async () => {
217
+ it('updates an existing user', async () => {
218
218
  const updatedUser = new UserEntity({
219
219
  ...mockUser,
220
220
  name: 'Novo Nome',
@@ -248,7 +248,7 @@ describe('UsersService', () => {
248
248
  expect(updateWhere).toHaveBeenCalled();
249
249
  });
250
250
 
251
- it('deve remover um usuário existente', async () => {
251
+ it('deletes an existing user', async () => {
252
252
  database.select.mockReturnValueOnce(
253
253
  createSelectBuilder([mockUser]),
254
254
  );
@@ -261,11 +261,11 @@ describe('UsersService', () => {
261
261
  expect(deleteWhere).toHaveBeenCalled();
262
262
 
263
263
  expect(result).toEqual({
264
- message: 'Usuário removido com sucesso',
264
+ message: 'User deleted successfully',
265
265
  });
266
266
  });
267
267
 
268
- it('deve atualizar o avatar de um usuário', async () => {
268
+ it('updates a user avatar', async () => {
269
269
  const updatedUser = new UserEntity({
270
270
  ...mockUser,
271
271
  avatarUrl: '/uploads/avatars/x.png',
@@ -298,4 +298,4 @@ describe('UsersService', () => {
298
298
 
299
299
  expect(updateWhere).toHaveBeenCalled();
300
300
  });
301
- });
301
+ });