@devstroupe/devkit-cli 1.0.1 → 1.1.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 (29) hide show
  1. package/dist/boilerplates/angular-template/src/app/services/user.service.ts +12 -0
  2. package/dist/boilerplates/nest-template/src/app.module.ts +23 -0
  3. package/dist/boilerplates/nest-template/src/database/migrations/1600000000000-create-roles.ts +20 -0
  4. package/dist/boilerplates/nest-template/src/database/migrations/1700000000000-create-users.ts +13 -2
  5. package/dist/boilerplates/nest-template/src/modules/auth/auth.controller.ts +16 -0
  6. package/dist/boilerplates/nest-template/src/modules/auth/auth.service.ts +44 -5
  7. package/dist/boilerplates/nest-template/src/modules/storage/infra/http/storage-download.controller.ts +38 -0
  8. package/dist/boilerplates/nest-template/src/modules/storage/storage.module.ts +7 -0
  9. package/dist/boilerplates/nest-template/src/modules/user/database-seed.service.ts +31 -18
  10. package/dist/boilerplates/nest-template/src/modules/user/domain/user.repository.ts +1 -0
  11. package/dist/boilerplates/nest-template/src/modules/user/domain/user.ts +1 -0
  12. package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.orm-entity.ts +3 -0
  13. package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.repository.adapter.ts +6 -0
  14. package/dist/boilerplates/nest-template/src/modules/user/infra/http/user.controller.ts +56 -0
  15. package/dist/boilerplates/nest-template/src/modules/user/service/user.service.ts +4 -0
  16. package/dist/boilerplates/nest-template/src/modules/user/user.module.ts +2 -0
  17. package/dist/index.js +24 -3
  18. package/dist/templates/angular/form.template.js +85 -9
  19. package/dist/templates/angular/list.template.js +27 -5
  20. package/dist/templates/nest/crud.templates.d.ts +16 -2
  21. package/dist/templates/nest/crud.templates.js +175 -13
  22. package/dist/templates/nest/migration.template.js +82 -6
  23. package/dist/templates/shared/relationships.js +7 -4
  24. package/dist/templates/ui/components/index.d.ts +1 -0
  25. package/dist/templates/ui/components/index.js +1 -0
  26. package/dist/templates/ui/components/input.template.js +17 -9
  27. package/dist/templates/ui/components/textarea.template.d.ts +4 -0
  28. package/dist/templates/ui/components/textarea.template.js +76 -0
  29. package/package.json +3 -3
@@ -0,0 +1,12 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { HttpClient } from '@angular/common/http';
3
+ import { BaseService } from '@devstroupe/devkit-angular';
4
+
5
+ @Injectable({
6
+ providedIn: 'root',
7
+ })
8
+ export class UserService extends BaseService<any> {
9
+ constructor(http: HttpClient) {
10
+ super(http as any, '/api/user');
11
+ }
12
+ }
@@ -1,8 +1,10 @@
1
1
  import { Module } from '@nestjs/common';
2
2
  import { TypeOrmModule } from '@nestjs/typeorm';
3
+ import { StorageModule } from '@devstroupe/devkit-nest';
3
4
  import { UserModule } from './modules/user/user.module';
4
5
  import { AuthModule } from './modules/auth/auth.module';
5
6
  import { RoleModule } from './modules/role/role.module';
7
+ import { StorageDownloadModule } from './modules/storage/storage.module';
6
8
 
7
9
  @Module({
8
10
  imports: [
@@ -16,9 +18,30 @@ import { RoleModule } from './modules/role/role.module';
16
18
  autoLoadEntities: true,
17
19
  synchronize: false,
18
20
  }),
21
+ (StorageModule as any).registerAsync({
22
+ useFactory: () => ({
23
+ driver: (process.env.STORAGE_DRIVER || 'local') as 'local' | 's3' | 'r2',
24
+ local: {
25
+ storagePath: process.env.STORAGE_LOCAL_PATH || './storage',
26
+ },
27
+ s3: {
28
+ bucket: process.env.S3_BUCKET || 'my-bucket',
29
+ region: process.env.SRegion || 'us-east-1',
30
+ accessKeyId: process.env.S3_ACCESS_KEY || '',
31
+ secretAccessKey: process.env.S3_SECRET_KEY || '',
32
+ },
33
+ r2: {
34
+ bucket: process.env.R2_BUCKET || 'my-bucket',
35
+ accountId: process.env.R2_ACCOUNT_ID || '',
36
+ accessKeyId: process.env.R2_ACCESS_KEY || '',
37
+ secretAccessKey: process.env.R2_SECRET_KEY || '',
38
+ }
39
+ })
40
+ }),
19
41
  UserModule,
20
42
  AuthModule,
21
43
  RoleModule,
44
+ StorageDownloadModule,
22
45
  ],
