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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -86
  3. package/index.js +228 -29
  4. package/package.json +12 -3
  5. package/templates/nest/.env.example +41 -0
  6. package/templates/nest/.prettierrc +4 -0
  7. package/templates/nest/Dockerfile +17 -0
  8. package/templates/nest/README.md +224 -0
  9. package/templates/nest/addons/.github/dependabot.yml +11 -0
  10. package/templates/nest/addons/.github/labeler.yml +34 -0
  11. package/templates/nest/addons/.github/workflows/check-branch.yml +24 -0
  12. package/templates/nest/addons/.github/workflows/ci.yml +29 -0
  13. package/templates/nest/addons/.github/workflows/draft-release.yml +22 -0
  14. package/templates/nest/addons/.github/workflows/pr-labeler.yml +16 -0
  15. package/templates/nest/addons/.github/workflows/release.yml +33 -0
  16. package/templates/nest/addons/.github/workflows/security-audit.yml +17 -0
  17. package/templates/nest/addons/.github/workflows/semantic-pr.yml +31 -0
  18. package/templates/nest/addons/.github/workflows/stale.yml +24 -0
  19. package/templates/nest/addons/.husky/commit-msg +17 -0
  20. package/templates/nest/addons/.husky/pre-commit +1 -0
  21. package/templates/nest/addons/addons.json +17 -0
  22. package/templates/nest/addons/cloudinary/cloudinary.module.ts +11 -0
  23. package/templates/nest/addons/cloudinary/cloudinary.provider.ts +16 -0
  24. package/templates/nest/addons/cloudinary/cloudinary.service.ts +54 -0
  25. package/templates/nest/docker-compose.yml +58 -0
  26. package/templates/nest/eslint.config.mjs +34 -0
  27. package/templates/nest/jest.config.js +17 -0
  28. package/templates/nest/nest-cli.json +12 -0
  29. package/templates/nest/package.json +99 -0
  30. package/templates/nest/src/app.controller.ts +7 -0
  31. package/templates/nest/src/app.module.ts +69 -0
  32. package/templates/nest/src/app.service.ts +4 -0
  33. package/templates/nest/src/auth/auth.controller.ts +97 -0
  34. package/templates/nest/src/auth/auth.module.ts +46 -0
  35. package/templates/nest/src/auth/auth.service.ts +231 -0
  36. package/templates/nest/src/auth/decorators/roles.decorator.ts +5 -0
  37. package/templates/nest/src/auth/dto/change-password.dto.ts +21 -0
  38. package/templates/nest/src/auth/dto/login-response.dto.ts +25 -0
  39. package/templates/nest/src/auth/dto/login.dto.ts +15 -0
  40. package/templates/nest/src/auth/dto/refresh-token.dto.ts +12 -0
  41. package/templates/nest/src/auth/entities/refresh-token.entity.ts +18 -0
  42. package/templates/nest/src/auth/enums/role.enum.ts +4 -0
  43. package/templates/nest/src/auth/guards/jwt-auth.guard.ts +5 -0
  44. package/templates/nest/src/auth/guards/refresh-token.guard.ts +5 -0
  45. package/templates/nest/src/auth/guards/roles.guard.ts +23 -0
  46. package/templates/nest/src/auth/interfaces/jwt-payload.interface.ts +10 -0
  47. package/templates/nest/src/auth/strategies/jwt.strategy.ts +28 -0
  48. package/templates/nest/src/auth/strategies/local.strategy.ts +23 -0
  49. package/templates/nest/src/auth/strategies/refresh-token.strategy.ts +32 -0
  50. package/templates/nest/src/common/base.entity.ts +19 -0
  51. package/templates/nest/src/common/base.repository.ts +79 -0
  52. package/templates/nest/src/common/base.service.ts +28 -0
  53. package/templates/nest/src/common/constants/errors.constants.ts +33 -0
  54. package/templates/nest/src/common/decorators/user.decorator.ts +9 -0
  55. package/templates/nest/src/common/dto/base-query.dto.ts +56 -0
  56. package/templates/nest/src/common/irepository.ts +18 -0
  57. package/templates/nest/src/common/utils/duration.utils.ts +33 -0
  58. package/templates/nest/src/common/utils/pagination.utils.ts +35 -0
  59. package/templates/nest/src/common/utils/slug.utils.ts +14 -0
  60. package/templates/nest/src/common/utils/transform.utils.ts +62 -0
  61. package/templates/nest/src/common/validators/is-date-after.validator.ts +40 -0
  62. package/templates/nest/src/data-source.ts +23 -0
  63. package/templates/nest/src/main.ts +44 -0
  64. package/templates/nest/src/user/dto/create-user.dto.ts +50 -0
  65. package/templates/nest/src/user/dto/query-users.dto.ts +23 -0
  66. package/templates/nest/src/user/dto/update-user.dto.ts +15 -0
  67. package/templates/nest/src/user/entities/user.entity.ts +66 -0
  68. package/templates/nest/src/user/user.controller.ts +172 -0
  69. package/templates/nest/src/user/user.module.ts +15 -0
  70. package/templates/nest/src/user/user.repository.ts +61 -0
  71. package/templates/nest/src/user/user.service.ts +138 -0
  72. package/templates/nest/template.json +5 -0
  73. package/templates/nest/test/jest-e2e.json +12 -0
  74. package/templates/nest/tsconfig.build.json +4 -0
  75. package/templates/nest/tsconfig.json +25 -0
  76. package/templates/python/.env.example +37 -0
  77. package/templates/python/Dockerfile +18 -0
  78. package/templates/python/README.md +219 -0
  79. package/templates/python/addons/.github/dependabot.yml +11 -0
  80. package/templates/python/addons/.github/labeler.yml +29 -0
  81. package/templates/python/addons/.github/workflows/check-branch.yml +24 -0
  82. package/templates/python/addons/.github/workflows/ci.yml +30 -0
  83. package/templates/python/addons/.github/workflows/draft-release.yml +22 -0
  84. package/templates/python/addons/.github/workflows/pr-labeler.yml +16 -0
  85. package/templates/python/addons/.github/workflows/release.yml +30 -0
  86. package/templates/python/addons/.github/workflows/security-audit.yml +21 -0
  87. package/templates/python/addons/.github/workflows/semantic-pr.yml +31 -0
  88. package/templates/python/addons/.github/workflows/stale.yml +24 -0
  89. package/templates/python/addons/addons.json +17 -0
  90. package/templates/python/addons/cloudinary/service.py +67 -0
  91. package/templates/python/addons/pre-commit/.pre-commit-config.yaml +31 -0
  92. package/templates/python/alembic/env.py +56 -0
  93. package/templates/python/alembic/script.py.mako +26 -0
  94. package/templates/python/alembic/versions/.gitkeep +0 -0
  95. package/templates/python/alembic.ini +39 -0
  96. package/templates/python/app/__init__.py +0 -0
  97. package/templates/python/app/auth/__init__.py +5 -0
  98. package/templates/python/app/auth/dependencies.py +118 -0
  99. package/templates/python/app/auth/enums.py +6 -0
  100. package/templates/python/app/auth/models.py +18 -0
  101. package/templates/python/app/auth/router.py +68 -0
  102. package/templates/python/app/auth/schemas.py +58 -0
  103. package/templates/python/app/auth/service.py +180 -0
  104. package/templates/python/app/common/__init__.py +18 -0
  105. package/templates/python/app/common/base_model.py +26 -0
  106. package/templates/python/app/common/base_repository.py +83 -0
  107. package/templates/python/app/common/errors.py +35 -0
  108. package/templates/python/app/common/pagination.py +22 -0
  109. package/templates/python/app/common/schemas.py +20 -0
  110. package/templates/python/app/common/utils.py +15 -0
  111. package/templates/python/app/core/__init__.py +4 -0
  112. package/templates/python/app/core/config.py +55 -0
  113. package/templates/python/app/core/database.py +20 -0
  114. package/templates/python/app/main.py +33 -0
  115. package/templates/python/app/user/__init__.py +4 -0
  116. package/templates/python/app/user/models.py +26 -0
  117. package/templates/python/app/user/repository.py +84 -0
  118. package/templates/python/app/user/router.py +170 -0
  119. package/templates/python/app/user/schemas.py +60 -0
  120. package/templates/python/app/user/service.py +114 -0
  121. package/templates/python/docker-compose.yml +55 -0
  122. package/templates/python/pyproject.toml +46 -0
  123. package/templates/python/requirements-dev.txt +7 -0
  124. package/templates/python/requirements.txt +20 -0
  125. package/templates/python/template.json +5 -0
  126. package/utils/template.js +165 -0
  127. 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,5 @@
1
+ {
2
+ "label": "NestJS",
3
+ "description": "Backend Node.js com TypeScript, TypeORM, JWT Auth",
4
+ "installCmd": "npm install"
5
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "moduleFileExtensions": ["js", "json", "ts"],
3
+ "rootDir": ".",
4
+ "testEnvironment": "node",
5
+ "testRegex": ".e2e-spec.ts$",
6
+ "transform": {
7
+ "^.+\\.(t|j)s$": "ts-jest"
8
+ },
9
+ "moduleNameMapper": {
10
+ "^src/(.*)$": "<rootDir>/../src/$1"
11
+ }
12
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["node_modules", "test", "dist", "addons", "**/*spec.ts"]
4
+ }
@@ -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"]