@devstroupe/devkit-cli 1.1.1 → 1.1.3
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/proxy.conf.json +1 -1
- package/dist/boilerplates/angular-template/src/app/core/services/auth.service.ts +19 -1
- package/dist/boilerplates/angular-template/src/app/modules/role/role-permissions.component.ts +7 -0
- package/dist/boilerplates/nest-template/package.json +6 -1
- package/dist/boilerplates/nest-template/src/app.module.ts +3 -0
- package/dist/boilerplates/nest-template/src/modules/role/domain/role.repository.ts +2 -0
- package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.repository.adapter.ts +19 -2
- package/dist/boilerplates/nest-template/src/modules/role/service/role.service.ts +5 -0
- package/dist/boilerplates/nest-template/src/modules/storage/infra/http/storage-download.controller.ts +1 -1
- package/dist/boilerplates/nest-template/src/modules/user/database-seed.service.ts +17 -5
- package/dist/boilerplates/nest-template/src/modules/user/domain/user.repository.ts +2 -0
- package/dist/boilerplates/nest-template/src/modules/user/domain/user.ts +2 -0
- package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.repository.adapter.ts +24 -2
- package/dist/boilerplates/nest-template/src/modules/user/infra/http/user.controller.ts +17 -21
- package/dist/boilerplates/nest-template/src/modules/user/service/user.service.ts +9 -0
- package/dist/index.js +194 -24
- package/dist/rules/linter-rules.js +1 -1
- package/dist/templates/nest/crud.templates.d.ts +5 -1
- package/dist/templates/nest/crud.templates.js +36 -15
- package/dist/templates/ui/layout/sidebar.template.js +47 -3
- package/package.json +2 -2
|
@@ -6,7 +6,7 @@ export interface User {
|
|
|
6
6
|
id: number;
|
|
7
7
|
name: string;
|
|
8
8
|
email: string;
|
|
9
|
-
role:
|
|
9
|
+
role: any;
|
|
10
10
|
avatar?: string;
|
|
11
11
|
tenantId?: string;
|
|
12
12
|
isActive: boolean;
|
|
@@ -95,4 +95,22 @@ export class AuthService {
|
|
|
95
95
|
get isAuthenticated(): boolean {
|
|
96
96
|
return !!this.token;
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
hasPermission(permission: string): boolean {
|
|
100
|
+
const user = this.currentUser;
|
|
101
|
+
if (!user) return false;
|
|
102
|
+
|
|
103
|
+
const roleName = typeof user.role === 'object' ? (user.role as any).name : user.role;
|
|
104
|
+
const permissions = typeof user.role === 'object' ? (user.role as any).permissions : [];
|
|
105
|
+
|
|
106
|
+
if (
|
|
107
|
+
roleName === 'ADMIN' ||
|
|
108
|
+
roleName === 'Administrador' ||
|
|
109
|
+
(Array.isArray(permissions) && permissions.includes('*'))
|
|
110
|
+
) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return Array.isArray(permissions) && permissions.includes(permission);
|
|
115
|
+
}
|
|
98
116
|
}
|
package/dist/boilerplates/angular-template/src/app/modules/role/role-permissions.component.ts
CHANGED
|
@@ -81,6 +81,7 @@ export class RolePermissionsComponent implements OnInit {
|
|
|
81
81
|
|
|
82
82
|
hasPermission(action: string, resource: string): boolean {
|
|
83
83
|
if (!this.selectedRole) return false;
|
|
84
|
+
if (this.selectedRole.permissions.includes('*')) return true;
|
|
84
85
|
const permission = `${action}:${resource}`;
|
|
85
86
|
return this.selectedRole.permissions.includes(permission);
|
|
86
87
|
}
|
|
@@ -98,6 +99,7 @@ export class RolePermissionsComponent implements OnInit {
|
|
|
98
99
|
|
|
99
100
|
hasSpecialPermission(permission: string): boolean {
|
|
100
101
|
if (!this.selectedRole) return false;
|
|
102
|
+
if (this.selectedRole.permissions.includes('*')) return true;
|
|
101
103
|
return this.selectedRole.permissions.includes(permission);
|
|
102
104
|
}
|
|
103
105
|
|
|
@@ -118,6 +120,11 @@ export class RolePermissionsComponent implements OnInit {
|
|
|
118
120
|
this.successMessage = '';
|
|
119
121
|
this.errorMessage = '';
|
|
120
122
|
|
|
123
|
+
// Se for o perfil do Administrador, garantimos que a permissão curinga '*' continue salva
|
|
124
|
+
if (this.selectedRole.name === 'Administrador' && !this.selectedRole.permissions.includes('*')) {
|
|
125
|
+
this.selectedRole.permissions.push('*');
|
|
126
|
+
}
|
|
127
|
+
|
|
121
128
|
this.roleService.update(this.selectedRole.id, this.selectedRole).subscribe({
|
|
122
129
|
next: (updated: any) => {
|
|
123
130
|
this.isSaving = false;
|
|
@@ -29,7 +29,12 @@
|
|
|
29
29
|
"bcryptjs": "^2.4.3",
|
|
30
30
|
"dotenv": "^16.4.5",
|
|
31
31
|
"@devstroupe/devkit-core": "workspace:*",
|
|
32
|
-
"@devstroupe/devkit-nest": "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"
|
|
33
38
|
},
|
|
34
39
|
"devDependencies": {
|
|
35
40
|
"@nestjs/cli": "^11.0.0",
|
|
@@ -5,6 +5,7 @@ import { UserModule } from './modules/user/user.module';
|
|
|
5
5
|
import { AuthModule } from './modules/auth/auth.module';
|
|
6
6
|
import { RoleModule } from './modules/role/role.module';
|
|
7
7
|
import { StorageDownloadModule } from './modules/storage/storage.module';
|
|
8
|
+
import * as path from 'path';
|
|
8
9
|
|
|
9
10
|
@Module({
|
|
10
11
|
imports: [
|
|
@@ -17,6 +18,8 @@ import { StorageDownloadModule } from './modules/storage/storage.module';
|
|
|
17
18
|
database: process.env.DB_NAME || 'devstroupe',
|
|
18
19
|
autoLoadEntities: true,
|
|
19
20
|
synchronize: false,
|
|
21
|
+
migrations: [path.join(__dirname, './database/migrations/*{.ts,.js}')],
|
|
22
|
+
migrationsRun: true,
|
|
20
23
|
}),
|
|
21
24
|
(StorageModule as any).registerAsync({
|
|
22
25
|
useFactory: () => ({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { IPaginator } from '@devstroupe/devkit-core';
|
|
1
2
|
import { Role } from './role';
|
|
2
3
|
|
|
3
4
|
export interface IRoleRepository {
|
|
@@ -6,5 +7,6 @@ export interface IRoleRepository {
|
|
|
6
7
|
findById(id: number): Promise<Role | null>;
|
|
7
8
|
findByName(name: string): Promise<Role | null>;
|
|
8
9
|
findAll(): Promise<Role[]>;
|
|
10
|
+
findMany(query: any): Promise<IPaginator<Role>>;
|
|
9
11
|
delete(id: number): Promise<void>;
|
|
10
12
|
}
|
package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.repository.adapter.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { Injectable } from '@nestjs/common';
|
|
2
2
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
3
3
|
import { Repository } from 'typeorm';
|
|
4
|
+
import { BaseRepository } from '@devstroupe/devkit-nest';
|
|
5
|
+
import { IPaginator } from '@devstroupe/devkit-core';
|
|
4
6
|
import { IRoleRepository } from '../../domain/role.repository';
|
|
5
7
|
import { Role } from '../../domain/role';
|
|
6
8
|
import { RoleOrmEntity } from './role.orm-entity';
|
|
7
9
|
|
|
8
10
|
@Injectable()
|
|
9
|
-
export class RoleRepositoryAdapter implements IRoleRepository {
|
|
11
|
+
export class RoleRepositoryAdapter extends BaseRepository<RoleOrmEntity> implements IRoleRepository {
|
|
10
12
|
constructor(
|
|
11
13
|
@InjectRepository(RoleOrmEntity)
|
|
12
14
|
private readonly repo: Repository<RoleOrmEntity>,
|
|
13
|
-
) {
|
|
15
|
+
) {
|
|
16
|
+
super(repo);
|
|
17
|
+
}
|
|
14
18
|
|
|
15
19
|
private toDomain(orm: RoleOrmEntity): Role {
|
|
16
20
|
return {
|
|
@@ -48,6 +52,19 @@ export class RoleRepositoryAdapter implements IRoleRepository {
|
|
|
48
52
|
return found.map(orm => this.toDomain(orm));
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
async findMany(query: any): Promise<IPaginator<Role>> {
|
|
56
|
+
const qb = this.repo.createQueryBuilder('role');
|
|
57
|
+
const paginated = await this.paginate(qb, query, {
|
|
58
|
+
allowedFilters: ['name'],
|
|
59
|
+
allowedSorts: ['id', 'name'],
|
|
60
|
+
searchableFields: ['name'],
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
items: paginated.items.map((orm) => this.toDomain(orm)),
|
|
64
|
+
meta: paginated.meta,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
51
68
|
async delete(id: number): Promise<void> {
|
|
52
69
|
await this.repo.delete(id);
|
|
53
70
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Injectable, Inject } from '@nestjs/common';
|
|
2
|
+
import { IPaginator } from '@devstroupe/devkit-core';
|
|
2
3
|
import { IRoleRepository } from '../domain/role.repository';
|
|
3
4
|
import { Role } from '../domain/role';
|
|
4
5
|
|
|
@@ -33,6 +34,10 @@ export class RoleService {
|
|
|
33
34
|
return this.roleRepo.findAll();
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
async findMany(query: any): Promise<IPaginator<Role>> {
|
|
38
|
+
return this.roleRepo.findMany(query);
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
async delete(id: number): Promise<void> {
|
|
37
42
|
await this.roleRepo.delete(id);
|
|
38
43
|
}
|
|
@@ -12,7 +12,7 @@ export class StorageDownloadController {
|
|
|
12
12
|
private readonly storageService: StorageService,
|
|
13
13
|
) {}
|
|
14
14
|
|
|
15
|
-
@Get('download
|
|
15
|
+
@Get('download/*key')
|
|
16
16
|
async download(@Param('key') key: string, @Res() res: Response) {
|
|
17
17
|
if (this.storageOptions.driver === 'local') {
|
|
18
18
|
const storagePath = path.resolve(this.storageOptions.local?.storagePath || './storage');
|
|
@@ -19,17 +19,25 @@ export class DatabaseSeedService implements OnApplicationBootstrap {
|
|
|
19
19
|
try {
|
|
20
20
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
21
21
|
|
|
22
|
+
const allPermissions = [
|
|
23
|
+
'*',
|
|
24
|
+
'read:cabin', 'create:cabin', 'update:cabin', 'delete:cabin',
|
|
25
|
+
'read:company', 'create:company', 'update:company', 'delete:company',
|
|
26
|
+
'read:role', 'create:role', 'update:role', 'delete:role', 'manage:permissions'
|
|
27
|
+
];
|
|
28
|
+
|
|
22
29
|
// 1. Criar Perfil Administrador se não existir
|
|
23
30
|
let adminRole = await this.roleService.findByName('Administrador');
|
|
24
31
|
if (!adminRole) {
|
|
25
32
|
this.logger.log('Semente: Criando Perfil Administrador...');
|
|
26
33
|
adminRole = await this.roleService.create({
|
|
27
34
|
name: 'Administrador',
|
|
28
|
-
permissions:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
permissions: allPermissions,
|
|
36
|
+
});
|
|
37
|
+
} else if (!adminRole.permissions.includes('*')) {
|
|
38
|
+
this.logger.log('Semente: Atualizando Perfil Administrador com curinga *...');
|
|
39
|
+
adminRole = await this.roleService.update(adminRole.id, {
|
|
40
|
+
permissions: [...new Set(['*', ...adminRole.permissions])],
|
|
33
41
|
});
|
|
34
42
|
}
|
|
35
43
|
|
|
@@ -61,6 +69,8 @@ export class DatabaseSeedService implements OnApplicationBootstrap {
|
|
|
61
69
|
password: adminPassword || 'adminpassword123',
|
|
62
70
|
role: adminRole,
|
|
63
71
|
isActive: true,
|
|
72
|
+
createdAt: new Date(),
|
|
73
|
+
updatedAt: new Date()
|
|
64
74
|
});
|
|
65
75
|
this.logger.log('Semente: Usuário administrador cadastrado com sucesso!');
|
|
66
76
|
}
|
|
@@ -77,6 +87,8 @@ export class DatabaseSeedService implements OnApplicationBootstrap {
|
|
|
77
87
|
password: 'userpassword123',
|
|
78
88
|
role: userRole,
|
|
79
89
|
isActive: true,
|
|
90
|
+
createdAt: new Date(),
|
|
91
|
+
updatedAt: new Date()
|
|
80
92
|
});
|
|
81
93
|
this.logger.log('Semente: Usuário comum cadastrado com sucesso!');
|
|
82
94
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { IPaginator } from '@devstroupe/devkit-core';
|
|
1
2
|
import { User } from './user';
|
|
2
3
|
|
|
3
4
|
export interface IUserRepository {
|
|
@@ -6,5 +7,6 @@ export interface IUserRepository {
|
|
|
6
7
|
findById(id: number): Promise<User | null>;
|
|
7
8
|
findByEmail(email: string): Promise<User | null>;
|
|
8
9
|
findAll(): Promise<User[]>;
|
|
10
|
+
findMany(filter: any): Promise<IPaginator<User>>;
|
|
9
11
|
delete(id: number): Promise<void>;
|
|
10
12
|
}
|
package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.repository.adapter.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { Injectable } from '@nestjs/common';
|
|
2
2
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
3
3
|
import { Repository } from 'typeorm';
|
|
4
|
+
import { BaseRepository } from '@devstroupe/devkit-nest';
|
|
5
|
+
import { IPaginator } from '@devstroupe/devkit-core';
|
|
4
6
|
import { IUserRepository } from '../../domain/user.repository';
|
|
5
7
|
import { User } from '../../domain/user';
|
|
6
8
|
import { UserOrmEntity } from './user.orm-entity';
|
|
7
9
|
|
|
8
10
|
@Injectable()
|
|
9
|
-
export class UserRepositoryAdapter implements IUserRepository {
|
|
11
|
+
export class UserRepositoryAdapter extends BaseRepository<UserOrmEntity> implements IUserRepository {
|
|
10
12
|
constructor(
|
|
11
13
|
@InjectRepository(UserOrmEntity)
|
|
12
14
|
private readonly repo: Repository<UserOrmEntity>,
|
|
13
|
-
) {
|
|
15
|
+
) {
|
|
16
|
+
super(repo);
|
|
17
|
+
}
|
|
14
18
|
|
|
15
19
|
private toDomain(orm: UserOrmEntity): User {
|
|
16
20
|
return {
|
|
@@ -22,6 +26,8 @@ export class UserRepositoryAdapter implements IUserRepository {
|
|
|
22
26
|
tenantId: orm.tenantId,
|
|
23
27
|
currentRefreshToken: orm.currentRefreshToken,
|
|
24
28
|
isActive: orm.isActive,
|
|
29
|
+
createdAt: orm.createdAt,
|
|
30
|
+
updatedAt: orm.updatedAt,
|
|
25
31
|
};
|
|
26
32
|
}
|
|
27
33
|
|
|
@@ -53,6 +59,22 @@ export class UserRepositoryAdapter implements IUserRepository {
|
|
|
53
59
|
return found.map((u) => this.toDomain(u));
|
|
54
60
|
}
|
|
55
61
|
|
|
62
|
+
async findMany(filter: any): Promise<IPaginator<User>> {
|
|
63
|
+
const alias = 'user';
|
|
64
|
+
const qb = this.repo.createQueryBuilder(alias);
|
|
65
|
+
qb.leftJoinAndSelect(`${alias}.role`, 'role');
|
|
66
|
+
const paginated = await this.paginate(qb, filter, {
|
|
67
|
+
allowedFilters: ['name', 'email', 'isActive'],
|
|
68
|
+
allowedSorts: ['id', 'name', 'email'],
|
|
69
|
+
searchableFields: ['name', 'email'],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
items: paginated.items.map((u) => this.toDomain(u)),
|
|
74
|
+
meta: paginated.meta,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
56
78
|
async delete(id: number): Promise<void> {
|
|
57
79
|
await this.repo.delete(id);
|
|
58
80
|
}
|
|
@@ -1,56 +1,52 @@
|
|
|
1
|
-
import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards,
|
|
1
|
+
import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards, Query } from '@nestjs/common';
|
|
2
2
|
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
|
3
3
|
import { UserService } from '../../service/user.service';
|
|
4
4
|
import { JwtAuthGuard } from '../../../auth/jwt-auth.guard';
|
|
5
|
-
import { Permissions, PermissionsGuard } from '@devstroupe/devkit-nest';
|
|
5
|
+
import { BaseCrudController, Permissions, PermissionsGuard } from '@devstroupe/devkit-nest';
|
|
6
6
|
import { User } from '../../domain/user';
|
|
7
7
|
|
|
8
8
|
@ApiTags('users')
|
|
9
9
|
@Controller('user')
|
|
10
10
|
@UseGuards(JwtAuthGuard, PermissionsGuard)
|
|
11
11
|
@ApiBearerAuth()
|
|
12
|
-
export class UserController {
|
|
13
|
-
constructor(private readonly userService: UserService) {
|
|
12
|
+
export class UserController extends BaseCrudController<User> {
|
|
13
|
+
constructor(private readonly userService: UserService) {
|
|
14
|
+
super(userService);
|
|
15
|
+
}
|
|
14
16
|
|
|
15
17
|
@Get()
|
|
16
18
|
@Permissions('read:user')
|
|
17
|
-
@ApiOperation({ summary: 'Listar
|
|
19
|
+
@ApiOperation({ summary: 'Listar usuários com filtros e paginação' })
|
|
18
20
|
@ApiResponse({ status: 200, description: 'Usuários listados com sucesso.' })
|
|
19
|
-
async
|
|
20
|
-
|
|
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();
|
|
21
|
+
override async findMany(@Query() query: any): Promise<any> {
|
|
22
|
+
return super.findMany(query);
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
@Get(':id')
|
|
29
26
|
@Permissions('read:user')
|
|
30
27
|
@ApiOperation({ summary: 'Obter um usuário pelo ID' })
|
|
31
|
-
async
|
|
32
|
-
return
|
|
28
|
+
override async findById(@Param('id') id: string): Promise<User> {
|
|
29
|
+
return super.findById(id);
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
@Post()
|
|
36
33
|
@Permissions('create:user')
|
|
37
34
|
@ApiOperation({ summary: 'Criar um novo usuário' })
|
|
38
|
-
async create(@Body() body:
|
|
39
|
-
return
|
|
35
|
+
override async create(@Body() body: any): Promise<User> {
|
|
36
|
+
return super.create(body);
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
@Put(':id')
|
|
43
40
|
@Permissions('update:user')
|
|
44
41
|
@ApiOperation({ summary: 'Atualizar dados de um usuário' })
|
|
45
|
-
async update(@Param('id'
|
|
46
|
-
return
|
|
42
|
+
override async update(@Param('id') id: string, @Body() body: any): Promise<User> {
|
|
43
|
+
return super.update(id, body);
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
@Delete(':id')
|
|
50
47
|
@Permissions('delete:user')
|
|
51
48
|
@ApiOperation({ summary: 'Excluir um usuário' })
|
|
52
|
-
async delete(@Param('id'
|
|
53
|
-
|
|
54
|
-
return { success: true };
|
|
49
|
+
override async delete(@Param('id') id: string): Promise<void> {
|
|
50
|
+
return super.delete(id);
|
|
55
51
|
}
|
|
56
52
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Injectable, Inject } from '@nestjs/common';
|
|
2
2
|
import * as bcrypt from 'bcryptjs';
|
|
3
|
+
import { IPaginator } from '@devstroupe/devkit-core';
|
|
3
4
|
import { IUserRepository } from '../domain/user.repository';
|
|
4
5
|
import { User } from '../domain/user';
|
|
5
6
|
|
|
@@ -27,6 +28,10 @@ export class UserService {
|
|
|
27
28
|
return this.userRepo.findById(id);
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
async findOne(id: number): Promise<User | null> {
|
|
32
|
+
return this.findById(id);
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
async findByEmail(email: string): Promise<User | null> {
|
|
31
36
|
return this.userRepo.findByEmail(email);
|
|
32
37
|
}
|
|
@@ -42,6 +47,10 @@ export class UserService {
|
|
|
42
47
|
return this.userRepo.findAll();
|
|
43
48
|
}
|
|
44
49
|
|
|
50
|
+
async findMany(filter: any): Promise<IPaginator<User>> {
|
|
51
|
+
return this.userRepo.findMany(filter);
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
async delete(id: number): Promise<void> {
|
|
46
55
|
await this.userRepo.delete(id);
|
|
47
56
|
}
|
package/dist/index.js
CHANGED
|
@@ -556,6 +556,32 @@ function initUi(frontendRoot, config) {
|
|
|
556
556
|
// Atualizar roteamento para suportar MainLayoutComponent
|
|
557
557
|
updateAppRoutes(frontendRoot, config);
|
|
558
558
|
}
|
|
559
|
+
function getEntityRoutePath(entityName, config) {
|
|
560
|
+
const navigation = config.layout?.navigation;
|
|
561
|
+
if (!Array.isArray(navigation)) {
|
|
562
|
+
return entityName;
|
|
563
|
+
}
|
|
564
|
+
const findInMenu = (items) => {
|
|
565
|
+
for (const item of items) {
|
|
566
|
+
if (item.route) {
|
|
567
|
+
const routeClean = item.route.replace(/^\//, '');
|
|
568
|
+
if (routeClean === entityName ||
|
|
569
|
+
routeClean === `${entityName}s` ||
|
|
570
|
+
routeClean === `${entityName}es` ||
|
|
571
|
+
routeClean.replace(/s$/, '') === entityName) {
|
|
572
|
+
return routeClean;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (item.children) {
|
|
576
|
+
const found = findInMenu(item.children);
|
|
577
|
+
if (found)
|
|
578
|
+
return found;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return null;
|
|
582
|
+
};
|
|
583
|
+
return findInMenu(navigation) || entityName;
|
|
584
|
+
}
|
|
559
585
|
function updateAppRoutes(frontendRoot, config = {}) {
|
|
560
586
|
const routesPath = path.join(frontendRoot, 'src', 'app', 'app.routes.ts');
|
|
561
587
|
if (!fs.existsSync(routesPath)) {
|
|
@@ -596,7 +622,8 @@ function updateAppRoutes(frontendRoot, config = {}) {
|
|
|
596
622
|
featureRoutes += `];\n`;
|
|
597
623
|
fs.writeFileSync(featureRoutesPath, featureRoutes, 'utf-8');
|
|
598
624
|
routeImports += `import { ${routeConstant} } from './modules/${entity.name}/${entity.name}.routes';\n`;
|
|
599
|
-
|
|
625
|
+
const routePath = getEntityRoutePath(entity.name, config);
|
|
626
|
+
entityRoutes += ` { path: '${routePath}', children: ${routeConstant} },\n`;
|
|
600
627
|
});
|
|
601
628
|
const entityRoutesPath = path.join(frontendRoot, 'src', 'app', 'app.entity-routes.ts');
|
|
602
629
|
fs.writeFileSync(entityRoutesPath, `import { Routes } from '@angular/router';
|
|
@@ -1042,7 +1069,7 @@ SERVICE_PORT=${port}
|
|
|
1042
1069
|
fs.ensureDirSync(path.join(modulePath, 'infra', 'database'));
|
|
1043
1070
|
fs.ensureDirSync(path.join(modulePath, 'infra', 'http'));
|
|
1044
1071
|
// Domain Entity
|
|
1045
|
-
fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.ts`), (0, cli_templates_1.nestDomainEntityTemplate)(entity.name, backendProperties, isTenantScoped), 'utf-8');
|
|
1072
|
+
fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.ts`), (0, cli_templates_1.nestDomainEntityTemplate)(entity.name, backendProperties, isTenantScoped, entity.automaticFields), 'utf-8');
|
|
1046
1073
|
// Repository Port
|
|
1047
1074
|
fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.repository.port.ts`), (0, cli_templates_1.nestRepositoryPortTemplate)(entity.name), 'utf-8');
|
|
1048
1075
|
// DTO
|
|
@@ -1084,7 +1111,7 @@ SERVICE_PORT=${port}
|
|
|
1084
1111
|
fs.ensureDirSync(path.join(modulePath, 'infra', 'database'));
|
|
1085
1112
|
fs.ensureDirSync(path.join(modulePath, 'infra', 'http'));
|
|
1086
1113
|
// Domain Entity
|
|
1087
|
-
fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.ts`), (0, cli_templates_1.nestDomainEntityTemplate)(entity.name, entity.properties, isTenantScoped), 'utf-8');
|
|
1114
|
+
fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.ts`), (0, cli_templates_1.nestDomainEntityTemplate)(entity.name, entity.properties, isTenantScoped, entity.automaticFields), 'utf-8');
|
|
1088
1115
|
// Repository Port
|
|
1089
1116
|
fs.writeFileSync(path.join(modulePath, 'domain', `${entity.name}.repository.port.ts`), (0, cli_templates_1.nestRepositoryPortTemplate)(entity.name), 'utf-8');
|
|
1090
1117
|
// DTO
|
|
@@ -1165,6 +1192,8 @@ function patchLocalDependencies(backendDest, frontendDest, boilerplatesDir) {
|
|
|
1165
1192
|
return;
|
|
1166
1193
|
const packageJson = fs.readJsonSync(packagePath);
|
|
1167
1194
|
delete packageJson.devDependencies;
|
|
1195
|
+
delete packageJson.peerDependencies;
|
|
1196
|
+
delete packageJson.dependencies;
|
|
1168
1197
|
packageJson.scripts = {};
|
|
1169
1198
|
fs.writeJsonSync(packagePath, packageJson, { spaces: 2 });
|
|
1170
1199
|
};
|
|
@@ -1503,6 +1532,10 @@ function generateDockerComposeAndDbInit(projectRoot, config) {
|
|
|
1503
1532
|
// 1. db-init/init.sql
|
|
1504
1533
|
const dbInitDir = path.join(projectRoot, 'db-init');
|
|
1505
1534
|
fs.ensureDirSync(dbInitDir);
|
|
1535
|
+
try {
|
|
1536
|
+
fs.chmodSync(dbInitDir, 0o755);
|
|
1537
|
+
}
|
|
1538
|
+
catch { }
|
|
1506
1539
|
const dbs = ['devstroupe']; // Banco principal (usado pelo gateway ou monolith)
|
|
1507
1540
|
if (isMicroservices && config.entities) {
|
|
1508
1541
|
config.entities.forEach((entity) => {
|
|
@@ -1510,12 +1543,18 @@ function generateDockerComposeAndDbInit(projectRoot, config) {
|
|
|
1510
1543
|
});
|
|
1511
1544
|
}
|
|
1512
1545
|
const initSqlContent = dbs.map(db => `CREATE DATABASE IF NOT EXISTS \`${db}\`;`).join('\n') + '\n';
|
|
1513
|
-
|
|
1546
|
+
const sqlPath = path.join(dbInitDir, 'init.sql');
|
|
1547
|
+
fs.writeFileSync(sqlPath, initSqlContent, 'utf-8');
|
|
1548
|
+
try {
|
|
1549
|
+
fs.chmodSync(sqlPath, 0o644);
|
|
1550
|
+
}
|
|
1551
|
+
catch { }
|
|
1514
1552
|
console.log(`[Docker] db-init/init.sql gerado com sucesso.`);
|
|
1515
|
-
// 2. docker-compose.yml
|
|
1553
|
+
// 2. docker-compose.yml e docker-compose.dev.yml
|
|
1516
1554
|
let servicesYaml = '';
|
|
1555
|
+
let devServicesYaml = '';
|
|
1517
1556
|
// Database service
|
|
1518
|
-
|
|
1557
|
+
const dbServiceYaml = ` database:
|
|
1519
1558
|
image: mysql:8.0
|
|
1520
1559
|
container_name: ${projectName}-database
|
|
1521
1560
|
restart: always
|
|
@@ -1527,6 +1566,8 @@ function generateDockerComposeAndDbInit(projectRoot, config) {
|
|
|
1527
1566
|
- mysql_data:/var/lib/mysql
|
|
1528
1567
|
- ./db-init:/docker-entrypoint-initdb.d
|
|
1529
1568
|
`;
|
|
1569
|
+
servicesYaml += dbServiceYaml;
|
|
1570
|
+
devServicesYaml += dbServiceYaml;
|
|
1530
1571
|
if (isMicroservices) {
|
|
1531
1572
|
// Gateway service
|
|
1532
1573
|
let gatewayEnv = ` DB_HOST: database
|
|
@@ -1560,6 +1601,20 @@ function generateDockerComposeAndDbInit(projectRoot, config) {
|
|
|
1560
1601
|
- "13000:13000"
|
|
1561
1602
|
environment:
|
|
1562
1603
|
${gatewayEnv} depends_on:
|
|
1604
|
+
${gatewayDependsOn}`;
|
|
1605
|
+
devServicesYaml += `\n api-gateway:
|
|
1606
|
+
image: node:20-alpine
|
|
1607
|
+
container_name: ${projectName}-api-gateway-dev
|
|
1608
|
+
working_dir: /app
|
|
1609
|
+
restart: always
|
|
1610
|
+
volumes:
|
|
1611
|
+
- ./backend/api-gateway:/app
|
|
1612
|
+
- /app/node_modules
|
|
1613
|
+
ports:
|
|
1614
|
+
- "13000:13000"
|
|
1615
|
+
environment:
|
|
1616
|
+
${gatewayEnv} command: sh -c "npm install && npm run start:dev"
|
|
1617
|
+
depends_on:
|
|
1563
1618
|
${gatewayDependsOn}`;
|
|
1564
1619
|
// Containers de microsserviços
|
|
1565
1620
|
if (config.entities) {
|
|
@@ -1582,6 +1637,27 @@ ${gatewayDependsOn}`;
|
|
|
1582
1637
|
SERVICE_PORT: ${port}
|
|
1583
1638
|
depends_on:
|
|
1584
1639
|
- database
|
|
1640
|
+
`;
|
|
1641
|
+
devServicesYaml += `\n\n ${entity.name}-service:
|
|
1642
|
+
image: node:20-alpine
|
|
1643
|
+
container_name: ${projectName}-${entity.name}-service-dev
|
|
1644
|
+
working_dir: /app
|
|
1645
|
+
restart: always
|
|
1646
|
+
volumes:
|
|
1647
|
+
- ./backend/services/${entity.name}:/app
|
|
1648
|
+
- /app/node_modules
|
|
1649
|
+
ports:
|
|
1650
|
+
- "${port}0:${port}"
|
|
1651
|
+
environment:
|
|
1652
|
+
DB_HOST: database
|
|
1653
|
+
DB_PORT: 3306
|
|
1654
|
+
DB_USER: root
|
|
1655
|
+
DB_PASSWORD: root
|
|
1656
|
+
DB_NAME: devstroupe_${entity.name.toLowerCase()}
|
|
1657
|
+
SERVICE_PORT: ${port}
|
|
1658
|
+
command: sh -c "npm install && npm run start:dev"
|
|
1659
|
+
depends_on:
|
|
1660
|
+
- database
|
|
1585
1661
|
`;
|
|
1586
1662
|
});
|
|
1587
1663
|
}
|
|
@@ -1596,6 +1672,20 @@ ${gatewayDependsOn}`;
|
|
|
1596
1672
|
- "14200:80"
|
|
1597
1673
|
depends_on:
|
|
1598
1674
|
- api-gateway
|
|
1675
|
+
`;
|
|
1676
|
+
devServicesYaml += `\n\n frontend:
|
|
1677
|
+
image: node:20-alpine
|
|
1678
|
+
container_name: ${projectName}-frontend-dev
|
|
1679
|
+
working_dir: /app
|
|
1680
|
+
restart: always
|
|
1681
|
+
volumes:
|
|
1682
|
+
- ./frontend:/app
|
|
1683
|
+
- /app/node_modules
|
|
1684
|
+
ports:
|
|
1685
|
+
- "14200:4200"
|
|
1686
|
+
command: sh -c "npm install && npm run start -- --host 0.0.0.0 --poll 2000"
|
|
1687
|
+
depends_on:
|
|
1688
|
+
- api-gateway-dev
|
|
1599
1689
|
`;
|
|
1600
1690
|
}
|
|
1601
1691
|
else {
|
|
@@ -1628,17 +1718,61 @@ ${gatewayDependsOn}`;
|
|
|
1628
1718
|
- "14200:80"
|
|
1629
1719
|
depends_on:
|
|
1630
1720
|
- backend
|
|
1721
|
+
`;
|
|
1722
|
+
devServicesYaml += `\n backend:
|
|
1723
|
+
image: node:20-alpine
|
|
1724
|
+
container_name: ${projectName}-backend-dev
|
|
1725
|
+
working_dir: /app
|
|
1726
|
+
restart: always
|
|
1727
|
+
volumes:
|
|
1728
|
+
- ./backend:/app
|
|
1729
|
+
- /app/node_modules
|
|
1730
|
+
ports:
|
|
1731
|
+
- "13000:13000"
|
|
1732
|
+
environment:
|
|
1733
|
+
DB_HOST: database
|
|
1734
|
+
DB_PORT: 3306
|
|
1735
|
+
DB_USER: root
|
|
1736
|
+
DB_PASSWORD: root
|
|
1737
|
+
DB_NAME: devstroupe
|
|
1738
|
+
JWT_SECRET: devstroupe-secret-key-12345
|
|
1739
|
+
command: sh -c "npm install && npm run start:dev"
|
|
1740
|
+
depends_on:
|
|
1741
|
+
- database
|
|
1742
|
+
|
|
1743
|
+
frontend:
|
|
1744
|
+
image: node:20-alpine
|
|
1745
|
+
container_name: ${projectName}-frontend-dev
|
|
1746
|
+
working_dir: /app
|
|
1747
|
+
restart: always
|
|
1748
|
+
volumes:
|
|
1749
|
+
- ./frontend:/app
|
|
1750
|
+
- /app/node_modules
|
|
1751
|
+
ports:
|
|
1752
|
+
- "14200:4200"
|
|
1753
|
+
command: sh -c "npm install && npm run start -- --host 0.0.0.0 --poll 2000"
|
|
1754
|
+
depends_on:
|
|
1755
|
+
- backend
|
|
1631
1756
|
`;
|
|
1632
1757
|
}
|
|
1633
1758
|
const dockerComposeContent = `version: '3.8'
|
|
1634
1759
|
|
|
1635
1760
|
services:
|
|
1636
1761
|
${servicesYaml}
|
|
1762
|
+
volumes:
|
|
1763
|
+
mysql_data:
|
|
1764
|
+
`;
|
|
1765
|
+
const dockerComposeDevContent = `version: '3.8'
|
|
1766
|
+
|
|
1767
|
+
services:
|
|
1768
|
+
${devServicesYaml}
|
|
1637
1769
|
volumes:
|
|
1638
1770
|
mysql_data:
|
|
1639
1771
|
`;
|
|
1640
1772
|
fs.writeFileSync(path.join(projectRoot, 'docker-compose.yml'), dockerComposeContent, 'utf-8');
|
|
1641
1773
|
console.log(`[Docker] docker-compose.yml regerado com sucesso.`);
|
|
1774
|
+
fs.writeFileSync(path.join(projectRoot, 'docker-compose.dev.yml'), dockerComposeDevContent, 'utf-8');
|
|
1775
|
+
console.log(`[Docker] docker-compose.dev.yml gerado com sucesso.`);
|
|
1642
1776
|
}
|
|
1643
1777
|
// Comando new-project
|
|
1644
1778
|
program
|
|
@@ -1795,12 +1929,13 @@ R2_SECRET_KEY=
|
|
|
1795
1929
|
// 3.2 Criar Makefile na raiz
|
|
1796
1930
|
console.log('Gerando arquivo Makefile na raiz...');
|
|
1797
1931
|
const backendServiceName = isMicroservices ? 'api-gateway' : 'backend';
|
|
1798
|
-
const makefileContent = `.PHONY: up down restart build logs ps clean backend-shell frontend-shell db-shell help
|
|
1799
|
-
|
|
1932
|
+
const makefileContent = `.PHONY: up down restart build logs ps clean backend-shell frontend-shell db-shell dev-up dev-down dev-restart dev-build dev-logs dev-ps dev-clean help
|
|
1933
|
+
|
|
1800
1934
|
DC = docker compose
|
|
1801
|
-
|
|
1935
|
+
DC_DEV = docker compose -f docker-compose.dev.yml
|
|
1936
|
+
|
|
1802
1937
|
help:
|
|
1803
|
-
@echo "Comandos disponíveis:"
|
|
1938
|
+
@echo "Comandos disponíveis (Produção):"
|
|
1804
1939
|
@echo " make up Inicia os containers em background"
|
|
1805
1940
|
@echo " make down Para os containers e mantém os dados"
|
|
1806
1941
|
@echo " make restart Reinicia os containers"
|
|
@@ -1808,40 +1943,75 @@ help:
|
|
|
1808
1943
|
@echo " make logs Exibe os logs em tempo real"
|
|
1809
1944
|
@echo " make ps Exibe o status dos containers"
|
|
1810
1945
|
@echo " make clean Para os containers e apaga todos os volumes (reseta o banco)"
|
|
1946
|
+
@echo ""
|
|
1947
|
+
@echo "Comandos disponíveis (Desenvolvimento local com Live Reload):"
|
|
1948
|
+
@echo " make dev-up Inicia os containers dev em background"
|
|
1949
|
+
@echo " make dev-down Para os containers dev e mantém os dados"
|
|
1950
|
+
@echo " make dev-restart Reinicia os containers dev"
|
|
1951
|
+
@echo " make dev-build Reconstrói as imagens dev Docker"
|
|
1952
|
+
@echo " make dev-logs Exibe os logs dev em tempo real"
|
|
1953
|
+
@echo " make dev-ps Exibe o status dos containers dev"
|
|
1954
|
+
@echo " make dev-clean Para os containers dev e apaga todos os volumes (reseta o banco)"
|
|
1955
|
+
@echo ""
|
|
1956
|
+
@echo "Utilitários:"
|
|
1811
1957
|
@echo " make backend-shell Abre o terminal sh do container do backend"
|
|
1812
1958
|
@echo " make frontend-shell Abre o terminal sh do container do frontend"
|
|
1813
1959
|
@echo " make db-shell Abre o cliente mysql no container do banco"
|
|
1814
|
-
|
|
1960
|
+
|
|
1961
|
+
# --- Produção ---
|
|
1815
1962
|
up:
|
|
1816
1963
|
\$(DC) up -d
|
|
1817
|
-
|
|
1964
|
+
|
|
1818
1965
|
down:
|
|
1819
1966
|
\$(DC) down
|
|
1820
|
-
|
|
1967
|
+
|
|
1821
1968
|
restart:
|
|
1822
1969
|
\$(DC) restart
|
|
1823
|
-
|
|
1970
|
+
|
|
1824
1971
|
build:
|
|
1825
1972
|
\$(DC) build
|
|
1826
|
-
|
|
1973
|
+
|
|
1827
1974
|
logs:
|
|
1828
1975
|
\$(DC) logs -f
|
|
1829
|
-
|
|
1976
|
+
|
|
1830
1977
|
ps:
|
|
1831
1978
|
\$(DC) ps
|
|
1832
|
-
|
|
1979
|
+
|
|
1833
1980
|
clean:
|
|
1834
1981
|
\$(DC) down -v
|
|
1835
|
-
|
|
1982
|
+
|
|
1983
|
+
# --- Desenvolvimento ---
|
|
1984
|
+
dev-up:
|
|
1985
|
+
\$(DC_DEV) up -d
|
|
1986
|
+
|
|
1987
|
+
dev-down:
|
|
1988
|
+
\$(DC_DEV) down
|
|
1989
|
+
|
|
1990
|
+
dev-restart:
|
|
1991
|
+
\$(DC_DEV) restart
|
|
1992
|
+
|
|
1993
|
+
dev-build:
|
|
1994
|
+
\$(DC_DEV) build
|
|
1995
|
+
|
|
1996
|
+
dev-logs:
|
|
1997
|
+
\$(DC_DEV) logs -f
|
|
1998
|
+
|
|
1999
|
+
dev-ps:
|
|
2000
|
+
\$(DC_DEV) ps
|
|
2001
|
+
|
|
2002
|
+
dev-clean:
|
|
2003
|
+
\$(DC_DEV) down -v
|
|
2004
|
+
|
|
2005
|
+
# --- Utilitários ---
|
|
1836
2006
|
backend-shell:
|
|
1837
|
-
\$(DC) exec ${backendServiceName} sh
|
|
1838
|
-
|
|
2007
|
+
\$(DC_DEV) exec -it ${backendServiceName} sh || \$(DC) exec -it ${backendServiceName} sh
|
|
2008
|
+
|
|
1839
2009
|
frontend-shell:
|
|
1840
|
-
\$(DC) exec frontend sh
|
|
1841
|
-
|
|
2010
|
+
\$(DC_DEV) exec -it frontend sh || \$(DC) exec -it frontend sh
|
|
2011
|
+
|
|
1842
2012
|
db-shell:
|
|
1843
|
-
\$(DC) exec database mysql -u root -p
|
|
1844
|
-
|
|
2013
|
+
\$(DC_DEV) exec -it database mysql -u root -p || \$(DC) exec -it database mysql -u root -p
|
|
2014
|
+
|
|
1845
2015
|
# Evita que argumentos (ex: main, staging) sejam interpretados como target
|
|
1846
2016
|
%:
|
|
1847
2017
|
@:
|
|
@@ -155,7 +155,7 @@ function runLinter(projectRoot, config) {
|
|
|
155
155
|
// Regra: use-standard-devkit-queries (Backend)
|
|
156
156
|
if (severityStandardQueries !== 'off') {
|
|
157
157
|
// Controllers devem herdar de BaseCrudController
|
|
158
|
-
if (file.endsWith('.controller.ts') && !file.endsWith('auth.controller.ts') && content.includes('@Controller') && !content.includes('extends BaseCrudController')) {
|
|
158
|
+
if (file.endsWith('.controller.ts') && !file.endsWith('auth.controller.ts') && !file.endsWith('storage.controller.ts') && !file.endsWith('storage-download.controller.ts') && content.includes('@Controller') && !content.includes('extends BaseCrudController')) {
|
|
159
159
|
violations.push({
|
|
160
160
|
file: relativePath,
|
|
161
161
|
line: 1,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { IDevkitEntityProperty } from '@devstroupe/devkit-core';
|
|
2
|
-
export declare function nestDomainEntityTemplate(name: string, properties: IDevkitEntityProperty[], isTenantScoped: boolean
|
|
2
|
+
export declare function nestDomainEntityTemplate(name: string, properties: IDevkitEntityProperty[], isTenantScoped: boolean, automaticFields?: {
|
|
3
|
+
timestamps?: boolean;
|
|
4
|
+
blamers?: boolean;
|
|
5
|
+
optimisticLocking?: boolean;
|
|
6
|
+
}): string;
|
|
3
7
|
export declare function nestRepositoryPortTemplate(name: string): string;
|
|
4
8
|
export declare function nestOrmEntityTemplate(name: string, properties: IDevkitEntityProperty[], tableName: string, isTenantScoped: boolean, indexes?: Array<{
|
|
5
9
|
columns: string[];
|
|
@@ -10,7 +10,7 @@ exports.nestControllerTemplate = nestControllerTemplate;
|
|
|
10
10
|
exports.nestModuleTemplate = nestModuleTemplate;
|
|
11
11
|
const names_1 = require("../shared/names");
|
|
12
12
|
const relationships_1 = require("../shared/relationships");
|
|
13
|
-
function nestDomainEntityTemplate(name, properties, isTenantScoped) {
|
|
13
|
+
function nestDomainEntityTemplate(name, properties, isTenantScoped, automaticFields) {
|
|
14
14
|
const pascalName = (0, names_1.kebabToPascal)(name);
|
|
15
15
|
const propsCode = properties
|
|
16
16
|
.map((p) => {
|
|
@@ -38,9 +38,19 @@ function nestDomainEntityTemplate(name, properties, isTenantScoped) {
|
|
|
38
38
|
})
|
|
39
39
|
.join('\n');
|
|
40
40
|
const tenantField = isTenantScoped ? '\n tenantId!: string;' : '';
|
|
41
|
+
let autoFieldsStr = '';
|
|
42
|
+
if (automaticFields?.timestamps) {
|
|
43
|
+
autoFieldsStr += '\n createdAt!: Date;\n updatedAt!: Date;';
|
|
44
|
+
}
|
|
45
|
+
if (automaticFields?.blamers) {
|
|
46
|
+
autoFieldsStr += '\n createdByUserId?: number;\n updatedByUserId?: number;';
|
|
47
|
+
}
|
|
48
|
+
if (automaticFields?.optimisticLocking) {
|
|
49
|
+
autoFieldsStr += '\n version!: number;';
|
|
50
|
+
}
|
|
41
51
|
return `export class ${pascalName} {
|
|
42
52
|
id!: number;
|
|
43
|
-
${propsCode}${tenantField}
|
|
53
|
+
${propsCode}${tenantField}${autoFieldsStr}
|
|
44
54
|
}
|
|
45
55
|
`;
|
|
46
56
|
}
|
|
@@ -74,7 +84,7 @@ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, inde
|
|
|
74
84
|
typeormImports.push('JoinColumn');
|
|
75
85
|
if (relationships.some((relation) => relation.kind === 'many-to-many' && relation.owner))
|
|
76
86
|
typeormImports.push('JoinTable');
|
|
77
|
-
if (indexes && indexes.length > 0)
|
|
87
|
+
if ((indexes && indexes.length > 0) || properties.some((p) => p.index))
|
|
78
88
|
typeormImports.push('Index');
|
|
79
89
|
if (persistence?.deletion === 'soft-delete')
|
|
80
90
|
typeormImports.push('DeleteDateColumn');
|
|
@@ -90,6 +100,7 @@ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, inde
|
|
|
90
100
|
.join('\n');
|
|
91
101
|
const propsCode = properties
|
|
92
102
|
.map((p) => {
|
|
103
|
+
const indexDecorator = p.index ? '@Index()\n ' : '';
|
|
93
104
|
if (p.type === 'relationship') {
|
|
94
105
|
const relation = (0, relationships_1.getRelationships)([p])[0];
|
|
95
106
|
const targetPascal = (0, names_1.kebabToPascal)(relation.target);
|
|
@@ -111,11 +122,20 @@ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, inde
|
|
|
111
122
|
return ` @ManyToMany(() => ${targetPascal}OrmEntity${relation.inverseSide ? inverse : ''})${joinTable}\n ${relation.relationName}?: ${targetPascal}OrmEntity[];`;
|
|
112
123
|
}
|
|
113
124
|
const decorator = relation.kind === 'one-to-one' ? 'OneToOne' : 'ManyToOne';
|
|
114
|
-
|
|
125
|
+
let fkOptions = [];
|
|
126
|
+
if (!p.required)
|
|
127
|
+
fkOptions.push('nullable: true');
|
|
128
|
+
if (p.unique)
|
|
129
|
+
fkOptions.push('unique: true');
|
|
130
|
+
const fkDecorator = fkOptions.length > 0 ? `@Column({ ${fkOptions.join(', ')} })` : '@Column()';
|
|
115
131
|
const joinColumn = relation.owner ? `\n @JoinColumn({ name: '${relation.foreignKey}', referencedColumnName: '${relation.valueField}' })` : '';
|
|
116
|
-
return ` ${fkDecorator}\n ${relation.foreignKey}!: number;\n\n @${decorator}(() => ${targetPascal}OrmEntity${relation.inverseSide ? inverse : ''}, ${options})${joinColumn}\n ${relation.relationName}?: ${targetPascal}OrmEntity;`;
|
|
132
|
+
return ` ${indexDecorator}${fkDecorator}\n ${relation.foreignKey}!: number;\n\n @${decorator}(() => ${targetPascal}OrmEntity${relation.inverseSide ? inverse : ''}, ${options})${joinColumn}\n ${relation.relationName}?: ${targetPascal}OrmEntity;`;
|
|
117
133
|
}
|
|
118
|
-
let
|
|
134
|
+
let colOptions = [];
|
|
135
|
+
if (!p.required)
|
|
136
|
+
colOptions.push('nullable: true');
|
|
137
|
+
if (p.unique)
|
|
138
|
+
colOptions.push('unique: true');
|
|
119
139
|
let typeStr = 'string';
|
|
120
140
|
const type = p.type;
|
|
121
141
|
if (type === 'number' || type === 'decimal' || type === 'currency' || type === 'integer') {
|
|
@@ -123,39 +143,40 @@ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, inde
|
|
|
123
143
|
if (type === 'decimal' || type === 'currency') {
|
|
124
144
|
const precision = p.precision !== undefined ? p.precision : 12;
|
|
125
145
|
const scale = p.scale !== undefined ? p.scale : 2;
|
|
126
|
-
|
|
146
|
+
colOptions.push(`type: 'decimal'`, `precision: ${precision}`, `scale: ${scale}`);
|
|
127
147
|
}
|
|
128
148
|
else if (type === 'integer') {
|
|
129
|
-
|
|
149
|
+
colOptions.push(`type: 'int'`);
|
|
130
150
|
}
|
|
131
151
|
}
|
|
132
152
|
else if (type === 'bigint') {
|
|
133
153
|
typeStr = 'string';
|
|
134
|
-
|
|
154
|
+
colOptions.push(`type: 'bigint'`);
|
|
135
155
|
}
|
|
136
156
|
else if (type === 'boolean') {
|
|
137
157
|
typeStr = 'boolean';
|
|
138
158
|
}
|
|
139
159
|
else if (type === 'datepicker' || type === 'datetime') {
|
|
140
160
|
typeStr = 'Date';
|
|
141
|
-
|
|
161
|
+
colOptions.push(`type: 'datetime'`);
|
|
142
162
|
}
|
|
143
163
|
else if (type === 'date') {
|
|
144
164
|
typeStr = 'Date';
|
|
145
|
-
|
|
165
|
+
colOptions.push(`type: 'date'`);
|
|
146
166
|
}
|
|
147
167
|
else if (type === 'text') {
|
|
148
|
-
|
|
168
|
+
colOptions.push(`type: 'text'`);
|
|
149
169
|
}
|
|
150
170
|
else if (type === 'json') {
|
|
151
171
|
typeStr = 'any';
|
|
152
|
-
|
|
172
|
+
colOptions.push(`type: 'json'`);
|
|
153
173
|
}
|
|
154
174
|
else if (type === 'enum' && p.enumOptions) {
|
|
155
175
|
typeStr = p.enumOptions.map(o => `'${o}'`).join(' | ');
|
|
156
|
-
|
|
176
|
+
colOptions.push(`type: 'enum'`, `enum: ${JSON.stringify(p.enumOptions)}`);
|
|
157
177
|
}
|
|
158
|
-
|
|
178
|
+
const decorator = colOptions.length > 0 ? `@Column({ ${colOptions.join(', ')} })` : '@Column()';
|
|
179
|
+
return ` ${indexDecorator}${decorator}\n ${p.name}!: ${typeStr};`;
|
|
159
180
|
})
|
|
160
181
|
.join('\n\n');
|
|
161
182
|
const tenantField = isTenantScoped ? '\n\n @Column()\n tenantId!: string;' : '';
|
|
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.devkitSidebarTemplate = devkitSidebarTemplate;
|
|
4
4
|
function devkitSidebarTemplate() {
|
|
5
5
|
return {
|
|
6
|
-
ts: `import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
|
6
|
+
ts: `import { Component, EventEmitter, Input, OnInit, Output, inject } from '@angular/core';
|
|
7
7
|
import { CommonModule } from '@angular/common';
|
|
8
8
|
import { RouterModule, Router, NavigationEnd } from '@angular/router';
|
|
9
9
|
import { NgIconComponent } from '@ng-icons/core';
|
|
10
10
|
import { filter } from 'rxjs/operators';
|
|
11
|
+
import { DEVKIT_AUTH_SERVICE } from '@devstroupe/devkit-angular';
|
|
11
12
|
|
|
12
13
|
export interface INavigationItem {
|
|
13
14
|
label: string;
|
|
@@ -26,13 +27,26 @@ export interface INavigationItem {
|
|
|
26
27
|
styleUrls: ['./sidebar.component.css']
|
|
27
28
|
})
|
|
28
29
|
export class DevkitSidebarComponent implements OnInit {
|
|
29
|
-
|
|
30
|
+
private _items: INavigationItem[] = [];
|
|
31
|
+
filteredItems: INavigationItem[] = [];
|
|
32
|
+
|
|
33
|
+
@Input() set items(value: INavigationItem[]) {
|
|
34
|
+
this._items = value || [];
|
|
35
|
+
this.filterItems();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get items(): INavigationItem[] {
|
|
39
|
+
return this._items;
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
@Input() brandLogo = '';
|
|
31
43
|
@Input() brandLogoCollapsed = '';
|
|
32
44
|
@Input() brandName = '';
|
|
33
45
|
@Input() isCollapsed = false;
|
|
34
46
|
@Output() closeSidebar = new EventEmitter<void>();
|
|
35
47
|
|
|
48
|
+
private authService = inject(DEVKIT_AUTH_SERVICE, { optional: true });
|
|
49
|
+
|
|
36
50
|
expandedItems = new Set<string>();
|
|
37
51
|
currentUrl = '';
|
|
38
52
|
|
|
@@ -45,6 +59,36 @@ export class DevkitSidebarComponent implements OnInit {
|
|
|
45
59
|
.subscribe((event: any) => {
|
|
46
60
|
this.currentUrl = event.urlAfterRedirects || event.url;
|
|
47
61
|
});
|
|
62
|
+
this.filterItems();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
filterItems(): void {
|
|
66
|
+
if (!this._items) {
|
|
67
|
+
this.filteredItems = [];
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.filteredItems = this._items.filter(item => {
|
|
72
|
+
if (!item.roles || item.roles.length === 0) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (!this.authService) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return item.roles.some(role => this.authService!.hasPermission(role));
|
|
79
|
+
}).map(item => {
|
|
80
|
+
if (item.children) {
|
|
81
|
+
return {
|
|
82
|
+
...item,
|
|
83
|
+
children: item.children.filter(child => {
|
|
84
|
+
if (!child.roles || child.roles.length === 0) return true;
|
|
85
|
+
if (!this.authService) return true;
|
|
86
|
+
return child.roles.some(role => this.authService!.hasPermission(role));
|
|
87
|
+
})
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return item;
|
|
91
|
+
});
|
|
48
92
|
}
|
|
49
93
|
|
|
50
94
|
toggleExpand(label: string): void {
|
|
@@ -119,7 +163,7 @@ export class DevkitSidebarComponent implements OnInit {
|
|
|
119
163
|
<!-- Menu / Navegação -->
|
|
120
164
|
<nav class="dt-sidebar-nav">
|
|
121
165
|
<ul class="dt-sidebar-list">
|
|
122
|
-
<li *ngFor="let item of
|
|
166
|
+
<li *ngFor="let item of filteredItems">
|
|
123
167
|
|
|
124
168
|
<!-- Link Simples (Sem Filhos) -->
|
|
125
169
|
<a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devstroupe/devkit-cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "DevsTroupe Development Kit CLI — scaffold NestJS+Angular projects, inject Spartan UI, generate CRUDs and audit architectural governance",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"commander": "^12.0.0",
|
|
38
38
|
"fs-extra": "^11.2.0",
|
|
39
|
-
"@devstroupe/devkit-core": "1.1.
|
|
39
|
+
"@devstroupe/devkit-core": "1.1.3"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/fs-extra": "^11.0.4",
|