23
46
  controllers: [],
24
47
  providers: [],
@@ -0,0 +1,20 @@
1
+ import { MigrationInterface, QueryRunner, Table } from 'typeorm';
2
+
3
+ export class CreateRoles1600000000000 implements MigrationInterface {
4
+ name = 'CreateRoles1600000000000';
5
+
6
+ async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.createTable(new Table({
8
+ name: 'roles',
9
+ columns: [
10
+ { name: 'id', type: 'int', isPrimary: true, isGenerated: true, generationStrategy: 'increment' },
11
+ { name: 'name', type: 'varchar', length: '255', isUnique: true },
12
+ { name: 'permissions', type: 'text', isNullable: true }
13
+ ]
14
+ }), true);
15
+ }
16
+
17
+ async down(queryRunner: QueryRunner): Promise<void> {
18
+ await queryRunner.dropTable('roles', true);
19
+ }
20
+ }
@@ -1,4 +1,4 @@
1
- import { MigrationInterface, QueryRunner, Table } from 'typeorm';
1
+ import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
2
2
 
3
3
  export class CreateUsers1700000000000 implements MigrationInterface {
4
4
  name = 'CreateUsers1700000000000';
@@ -11,16 +11,27 @@ export class CreateUsers1700000000000 implements MigrationInterface {
11
11
  { name: 'name', type: 'varchar', length: '255' },
12
12
  { name: 'email', type: 'varchar', length: '255', isUnique: true },
13
13
  { name: 'password', type: 'varchar', length: '255' },
14
- { name: 'role', type: 'varchar', length: '50', default: "'USER'" },
14
+ { name: 'role_id', type: 'int' },
15
+ { name: 'current_refresh_token', type: 'varchar', length: '500', isNullable: true },
15
16
  { name: 'tenant_id', type: 'varchar', length: '255', isNullable: true },
16
17
  { name: 'is_active', type: 'boolean', default: "'1'" },
17
18
  { name: 'created_at', type: 'datetime', default: 'CURRENT_TIMESTAMP' },
18
19
  { name: 'updated_at', type: 'datetime', default: 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP' }
19
20
  ]
20
21
  }), true);
22
+
23
+ await queryRunner.createForeignKey('users', new TableForeignKey({
24
+ name: 'FK_users_role_id',
25
+ columnNames: ['role_id'],
26
+ referencedColumnNames: ['id'],
27
+ referencedTableName: 'roles',
28
+ onDelete: 'RESTRICT'
29
+ }));
21
30
  }
22
31
 
23
32
  async down(queryRunner: QueryRunner): Promise<void> {
33
+ await queryRunner.dropForeignKey('users', 'FK_users_role_id');
24
34
  await queryRunner.dropTable('users', true);
25
35
  }
26
36
  }
37
+
@@ -16,6 +16,22 @@ export class AuthController {
16
16
  return this.authService.login(body.email, body.password);
17
17
  }
18
18
 
19
+ @Post('refresh')
20
+ @ApiOperation({ summary: 'Atualiza o access token a partir do refresh token' })
21
+ @ApiResponse({ status: 200, description: 'Tokens atualizados com sucesso.' })
22
+ async refresh(@Body() body: any) {
23
+ return this.authService.refresh(body.email, body.refreshToken);
24
+ }
25
+
26
+ @Post('logout')
27
+ @UseGuards(JwtAuthGuard)
28
+ @ApiBearerAuth()
29
+ @ApiOperation({ summary: 'Efetua logout e revoga a sessão do usuário' })
30
+ @ApiResponse({ status: 200, description: 'Logout efetuado com sucesso.' })
31
+ async logout(@CurrentUser() user: any) {
32
+ return this.authService.logout(user.sub);
33
+ }
34
+
19
35
  @Post('register')
