@devstroupe/devkit-cli 1.1.16 → 1.2.0-beta.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.
Files changed (90) hide show
  1. package/README.md +18 -0
  2. package/dist/boilerplates/angular-template/src/app/core/services/auth.service.ts +1 -1
  3. package/dist/boilerplates/angular-template/src/app/services/role.service.ts +1 -1
  4. package/dist/boilerplates/angular-template/src/app/services/user.service.ts +1 -1
  5. package/dist/boilerplates/nest-template/.dockerignore +8 -0
  6. package/dist/boilerplates/nest-template/package.json +13 -15
  7. package/dist/boilerplates/nest-template/src/app.module.ts +7 -10
  8. package/dist/boilerplates/nest-template/src/database/data-source.ts +4 -1
  9. package/dist/boilerplates/nest-template/src/database/migrations/1600000000000-create-roles.ts +2 -1
  10. package/dist/boilerplates/nest-template/src/database/migrations/1700000000000-create-users.ts +2 -2
  11. package/dist/boilerplates/nest-template/src/modules/auth/auth.controller.ts +1 -1
  12. package/dist/boilerplates/nest-template/src/modules/auth/auth.module.ts +2 -7
  13. package/dist/boilerplates/nest-template/src/modules/auth/auth.service.ts +13 -11
  14. package/dist/boilerplates/nest-template/src/modules/auth/jwt-auth.guard.ts +5 -6
  15. package/dist/boilerplates/nest-template/src/modules/core/core.module.ts +9 -0
  16. package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.orm-entity.ts +4 -1
  17. package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.repository.adapter.ts +2 -2
  18. package/dist/boilerplates/nest-template/src/modules/role/infra/http/role.controller.ts +1 -1
  19. package/dist/boilerplates/nest-template/src/modules/storage/infra/http/storage.controller.ts +1 -1
  20. package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.orm-entity.ts +4 -1
  21. package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.repository.adapter.ts +2 -2
  22. package/dist/boilerplates/nest-template/src/modules/user/infra/http/user.controller.ts +1 -1
  23. package/dist/boilerplates/nest-template/src/modules/user/service/user.service.ts +6 -3
  24. package/dist/boilerplates/nest-template/src/platform/security/security.module.ts +20 -0
  25. package/dist/boilerplates/nest-template/src/platform/security/security.tokens.ts +4 -0
  26. package/dist/boilerplates/nest-template/src/platform/security/token-verification.module.ts +22 -0
  27. package/dist/boilerplates/nest-template/tsconfig.json +1 -1
  28. package/dist/generation/backend-generator.integration.test.d.ts +1 -0
  29. package/dist/generation/backend-generator.integration.test.js +232 -0
  30. package/dist/generation/backend-layout.d.ts +15 -0
  31. package/dist/generation/backend-layout.js +77 -0
  32. package/dist/generation/backend-layout.test.d.ts +1 -0
  33. package/dist/generation/backend-layout.test.js +48 -0
  34. package/dist/index.d.ts +4 -1
  35. package/dist/index.js +364 -205
  36. package/dist/migrations/entity-module-move.d.ts +16 -0
  37. package/dist/migrations/entity-module-move.js +98 -0
  38. package/dist/migrations/entity-module-move.test.d.ts +1 -0
  39. package/dist/migrations/entity-module-move.test.js +30 -0
  40. package/dist/rules/linter-rules.js +252 -22
  41. package/dist/rules/linter-rules.test.js +125 -0
  42. package/dist/templates/angular/service.template.d.ts +1 -1
  43. package/dist/templates/angular/service.template.js +2 -2
  44. package/dist/templates/generator-characterization.test.d.ts +1 -0
  45. package/dist/templates/generator-characterization.test.js +42 -0
  46. package/dist/templates/nest/crud.templates.d.ts +3 -3
  47. package/dist/templates/nest/crud.templates.js +20 -13
  48. package/dist/templates/nest/functional-module.templates.d.ts +10 -0
  49. package/dist/templates/nest/functional-module.templates.js +261 -0
  50. package/dist/templates/nest/index.d.ts +1 -1
  51. package/dist/templates/nest/index.js +1 -1
  52. package/dist/templates/nest/migration.template.js +6 -3
  53. package/dist/templates/relationship-templates.test.js +0 -4
  54. package/package.json +4 -4
  55. package/dist/boilerplates/angular-template/node_modules/.bin/acorn +0 -21
  56. package/dist/boilerplates/angular-template/node_modules/.bin/browserslist +0 -21
  57. package/dist/boilerplates/angular-template/node_modules/.bin/eslint +0 -21
  58. package/dist/boilerplates/angular-template/node_modules/.bin/jiti +0 -21
  59. package/dist/boilerplates/angular-template/node_modules/.bin/js-yaml +0 -21
  60. package/dist/boilerplates/angular-template/node_modules/.bin/lessc +0 -21
  61. package/dist/boilerplates/angular-template/node_modules/.bin/ng +0 -21
  62. package/dist/boilerplates/angular-template/node_modules/.bin/ng-xi18n +0 -21
  63. package/dist/boilerplates/angular-template/node_modules/.bin/ngc +0 -21
  64. package/dist/boilerplates/angular-template/node_modules/.bin/nx +0 -21
  65. package/dist/boilerplates/angular-template/node_modules/.bin/nx-cloud +0 -21
  66. package/dist/boilerplates/angular-template/node_modules/.bin/sass +0 -21
  67. package/dist/boilerplates/angular-template/node_modules/.bin/terser +0 -21
  68. package/dist/boilerplates/angular-template/node_modules/.bin/tsc +0 -21
  69. package/dist/boilerplates/angular-template/node_modules/.bin/tsserver +0 -21
  70. package/dist/boilerplates/angular-template/node_modules/.bin/vite +0 -21
  71. package/dist/boilerplates/angular-template/node_modules/.bin/vitest +0 -21
  72. package/dist/boilerplates/angular-template/node_modules/.bin/webpack +0 -21
  73. package/dist/boilerplates/angular-template/node_modules/.bin/yaml +0 -21
  74. package/dist/boilerplates/nest-template/node_modules/.bin/acorn +0 -21
  75. package/dist/boilerplates/nest-template/node_modules/.bin/nest +0 -21
  76. package/dist/boilerplates/nest-template/node_modules/.bin/prettier +0 -21
  77. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node +0 -21
  78. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-cwd +0 -21
  79. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-esm +0 -21
  80. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-script +0 -21
  81. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-transpile-only +0 -21
  82. package/dist/boilerplates/nest-template/node_modules/.bin/ts-script +0 -21
  83. package/dist/boilerplates/nest-template/node_modules/.bin/tsc +0 -21
  84. package/dist/boilerplates/nest-template/node_modules/.bin/tsserver +0 -21
  85. package/dist/boilerplates/nest-template/node_modules/.bin/typeorm +0 -21
  86. package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-commonjs +0 -21
  87. package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-esm +0 -21
  88. package/dist/boilerplates/nest-template/node_modules/.bin/webpack +0 -21
  89. package/dist/templates/nest/microservice.templates.d.ts +0 -4
  90. package/dist/templates/nest/microservice.templates.js +0 -157
