innovationhub-cli 1.1.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +148 -86
- package/index.js +228 -29
- package/package.json +12 -3
- package/templates/nest/.env.example +41 -0
- package/templates/nest/.prettierrc +4 -0
- package/templates/nest/Dockerfile +17 -0
- package/templates/nest/README.md +224 -0
- package/templates/nest/addons/.github/dependabot.yml +11 -0
- package/templates/nest/addons/.github/labeler.yml +34 -0
- package/templates/nest/addons/.github/workflows/check-branch.yml +24 -0
- package/templates/nest/addons/.github/workflows/ci.yml +29 -0
- package/templates/nest/addons/.github/workflows/draft-release.yml +22 -0
- package/templates/nest/addons/.github/workflows/pr-labeler.yml +16 -0
- package/templates/nest/addons/.github/workflows/release.yml +33 -0
- package/templates/nest/addons/.github/workflows/security-audit.yml +17 -0
- package/templates/nest/addons/.github/workflows/semantic-pr.yml +31 -0
- package/templates/nest/addons/.github/workflows/stale.yml +24 -0
- package/templates/nest/addons/.husky/commit-msg +17 -0
- package/templates/nest/addons/.husky/pre-commit +1 -0
- package/templates/nest/addons/addons.json +17 -0
- package/templates/nest/addons/cloudinary/cloudinary.module.ts +11 -0
- package/templates/nest/addons/cloudinary/cloudinary.provider.ts +16 -0
- package/templates/nest/addons/cloudinary/cloudinary.service.ts +54 -0
- package/templates/nest/docker-compose.yml +58 -0
- package/templates/nest/eslint.config.mjs +34 -0
- package/templates/nest/jest.config.js +17 -0
- package/templates/nest/nest-cli.json +12 -0
- package/templates/nest/package.json +99 -0
- package/templates/nest/src/app.controller.ts +7 -0
- package/templates/nest/src/app.module.ts +69 -0
- package/templates/nest/src/app.service.ts +4 -0
- package/templates/nest/src/auth/auth.controller.ts +97 -0
- package/templates/nest/src/auth/auth.module.ts +46 -0
- package/templates/nest/src/auth/auth.service.ts +231 -0
- package/templates/nest/src/auth/decorators/roles.decorator.ts +5 -0
- package/templates/nest/src/auth/dto/change-password.dto.ts +21 -0
- package/templates/nest/src/auth/dto/login-response.dto.ts +25 -0
- package/templates/nest/src/auth/dto/login.dto.ts +15 -0
- package/templates/nest/src/auth/dto/refresh-token.dto.ts +12 -0
- package/templates/nest/src/auth/entities/refresh-token.entity.ts +18 -0
- package/templates/nest/src/auth/enums/role.enum.ts +4 -0
- package/templates/nest/src/auth/guards/jwt-auth.guard.ts +5 -0
- package/templates/nest/src/auth/guards/refresh-token.guard.ts +5 -0
- package/templates/nest/src/auth/guards/roles.guard.ts +23 -0
- package/templates/nest/src/auth/interfaces/jwt-payload.interface.ts +10 -0
- package/templates/nest/src/auth/strategies/jwt.strategy.ts +28 -0
- package/templates/nest/src/auth/strategies/local.strategy.ts +23 -0
- package/templates/nest/src/auth/strategies/refresh-token.strategy.ts +32 -0
- package/templates/nest/src/common/base.entity.ts +19 -0
- package/templates/nest/src/common/base.repository.ts +79 -0
- package/templates/nest/src/common/base.service.ts +28 -0
- package/templates/nest/src/common/constants/errors.constants.ts +33 -0
- package/templates/nest/src/common/decorators/user.decorator.ts +9 -0
- package/templates/nest/src/common/dto/base-query.dto.ts +56 -0
- package/templates/nest/src/common/irepository.ts +18 -0
- package/templates/nest/src/common/utils/duration.utils.ts +33 -0
- package/templates/nest/src/common/utils/pagination.utils.ts +35 -0
- package/templates/nest/src/common/utils/slug.utils.ts +14 -0
- package/templates/nest/src/common/utils/transform.utils.ts +62 -0
- package/templates/nest/src/common/validators/is-date-after.validator.ts +40 -0
- package/templates/nest/src/data-source.ts +23 -0
- package/templates/nest/src/main.ts +44 -0
- package/templates/nest/src/user/dto/create-user.dto.ts +50 -0
- package/templates/nest/src/user/dto/query-users.dto.ts +23 -0
- package/templates/nest/src/user/dto/update-user.dto.ts +15 -0
- package/templates/nest/src/user/entities/user.entity.ts +66 -0
- package/templates/nest/src/user/user.controller.ts +172 -0
- package/templates/nest/src/user/user.module.ts +15 -0
- package/templates/nest/src/user/user.repository.ts +61 -0
- package/templates/nest/src/user/user.service.ts +138 -0
- package/templates/nest/template.json +5 -0
- package/templates/nest/test/jest-e2e.json +12 -0
- package/templates/nest/tsconfig.build.json +4 -0
- package/templates/nest/tsconfig.json +25 -0
- package/templates/python/.env.example +37 -0
- package/templates/python/Dockerfile +18 -0
- package/templates/python/README.md +219 -0
- package/templates/python/addons/.github/dependabot.yml +11 -0
- package/templates/python/addons/.github/labeler.yml +29 -0
- package/templates/python/addons/.github/workflows/check-branch.yml +24 -0
- package/templates/python/addons/.github/workflows/ci.yml +30 -0
- package/templates/python/addons/.github/workflows/draft-release.yml +22 -0
- package/templates/python/addons/.github/workflows/pr-labeler.yml +16 -0
- package/templates/python/addons/.github/workflows/release.yml +30 -0
- package/templates/python/addons/.github/workflows/security-audit.yml +21 -0
- package/templates/python/addons/.github/workflows/semantic-pr.yml +31 -0
- package/templates/python/addons/.github/workflows/stale.yml +24 -0
- package/templates/python/addons/addons.json +17 -0
- package/templates/python/addons/cloudinary/service.py +67 -0
- package/templates/python/addons/pre-commit/.pre-commit-config.yaml +31 -0
- package/templates/python/alembic/env.py +56 -0
- package/templates/python/alembic/script.py.mako +26 -0
- package/templates/python/alembic/versions/.gitkeep +0 -0
- package/templates/python/alembic.ini +39 -0
- package/templates/python/app/__init__.py +0 -0
- package/templates/python/app/auth/__init__.py +5 -0
- package/templates/python/app/auth/dependencies.py +118 -0
- package/templates/python/app/auth/enums.py +6 -0
- package/templates/python/app/auth/models.py +18 -0
- package/templates/python/app/auth/router.py +68 -0
- package/templates/python/app/auth/schemas.py +58 -0
- package/templates/python/app/auth/service.py +180 -0
- package/templates/python/app/common/__init__.py +18 -0
- package/templates/python/app/common/base_model.py +26 -0
- package/templates/python/app/common/base_repository.py +83 -0
- package/templates/python/app/common/errors.py +35 -0
- package/templates/python/app/common/pagination.py +22 -0
- package/templates/python/app/common/schemas.py +20 -0
- package/templates/python/app/common/utils.py +15 -0
- package/templates/python/app/core/__init__.py +4 -0
- package/templates/python/app/core/config.py +55 -0
- package/templates/python/app/core/database.py +20 -0
- package/templates/python/app/main.py +33 -0
- package/templates/python/app/user/__init__.py +4 -0
- package/templates/python/app/user/models.py +26 -0
- package/templates/python/app/user/repository.py +84 -0
- package/templates/python/app/user/router.py +170 -0
- package/templates/python/app/user/schemas.py +60 -0
- package/templates/python/app/user/service.py +114 -0
- package/templates/python/docker-compose.yml +55 -0
- package/templates/python/pyproject.toml +46 -0
- package/templates/python/requirements-dev.txt +7 -0
- package/templates/python/requirements.txt +20 -0
- package/templates/python/template.json +5 -0
- package/utils/template.js +165 -0
- package/utils/git.js +0 -71
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
2
|
+
import {
|
|
3
|
+
IsEmail,
|
|
4
|
+
IsNotEmpty,
|
|
5
|
+
IsOptional,
|
|
6
|
+
IsBoolean,
|
|
7
|
+
IsEnum,
|
|
8
|
+
} from 'class-validator';
|
|
9
|
+
import { Role } from '../../auth/enums/role.enum';
|
|
10
|
+
import { Transform } from 'class-transformer';
|
|
11
|
+
|
|
12
|
+
export class CreateUserDto {
|
|
13
|
+
@ApiProperty({
|
|
14
|
+
description: 'O endereço de e-mail do usuário',
|
|
15
|
+
example: 'joao.silva@exemplo.com',
|
|
16
|
+
})
|
|
17
|
+
@IsEmail()
|
|
18
|
+
email!: string;
|
|
19
|
+
|
|
20
|
+
@ApiProperty({
|
|
21
|
+
description: 'O nome completo do usuário',
|
|
22
|
+
example: 'João da Silva',
|
|
23
|
+
})
|
|
24
|
+
@IsNotEmpty()
|
|
25
|
+
name!: string;
|
|
26
|
+
|
|
27
|
+
@ApiProperty({
|
|
28
|
+
description: 'Status de ativação do usuário',
|
|
29
|
+
default: true,
|
|
30
|
+
required: false,
|
|
31
|
+
})
|
|
32
|
+
@Transform(({ value }: { value: string | boolean }) => {
|
|
33
|
+
if (value === 'true') return true;
|
|
34
|
+
if (value === 'false') return false;
|
|
35
|
+
return value;
|
|
36
|
+
})
|
|
37
|
+
@IsOptional()
|
|
38
|
+
@IsBoolean()
|
|
39
|
+
isActive?: boolean;
|
|
40
|
+
|
|
41
|
+
@ApiProperty({
|
|
42
|
+
description: 'A função do novo usuário (opcional, padrão será "user").',
|
|
43
|
+
enum: Role,
|
|
44
|
+
required: false,
|
|
45
|
+
default: Role.User,
|
|
46
|
+
})
|
|
47
|
+
@IsOptional()
|
|
48
|
+
@IsEnum(Role)
|
|
49
|
+
role?: Role;
|
|
50
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
import { IsIn, IsOptional, IsString } from 'class-validator';
|
|
3
|
+
import { BaseQueryDto } from '../../common/dto/base-query.dto';
|
|
4
|
+
|
|
5
|
+
export class QueryUsersDto extends BaseQueryDto {
|
|
6
|
+
@ApiPropertyOptional({
|
|
7
|
+
description: 'Coluna pela qual os resultados serão ordenados.',
|
|
8
|
+
default: 'id',
|
|
9
|
+
example: 'name',
|
|
10
|
+
enum: ['id', 'name', 'email', 'isActive', 'role', 'mustChangePassword'],
|
|
11
|
+
})
|
|
12
|
+
@IsOptional()
|
|
13
|
+
@IsString()
|
|
14
|
+
@IsIn(['id', 'name', 'email', 'isActive', 'role', 'mustChangePassword'])
|
|
15
|
+
override sortBy?: string = 'id';
|
|
16
|
+
|
|
17
|
+
@ApiPropertyOptional({
|
|
18
|
+
description: 'Ordem da classificação (ascendente ou descendente).',
|
|
19
|
+
default: 'ASC',
|
|
20
|
+
enum: ['ASC', 'DESC'],
|
|
21
|
+
})
|
|
22
|
+
override sortOrder?: 'ASC' | 'DESC' = 'ASC';
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PartialType } from '@nestjs/swagger';
|
|
2
|
+
import { CreateUserDto } from './create-user.dto';
|
|
3
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
4
|
+
import { IsOptional, IsString } from 'class-validator';
|
|
5
|
+
|
|
6
|
+
export class UpdateUserDto extends PartialType(CreateUserDto) {
|
|
7
|
+
@ApiProperty({
|
|
8
|
+
description: 'O número de telefone do usuário.',
|
|
9
|
+
example: '(11) 99999-9999',
|
|
10
|
+
required: false,
|
|
11
|
+
})
|
|
12
|
+
@IsOptional()
|
|
13
|
+
@IsString()
|
|
14
|
+
phone?: string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Column, DeleteDateColumn, Entity, OneToMany } from 'typeorm';
|
|
2
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
3
|
+
import { Role } from '../../auth/enums/role.enum';
|
|
4
|
+
import { Exclude, Expose } from 'class-transformer';
|
|
5
|
+
import { BaseEntity } from '../../common/base.entity';
|
|
6
|
+
import { RefreshTokenEntity } from '../../auth/entities/refresh-token.entity';
|
|
7
|
+
|
|
8
|
+
@Entity('users')
|
|
9
|
+
@Exclude()
|
|
10
|
+
export class UserEntity extends BaseEntity {
|
|
11
|
+
@Expose()
|
|
12
|
+
@ApiProperty({
|
|
13
|
+
description: 'E-mail do usuário',
|
|
14
|
+
example: 'user@example.com',
|
|
15
|
+
})
|
|
16
|
+
@Column({ unique: true })
|
|
17
|
+
email: string;
|
|
18
|
+
|
|
19
|
+
@Expose()
|
|
20
|
+
@ApiProperty({ description: 'Nome do usuário', example: 'João Silva' })
|
|
21
|
+
@Column()
|
|
22
|
+
name: string;
|
|
23
|
+
|
|
24
|
+
@Expose()
|
|
25
|
+
@ApiProperty({
|
|
26
|
+
description: 'Telefone do usuário',
|
|
27
|
+
example: '+55 11 99999-9999',
|
|
28
|
+
required: false,
|
|
29
|
+
})
|
|
30
|
+
@Column({ nullable: true })
|
|
31
|
+
phone?: string;
|
|
32
|
+
|
|
33
|
+
@Column({ select: false })
|
|
34
|
+
password: string;
|
|
35
|
+
|
|
36
|
+
@Expose()
|
|
37
|
+
@Column({
|
|
38
|
+
type: 'boolean',
|
|
39
|
+
default: true,
|
|
40
|
+
})
|
|
41
|
+
isActive: boolean;
|
|
42
|
+
|
|
43
|
+
@Expose()
|
|
44
|
+
@ApiProperty({ enum: Role, description: 'Papel do usuário no sistema' })
|
|
45
|
+
@Column({
|
|
46
|
+
type: 'enum',
|
|
47
|
+
enum: Role,
|
|
48
|
+
default: Role.User,
|
|
49
|
+
})
|
|
50
|
+
role: Role;
|
|
51
|
+
|
|
52
|
+
@Expose()
|
|
53
|
+
@Column({
|
|
54
|
+
name: 'must_change_password',
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
default: false,
|
|
57
|
+
})
|
|
58
|
+
mustChangePassword: boolean;
|
|
59
|
+
|
|
60
|
+
@OneToMany(() => RefreshTokenEntity, (refreshToken) => refreshToken.user)
|
|
61
|
+
refreshTokens: RefreshTokenEntity[];
|
|
62
|
+
|
|
63
|
+
@Expose()
|
|
64
|
+
@DeleteDateColumn()
|
|
65
|
+
deletedAt: Date;
|
|
66
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Controller,
|
|
3
|
+
Get,
|
|
4
|
+
Post,
|
|
5
|
+
Body,
|
|
6
|
+
UseGuards,
|
|
7
|
+
Param,
|
|
8
|
+
Patch,
|
|
9
|
+
HttpCode,
|
|
10
|
+
NotFoundException,
|
|
11
|
+
HttpStatus,
|
|
12
|
+
Query,
|
|
13
|
+
Req,
|
|
14
|
+
Delete,
|
|
15
|
+
} from '@nestjs/common';
|
|
16
|
+
import { ERRORS } from '../common/constants/errors.constants';
|
|
17
|
+
import { ApiTags, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger';
|
|
18
|
+
import { UserService } from './user.service';
|
|
19
|
+
import { UserEntity } from './entities/user.entity';
|
|
20
|
+
import { UpdateUserDto } from './dto/update-user.dto';
|
|
21
|
+
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
|
22
|
+
import { CreateUserDto } from './dto/create-user.dto';
|
|
23
|
+
import { RolesGuard } from '../auth/guards/roles.guard';
|
|
24
|
+
import { Roles } from '../auth/decorators/roles.decorator';
|
|
25
|
+
import { Role } from '../auth/enums/role.enum';
|
|
26
|
+
import { QueryUsersDto } from './dto/query-users.dto';
|
|
27
|
+
|
|
28
|
+
@ApiTags('users')
|
|
29
|
+
@Controller('users')
|
|
30
|
+
export class UserController {
|
|
31
|
+
constructor(private readonly userService: UserService) {}
|
|
32
|
+
|
|
33
|
+
@Post()
|
|
34
|
+
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
35
|
+
@Roles(Role.Admin)
|
|
36
|
+
@ApiOperation({ summary: 'Cria um novo usuário (admin)' })
|
|
37
|
+
@ApiBody({
|
|
38
|
+
type: CreateUserDto,
|
|
39
|
+
})
|
|
40
|
+
@ApiResponse({
|
|
41
|
+
status: 201,
|
|
42
|
+
description: 'Usuário criado com sucesso.',
|
|
43
|
+
type: UserEntity,
|
|
44
|
+
})
|
|
45
|
+
async create(@Body() createUserDto: CreateUserDto) {
|
|
46
|
+
return this.userService.create(createUserDto);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Get('me')
|
|
50
|
+
@UseGuards(JwtAuthGuard)
|
|
51
|
+
@ApiOperation({ summary: 'Retorna o perfil do usuário logado' })
|
|
52
|
+
@ApiResponse({
|
|
53
|
+
status: 200,
|
|
54
|
+
description: 'Perfil do usuário.',
|
|
55
|
+
type: UserEntity,
|
|
56
|
+
})
|
|
57
|
+
async getMe(@Req() req: { user: { id: string } }): Promise<UserEntity> {
|
|
58
|
+
const id = req.user.id;
|
|
59
|
+
const user = await this.userService.findById(id);
|
|
60
|
+
if (!user) {
|
|
61
|
+
throw new NotFoundException(ERRORS.USER.NOT_FOUND);
|
|
62
|
+
}
|
|
63
|
+
return user;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Patch('me')
|
|
67
|
+
@UseGuards(JwtAuthGuard)
|
|
68
|
+
@ApiOperation({ summary: 'Atualiza o perfil do usuário logado' })
|
|
69
|
+
@ApiBody({
|
|
70
|
+
type: UpdateUserDto,
|
|
71
|
+
})
|
|
72
|
+
@HttpCode(200)
|
|
73
|
+
@ApiResponse({
|
|
74
|
+
status: 200,
|
|
75
|
+
description: 'Perfil atualizado com sucesso.',
|
|
76
|
+
type: UserEntity,
|
|
77
|
+
})
|
|
78
|
+
async updateMe(
|
|
79
|
+
@Req() req: { user: { id: string } },
|
|
80
|
+
@Body() updateUserDto: UpdateUserDto,
|
|
81
|
+
): Promise<UserEntity> {
|
|
82
|
+
const id = req.user.id;
|
|
83
|
+
await this.userService.updateProfile(id, updateUserDto);
|
|
84
|
+
return this.getMe(req);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Get()
|
|
88
|
+
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
89
|
+
@Roles(Role.Admin)
|
|
90
|
+
@ApiOperation({ summary: 'Busca todos os usuários' })
|
|
91
|
+
@ApiResponse({
|
|
92
|
+
status: 200,
|
|
93
|
+
description: 'Lista de usuários retornada com sucesso.',
|
|
94
|
+
})
|
|
95
|
+
findAll() {
|
|
96
|
+
return this.userService.findAll();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Get('paginated')
|
|
100
|
+
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
101
|
+
@Roles(Role.Admin)
|
|
102
|
+
@ApiOperation({ summary: 'Busca todos os usuários com paginação' })
|
|
103
|
+
@ApiResponse({
|
|
104
|
+
status: 200,
|
|
105
|
+
description: 'Lista de usuários retornada com sucesso.',
|
|
106
|
+
})
|
|
107
|
+
findAndCountUsers(@Query() query: QueryUsersDto) {
|
|
108
|
+
return this.userService.findAndCountUsers(query);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@Get('/:id')
|
|
112
|
+
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
113
|
+
@Roles(Role.Admin)
|
|
114
|
+
@ApiOperation({ summary: 'Busca usuários pelo Id' })
|
|
115
|
+
@ApiResponse({
|
|
116
|
+
status: 200,
|
|
117
|
+
description: 'Usuário encontrado.',
|
|
118
|
+
type: UserEntity,
|
|
119
|
+
})
|
|
120
|
+
@ApiResponse({ status: 404, description: 'Usuário não encontrado.' })
|
|
121
|
+
async findById(@Param('id') id: string): Promise<UserEntity> {
|
|
122
|
+
const user = await this.userService.findById(id);
|
|
123
|
+
if (!user) {
|
|
124
|
+
throw new NotFoundException(ERRORS.USER.NOT_FOUND);
|
|
125
|
+
}
|
|
126
|
+
return user;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@Patch(':id')
|
|
130
|
+
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
131
|
+
@Roles(Role.Admin)
|
|
132
|
+
@ApiOperation({ summary: 'Atualiza um usuário pelo ID (admin)' })
|
|
133
|
+
@ApiBody({
|
|
134
|
+
type: UpdateUserDto,
|
|
135
|
+
})
|
|
136
|
+
@HttpCode(204)
|
|
137
|
+
@ApiResponse({ status: 204, description: 'Usuário atualizado com sucesso.' })
|
|
138
|
+
async update(
|
|
139
|
+
@Param('id') id: string,
|
|
140
|
+
@Body() updateUserDto: UpdateUserDto,
|
|
141
|
+
): Promise<void> {
|
|
142
|
+
await this.userService.updateProfile(id, updateUserDto);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@Patch(':id/reset-password')
|
|
146
|
+
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
147
|
+
@Roles(Role.Admin)
|
|
148
|
+
@HttpCode(HttpStatus.OK)
|
|
149
|
+
@ApiOperation({ summary: 'Reseta a senha de um usuário (admin)' })
|
|
150
|
+
@ApiResponse({
|
|
151
|
+
status: 200,
|
|
152
|
+
description:
|
|
153
|
+
'Senha resetada com sucesso. A nova senha foi enviada para o e-mail do usuário.',
|
|
154
|
+
})
|
|
155
|
+
@ApiResponse({ status: 404, description: 'Usuário não encontrado.' })
|
|
156
|
+
async resetPasswordByAdmin(
|
|
157
|
+
@Param('id') id: string,
|
|
158
|
+
): Promise<{ message: string }> {
|
|
159
|
+
return this.userService.resetPasswordByAdmin(id);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@Delete(':id')
|
|
163
|
+
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
164
|
+
@Roles(Role.Admin)
|
|
165
|
+
@HttpCode(HttpStatus.NO_CONTENT)
|
|
166
|
+
@ApiOperation({ summary: 'Deleta um usuário (admin)' })
|
|
167
|
+
@ApiResponse({ status: 204, description: 'Usuário deletado com sucesso.' })
|
|
168
|
+
@ApiResponse({ status: 404, description: 'Usuário não encontrado.' })
|
|
169
|
+
async delete(@Param('id') id: string): Promise<void> {
|
|
170
|
+
await this.userService.delete(id);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
3
|
+
import { UserController } from './user.controller';
|
|
4
|
+
import { UserService } from './user.service';
|
|
5
|
+
|
|
6
|
+
import { UserEntity } from './entities/user.entity';
|
|
7
|
+
import { UsersRepository } from './user.repository';
|
|
8
|
+
|
|
9
|
+
@Module({
|
|
10
|
+
imports: [TypeOrmModule.forFeature([UserEntity])],
|
|
11
|
+
controllers: [UserController],
|
|
12
|
+
providers: [UserService, UsersRepository],
|
|
13
|
+
exports: [UserService, UsersRepository],
|
|
14
|
+
})
|
|
15
|
+
export class UserModule {}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
3
|
+
import { Brackets, Repository, IsNull } from 'typeorm';
|
|
4
|
+
import { BaseRepository } from '../common/base.repository';
|
|
5
|
+
import { UserEntity } from './entities/user.entity';
|
|
6
|
+
import { QueryUsersDto } from './dto/query-users.dto';
|
|
7
|
+
import { buildPaginationMeta } from '../common/utils/pagination.utils';
|
|
8
|
+
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class UsersRepository extends BaseRepository<UserEntity> {
|
|
11
|
+
constructor(
|
|
12
|
+
@InjectRepository(UserEntity)
|
|
13
|
+
repository: Repository<UserEntity>,
|
|
14
|
+
) {
|
|
15
|
+
super(repository);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async findById(id: string): Promise<UserEntity | null> {
|
|
19
|
+
if (!id) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return this.repo.findOne({
|
|
23
|
+
where: { id, deletedAt: IsNull() },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async findAll(): Promise<UserEntity[]> {
|
|
28
|
+
return this.repo.find({
|
|
29
|
+
where: { deletedAt: IsNull() },
|
|
30
|
+
order: { createdAt: 'DESC' },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async findAndCountUsers(queryUsersDto: QueryUsersDto) {
|
|
35
|
+
const { page, limit, sortBy, sortOrder, search } = queryUsersDto;
|
|
36
|
+
|
|
37
|
+
const queryBuilder = this.repo.createQueryBuilder('user');
|
|
38
|
+
|
|
39
|
+
if (search) {
|
|
40
|
+
queryBuilder.andWhere(
|
|
41
|
+
new Brackets((qb) => {
|
|
42
|
+
qb.where('user.name ILIKE :search', {
|
|
43
|
+
search: `%${search}%`,
|
|
44
|
+
}).orWhere('user.email ILIKE :search', { search: `%${search}%` });
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
queryBuilder.orderBy(`user.${sortBy}`, sortOrder);
|
|
50
|
+
|
|
51
|
+
const offset = (page - 1) * limit;
|
|
52
|
+
queryBuilder.skip(offset).take(limit);
|
|
53
|
+
|
|
54
|
+
const [users, totalItems] = await queryBuilder.getManyAndCount();
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
data: users,
|
|
58
|
+
meta: buildPaginationMeta(totalItems, page, limit, users.length),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BadRequestException,
|
|
3
|
+
Injectable,
|
|
4
|
+
InternalServerErrorException,
|
|
5
|
+
NotFoundException,
|
|
6
|
+
} from '@nestjs/common';
|
|
7
|
+
import { ERRORS } from '../common/constants/errors.constants';
|
|
8
|
+
import { UserEntity } from './entities/user.entity';
|
|
9
|
+
import * as bcrypt from 'bcryptjs';
|
|
10
|
+
import { CreateUserDto } from './dto/create-user.dto';
|
|
11
|
+
import { ConfigService } from '@nestjs/config';
|
|
12
|
+
import { QueryUsersDto } from './dto/query-users.dto';
|
|
13
|
+
import { UsersRepository } from './user.repository';
|
|
14
|
+
import { BaseService } from '../common/base.service';
|
|
15
|
+
import { UpdateUserDto } from './dto/update-user.dto';
|
|
16
|
+
|
|
17
|
+
@Injectable()
|
|
18
|
+
export class UserService extends BaseService<UserEntity> {
|
|
19
|
+
constructor(
|
|
20
|
+
private usersRepository: UsersRepository,
|
|
21
|
+
private configService: ConfigService,
|
|
22
|
+
) {
|
|
23
|
+
super(usersRepository);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async create(newUser: CreateUserDto): Promise<UserEntity> {
|
|
27
|
+
const existingUser = await this.findOneByEmail(newUser.email);
|
|
28
|
+
|
|
29
|
+
if (existingUser) {
|
|
30
|
+
throw new BadRequestException(
|
|
31
|
+
`${ERRORS.USER.EMAIL_IN_USE} (Email: ${newUser.email})`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const defaultPassword = this.configService.get<string>('DEFAULT_PASSWORD');
|
|
36
|
+
if (!defaultPassword) {
|
|
37
|
+
throw new InternalServerErrorException(
|
|
38
|
+
ERRORS.USER.DEFAULT_PASSWORD_NOT_SET,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const hashedPassword = await bcrypt.hash(defaultPassword, 12);
|
|
43
|
+
|
|
44
|
+
return super.create({
|
|
45
|
+
...newUser,
|
|
46
|
+
password: hashedPassword,
|
|
47
|
+
mustChangePassword: true,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async updateProfile(
|
|
52
|
+
id: string,
|
|
53
|
+
updateUserDto: UpdateUserDto,
|
|
54
|
+
): Promise<UserEntity | null> {
|
|
55
|
+
const user = await this.usersRepository.findById(id);
|
|
56
|
+
if (!user) {
|
|
57
|
+
throw new NotFoundException(ERRORS.USER.NOT_FOUND);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const dataToUpdate: UpdateUserDto = { ...updateUserDto };
|
|
61
|
+
Object.assign(user, dataToUpdate);
|
|
62
|
+
|
|
63
|
+
return this.usersRepository.repo.save(user);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async findAndCountUsers(queryUsersDto: QueryUsersDto) {
|
|
67
|
+
return this.usersRepository.findAndCountUsers(queryUsersDto);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async findOneByEmail(email: string): Promise<UserEntity | null> {
|
|
71
|
+
return this.usersRepository.repo.findOne({
|
|
72
|
+
where: { email: email },
|
|
73
|
+
select: ['id', 'email', 'name', 'isActive', 'role', 'mustChangePassword'],
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async findOneByEmailWithPassword(email: string): Promise<UserEntity | null> {
|
|
78
|
+
return this.usersRepository.repo.findOne({
|
|
79
|
+
where: { email },
|
|
80
|
+
select: [
|
|
81
|
+
'id',
|
|
82
|
+
'email',
|
|
83
|
+
'name',
|
|
84
|
+
'isActive',
|
|
85
|
+
'role',
|
|
86
|
+
'mustChangePassword',
|
|
87
|
+
'password',
|
|
88
|
+
'deletedAt',
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async findOneByIdWithPassword(id: string): Promise<UserEntity | null> {
|
|
94
|
+
return this.usersRepository.repo.findOne({
|
|
95
|
+
where: { id },
|
|
96
|
+
select: ['id', 'email', 'name', 'password', 'mustChangePassword'],
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async comparePassword(plainPassword: string, hash: string): Promise<boolean> {
|
|
101
|
+
return bcrypt.compare(plainPassword, hash);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async updatePassword(id: string, newPasswordHash: string): Promise<void> {
|
|
105
|
+
await this.update(id, { password: newPasswordHash });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async resetPasswordByAdmin(id: string): Promise<{ message: string }> {
|
|
109
|
+
const user = await this.findById(id);
|
|
110
|
+
|
|
111
|
+
if (!user) throw new NotFoundException(ERRORS.USER.NOT_FOUND);
|
|
112
|
+
|
|
113
|
+
const defaultPassword = this.configService.get<string>('DEFAULT_PASSWORD');
|
|
114
|
+
if (!defaultPassword)
|
|
115
|
+
throw new InternalServerErrorException(
|
|
116
|
+
ERRORS.USER.DEFAULT_PASSWORD_NOT_SET,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const hashedPassword = await bcrypt.hash(defaultPassword, 12);
|
|
120
|
+
|
|
121
|
+
await this.update(id, {
|
|
122
|
+
password: hashedPassword,
|
|
123
|
+
mustChangePassword: true,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
message: `A senha do usuário ${user.name} foi resetada com sucesso`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async disableMustChangePasswordFlag(id: string): Promise<void> {
|
|
132
|
+
await this.update(id, { mustChangePassword: false });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async delete(id: string): Promise<void> {
|
|
136
|
+
await this.usersRepository.repo.softDelete(id);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "nodenext",
|
|
4
|
+
"moduleResolution": "nodenext",
|
|
5
|
+
"resolvePackageJsonExports": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"isolatedModules": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"removeComments": true,
|
|
10
|
+
"emitDecoratorMetadata": true,
|
|
11
|
+
"experimentalDecorators": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"target": "ES2023",
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"outDir": "./dist",
|
|
16
|
+
"baseUrl": "./",
|
|
17
|
+
"incremental": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"strictNullChecks": true,
|
|
20
|
+
"forceConsistentCasingInFileNames": true,
|
|
21
|
+
"noImplicitAny": false,
|
|
22
|
+
"strictBindCallApply": false,
|
|
23
|
+
"noFallthroughCasesInSwitch": false
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Configurações do Postgres - Docker (OBRIGATÓRIAS)
|
|
2
|
+
POSTGRES_USER=postgres
|
|
3
|
+
POSTGRES_PASSWORD=docker
|
|
4
|
+
POSTGRES_DB=innovationhub
|
|
5
|
+
|
|
6
|
+
# Configurações do Banco de Dados
|
|
7
|
+
DATABASE_HOST=postgres
|
|
8
|
+
DATABASE_PORT=5432
|
|
9
|
+
DATABASE_USERNAME=postgres
|
|
10
|
+
DATABASE_PASSWORD=docker
|
|
11
|
+
DATABASE_NAME=innovationhub
|
|
12
|
+
DATABASE_SSL=false
|
|
13
|
+
|
|
14
|
+
# Configurações JWT
|
|
15
|
+
JWT_SECRET=codigo-longo-aqui
|
|
16
|
+
JWT_EXPIRATION_MINUTES=15
|
|
17
|
+
JWT_REFRESH_SECRET=outro-codigo-longo-aqui
|
|
18
|
+
JWT_REFRESH_EXPIRATION_DAYS=7
|
|
19
|
+
|
|
20
|
+
# Senha padrão para novos usuários criados pelo admin ou para reset de senha
|
|
21
|
+
DEFAULT_PASSWORD=ih123
|
|
22
|
+
|
|
23
|
+
# variavel de ambiente para configuração do CORS
|
|
24
|
+
CORS_ORIGIN=http://localhost:3001
|
|
25
|
+
|
|
26
|
+
# Configurações do Cloudinary
|
|
27
|
+
CLOUDINARY_CLOUD_NAME=####SEU_NOME_DE_NUVEM_DO_CLOUDINARY####
|
|
28
|
+
CLOUDINARY_API_KEY=####SUA_CHAVE_DE_API_DO_CLOUDINARY####
|
|
29
|
+
CLOUDINARY_API_SECRET=####SUA_CHAVE_SECRETA_DO_CLOUDINARY####
|
|
30
|
+
|
|
31
|
+
# Serviço de email
|
|
32
|
+
MAIL_HOST=smtp.gmail.com
|
|
33
|
+
MAIL_PORT=587
|
|
34
|
+
MAIL_USER=noreply@innovationhub.com
|
|
35
|
+
MAIL_PASSWORD=#xxxx_xxxx_xxxx_xxxx
|
|
36
|
+
MAIL_FROM="Innovation Hub <noreply@innovationhub.com>"
|
|
37
|
+
MAIL_DESTINATION=innovationhub@gmail.com
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
FROM python:3.11-slim AS base
|
|
2
|
+
WORKDIR /app
|
|
3
|
+
|
|
4
|
+
COPY pyproject.toml ./
|
|
5
|
+
RUN pip install --no-cache-dir .
|
|
6
|
+
|
|
7
|
+
FROM base AS production
|
|
8
|
+
WORKDIR /app
|
|
9
|
+
COPY --from=base /usr/local /usr/local
|
|
10
|
+
COPY . .
|
|
11
|
+
RUN alembic upgrade head || true
|
|
12
|
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "3000"]
|
|
13
|
+
|
|
14
|
+
FROM base AS development
|
|
15
|
+
WORKDIR /app
|
|
16
|
+
COPY --from=base /usr/local /usr/local
|
|
17
|
+
COPY . .
|
|
18
|
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "3000", "--reload"]
|