20
36
  @ApiOperation({ summary: 'Auto-cadastro de novos usuários' })
21
37
  @ApiResponse({ status: 201, description: 'Usuário cadastrado com sucesso.' })
@@ -25,7 +25,7 @@ export class AuthService {
25
25
  });
26
26
  }
27
27
 
28
- async login(email: string, pass: string): Promise<{ accessToken: string; user: Omit<User, 'password'> }> {
28
+ async login(email: string, pass: string): Promise<{ accessToken: string; refreshToken: string; user: Omit<User, 'password' | 'currentRefreshToken'> }> {
29
29
  const user = await this.userService.findByEmail(email);
30
30
  if (!user) {
31
31
  throw new UnauthorizedException('Invalid credentials');
@@ -40,6 +40,37 @@ export class AuthService {
40
40
  throw new UnauthorizedException('User is inactive');
41
41
  }
42
42
 
43
+ return this.generateTokensForUser(user);
44
+ }
45
+
46
+ async refresh(email: string, refreshToken: string): Promise<{ accessToken: string; refreshToken: string; user: Omit<User, 'password' | 'currentRefreshToken'> }> {
47
+ try {
48
+ const payload = await this.jwtService.verifyAsync(refreshToken);
49
+ if (payload.email !== email) {
50
+ throw new UnauthorizedException('Invalid refresh token');
51
+ }
52
+
53
+ const user = await this.userService.findByEmail(email);
54
+ if (!user || !user.currentRefreshToken || !user.isActive) {
55
+ throw new UnauthorizedException('Session expired or user inactive');
56
+ }
57
+
58
+ const isMatch = await bcrypt.compare(refreshToken, user.currentRefreshToken);
59
+ if (!isMatch) {
60
+ throw new UnauthorizedException('Invalid session');
61
+ }
62
+
63
+ return this.generateTokensForUser(user);
64
+ } catch (e) {
65
+ throw new UnauthorizedException('Invalid or expired refresh token');
66
+ }
67
+ }
68
+
69
+ async logout(userId: number): Promise<void> {
70
+ await this.userService.update(userId, { currentRefreshToken: undefined } as any);
71
+ }
72
+
73
+ private async generateTokensForUser(user: User) {
43
74
  const payload = {
44
75
  sub: user.id,
45
76
  email: user.email,
@@ -48,17 +79,25 @@ export class AuthService {
48
79
  permissions: user.role?.permissions || []
49
80
  };
50
81
 
51
- const { password, ...result } = user;
82
+ const accessToken = await this.jwtService.signAsync(payload, { expiresIn: '15m' });
83
+ const refreshToken = await this.jwtService.signAsync({ email: user.email }, { expiresIn: '7d' });
84
+
85
+ const salt = await bcrypt.genSalt(10);
86
+ const hashedRefreshToken = await bcrypt.hash(refreshToken, salt);
87
+ await this.userService.update(user.id, { currentRefreshToken: hashedRefreshToken } as any);
88
+
89
+ const { password, currentRefreshToken, ...result } = user;
52
90
  return {
53
- accessToken: await this.jwtService.signAsync(payload),
91
+ accessToken,
92
+ refreshToken,
54
93
  user: result,
55
94
  };
56
95
  }
57
96
 
58
- async validateUserById(id: number): Promise<Omit<User, 'password'> | null> {
97
+ async validateUserById(id: number): Promise<Omit<User, 'password' | 'currentRefreshToken'> | null> {
59
98
  const user = await this.userService.findById(id);
60
99
  if (!user) return null;
61
- const { password, ...result } = user;
100
+ const { password, currentRefreshToken, ...result } = user;
62
101
  return result;
63
102
  }
64
103
  }
@@ -0,0 +1,38 @@
1
+ import { Controller, Get, Param, Res, NotFoundException, Inject } from '@nestjs/common';
2
+ import { Response } from 'express';
3
+ import * as path from 'path';
4
+ import * as fs from 'fs';
5
+ import { STORAGE_OPTIONS_TOKEN, StorageService } from '@devstroupe/devkit-nest';
6
+
7
+ @Controller('storage')
8
+ export class StorageDownloadController {
9
+ constructor(
10
+ @Inject(STORAGE_OPTIONS_TOKEN)
11
+ private readonly storageOptions: any,
12
+ private readonly storageService: StorageService,
13
+ ) {}
14
+
15
+ @Get('download/:key(*)')
16
+ async download(@Param('key') key: string, @Res() res: Response) {
17
+ if (this.storageOptions.driver === 'local') {
18
+ const storagePath = path.resolve(this.storageOptions.local?.storagePath || './storage');
19
+ const targetFile = path.resolve(storagePath, key);
20
+ const rootPrefix = storagePath.endsWith(path.sep) ? storagePath : storagePath + path.sep;
21
+
22
+ // Mitigação Directory Traversal
23
+ if (!targetFile.startsWith(rootPrefix)) {
24
+ throw new NotFoundException('Arquivo não encontrado ou acesso inválido.');
25
+ }
26
+
27
+ if (!fs.existsSync(targetFile)) {
28
+ throw new NotFoundException('Arquivo não encontrado.');
29
+ }
30
+
31
+ return res.sendFile(targetFile);
32
+ } else {
33
+ // S3 / R2
34
+ const url = await this.storageService.getSignedUrl(key, 300);
35
+ return res.redirect(url);
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,7 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { StorageDownloadController } from './infra/http/storage-download.controller';
3
+
4
+ @Module({
5
+ controllers: [StorageDownloadController],
6
+ })
7
+ export class StorageDownloadModule {}
@@ -17,6 +17,8 @@ export class DatabaseSeedService implements OnApplicationBootstrap {
17
17
 
18
18
  private async seedUsers() {
19
19
  try {
20
+ const isProduction = process.env.NODE_ENV === 'production';
21
+
20
22
  // 1. Criar Perfil Administrador se não existir
21
23
  let adminRole = await this.roleService.findByName('Administrador');
22
24
  if (!adminRole) {
@@ -31,9 +33,9 @@ export class DatabaseSeedService implements OnApplicationBootstrap {
31
33
  });
32
34
  }
33
35
 
34
- // 2. Criar Perfil Usuário Comum se não existir
36
+ // 2. Criar Perfil Usuário Comum se não existir (apenas se não for produção)
35
37
  let userRole = await this.roleService.findByName('Usuário Comum');
36
- if (!userRole) {
38
+ if (!userRole && !isProduction) {
37
39
  this.logger.log('Semente: Criando Perfil Usuário Comum...');
38
40
  userRole = await this.roleService.create({
39
41
  name: 'Usuário Comum',
@@ -41,32 +43,43 @@ export class DatabaseSeedService implements OnApplicationBootstrap {
41
43
  });
42
44
  }
43
45
 
44
- const adminEmail = 'admin@devstroupe.com';
46
+ // 3. Admin Inicial das Envs
47
+ const adminEmail = process.env.ADMIN_EMAIL || 'admin@devstroupe.com';
48
+ const adminPassword = process.env.ADMIN_PASSWORD;
49
+
50
+ if (isProduction && !process.env.ADMIN_PASSWORD) {
51
+ this.logger.error('Semente: ERRO! A variável de ambiente ADMIN_PASSWORD precisa ser definida em produção.');
52
+ throw new Error('ADMIN_PASSWORD não configurada em produção.');
53
+ }
54
+
45
55
  const existingAdmin = await this.userService.findByEmail(adminEmail);
46
56
  if (!existingAdmin) {
47
- this.logger.log('Semente: Criando usuário administrador padrão...');
57
+ this.logger.log('Semente: Criando usuário administrador...');
48
58
  await this.userService.create({
49
- name: 'Administrador DevsTroupe',
59
+ name: 'Administrador',
50
60
  email: adminEmail,
51
- password: 'adminpassword123',
61
+ password: adminPassword || 'adminpassword123',
52
62
  role: adminRole,
53
63
  isActive: true,
54
64
  });
55
65
  this.logger.log('Semente: Usuário administrador cadastrado com sucesso!');
56
66
  }
57
67
 
58
- const userEmail = 'user@devstroupe.com';
59
- const existingUser = await this.userService.findByEmail(userEmail);
60
- if (!existingUser) {
61
- this.logger.log('Semente: Criando usuário comum padrão...');
62
- await this.userService.create({
63
- name: 'Usuário Comum',
64
- email: userEmail,
65
- password: 'userpassword123',
66
- role: userRole,
67
- isActive: true,
68
- });
69
- this.logger.log('Semente: Usuário comum cadastrado com sucesso!');
68
+ // 4. Criar Usuário Comum apenas se não for ambiente de produção
69
+ if (!isProduction) {
70
+ const userEmail = 'user@devstroupe.com';
71
+ const existingUser = await this.userService.findByEmail(userEmail);
72
+ if (!existingUser && userRole) {
73
+ this.logger.log('Semente: Criando usuário comum padrão...');
74
+ await this.userService.create({
75
+ name: 'Usuário Comum',
76
+ email: userEmail,
77
+ password: 'userpassword123',
78
+ role: userRole,
79
+ isActive: true,
80
+ });
81
+ this.logger.log('Semente: Usuário comum cadastrado com sucesso!');
82
+ }
70
83
  }
71
84
  } catch (error) {
72
85
  this.logger.error('Erro durante a execução do seeding de usuários:', error);
@@ -5,5 +5,6 @@ export interface IUserRepository {
5
5
  update(id: number, user: Partial<User>): Promise<User>;
6
6
  findById(id: number): Promise<User | null>;
7
7
  findByEmail(email: string): Promise<User | null>;
8
+ findAll(): Promise<User[]>;
8
9
  delete(id: number): Promise<void>;
9
10
  }
@@ -7,5 +7,6 @@ export class User {
7
7
  password!: string;
8
8
  role!: Role;
9
9
  tenantId?: string;
10
+ currentRefreshToken?: string;
10
11
  isActive!: boolean;
11
12
  }
@@ -22,6 +22,9 @@ export class UserOrmEntity {
22
22
  @Column({ type: 'varchar', length: 255, nullable: true, name: 'tenant_id' })
23
23
  tenantId?: string;
24
24
 
25
+ @Column({ type: 'varchar', length: 500, nullable: true, name: 'current_refresh_token' })
26
+ currentRefreshToken?: string;
27
+
25
28
  @Column({ type: 'boolean', default: true, name: 'is_active' })
26
29
  isActive!: boolean;
27
30
 
@@ -20,6 +20,7 @@ export class UserRepositoryAdapter implements IUserRepository {
20
20
  password: orm.password,
21
21
  role: orm.role,
22
22
  tenantId: orm.tenantId,
23
+ currentRefreshToken: orm.currentRefreshToken,
23
24
  isActive: orm.isActive,
24
25
  };
25
26
  }
@@ -47,6 +48,11 @@ export class UserRepositoryAdapter implements IUserRepository {
47
48
  return found ? this.toDomain(found) : null;
48
49
  }
49
50
 
51
+ async findAll(): Promise<User[]> {
52
+ const found = await this.repo.find({ relations: ['role'] });
53
+ return found.map((u) => this.toDomain(u));
54
+ }
55
+
50
56
  async delete(id: number): Promise<void> {
51
57
  await this.repo.delete(id);
52
58
  }
@@ -0,0 +1,56 @@
1
+ import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards, ParseIntPipe } from '@nestjs/common';
2
+ import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
3
+ import { UserService } from '../../service/user.service';
4
+ import { JwtAuthGuard } from '../../../auth/jwt-auth.guard';
5
+ import { Permissions, PermissionsGuard } from '@devstroupe/devkit-nest';
6
+ import { User } from '../../domain/user';
7
+
8
+ @ApiTags('users')
9
+ @Controller('user')
10
+ @UseGuards(JwtAuthGuard, PermissionsGuard)
11
+ @ApiBearerAuth()
12
+ export class UserController {
13
+ constructor(private readonly userService: UserService) {}
14
+
15
+ @Get()
16
+ @Permissions('read:user')
17
+ @ApiOperation({ summary: 'Listar todos os usuários (Admin)' })
18
+ @ApiResponse({ status: 200, description: 'Usuários listados com sucesso.' })
19
+ async findAll() {
20
+ // Retorna todos os usuários (como é para administração)
21
+ // Nota: O ideal em produção seria paginar, mas para o CRUD administrativo básico serve
22
+ // Vamos chamar o repositório ou expor um método de listagem.
23
+ // Como o UserService atual não possui findAll, vamos buscar direto
24
+ // do repositório ou implementar no UserService.
25
+ return this.userService.findAll();
26
+ }
27
+
28
+ @Get(':id')
29
+ @Permissions('read:user')
30
+ @ApiOperation({ summary: 'Obter um usuário pelo ID' })
31
+ async findOne(@Param('id', ParseIntPipe) id: number) {
32
+ return this.userService.findById(id);
33
+ }
34
+
35
+ @Post()
36
+ @Permissions('create:user')
37
+ @ApiOperation({ summary: 'Criar um novo usuário' })
38
+ async create(@Body() body: Omit<User, 'id'>) {
39
+ return this.userService.create(body);
40
+ }
41
+
42
+ @Put(':id')
43
+ @Permissions('update:user')
44
+ @ApiOperation({ summary: 'Atualizar dados de um usuário' })
45
+ async update(@Param('id', ParseIntPipe) id: number, @Body() body: Partial<User>) {
46
+ return this.userService.update(id, body);
47
+ }
48
+
49
+ @Delete(':id')
50
+ @Permissions('delete:user')
51
+ @ApiOperation({ summary: 'Excluir um usuário' })
52
+ async delete(@Param('id', ParseIntPipe) id: number) {
53
+ await this.userService.delete(id);
54
+ return { success: true };
55
+ }
56
+ }
@@ -38,6 +38,10 @@ export class UserService {
38
38
  return this.userRepo.update(id, user);
39
39
  }
40
40
 
41
+ async findAll(): Promise<User[]> {
42
+ return this.userRepo.findAll();
43
+ }
44
+
41
45
  async delete(id: number): Promise<void> {
42
46
  await this.userRepo.delete(id);
43
47
  }
@@ -5,12 +5,14 @@ import { UserRepositoryAdapter } from './infra/database/user.repository.adapter'
5
5
  import { UserService } from './service/user.service';
6
6
  import { DatabaseSeedService } from './database-seed.service';
7
7
  import { RoleModule } from '../role/role.module';
8
+ import { UserController } from './infra/http/user.controller';
8
9
 
9
10
  @Module({
10
11
  imports: [
11
12
  TypeOrmModule.forFeature([UserOrmEntity]),
12
13
  RoleModule,
13
14
  ],
15
+ controllers: [UserController],
14
16
  providers: [
15
17
  UserService,
16
18
  DatabaseSeedService,
package/dist/index.js CHANGED
@@ -450,6 +450,7 @@ function initUi(frontendRoot, config) {
450
450
  fs.ensureDirSync(path.join(targetDir, 'card-list'));
451
451
  fs.ensureDirSync(path.join(targetDir, 'simple-list'));
452
452
  fs.ensureDirSync(path.join(targetDir, 'dialog'));
453
+ fs.ensureDirSync(path.join(targetDir, 'textarea'));
453
454
  fs.ensureDirSync(path.join(targetDir, 'shell'));
454
455
  fs.ensureDirSync(path.join(targetDir, 'sidebar'));
455
456
  fs.ensureDirSync(path.join(targetDir, 'header'));
@@ -468,6 +469,10 @@ function initUi(frontendRoot, config) {
468
469
  const inp = (0, ui_templates_1.devkitInputTemplate)();
469
470
  fs.writeFileSync(path.join(targetDir, 'input', 'input.component.ts'), inp.ts, 'utf-8');
470
471
  fs.writeFileSync(path.join(targetDir, 'input', 'input.component.html'), inp.html, 'utf-8');
472
+ // 3.5. Escrever Textarea Component
473
+ const txt = (0, ui_templates_1.devkitTextareaTemplate)();
474
+ fs.writeFileSync(path.join(targetDir, 'textarea', 'textarea.component.ts'), txt.ts, 'utf-8');
475
+ fs.writeFileSync(path.join(targetDir, 'textarea', 'textarea.component.html'), txt.html, 'utf-8');
471
476
  // 4. Escrever Select Component
472
477
  const sel = (0, ui_templates_1.devkitSelectTemplate)();
473
478
  fs.writeFileSync(path.join(targetDir, 'select', 'select.component.ts'), sel.ts, 'utf-8');
@@ -1039,7 +1044,7 @@ SERVICE_PORT=${port}
1039
1044
  // Controller (TCP)
1040
1045
  fs.writeFileSync(path.join(modulePath, 'infra', 'http', `${entity.name}.ms.controller.ts`), (0, cli_templates_1.nestMsControllerTemplate)(entity.name), 'utf-8');
1041
1046
  // ORM Entity
1042
- fs.writeFileSync(path.join(modulePath, 'infra', 'database', `${entity.name}.orm-entity.ts`), (0, cli_templates_1.nestOrmEntityTemplate)(entity.name, backendProperties, entity.tableName || `${entity.name}s`, isTenantScoped), 'utf-8');
1047
+ fs.writeFileSync(path.join(modulePath, 'infra', 'database', `${entity.name}.orm-entity.ts`), (0, cli_templates_1.nestOrmEntityTemplate)(entity.name, backendProperties, entity.tableName || `${entity.name}s`, isTenantScoped, entity.indexes, entity.persistence, entity.automaticFields), 'utf-8');
1043
1048
  // Repository Adapter
1044
1049
  fs.writeFileSync(path.join(modulePath, 'infra', 'database', `${entity.name}.repository.ts`), (0, cli_templates_1.nestRepositoryAdapterTemplate)(entity.name, backendProperties, isTenantScoped), 'utf-8');
1045
1050
  // Service
@@ -1079,9 +1084,9 @@ SERVICE_PORT=${port}
1079
1084
  // DTO
1080
1085
  fs.writeFileSync(path.join(modulePath, 'infra', 'http', `${entity.name}.dto.ts`), (0, cli_templates_1.nestDtoTemplate)(entity.name, entity.properties), 'utf-8');
1081
1086
  // Controller
1082
- fs.writeFileSync(path.join(modulePath, 'infra', 'http', `${entity.name}.controller.ts`), (0, cli_templates_1.nestControllerTemplate)(entity.name), 'utf-8');
1087
+ fs.writeFileSync(path.join(modulePath, 'infra', 'http', `${entity.name}.controller.ts`), (0, cli_templates_1.nestControllerTemplate)(entity.name, entity.persistence), 'utf-8');
1083
1088
  // ORM Entity
1084
- fs.writeFileSync(path.join(modulePath, 'infra', 'database', `${entity.name}.orm-entity.ts`), (0, cli_templates_1.nestOrmEntityTemplate)(entity.name, entity.properties, entity.tableName || `${entity.name}s`, isTenantScoped), 'utf-8');
1089
+ fs.writeFileSync(path.join(modulePath, 'infra', 'database', `${entity.name}.orm-entity.ts`), (0, cli_templates_1.nestOrmEntityTemplate)(entity.name, entity.properties, entity.tableName || `${entity.name}s`, isTenantScoped, entity.indexes, entity.persistence, entity.automaticFields), 'utf-8');
1085
1090
  // Repository Adapter
1086
1091
  fs.writeFileSync(path.join(modulePath, 'infra', 'database', `${entity.name}.repository.ts`), (0, cli_templates_1.nestRepositoryAdapterTemplate)(entity.name, entity.properties, isTenantScoped), 'utf-8');
1087
1092
  // Service
@@ -1681,6 +1686,22 @@ DB_PORT=3306
1681
1686
  DB_USER=root
1682
1687
  DB_PASSWORD=root
1683
1688
  DB_NAME=devstroupe
1689
+
1690
+ # Configuração de Storage Nátivo
1691
+ STORAGE_DRIVER=local
1692
+ STORAGE_LOCAL_PATH=./storage
1693
+
1694
+ # Configuração AWS S3 (Opcional se STORAGE_DRIVER=s3)
1695
+ S3_BUCKET=my-bucket
1696
+ SRegion=us-east-1
1697
+ S3_ACCESS_KEY=
1698
+ S3_SECRET_KEY=
1699
+
1700
+ # Configuração Cloudflare R2 (Opcional se STORAGE_DRIVER=r2)
1701
+ R2_BUCKET=my-bucket
1702
+ R2_ACCOUNT_ID=
1703
+ R2_ACCESS_KEY=
1704
+ R2_SECRET_KEY=
1684
1705
  `;
1685
1706
  fs.writeFileSync(path.join(backendDest, '.env'), envContent, 'utf-8');
1686
1707
  fs.writeFileSync(path.join(backendDest, '.env.example'), envContent, 'utf-8');