package/README.md CHANGED
@@ -19,6 +19,8 @@ npm install -g @devstroupe/devkit-cli
19
19
  | `devstroupe init` | Cria `devstroupe.config.ts` na raiz do projeto |
20
20
  | `devstroupe init-ui [--style <style>]` | Injeta Design System Spartan NG, wrappers devkit-*, ThemeService e ProfileComponent |
21
21
  | `devstroupe generate crud <entity>` | Gera CRUD completo (backend NestJS + frontend Angular) para a entidade |
22
+ | `devstroupe entity:set-module <entity> <module> --dry-run` | Planeja mudança de ownership sem alterar arquivos |
23
+ | `devstroupe entity:set-module <entity> <module> --apply` | Aplica mudança monolítica sem conflito e preserva backup |
22
24
  | `devstroupe lint:rules` | Auditoria estática de governança arquitetural |
23
25
  | `devstroupe ui:set-style <style>` | Troca o style Spartan e regenera `libs/ui/` |
24
26
 
@@ -28,6 +30,22 @@ O `init-ui` requer que o projeto Angular tenha o `@spartan-ng/cli` instalado. O
28
30
 
29
31
  Styles disponíveis: `nova`, `vega`, `lyra`, `maia`, `mira`, `luma`.
30
32
 
33
+ ## Backend gerado
34
+
35
+ `entity.module` agrupa entidades por capacidade; ausência resolve para `core`.
36
+ No monólito existe um Nest module por grupo. Em microsserviços existe uma
37
+ aplicação, banco, porta, container e rota HTTP de gateway por grupo — nunca por
38
+ entidade. O gateway é stateless, não possui TypeORM e encaminha
39
+ `/api/<module>/*`; controllers e DTOs existem somente na aplicação funcional.
40
+ User, Role e Auth pertencem ao Core e ao banco `devstroupe_core`. O CRUD usa use
41
+ cases tipados, TypeORM 1.x, soft delete obrigatório e tenant obtido
42
+ do contexto autenticado. No modo distribuído, `backend/packages/platform` é um
43
+ package local reutilizável, ligado ao gateway e a cada aplicação funcional via
44
+ `local_packages`; edite a origem central, não as cópias sincronizadas.
45
+
46
+ Veja [a arquitetura backend](../../docs/backend-architecture.md) e
47
+ [a migração TypeORM 1.x](../../docs/typeorm-1-migration.md).
48
+
31
49
  ## Pré-requisitos do `init-ui`
