@devstroupe/devkit-cli 1.0.0 → 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.
- package/dist/boilerplates/angular-template/node_modules/.bin/acorn +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/eslint +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/js-yaml +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/ng +2 -2
- package/dist/boilerplates/angular-template/node_modules/.bin/nx +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/nx-cloud +21 -0
- package/dist/boilerplates/angular-template/node_modules/.bin/sass +4 -4
- package/dist/boilerplates/angular-template/node_modules/.bin/vite +4 -4
- package/dist/boilerplates/angular-template/node_modules/.bin/webpack +21 -0
- package/dist/boilerplates/angular-template/package.json +1 -1
- package/dist/boilerplates/angular-template/src/app/app.config.ts +3 -0
- package/dist/boilerplates/angular-template/src/app/app.routes.ts +7 -0
- package/dist/boilerplates/angular-template/src/app/modules/role/role-permissions.component.css +38 -0
- package/dist/boilerplates/angular-template/src/app/modules/role/role-permissions.component.html +175 -0
- package/dist/boilerplates/angular-template/src/app/modules/role/role-permissions.component.ts +134 -0
- package/dist/boilerplates/angular-template/src/app/services/role.service.ts +12 -0
- package/dist/boilerplates/angular-template/src/app/services/user.service.ts +12 -0
- package/dist/boilerplates/angular-template/src/app/shared/components/unauthorized/unauthorized.component.css +4 -0
- package/dist/boilerplates/angular-template/src/app/shared/components/unauthorized/unauthorized.component.html +18 -0
- package/dist/boilerplates/angular-template/src/app/shared/components/unauthorized/unauthorized.component.ts +18 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-cwd +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-esm +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-script +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-transpile-only +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-script +21 -0
- package/dist/boilerplates/nest-template/node_modules/.bin/typeorm +2 -2
- package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-commonjs +2 -2
- package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-esm +2 -2
- package/dist/boilerplates/nest-template/package.json +1 -1
- package/dist/boilerplates/nest-template/src/app.module.ts +25 -0
- package/dist/boilerplates/nest-template/src/database/migrations/1600000000000-create-roles.ts +20 -0
- package/dist/boilerplates/nest-template/src/database/migrations/1700000000000-create-users.ts +13 -2
- package/dist/boilerplates/nest-template/src/modules/auth/auth.controller.ts +16 -0
- package/dist/boilerplates/nest-template/src/modules/auth/auth.module.ts +2 -0
- package/dist/boilerplates/nest-template/src/modules/auth/auth.service.ts +54 -8
- package/dist/boilerplates/nest-template/src/modules/role/domain/role.repository.ts +10 -0
- package/dist/boilerplates/nest-template/src/modules/role/domain/role.ts +5 -0
- package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.orm-entity.ts +13 -0
- package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.repository.adapter.ts +54 -0
- package/dist/boilerplates/nest-template/src/modules/role/infra/http/role.controller.ts +51 -0
- package/dist/boilerplates/nest-template/src/modules/role/infra/http/role.dto.ts +17 -0
- package/dist/boilerplates/nest-template/src/modules/role/role.module.ts +20 -0
- package/dist/boilerplates/nest-template/src/modules/role/service/role.service.ts +39 -0
- package/dist/boilerplates/nest-template/src/modules/storage/infra/http/storage-download.controller.ts +38 -0
- package/dist/boilerplates/nest-template/src/modules/storage/storage.module.ts +7 -0
- package/dist/boilerplates/nest-template/src/modules/user/database-seed.service.ts +59 -18
- package/dist/boilerplates/nest-template/src/modules/user/domain/user.repository.ts +1 -0
- package/dist/boilerplates/nest-template/src/modules/user/domain/user.ts +4 -1
- package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.orm-entity.ts +8 -3
- package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.repository.adapter.ts +6 -0
- package/dist/boilerplates/nest-template/src/modules/user/infra/http/user.controller.ts +56 -0
- package/dist/boilerplates/nest-template/src/modules/user/service/user.service.ts +4 -0
- package/dist/boilerplates/nest-template/src/modules/user/user.module.ts +7 -1
- package/dist/index.js +24 -3
- package/dist/templates/angular/form.template.js +85 -9
- package/dist/templates/angular/list.template.js +27 -5
- package/dist/templates/nest/crud.templates.d.ts +16 -2
- package/dist/templates/nest/crud.templates.js +175 -13
- package/dist/templates/nest/migration.template.js +82 -6
- package/dist/templates/shared/relationships.js +7 -4
- package/dist/templates/ui/components/index.d.ts +1 -0
- package/dist/templates/ui/components/index.js +1 -0
- package/dist/templates/ui/components/input.template.js +17 -9
- package/dist/templates/ui/components/textarea.template.d.ts +4 -0
- package/dist/templates/ui/components/textarea.template.js +76 -0
- package/dist/templates/ui/shared/profile-component.template.js +3 -3
- package/package.json +3 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
3
|
+
import { RoleOrmEntity } from './infra/database/role.orm-entity';
|
|
4
|
+
import { RoleController } from './infra/http/role.controller';
|
|
5
|
+
import { RoleService } from './service/role.service';
|
|
6
|
+
import { RoleRepositoryAdapter } from './infra/database/role.repository.adapter';
|
|
7
|
+
|
|
8
|
+
@Module({
|
|
9
|
+
imports: [TypeOrmModule.forFeature([RoleOrmEntity])],
|
|
10
|
+
controllers: [RoleController],
|
|
11
|
+
providers: [
|
|
12
|
+
RoleService,
|
|
13
|
+
{
|
|
14
|
+
provide: 'IRoleRepository',
|
|
15
|
+
useClass: RoleRepositoryAdapter,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
exports: [RoleService],
|
|
19
|
+
})
|
|
20
|
+
export class RoleModule {}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
2
|
+
import { IRoleRepository } from '../domain/role.repository';
|
|
3
|
+
import { Role } from '../domain/role';
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class RoleService {
|
|
7
|
+
constructor(
|
|
8
|
+
@Inject('IRoleRepository')
|
|
9
|
+
private readonly roleRepo: IRoleRepository,
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
async create(role: Omit<Role, 'id'>): Promise<Role> {
|
|
13
|
+
const existing = await this.roleRepo.findByName(role.name);
|
|
14
|
+
if (existing) {
|
|
15
|
+
throw new Error('Role name already exists');
|
|
16
|
+
}
|
|
17
|
+
return this.roleRepo.create(role);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async update(id: number, role: Partial<Role>): Promise<Role> {
|
|
21
|
+
return this.roleRepo.update(id, role);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async findById(id: number): Promise<Role | null> {
|
|
25
|
+
return this.roleRepo.findById(id);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async findByName(name: string): Promise<Role | null> {
|
|
29
|
+
return this.roleRepo.findByName(name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async findAll(): Promise<Role[]> {
|
|
33
|
+
return this.roleRepo.findAll();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async delete(id: number): Promise<void> {
|
|
37
|
+
await this.roleRepo.delete(id);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { Injectable, OnApplicationBootstrap, Logger } from '@nestjs/common';
|
|
2
2
|
import { UserService } from './service/user.service';
|
|
3
|
+
import { RoleService } from '../role/service/role.service';
|
|
3
4
|
|
|
4
5
|
@Injectable()
|
|
5
6
|
export class DatabaseSeedService implements OnApplicationBootstrap {
|
|
6
7
|
private readonly logger = new Logger(DatabaseSeedService.name);
|
|
7
8
|
|
|
8
|
-
constructor(
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly userService: UserService,
|
|
11
|
+
private readonly roleService: RoleService,
|
|
12
|
+
) {}
|
|
9
13
|
|
|
10
14
|
async onApplicationBootstrap() {
|
|
11
15
|
await this.seedUsers();
|
|
@@ -13,32 +17,69 @@ export class DatabaseSeedService implements OnApplicationBootstrap {
|
|
|
13
17
|
|
|
14
18
|
private async seedUsers() {
|
|
15
19
|
try {
|
|
16
|
-
const
|
|
20
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
21
|
+
|
|
22
|
+
// 1. Criar Perfil Administrador se não existir
|
|
23
|
+
let adminRole = await this.roleService.findByName('Administrador');
|
|
24
|
+
if (!adminRole) {
|
|
25
|
+
this.logger.log('Semente: Criando Perfil Administrador...');
|
|
26
|
+
adminRole = await this.roleService.create({
|
|
27
|
+
name: 'Administrador',
|
|
28
|
+
permissions: [
|
|
29
|
+
'read:cabin', 'create:cabin', 'update:cabin', 'delete:cabin',
|
|
30
|
+
'read:company', 'create:company', 'update:company', 'delete:company',
|
|
31
|
+
'read:role', 'create:role', 'update:role', 'delete:role', 'manage:permissions'
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. Criar Perfil Usuário Comum se não existir (apenas se não for produção)
|
|
37
|
+
let userRole = await this.roleService.findByName('Usuário Comum');
|
|
38
|
+
if (!userRole && !isProduction) {
|
|
39
|
+
this.logger.log('Semente: Criando Perfil Usuário Comum...');
|
|
40
|
+
userRole = await this.roleService.create({
|
|
41
|
+
name: 'Usuário Comum',
|
|
42
|
+
permissions: ['read:cabin', 'read:company'],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
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
|
+
|
|
17
55
|
const existingAdmin = await this.userService.findByEmail(adminEmail);
|
|
18
56
|
if (!existingAdmin) {
|
|
19
|
-
this.logger.log('Semente: Criando usuário administrador
|
|
57
|
+
this.logger.log('Semente: Criando usuário administrador...');
|
|
20
58
|
await this.userService.create({
|
|
21
|
-
name: 'Administrador
|
|
59
|
+
name: 'Administrador',
|
|
22
60
|
email: adminEmail,
|
|
23
|
-
password: 'adminpassword123',
|
|
24
|
-
role:
|
|
61
|
+
password: adminPassword || 'adminpassword123',
|
|
62
|
+
role: adminRole,
|
|
25
63
|
isActive: true,
|
|
26
64
|
});
|
|
27
65
|
this.logger.log('Semente: Usuário administrador cadastrado com sucesso!');
|
|
28
66
|
}
|
|
29
67
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
}
|
|
42
83
|
}
|
|
43
84
|
} catch (error) {
|
|
44
85
|
this.logger.error('Erro durante a execução do seeding de usuários:', error);
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { Role } from '../../role/domain/role';
|
|
2
|
+
|
|
1
3
|
export class User {
|
|
2
4
|
id!: number;
|
|
3
5
|
name!: string;
|
|
4
6
|
email!: string;
|
|
5
7
|
password!: string;
|
|
6
|
-
role!:
|
|
8
|
+
role!: Role;
|
|
7
9
|
tenantId?: string;
|
|
10
|
+
currentRefreshToken?: string;
|
|
8
11
|
isActive!: boolean;
|
|
9
12
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
|
1
|
+
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
|
|
2
|
+
import { RoleOrmEntity } from '../../../role/infra/database/role.orm-entity';
|
|
2
3
|
|
|
3
4
|
@Entity('users')
|
|
4
5
|
export class UserOrmEntity {
|
|
@@ -14,12 +15,16 @@ export class UserOrmEntity {
|
|
|
14
15
|
@Column({ type: 'varchar', length: 255 })
|
|
15
16
|
password!: string;
|
|
16
17
|
|
|
17
|
-
@
|
|
18
|
-
|
|
18
|
+
@ManyToOne(() => RoleOrmEntity, { nullable: false, eager: true })
|
|
19
|
+
@JoinColumn({ name: 'role_id' })
|
|
20
|
+
role!: RoleOrmEntity;
|
|
19
21
|
|
|
20
22
|
@Column({ type: 'varchar', length: 255, nullable: true, name: 'tenant_id' })
|
|
21
23
|
tenantId?: string;
|
|
22
24
|
|
|
25
|
+
@Column({ type: 'varchar', length: 500, nullable: true, name: 'current_refresh_token' })
|
|
26
|
+
currentRefreshToken?: string;
|
|
27
|
+
|
|
23
28
|
@Column({ type: 'boolean', default: true, name: 'is_active' })
|
|
24
29
|
isActive!: boolean;
|
|
25
30
|
|
package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.repository.adapter.ts
CHANGED
|
@@ -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
|
+
}
|
|
@@ -4,9 +4,15 @@ import { UserOrmEntity } from './infra/database/user.orm-entity';
|
|
|
4
4
|
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
|
+
import { RoleModule } from '../role/role.module';
|
|
8
|
+
import { UserController } from './infra/http/user.controller';
|
|
7
9
|
|
|
8
10
|
@Module({
|
|
9
|
-
imports: [
|
|
11
|
+
imports: [
|
|
12
|
+
TypeOrmModule.forFeature([UserOrmEntity]),
|
|
13
|
+
RoleModule,
|
|
14
|
+
],
|
|
15
|
+
controllers: [UserController],
|
|
10
16
|
providers: [
|
|
11
17
|
UserService,
|
|
12
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');
|
|
@@ -38,13 +38,14 @@ function angularFormHtmlTemplate(name, properties, entities = []) {
|
|
|
38
38
|
.filter((property) => property.type !== 'relationship' || (0, relationships_1.getRelationships)([property])[0].kind !== 'one-to-many')
|
|
39
39
|
.map((property) => {
|
|
40
40
|
const controlName = formControlName(property);
|
|
41
|
-
|
|
41
|
+
const type = property.type;
|
|
42
|
+
if (type === 'boolean') {
|
|
42
43
|
return ` <div *ngIf="!isFieldHidden('${controlName}')" class="flex items-center space-x-2 pt-2">
|
|
43
44
|
<input type="checkbox" formControlName="${controlName}" id="${controlName}" class="h-4 w-4 rounded border-input bg-background text-primary focus:ring-ring">
|
|
44
45
|
<label for="${controlName}" class="text-sm font-medium text-foreground">${property.label}</label>
|
|
45
46
|
</div>`;
|
|
46
47
|
}
|
|
47
|
-
if (
|
|
48
|
+
if (type === 'relationship') {
|
|
48
49
|
const relationship = (0, relationships_1.getRelationships)([property])[0];
|
|
49
50
|
const stateName = relationStateName(relationship);
|
|
50
51
|
return ` <devkit-select
|
|
@@ -62,15 +63,81 @@ function angularFormHtmlTemplate(name, properties, entities = []) {
|
|
|
62
63
|
[required]="${Boolean(property.required)}">
|
|
63
64
|
</devkit-select>`;
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
if (type === 'text' || type === 'json') {
|
|
67
|
+
return ` <devkit-textarea
|
|
67
68
|
*ngIf="!isFieldHidden('${controlName}')"
|
|
68
|
-
${
|
|
69
|
+
formControlName="${controlName}"
|
|
70
|
+
label="${property.label}"
|
|
71
|
+
placeholder="Digite ${property.label.toLowerCase()}"
|
|
72
|
+
[required]="${Boolean(property.required)}">
|
|
73
|
+
</devkit-textarea>`;
|
|
74
|
+
}
|
|
75
|
+
if (type === 'enum') {
|
|
76
|
+
return ` <devkit-select
|
|
77
|
+
*ngIf="!isFieldHidden('${controlName}')"
|
|
78
|
+
formControlName="${controlName}"
|
|
79
|
+
label="${property.label}"
|
|
80
|
+
placeholder="Selecione ${property.label.toLowerCase()}"
|
|
81
|
+
[options]="${controlName}Options"
|
|
82
|
+
[required]="${Boolean(property.required)}">
|
|
83
|
+
</devkit-select>`;
|
|
84
|
+
}
|
|
85
|
+
if (type === 'string' ||
|
|
86
|
+
type === 'number' ||
|
|
87
|
+
type === 'decimal' ||
|
|
88
|
+
type === 'currency' ||
|
|
89
|
+
type === 'integer' ||
|
|
90
|
+
type === 'bigint' ||
|
|
91
|
+
type === 'email' ||
|
|
92
|
+
type === 'phone' ||
|
|
93
|
+
type === 'document' ||
|
|
94
|
+
type === 'color' ||
|
|
95
|
+
type === 'datepicker' ||
|
|
96
|
+
type === 'date' ||
|
|
97
|
+
type === 'datetime' ||
|
|
98
|
+
type === 'file') {
|
|
99
|
+
let typeAttr = 'type="text"';
|
|
100
|
+
let stepAttr = '';
|
|
101
|
+
let prefixAttr = '';
|
|
102
|
+
if (type === 'number' || type === 'integer') {
|
|
103
|
+
typeAttr = 'type="number"';
|
|
104
|
+
}
|
|
105
|
+
else if (type === 'decimal') {
|
|
106
|
+
typeAttr = 'type="number"';
|
|
107
|
+
stepAttr = 'step="any"';
|
|
108
|
+
}
|
|
109
|
+
else if (type === 'currency') {
|
|
110
|
+
typeAttr = 'type="number"';
|
|
111
|
+
stepAttr = 'step="0.01"';
|
|
112
|
+
prefixAttr = 'prefix="R$"';
|
|
113
|
+
}
|
|
114
|
+
else if (type === 'email') {
|
|
115
|
+
typeAttr = 'type="email"';
|
|
116
|
+
}
|
|
117
|
+
else if (type === 'color') {
|
|
118
|
+
typeAttr = 'type="color"';
|
|
119
|
+
}
|
|
120
|
+
else if (type === 'date' || type === 'datepicker') {
|
|
121
|
+
typeAttr = 'type="date"';
|
|
122
|
+
}
|
|
123
|
+
else if (type === 'datetime') {
|
|
124
|
+
typeAttr = 'type="datetime-local"';
|
|
125
|
+
}
|
|
126
|
+
else if (type === 'file') {
|
|
127
|
+
typeAttr = 'type="file"';
|
|
128
|
+
}
|
|
129
|
+
return ` <devkit-input
|
|
130
|
+
*ngIf="!isFieldHidden('${controlName}')"
|
|
131
|
+
${typeAttr}
|
|
132
|
+
${stepAttr}
|
|
133
|
+
${prefixAttr}
|
|
69
134
|
formControlName="${controlName}"
|
|
70
135
|
label="${property.label}"
|
|
71
136
|
placeholder="Digite ${property.label.toLowerCase()}"
|
|
72
137
|
[required]="${Boolean(property.required)}">
|
|
73
138
|
</devkit-input>`;
|
|
139
|
+
}
|
|
140
|
+
return '';
|
|
74
141
|
})
|
|
75
142
|
.join('\n\n');
|
|
76
143
|
const dependentSections = dependentRelationships.map((relationship) => {
|
|
@@ -155,8 +222,9 @@ function angularFormTsTemplate(name, properties, entities = []) {
|
|
|
155
222
|
const relationships = (0, relationships_1.getRelationships)(properties);
|
|
156
223
|
const optionRelationships = relationships.filter((relationship) => relationship.kind !== 'one-to-many');
|
|
157
224
|
const dependents = relationships.filter((relationship) => relationship.kind === 'one-to-many' && relationship.embedded);
|
|
158
|
-
const usesInput = properties.some((
|
|
159
|
-
const
|
|
225
|
+
const usesInput = properties.some((p) => p.type === 'string' || p.type === 'number' || p.type === 'decimal' || p.type === 'currency' || p.type === 'datepicker');
|
|
226
|
+
const usesTextarea = properties.some((property) => property.type === 'text');
|
|
227
|
+
const usesSelect = optionRelationships.length > 0 || properties.some((property) => property.type === 'enum');
|
|
160
228
|
const formGroupProps = properties
|
|
161
229
|
.filter((property) => property.type !== 'relationship' || (0, relationships_1.getRelationships)([property])[0].kind !== 'one-to-many')
|
|
162
230
|
.map((property) => {
|
|
@@ -198,6 +266,13 @@ function angularFormTsTemplate(name, properties, entities = []) {
|
|
|
198
266
|
minSearchLength: ${relationship.minSearchLength}
|
|
199
267
|
});`;
|
|
200
268
|
}).join('\n');
|
|
269
|
+
const enumOptionsState = properties
|
|
270
|
+
.filter((p) => p.type === 'enum' && p.enumOptions)
|
|
271
|
+
.map((p) => {
|
|
272
|
+
const options = p.enumOptions.map(o => ({ label: o, value: o }));
|
|
273
|
+
return ` readonly ${p.name}Options = ${JSON.stringify(options)};`;
|
|
274
|
+
})
|
|
275
|
+
.join('\n');
|
|
201
276
|
const dependentState = dependents.map((relationship) => {
|
|
202
277
|
const stateName = relationStateName(relationship);
|
|
203
278
|
const columns = relationshipColumns(relationship, entities);
|
|
@@ -315,6 +390,7 @@ import { CommonModule } from '@angular/common';
|
|
|
315
390
|
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
316
391
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
317
392
|
${usesInput ? "import { DevkitInputComponent } from '@devstroupe/ui/input/input.component';" : ''}
|
|
393
|
+
${usesTextarea ? "import { DevkitTextareaComponent } from '@devstroupe/ui/textarea/textarea.component';" : ''}
|
|
318
394
|
${usesSelect ? "import { DevkitSelectComponent } from '@devstroupe/ui/select/select.component';" : ''}
|
|
319
395
|
import { DevkitButtonComponent } from '@devstroupe/ui/button/button.component';
|
|
320
396
|
import { HlmCardImports } from '@spartan-ng/helm/card';
|
|
@@ -328,7 +404,7 @@ import { ${pascalName}Service } from '../../../services/${name}.service';
|
|
|
328
404
|
@Component({
|
|
329
405
|
selector: 'app-${name}-form',
|
|
330
406
|
standalone: true,
|
|
331
|
-
imports: [CommonModule, ReactiveFormsModule${usesInput ? ', DevkitInputComponent' : ''}${usesSelect ? ', DevkitSelectComponent' : ''}, DevkitButtonComponent, HlmCardImports, HlmSkeletonImports${componentImports.map((item) => `, ${item}`).join('')}],
|
|
407
|
+
imports: [CommonModule, ReactiveFormsModule${usesInput ? ', DevkitInputComponent' : ''}${usesTextarea ? ', DevkitTextareaComponent' : ''}${usesSelect ? ', DevkitSelectComponent' : ''}, DevkitButtonComponent, HlmCardImports, HlmSkeletonImports${componentImports.map((item) => `, ${item}`).join('')}],
|
|
332
408
|
templateUrl: './${name}-form.component.html'
|
|
333
409
|
})
|
|
334
410
|
export class ${pascalName}FormComponent implements OnInit${optionRelationships.length > 0 ? ', OnDestroy' : ''} {
|
|
@@ -351,7 +427,7 @@ ${relationServiceInjections}
|
|
|
351
427
|
isEdit = false;
|
|
352
428
|
isFetching = false;
|
|
353
429
|
loading = false;
|
|
354
|
-
${optionState}
|
|
430
|
+
${optionState}${enumOptionsState ? '\n' + enumOptionsState : ''}
|
|
355
431
|
${dependentState}
|
|
356
432
|
|
|
357
433
|
ngOnInit(): void {
|