32
50
 
33
51
  ```bash
@@ -22,7 +22,7 @@ export interface AuthResponse {
22
22
  })
23
23
  export class AuthService {
24
24
  private http = inject(HttpClient);
25
- private apiUrl = '/api/auth';
25
+ private apiUrl = '/api/core/auth';
26
26
 
27
27
  private currentUserSubject = new BehaviorSubject<User | null>(null);
28
28
  public currentUser$ = this.currentUserSubject.asObservable();
@@ -7,6 +7,6 @@ import { BaseService } from '@devstroupe/devkit-angular';
7
7
  })
8
8
  export class RoleService extends BaseService<any> {
9
9
  constructor(http: HttpClient) {
10
- super(http, '/api/role');
10
+ super(http, '/api/core/role');
11
11
  }
12
12
  }
@@ -7,6 +7,6 @@ import { BaseService } from '@devstroupe/devkit-angular';
7
7
  })
8
8
  export class UserService extends BaseService<any> {
9
9
  constructor(http: HttpClient) {
10
- super(http as any, '/api/user');
10
+ super(http as any, '/api/core/user');
11
11
  }
12
12
  }
@@ -0,0 +1,8 @@
1
+ node_modules
2
+ dist
3
+ coverage
4
+ .git
5
+ .env
6
+ *.log
7
+ storage
8
+ local_packages/*/node_modules
@@ -3,6 +3,7 @@
3
3
  "version": "1.0.1",
4
4
  "description": "Boilerplate NestJS integrado ao DT-DevKit",
5
5
  "private": true,
6
+ "engines": { "node": ">=20" },
6
7
  "scripts": {
7
8
  "build": "nest build",
8
9
  "format": "prettier --write \"src/**/*.ts\"",
@@ -15,34 +16,31 @@
15
16
  "migration:generate": "typeorm-ts-node-commonjs -d src/database/data-source.ts migration:generate src/database/migrations/schema"
16
17
  },
17
18
  "dependencies": {
19
+ "@devstroupe/devkit-core": "workspace:*",
20
+ "@devstroupe/devkit-nest": "workspace:*",
18
21
  "@nestjs/common": "^11.0.0",
19
22
  "@nestjs/core": "^11.0.0",
23
+ "@nestjs/microservices": "^11.0.0",
20
24
  "@nestjs/platform-express": "^11.0.0",
25
+ "@nestjs/platform-socket.io": "^11.0.0",
21
26
  "@nestjs/swagger": "^11.0.0",
22
27
  "@nestjs/typeorm": "^11.0.0",
23
- "@nestjs/jwt": "^11.0.0",
24
- "@nestjs/microservices": "^11.0.0",
25
- "typeorm": "^0.3.20",
28
+ "@nestjs/websockets": "^11.0.0",
29
+ "class-transformer": "^0.5.1",
30
+ "class-validator": "^0.14.1",
31
+ "dotenv": "^16.4.5",
26
32
  "mysql2": "^3.9.0",
27
33
  "reflect-metadata": "^0.2.0",
28
34
  "rxjs": "^7.8.1",
29
- "bcryptjs": "^2.4.3",
30
- "dotenv": "^16.4.5",
31
- "@devstroupe/devkit-core": "workspace:*",
32
- "@devstroupe/devkit-nest": "workspace:*",
33
- "class-validator": "^0.14.1",
34
- "class-transformer": "^0.5.1",
35
- "@nestjs/websockets": "^11.0.0",
36
- "@nestjs/platform-socket.io": "^11.0.0",
37
- "socket.io": "^4.7.5"
35
+ "socket.io": "^4.7.5",
36
+ "typeorm": "^1.0.0"
38
37
  },
39
38
  "devDependencies": {
40
39
  "@nestjs/cli": "^11.0.0",
41
40
  "@nestjs/schematics": "^11.0.0",
42
41
  "@types/express": "^5.0.0",
43
42
  "@types/node": "^20.11.0",
44
- "@types/bcryptjs": "^2.4.6",
45
- "typescript": "^5.4.5",
46
- "ts-node": "^10.9.2"
43
+ "ts-node": "^10.9.2",
44
+ "typescript": "^5.4.5"
47
45
  }
48
46
  }
@@ -1,14 +1,14 @@
1
1
  import { Module } from '@nestjs/common';
2
+ import { APP_INTERCEPTOR } from '@nestjs/core';
2
3
  import { TypeOrmModule } from '@nestjs/typeorm';
3
- import { StorageModule } from '@devstroupe/devkit-nest';
4
- import { UserModule } from './modules/user/user.module';
5
- import { AuthModule } from './modules/auth/auth.module';
6
- import { RoleModule } from './modules/role/role.module';
7
- import { StorageDownloadModule } from './modules/storage/storage.module';
4
+ import { ExecutionContextInterceptor, StorageModule } from '@devstroupe/devkit-nest';
5
+ import { CoreModule } from './modules/core/core.module';
8
6
  import * as path from 'path';
7
+ import { SecurityModule } from './platform/security/security.module';
9
8
 
10
9
  @Module({
11
10
  imports: [
11
+ SecurityModule,
12
12
  TypeOrmModule.forRoot({
13
13
  type: 'mysql',
14
14
  host: process.env.DB_HOST || 'localhost',
@@ -41,12 +41,9 @@ import * as path from 'path';
41
41
  }
42
42
  })
43
43
  }),
44
- UserModule,
45
- AuthModule,
46
- RoleModule,
47
- StorageDownloadModule,
44
+ CoreModule,
48
45
  ],
49
46
  controllers: [],
50
- providers: [],
47
+ providers: [{ provide: APP_INTERCEPTOR, useClass: ExecutionContextInterceptor }],
51
48
  })
52
49
  export class AppModule {}
@@ -10,7 +10,10 @@ export default new DataSource({
10
10
  username: process.env.DB_USER || 'root',
11
11
  password: process.env.DB_PASSWORD || 'root',
12
12
  database: process.env.DB_NAME || 'devstroupe',
13
- entities: [path.join(__dirname, '../modules/**/infra/database/*.orm-entity{.ts,.js}')],
13
+ entities: [
14
+ path.join(__dirname, '../modules/**/infra/database/*.orm-entity{.ts,.js}'),
15
+ path.join(__dirname, '../modules/**/infrastructure/persistence/typeorm/*.orm-entity{.ts,.js}'),
16
+ ],
14
17
  migrations: [path.join(__dirname, './migrations/*{.ts,.js}')],
15
18
  synchronize: false,
16
19
  });
@@ -9,7 +9,8 @@ export class CreateRoles1600000000000 implements MigrationInterface {
9
9
  columns: [
10
10
  { name: 'id', type: 'int', isPrimary: true, isGenerated: true, generationStrategy: 'increment' },
11
11
  { name: 'name', type: 'varchar', length: '255', isUnique: true },
12
- { name: 'permissions', type: 'text', isNullable: true }
12
+ { name: 'permissions', type: 'text', isNullable: true },
13
+ { name: 'deleted_at', type: 'datetime', isNullable: true }
13
14
  ]
14
15
  }), true);
15
16
  }
@@ -16,7 +16,8 @@ export class CreateUsers1700000000000 implements MigrationInterface {
16
16
  { name: 'tenant_id', type: 'varchar', length: '255', isNullable: true },
17
17
  { name: 'is_active', type: 'boolean', default: "'1'" },
18
18
  { name: 'created_at', type: 'datetime', default: 'CURRENT_TIMESTAMP' },
19
- { name: 'updated_at', type: 'datetime', default: 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP' }
19
+ { name: 'updated_at', type: 'datetime', default: 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP' },
20
+ { name: 'deleted_at', type: 'datetime', isNullable: true }
20
21
  ]
21
22
  }), true);
22
23
 
@@ -34,4 +35,3 @@ export class CreateUsers1700000000000 implements MigrationInterface {
34
35
  await queryRunner.dropTable('users', true);
35
36
  }
36
37
  }
37
-
@@ -5,7 +5,7 @@ import { JwtAuthGuard } from './jwt-auth.guard';
5
5
  import { CurrentUser } from './current-user.decorator';
6
6
 
7
7
  @ApiTags('auth')
8
- @Controller('auth')
8
+ @Controller('core/auth')
9
9
  export class AuthController {
10
10
  constructor(private readonly authService: AuthService) {}
11
11
 
@@ -1,5 +1,4 @@
1
- import { Module } from '@nestjs/common';
2
- import { JwtModule } from '@nestjs/jwt';
1
+ import { Global, Module } from '@nestjs/common';
3
2
  import { UserModule } from '../user/user.module';
4
3
  import { RoleModule } from '../role/role.module';
5
4
  import { AuthService } from './auth.service';
@@ -7,15 +6,11 @@ import { AuthController } from './auth.controller';
7
6
  import { JwtAuthGuard } from './jwt-auth.guard';
8
7
  import { RolesGuard } from './roles.guard';
9
8
 
9
+ @Global()
10
10
  @Module({
11
11
  imports: [
12
12
  UserModule,
13
13
  RoleModule,
14
- JwtModule.register({
15
- global: true,
16
- secret: process.env.JWT_SECRET || 'devstroupe-secret-key-12345',
17
- signOptions: { expiresIn: '1d' },
18
- }),
19
14
  ],
20
15
  providers: [AuthService, JwtAuthGuard, RolesGuard],
21
16
  controllers: [AuthController],
@@ -1,16 +1,19 @@
1
- import { Injectable, UnauthorizedException } from '@nestjs/common';
2
- import { JwtService } from '@nestjs/jwt';
3
- import * as bcrypt from 'bcryptjs';
1
+ import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
2
+ import type { PasswordHasher, TokenIssuer, TokenVerifier } from '@devstroupe/devkit-nest';
4
3
  import { UserService } from '../user/service/user.service';
5
4
  import { User } from '../user/domain/user';
6
5
  import { RoleService } from '../role/service/role.service';
6
+ import { ACCESS_TOKEN_ISSUER, PASSWORD_HASHER, REFRESH_TOKEN_ISSUER, TOKEN_VERIFIER } from '../../platform/security/security.tokens';
7
7
 
8
8
  @Injectable()
9
9
  export class AuthService {
10
10
  constructor(
11
11
  private readonly userService: UserService,
12
- private readonly jwtService: JwtService,
13
12
  private readonly roleService: RoleService,
13
+ @Inject(PASSWORD_HASHER) private readonly passwordHasher: PasswordHasher,
14
+ @Inject(ACCESS_TOKEN_ISSUER) private readonly accessTokenIssuer: TokenIssuer,
15
+ @Inject(REFRESH_TOKEN_ISSUER) private readonly refreshTokenIssuer: TokenIssuer,
16
+ @Inject(TOKEN_VERIFIER) private readonly tokenVerifier: TokenVerifier,
14
17
  ) {}
15
18
 
16
19
  async register(data: Omit<User, 'id' | 'role' | 'isActive'>): Promise<User> {
@@ -31,7 +34,7 @@ export class AuthService {
31
34
  throw new UnauthorizedException('Invalid credentials');
32
35
  }
33
36
 
34
- const isMatch = await bcrypt.compare(pass, user.password);
37
+ const isMatch = await this.passwordHasher.verify(pass, user.password);
35
38
  if (!isMatch) {
36
39
  throw new UnauthorizedException('Invalid credentials');
37
40
  }
@@ -45,7 +48,7 @@ export class AuthService {
45
48
 
46
49
  async refresh(email: string, refreshToken: string): Promise<{ accessToken: string; refreshToken: string; user: Omit<User, 'password' | 'currentRefreshToken'> }> {
47
50
  try {
48
- const payload = await this.jwtService.verifyAsync(refreshToken);
51
+ const payload = await this.tokenVerifier.verify(refreshToken);
49
52
  if (payload.email !== email) {
50
53
  throw new UnauthorizedException('Invalid refresh token');
51
54
  }
@@ -55,7 +58,7 @@ export class AuthService {
55
58
  throw new UnauthorizedException('Session expired or user inactive');
56
59
  }
57
60
 
58
- const isMatch = await bcrypt.compare(refreshToken, user.currentRefreshToken);
61
+ const isMatch = await this.passwordHasher.verify(refreshToken, user.currentRefreshToken);
59
62
  if (!isMatch) {
60
63
  throw new UnauthorizedException('Invalid session');
61
64
  }
@@ -79,11 +82,10 @@ export class AuthService {
79
82
  permissions: user.role?.permissions || []
80
83
  };
81
84
 
82
- const accessToken = await this.jwtService.signAsync(payload, { expiresIn: '15m' });
83
- const refreshToken = await this.jwtService.signAsync({ email: user.email }, { expiresIn: '7d' });
85
+ const accessToken = await this.accessTokenIssuer.issue(payload);
86
+ const refreshToken = await this.refreshTokenIssuer.issue({ email: user.email });
84
87
 
85
- const salt = await bcrypt.genSalt(10);
86
- const hashedRefreshToken = await bcrypt.hash(refreshToken, salt);
88
+ const hashedRefreshToken = await this.passwordHasher.hash(refreshToken);
87
89
  await this.userService.update(user.id, { currentRefreshToken: hashedRefreshToken } as any);
88
90
 
89
91
  const { password, currentRefreshToken, ...result } = user;
@@ -1,10 +1,11 @@
1
- import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
2
- import { JwtService } from '@nestjs/jwt';
1
+ import { CanActivate, ExecutionContext, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
2
+ import type { TokenVerifier } from '@devstroupe/devkit-nest';
3
3
  import { Request } from 'express';
4
+ import { TOKEN_VERIFIER } from '../../platform/security/security.tokens';
4
5
 
5
6
  @Injectable()
6
7
  export class JwtAuthGuard implements CanActivate {
7
- constructor(private readonly jwtService: JwtService) {}
8
+ constructor(@Inject(TOKEN_VERIFIER) private readonly tokenVerifier: TokenVerifier) {}
8
9
 
9
10
  async canActivate(context: ExecutionContext): Promise<boolean> {
10
11
  const request = context.switchToHttp().getRequest<Request>();
@@ -13,9 +14,7 @@ export class JwtAuthGuard implements CanActivate {
13
14
  throw new UnauthorizedException('Token not found');
14
15
  }
15
16
  try {
16
- const payload = await this.jwtService.verifyAsync(token, {
17
- secret: process.env.JWT_SECRET || 'devstroupe-secret-key-12345',
18
- });
17
+ const payload = await this.tokenVerifier.verify(token);
19
18
  // Injeta o payload decodificado (contendo sub, email, role, tenantId) na request
20
19
  request['user'] = payload;
21
20
  } catch {
@@ -0,0 +1,9 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { AuthModule } from '../auth/auth.module';
3
+ import { StorageDownloadModule } from '../storage/storage.module';
4
+
5
+ @Module({
6
+ imports: [AuthModule, StorageDownloadModule],
7
+ exports: [AuthModule, StorageDownloadModule],
8
+ })
9
+ export class CoreModule {}
@@ -1,4 +1,4 @@
1
- import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
1
+ import { Entity, PrimaryGeneratedColumn, Column, DeleteDateColumn } from 'typeorm';
2
2
 
3
3
  @Entity('roles')
4
4
  export class RoleOrmEntity {
@@ -10,4 +10,7 @@ export class RoleOrmEntity {
10
10
 
11
11
  @Column({ type: 'simple-array', nullable: true })
12
12
  permissions!: string[];
13
+
14
+ @DeleteDateColumn({ name: 'deleted_at', nullable: true })
15
+ deletedAt?: Date;
13
16
  }
@@ -48,7 +48,7 @@ export class RoleRepositoryAdapter extends BaseRepository<RoleOrmEntity> impleme
48
48
  }
49
49
 
50
50
  async findAll(): Promise<Role[]> {
51
- const found = await this.repo.find();
51
+ const found = await this.repo.find({ take: 100 });
52
52
  return found.map(orm => this.toDomain(orm));
53
53
  }
54
54
 
@@ -66,6 +66,6 @@ export class RoleRepositoryAdapter extends BaseRepository<RoleOrmEntity> impleme
66
66
  }
67
67
 
68
68
  async delete(id: number): Promise<void> {
69
- await this.repo.delete(id);
69
+ await super.delete(id);
70
70
  }
71
71
  }
@@ -7,7 +7,7 @@ import { CreateRoleDto, UpdateRoleDto } from './role.dto';
7
7
  import { JwtAuthGuard } from '../../../auth/jwt-auth.guard';
8
8
 
9
9
  @UseGuards(JwtAuthGuard, PermissionsGuard)
10
- @Controller('role')
10
+ @Controller('core/role')
11
11
  @ApiTags('role')
12
12
  export class RoleController extends BaseCrudController<Role> {
13
13
  constructor(private readonly roleService: RoleService) {
@@ -3,7 +3,7 @@ import { FileInterceptor } from '@nestjs/platform-express';
3
3
  import { Response } from 'express';
4
4
  import { StorageService } from '@devstroupe/devkit-nest';
5
5
 
6
- @Controller('storage')
6
+ @Controller('core/storage')
7
7
  export class StorageController {
8
8
  constructor(
9
9
  private readonly storageService: StorageService,
@@ -1,4 +1,4 @@
1
- import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
1
+ import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, ManyToOne, JoinColumn } from 'typeorm';
2
2
  import { RoleOrmEntity } from '../../../role/infra/database/role.orm-entity';
3
3
 
4
4
  @Entity('users')
@@ -33,4 +33,7 @@ export class UserOrmEntity {
33
33
 
34
34
  @UpdateDateColumn({ name: 'updated_at' })
35
35
  updatedAt!: Date;
36
+
37
+ @DeleteDateColumn({ name: 'deleted_at', nullable: true })
38
+ deletedAt?: Date;
36
39
  }
@@ -55,7 +55,7 @@ export class UserRepositoryAdapter extends BaseRepository<UserOrmEntity> impleme
55
55
  }
56
56
 
57
57
  async findAll(): Promise<User[]> {
58
- const found = await this.repo.find({ relations: ['role'] });
58
+ const found = await this.repo.find({ relations: { role: true }, take: 100 });
59
59
  return found.map((u) => this.toDomain(u));
60
60
  }
61
61
 
@@ -76,6 +76,6 @@ export class UserRepositoryAdapter extends BaseRepository<UserOrmEntity> impleme
76
76
  }
77
77
 
78
78
  async delete(id: number): Promise<void> {
79
- await this.repo.delete(id);
79
+ await super.delete(id);
80
80
  }
81
81
  }
@@ -6,7 +6,7 @@ import { BaseCrudController, Permissions, PermissionsGuard } from '@devstroupe/d
6
6
  import { User } from '../../domain/user';
7
7
 
8
8
  @ApiTags('users')
9
- @Controller('user')
9
+ @Controller('core/user')
10
10
  @UseGuards(JwtAuthGuard, PermissionsGuard)
11
11
  @ApiBearerAuth()
12
12
  export class UserController extends BaseCrudController<User> {
@@ -1,5 +1,6 @@
1
1
  import { Injectable, Inject } from '@nestjs/common';
2
- import * as bcrypt from 'bcryptjs';
2
+ import type { PasswordHasher } from '@devstroupe/devkit-nest';
3
+ import { PASSWORD_HASHER } from '../../../platform/security/security.tokens';
3
4
  import { IPaginator } from '@devstroupe/devkit-core';
4
5
  import { IUserRepository } from '../domain/user.repository';
5
6
  import { User } from '../domain/user';
@@ -9,6 +10,8 @@ export class UserService {
9
10
  constructor(
10
11
  @Inject('IUserRepository')
11
12
  private readonly userRepo: IUserRepository,
13
+ @Inject(PASSWORD_HASHER)
14
+ private readonly passwordHasher: PasswordHasher,
12
15
  ) {}
13
16
 
14
17
  async create(user: Omit<User, 'id'>): Promise<User> {
@@ -17,7 +20,7 @@ export class UserService {
17
20
  throw new Error('Email already registered');
18
21
  }
19
22
 
20
- const hashedPassword = await bcrypt.hash(user.password, 10);
23
+ const hashedPassword = await this.passwordHasher.hash(user.password);
21
24
  return this.userRepo.create({
22
25
  ...user,
23
26
  password: hashedPassword,
@@ -38,7 +41,7 @@ export class UserService {
38
41
 
39
42
  async update(id: number, user: Partial<User>): Promise<User> {
40
43
  if (user.password) {
41
- user.password = await bcrypt.hash(user.password, 10);
44
+ user.password = await this.passwordHasher.hash(user.password);
42
45
  }
43
46
  return this.userRepo.update(id, user);
44
47
  }
@@ -0,0 +1,20 @@
1
+ import { Global, Module } from '@nestjs/common';
2
+ import { BcryptPasswordHasher, JwtTokenIssuer } from '@devstroupe/devkit-nest';
3
+ import { ACCESS_TOKEN_ISSUER, PASSWORD_HASHER, REFRESH_TOKEN_ISSUER } from './security.tokens';
4
+ import { TokenVerificationModule } from './token-verification.module';
5
+
6
+ function signingKey(): string {
7
+ return process.env.JWT_PRIVATE_KEY || process.env.JWT_SECRET || 'development-secret-with-at-least-32-characters';
8
+ }
9
+
10
+ @Global()
11
+ @Module({
12
+ imports: [TokenVerificationModule],
13
+ providers: [
14
+ { provide: PASSWORD_HASHER, useFactory: () => new BcryptPasswordHasher(12) },
15
+ { provide: ACCESS_TOKEN_ISSUER, useFactory: () => new JwtTokenIssuer(signingKey(), { algorithm: process.env.JWT_PRIVATE_KEY ? 'RS256' : 'HS256', expiresIn: '15m' }) },
16
+ { provide: REFRESH_TOKEN_ISSUER, useFactory: () => new JwtTokenIssuer(signingKey(), { algorithm: process.env.JWT_PRIVATE_KEY ? 'RS256' : 'HS256', expiresIn: '7d' }) },
17
+ ],
18
+ exports: [PASSWORD_HASHER, ACCESS_TOKEN_ISSUER, REFRESH_TOKEN_ISSUER, TokenVerificationModule],
19
+ })
20
+ export class SecurityModule {}
@@ -0,0 +1,4 @@
1
+ export const PASSWORD_HASHER = Symbol('PASSWORD_HASHER');
2
+ export const ACCESS_TOKEN_ISSUER = Symbol('ACCESS_TOKEN_ISSUER');
3
+ export const REFRESH_TOKEN_ISSUER = Symbol('REFRESH_TOKEN_ISSUER');
4
+ export const TOKEN_VERIFIER = Symbol('TOKEN_VERIFIER');
@@ -0,0 +1,22 @@
1
+ import { Global, Module } from '@nestjs/common';
2
+ import { JwtTokenVerifier, staticVerificationKey } from '@devstroupe/devkit-nest';
3
+ import { TOKEN_VERIFIER } from './security.tokens';
4
+
5
+ function verificationKey(): string {
6
+ return process.env.JWT_PUBLIC_KEY || process.env.JWT_SECRET || 'development-secret-with-at-least-32-characters';
7
+ }
8
+
9
+ @Global()
10
+ @Module({
11
+ providers: [
12
+ {
13
+ provide: TOKEN_VERIFIER,
14
+ useFactory: () => new JwtTokenVerifier(
15
+ staticVerificationKey(verificationKey()),
16
+ [process.env.JWT_PUBLIC_KEY ? 'RS256' : 'HS256'],
17
+ ),
18
+ },
19
+ ],
20
+ exports: [TOKEN_VERIFIER],
21
+ })
22
+ export class TokenVerificationModule {}
@@ -6,7 +6,7 @@
6
6
  "emitDecoratorMetadata": true,
7
7
  "experimentalDecorators": true,
8
8
  "allowSyntheticDefaultImports": true,
9
- "target": "ES2021",
9
+ "target": "ES2023",
10
10
  "sourceMap": true,
11
11
  "outDir": "./dist",
12
12
  "baseUrl": "./",