xiongzhu há 2 anos atrás
pai
commit
73e48409a8
79 ficheiros alterados com 2370 adições e 1530 exclusões
  1. 0 10
      .github/dependabot.yml
  2. 0 31
      .github/workflows/codeql-analysis.yml
  3. 0 81
      .github/workflows/nodejs-environment.yml
  4. 17 0
      .vscode/launch.json
  5. 63 63
      src/app.controller.spec.ts
  6. 16 16
      src/app.controller.ts
  7. 54 54
      src/app.module.ts
  8. 30 30
      src/app.service.spec.ts
  9. 12 12
      src/app.service.ts
  10. 31 27
      src/helpers/configure-swagger-docs.helper.ts
  11. 0 63
      src/iam/auth.controller.ts
  12. 61 0
      src/iam/change-password/change-password.controller.spec.ts
  13. 35 0
      src/iam/change-password/change-password.controller.ts
  14. 39 0
      src/iam/change-password/change-password.module.ts
  15. 70 0
      src/iam/change-password/change-password.service.spec.ts
  16. 45 0
      src/iam/change-password/change-password.service.ts
  17. 7 0
      src/iam/change-password/dto/change-password.dto.ts
  18. 0 13
      src/iam/config/jwt.config.ts
  19. 0 6
      src/iam/decorators/auth-guard.decorator.ts
  20. 0 4
      src/iam/dto/change-password.dto.ts
  21. 0 4
      src/iam/dto/forgot-password.dto.ts
  22. 0 4
      src/iam/dto/login.dto.ts
  23. 0 3
      src/iam/dto/register-user.dto.ts
  24. 4 0
      src/iam/forgot-password/dto/forgot-password.dto.ts
  25. 59 0
      src/iam/forgot-password/forgot-password.controller.spec.ts
  26. 35 0
      src/iam/forgot-password/forgot-password.controller.ts
  27. 24 0
      src/iam/forgot-password/forgot-password.module.ts
  28. 83 0
      src/iam/forgot-password/forgot-password.service.spec.ts
  29. 54 0
      src/iam/forgot-password/forgot-password.service.ts
  30. 0 36
      src/iam/guards/access-token/access-token.guard.ts
  31. 0 36
      src/iam/guards/authentication/authentication.guard.ts
  32. 2 2
      src/iam/iam.constants.ts
  33. 17 8
      src/iam/iam.module.ts
  34. 0 5
      src/iam/interfaces/jwt-payload.interface.ts
  35. 16 0
      src/iam/login/config/jwt.config.ts
  36. 7 0
      src/iam/login/decorators/auth-guard.decorator.ts
  37. 7 0
      src/iam/login/dto/login.dto.ts
  38. 2 2
      src/iam/login/enums/auth-type.enum.ts
  39. 46 0
      src/iam/login/guards/access-token/access-token.guard.ts
  40. 49 0
      src/iam/login/guards/authentication/authentication.guard.ts
  41. 5 0
      src/iam/login/interfaces/jwt-payload.interface.ts
  42. 51 0
      src/iam/login/login.controller.spec.ts
  43. 18 0
      src/iam/login/login.controller.ts
  44. 37 0
      src/iam/login/login.module.ts
  45. 129 0
      src/iam/login/login.service.spec.ts
  46. 68 0
      src/iam/login/login.service.ts
  47. 3 0
      src/iam/register/dto/register-user.dto.ts
  48. 81 0
      src/iam/register/register.controller.spec.ts
  49. 35 0
      src/iam/register/register.controller.ts
  50. 23 0
      src/iam/register/register.module.ts
  51. 80 0
      src/iam/register/register.service.spec.ts
  52. 46 0
      src/iam/register/register.service.ts
  53. 28 28
      src/main.ts
  54. 4 6
      src/migrations/1589834500772-Api.ts
  55. 4 4
      src/repl.ts
  56. 21 21
      src/shared/hashing/bcrypt.service.spec.ts
  57. 10 10
      src/shared/hashing/bcrypt.service.ts
  58. 19 19
      src/shared/hashing/hashing.service.spec.ts
  59. 3 3
      src/shared/hashing/hashing.service.ts
  60. 6 6
      src/shared/mailer/mailer.module.ts
  61. 14 14
      src/shared/mailer/mailer.service.spec.ts
  62. 39 32
      src/shared/mailer/mailer.service.ts
  63. 4 4
      src/shared/utils/utils.module.ts
  64. 13 13
      src/shared/utils/utils.service.spec.ts
  65. 5 5
      src/shared/utils/utils.service.ts
  66. 2 2
      src/users/dto/user-profile.dto.ts
  67. 2 2
      src/users/dto/user-update.dto.ts
  68. 15 15
      src/users/dto/user.dto.ts
  69. 13 13
      src/users/entities/users.entity.ts
  70. 5 5
      src/users/interfaces/users.interface.ts
  71. 134 132
      src/users/users.controller.spec.ts
  72. 69 66
      src/users/users.controller.ts
  73. 17 33
      src/users/users.module.ts
  74. 232 218
      src/users/users.service.spec.ts
  75. 121 250
      src/users/users.service.ts
  76. 1 1
      test/app.e2e-spec.ts
  77. 126 116
      test/change-password/change-password.e2e-spec.ts
  78. 1 1
      test/forgot-password/forgot-password.e2e-spec.ts
  79. 1 1
      test/users/users.e2e-spec.ts

+ 0 - 10
.github/dependabot.yml

@@ -1,10 +0,0 @@
-version: 2
-updates:
-- package-ecosystem: npm
-  directory: "/"
-  schedule:
-    interval: daily
-    time: "13:00"
-  open-pull-requests-limit: 10
-  labels:
-  - dependabot

+ 0 - 31
.github/workflows/codeql-analysis.yml

@@ -1,31 +0,0 @@
-name: "CodeQL"
-
-on:
-  push:
-    branches: [ master ]
-  pull_request:
-    branches: [ master ]
-  schedule:
-    - cron: '0 0 * * *'
-
-jobs:
-  analyze:
-    name: Analyze
-    runs-on: ubuntu-latest
-
-    steps:
-    - name: Checkout repository
-      uses: actions/checkout@v2
-      with:
-        fetch-depth: 0
-
-    - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
-      with:
-        languages: javascript
-
-    - name: Autobuild
-      uses: github/codeql-action/autobuild@v1
-
-    - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1

+ 0 - 81
.github/workflows/nodejs-environment.yml

@@ -1,81 +0,0 @@
-name: Node.js CI
-
-on:
-  push:
-    branches: [ main ]
-  pull_request:
-    branches: [ main ]
-
-jobs:
-  build:
-
-    runs-on: ubuntu-latest
-
-    strategy:
-      matrix:
-        node-version: [14.x, 16.x, 18.x]
-
-    steps:
-      - uses: actions/checkout@v3
-      - name: Use Node.js ${{ matrix.node-version }}
-        uses: actions/setup-node@v3
-        with:
-          node-version: ${{ matrix.node-version }}
-      - run: npm ci
-      - run: npm run build --if-present
-      
-  unit-tests:
-
-    runs-on: ubuntu-latest
-
-    strategy:
-      matrix:
-        node-version: [14.x, 16.x, 18.x]
-    needs: [build]
-
-    steps:
-      - uses: actions/checkout@v3
-      - name: Use Node.js ${{ matrix.node-version }}
-        uses: actions/setup-node@v3
-        with:
-          node-version: ${{ matrix.node-version }}
-      - run: npm ci
-      - run: npm run test
-
-  e2e-test:
-
-    runs-on: ubuntu-latest
-  
-    strategy:
-      matrix:
-        node-version: [14.x, 16.x, 18.x]
-    needs: [unit-tests]
-  
-    steps:
-      - uses: actions/checkout@v3
-      - name: Use Node.js ${{ matrix.node-version }}
-        uses: actions/setup-node@v3
-        with:
-          node-version: ${{ matrix.node-version }}
-      - name: Start Docker-Compose
-        run: docker-compose up -d db-test
-      - name: Install npm
-        uses: actions/setup-node@v3
-        with:
-          node-version: ${{ matrix.node-version }}
-      - run: npm install
-      - run: cp .env.example-test-e2e .env
-      - name: Run tests e2e -- register
-        run: npm run test:e2e -- register.e2e-spec.ts
-      - name: Run tests e2e -- login
-        run: npm run test:e2e -- login.e2e-spec.ts
-      - name: Run tests e2e -- hello
-        run: npm run test:e2e -- app.e2e-spec.ts
-      - name: Run tests e2e -- change-password
-        run: npm run test:e2e -- change-password.e2e-spec.ts
-      - name: Run tests e2e -- users
-        run: npm run test:e2e -- users.e2e-spec.ts
-      - name: Run tests e2e -- forgot-password
-        run: npm run test:e2e -- forgot-password.e2e-spec.ts
-      - name: Stop Docker-Compose
-        run: docker-compose down

+ 17 - 0
.vscode/launch.json

@@ -0,0 +1,17 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "node",
+            "request": "launch",
+            "name": "Debug Nest Framework",
+            "args": ["${workspaceFolder}/src/main.ts"],
+            "runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
+            "sourceMaps": true,
+            "cwd": "${workspaceRoot}"
+        }
+    ]
+}

+ 63 - 63
src/app.controller.spec.ts

@@ -1,75 +1,75 @@
-import { Test, TestingModule } from '@nestjs/testing'
-import { AppController } from './app.controller'
-import { AppService } from './app.service'
+import { Test, TestingModule } from '@nestjs/testing';
+import { AppController } from './app.controller';
+import { AppService } from './app.service';
 
 class MockResponse {
-    res: any
-    constructor() {
-        this.res = {}
-    }
-    status = jest
-        .fn()
-        .mockReturnThis()
-        .mockImplementationOnce((code) => {
-            this.res.code = code
-            return this
-        })
-    send = jest
-        .fn()
-        .mockReturnThis()
-        .mockImplementationOnce((message) => {
-            this.res.message = message
-            return this
-        })
-    json = jest
-        .fn()
-        .mockReturnThis()
-        .mockImplementationOnce((json) => {
-            this.res.json = json
-            return this
-        })
+  res: any;
+  constructor() {
+    this.res = {};
+  }
+  status = jest
+    .fn()
+    .mockReturnThis()
+    .mockImplementationOnce((code) => {
+      this.res.code = code;
+      return this;
+    });
+  send = jest
+    .fn()
+    .mockReturnThis()
+    .mockImplementationOnce((message) => {
+      this.res.message = message;
+      return this;
+    });
+  json = jest
+    .fn()
+    .mockReturnThis()
+    .mockImplementationOnce((json) => {
+      this.res.json = json;
+      return this;
+    });
 }
 
 describe('AppController', () => {
-    let appController: AppController
-    let appService: AppService
-    const response = new MockResponse()
+  let appController: AppController;
+  let appService: AppService;
+  const response = new MockResponse();
 
-    beforeEach(async () => {
-        const app: TestingModule = await Test.createTestingModule({
-            controllers: [AppController],
-            providers: [
-                {
-                    provide: AppService,
-                    useValue: {
-                        getHello: jest.fn(() => {}),
-                        getSecureResource: jest.fn(() => {})
-                    }
-                }
-            ]
-        }).compile()
+  beforeEach(async () => {
+    const app: TestingModule = await Test.createTestingModule({
+      controllers: [AppController],
+      providers: [
+        {
+          provide: AppService,
+          useValue: {
+            getHello: jest.fn(() => {}),
+            getSecureResource: jest.fn(() => {}),
+          },
+        },
+      ],
+    }).compile();
 
-        appController = app.get<AppController>(AppController)
-        appService = app.get<AppService>(AppService)
-    })
+    appController = app.get<AppController>(AppController);
+    appService = app.get<AppService>(AppService);
+  });
 
-    describe('root', () => {
-        it('should be defined', () => {
-            expect(appController).toBeDefined()
-        })
+  describe('root', () => {
+    it('should be defined', () => {
+      expect(appController).toBeDefined();
+    });
 
-        it('should call method getHello() in AppService', () => {
-            const createSpy = jest.spyOn(appService, 'getHello')
+    it('should call method getHello() in AppService', () => {
+      const createSpy = jest.spyOn(appService, 'getHello');
 
-            appController.getHello(response as any)
-            expect(createSpy).toHaveBeenCalled()
-        })
+      appController.getHello(response as any);
+      expect(createSpy).toHaveBeenCalled();
+    });
 
-        it('should call method getProtectedResource() in AppService', () => {
-            const createSpy = jest.spyOn(appService, 'getSecureResource')
+    it('should call method getProtectedResource() in AppService', () => {
+      const createSpy = jest.spyOn(appService, 'getSecureResource');
 
-            appController.getProtectedResource(response as any)
-            expect(createSpy).toHaveBeenCalled()
-        })
-    })
-})
+      appController.getProtectedResource(response as any);
+      expect(createSpy).toHaveBeenCalled();
+    });
+  });
+});

+ 16 - 16
src/app.controller.ts

@@ -1,22 +1,22 @@
-import { Controller, Get, Res, HttpStatus, UseGuards } from '@nestjs/common'
-import { AppService } from './app.service'
-import { Response } from 'express'
-import { AuthGuard } from './iam/login/decorators/auth-guard.decorator'
-import { AuthType } from './iam/login/enums/auth-type.enum'
+import { Controller, Get, Res, HttpStatus, UseGuards } from '@nestjs/common';
+import { AppService } from './app.service';
+import { Response } from 'express';
+import { AuthGuard } from './iam/login/decorators/auth-guard.decorator';
+import { AuthType } from './iam/login/enums/auth-type.enum';
 
 @Controller()
 export class AppController {
-    constructor(private readonly appService: AppService) {}
+  constructor(private readonly appService: AppService) {}
 
-    @AuthGuard(AuthType.None)
-    @Get()
-    getHello(@Res() res: Response) {
-        return res.status(HttpStatus.OK).json(this.appService.getHello())
-    }
+  @AuthGuard(AuthType.None)
+  @Get()
+  getHello(@Res() res: Response) {
+    return res.status(HttpStatus.OK).json(this.appService.getHello());
+  }
 
-    @AuthGuard(AuthType.Bearer)
-    @Get('secure')
-    getProtectedResource(@Res() res: Response) {
-        return res.status(HttpStatus.OK).json(this.appService.getSecureResource())
-    }
+  @AuthGuard(AuthType.Bearer)
+  @Get('secure')
+  getProtectedResource(@Res() res: Response) {
+    return res.status(HttpStatus.OK).json(this.appService.getSecureResource());
+  }
 }

+ 54 - 54
src/app.module.ts

@@ -1,58 +1,58 @@
-import { Module } from '@nestjs/common'
-import { ConfigModule, ConfigService } from '@nestjs/config'
-import { TypeOrmModule } from '@nestjs/typeorm'
-import { AppController } from './app.controller'
-import { AppService } from './app.service'
-import { UsersModule } from './users/users.module'
-import { ThrottlerModule } from '@nestjs/throttler'
-import { IamModule } from './iam/iam.module'
-import * as Yup from 'yup'
+import { Module } from '@nestjs/common';
+import { ConfigModule, ConfigService } from '@nestjs/config';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { AppController } from './app.controller';
+import { AppService } from './app.service';
+import { UsersModule } from './users/users.module';
+import { ThrottlerModule } from '@nestjs/throttler';
+import { IamModule } from './iam/iam.module';
+import * as Yup from 'yup';
 
 @Module({
-    imports: [
-        ConfigModule.forRoot({
-            isGlobal: true,
-            envFilePath: ['.env', '.env.dev', '.env.stage', '.env.prod'],
-            validationSchema: Yup.object({
-                TYPEORM_HOST: Yup.string().required(),
-                TYPEORM_PORT: Yup.number().default(3306),
-                TYPEORM_USERNAME: Yup.string().required(),
-                TYPEORM_PASSWORD: Yup.string().required(),
-                TYPEORM_DATABASE: Yup.string().required()
-            })
-        }),
-        ThrottlerModule.forRootAsync({
-            imports: [ConfigModule],
-            inject: [ConfigService],
-            useFactory: (config: ConfigService) => ({
-                ttl: config.get<number>('THROTTLE_TTL'),
-                limit: config.get<number>('THROTTLE_LIMIT')
-            })
-        }),
-        TypeOrmModule.forRootAsync({
-            imports: [ConfigModule],
-            inject: [ConfigService],
-            useFactory: (config: ConfigService) => ({
-                type: 'mysql',
-                host: config.get<string>('TYPEORM_HOST'),
-                port: config.get<number>('TYPEORM_PORT'),
-                username: config.get<string>('TYPEORM_USERNAME'),
-                password: config.get<string>('TYPEORM_PASSWORD'),
-                database: config.get<string>('TYPEORM_DATABASE'),
-                synchronize: true,
-                entities: [__dirname + '/**/*.entity.{ts,js}'],
-                migrations: ['dist/migrations/**/*.js'],
-                subscribers: ['dist/subscriber/**/*.js'],
-                cli: {
-                    migrationsDir: config.get<string>('TYPEORM_MIGRATIONS_DIR'),
-                    subscribersDir: config.get<string>('TYPEORM_SUBSCRIBERS_DIR')
-                }
-            })
-        }),
-        IamModule,
-        UsersModule
-    ],
-    controllers: [AppController],
-    providers: [AppService]
+  imports: [
+    ConfigModule.forRoot({
+      isGlobal: true,
+      envFilePath: ['.env', '.env.dev', '.env.stage', '.env.prod'],
+      validationSchema: Yup.object({
+        TYPEORM_HOST: Yup.string().required(),
+        TYPEORM_PORT: Yup.number().default(3306),
+        TYPEORM_USERNAME: Yup.string().required(),
+        TYPEORM_PASSWORD: Yup.string().required(),
+        TYPEORM_DATABASE: Yup.string().required(),
+      }),
+    }),
+    ThrottlerModule.forRootAsync({
+      imports: [ConfigModule],
+      inject: [ConfigService],
+      useFactory: (config: ConfigService) => ({
+        ttl: config.get<number>('THROTTLE_TTL'),
+        limit: config.get<number>('THROTTLE_LIMIT'),
+      }),
+    }),
+    TypeOrmModule.forRootAsync({
+      imports: [ConfigModule],
+      inject: [ConfigService],
+      useFactory: (config: ConfigService) => ({
+        type: 'mysql',
+        host: config.get<string>('TYPEORM_HOST'),
+        port: config.get<number>('TYPEORM_PORT'),
+        username: config.get<string>('TYPEORM_USERNAME'),
+        password: config.get<string>('TYPEORM_PASSWORD'),
+        database: config.get<string>('TYPEORM_DATABASE'),
+        synchronize: true,
+        entities: [__dirname + '/**/*.entity.{ts,js}'],
+        migrations: ['dist/migrations/**/*.js'],
+        subscribers: ['dist/subscriber/**/*.js'],
+        cli: {
+          migrationsDir: config.get<string>('TYPEORM_MIGRATIONS_DIR'),
+          subscribersDir: config.get<string>('TYPEORM_SUBSCRIBERS_DIR'),
+        },
+      }),
+    }),
+    IamModule,
+    UsersModule,
+  ],
+  controllers: [AppController],
+  providers: [AppService],
 })
 export class AppModule {}

+ 30 - 30
src/app.service.spec.ts

@@ -1,37 +1,37 @@
-import { Test, TestingModule } from '@nestjs/testing'
-import { AppService } from './app.service'
+import { Test, TestingModule } from '@nestjs/testing';
+import { AppService } from './app.service';
 
 describe('AppService', () => {
-    let service: AppService
+  let service: AppService;
 
-    beforeEach(async () => {
-        const module: TestingModule = await Test.createTestingModule({
-            providers: [AppService]
-        }).compile()
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [AppService],
+    }).compile();
 
-        service = module.get<AppService>(AppService)
-    })
+    service = module.get<AppService>(AppService);
+  });
 
-    describe('App service', () => {
-        it('should be defined', () => {
-            expect(service).toBeDefined()
-        })
+  describe('App service', () => {
+    it('should be defined', () => {
+      expect(service).toBeDefined();
+    });
 
-        describe('getHello() method', () => {
-            it('should return message "This is a simple example of item returned by your APIs"', () => {
-                expect(service.getHello()).toEqual({
-                    message: 'This is a simple example of item returned by your APIs.'
-                })
-            })
-        })
+    describe('getHello() method', () => {
+      it('should return message "This is a simple example of item returned by your APIs"', () => {
+        expect(service.getHello()).toEqual({
+          message: 'This is a simple example of item returned by your APIs.',
+        });
+      });
+    });
 
-        describe('getSecureResource() method', () => {
-            it('should return message "Access to protected resources granted! This protected resource is displayed when the token is successfully provided"', () => {
-                expect(service.getSecureResource()).toEqual({
-                    message:
-                        'Access to protected resources granted! This protected resource is displayed when the token is successfully provided.'
-                })
-            })
-        })
-    })
-})
+    describe('getSecureResource() method', () => {
+      it('should return message "Access to protected resources granted! This protected resource is displayed when the token is successfully provided"', () => {
+        expect(service.getSecureResource()).toEqual({
+          message:
+            'Access to protected resources granted! This protected resource is displayed when the token is successfully provided.',
+        });
+      });
+    });
+  });
+});

+ 12 - 12
src/app.service.ts

@@ -1,17 +1,17 @@
-import { Injectable } from '@nestjs/common'
+import { Injectable } from '@nestjs/common';
 
 @Injectable()
 export class AppService {
-    getHello() {
-        return {
-            message: 'This is a simple example of item returned by your APIs.'
-        }
-    }
+  getHello() {
+    return {
+      message: 'This is a simple example of item returned by your APIs.',
+    };
+  }
 
-    getSecureResource() {
-        return {
-            message:
-                'Access to protected resources granted! This protected resource is displayed when the token is successfully provided.'
-        }
-    }
+  getSecureResource() {
+    return {
+      message:
+        'Access to protected resources granted! This protected resource is displayed when the token is successfully provided.',
+    };
+  }
 }

+ 31 - 27
src/helpers/configure-swagger-docs.helper.ts

@@ -1,31 +1,35 @@
-import { INestApplication } from '@nestjs/common'
-import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
-import { ConfigService } from '@nestjs/config'
-import * as basicAuth from 'express-basic-auth'
+import { INestApplication } from '@nestjs/common';
+import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
+import { ConfigService } from '@nestjs/config';
+import * as basicAuth from 'express-basic-auth';
 
-const SWAGGER_ENVS = ['local', 'dev', 'staging']
+const SWAGGER_ENVS = ['local', 'dev', 'staging'];
 
-export function configureSwaggerDocs(app: INestApplication, configService: ConfigService) {
-    if (SWAGGER_ENVS.includes(configService.get<string>('NODE_ENV'))) {
-        app.use(
-            ['/docs', '/docs-json', '/docs-yaml'],
-            basicAuth({
-                challenge: true,
-                users: {
-                    [configService.get<string>('SWAGGER_USER')]: configService.get<string>('SWAGGER_PASSWORD')
-                }
-            })
-        )
+export function configureSwaggerDocs(
+  app: INestApplication,
+  configService: ConfigService,
+) {
+  if (SWAGGER_ENVS.includes(configService.get<string>('NODE_ENV'))) {
+    app.use(
+      ['/docs', '/docs-json', '/docs-yaml'],
+      basicAuth({
+        challenge: true,
+        users: {
+          [configService.get<string>('SWAGGER_USER')]:
+            configService.get<string>('SWAGGER_PASSWORD'),
+        },
+      }),
+    );
 
-        const config = new DocumentBuilder()
-            .setTitle('API')
-            .setDescription('The API description')
-            .setVersion('1.0')
-            .addBearerAuth()
-            .addTag('auth')
-            .addTag('users')
-            .build()
-        const document = SwaggerModule.createDocument(app, config)
-        SwaggerModule.setup('/docs', app, document)
-    }
+    const config = new DocumentBuilder()
+      .setTitle('API')
+      .setDescription('The API description')
+      .setVersion('1.0')
+      .addBearerAuth()
+      .addTag('auth')
+      .addTag('users')
+      .build();
+    const document = SwaggerModule.createDocument(app, config);
+    SwaggerModule.setup('/docs', app, document);
+  }
 }

+ 0 - 63
src/iam/auth.controller.ts

@@ -1,63 +0,0 @@
-import { Controller, Post, Body, HttpStatus, BadRequestException } from '@nestjs/common'
-import { ApiTags } from '@nestjs/swagger'
-import { UsersService } from 'src/users/users.service'
-import { RegisterUserDto } from './dto/register-user.dto'
-import { LoginDto } from './dto/login.dto'
-import { AuthType } from './enums/auth-type.enum'
-import { AuthGuard } from './decorators/auth-guard.decorator'
-import { ForgotPasswordDto } from './dto/forgot-password.dto'
-import { ChangePasswordDto } from './dto/change-password.dto'
-
-@ApiTags('auth')
-@AuthGuard(AuthType.None)
-@Controller('/auth')
-export class AuthController {
-    constructor(private readonly userService: UsersService) {}
-
-    @Post('/register')
-    public async register(@Body() registerUserDto: RegisterUserDto): Promise<any> {
-        try {
-            await this.userService.register(registerUserDto)
-
-            return {
-                message: 'User registration successfully!',
-                status: HttpStatus.CREATED
-            }
-        } catch (err) {
-            throw new BadRequestException(err, 'Error: User not registration!')
-        }
-    }
-
-    @Post('/login')
-    public async login(@Body() loginDto: LoginDto): Promise<any> {
-        return await this.userService.login(loginDto)
-    }
-
-    @Post('/change-password')
-    public async changePassword(@Body() changePasswordDto: ChangePasswordDto): Promise<any> {
-        try {
-            await this.userService.changePassword(changePasswordDto)
-
-            return {
-                message: 'Request Change Password Successfully!',
-                status: HttpStatus.OK
-            }
-        } catch (err) {
-            throw new BadRequestException(err, 'Error: Change password failed!')
-        }
-    }
-
-    @Post('/forgot-password')
-    public async forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto): Promise<any> {
-        try {
-            await this.userService.forgotPassword(forgotPasswordDto)
-
-            return {
-                message: 'Request Reset Password Successfully!',
-                status: HttpStatus.OK
-            }
-        } catch (err) {
-            throw new BadRequestException(err, 'Error: Forgot password failed!')
-        }
-    }
-}

+ 61 - 0
src/iam/change-password/change-password.controller.spec.ts

@@ -0,0 +1,61 @@
+import { BadRequestException } from '@nestjs/common';
+import { Test, TestingModule } from '@nestjs/testing';
+import { ChangePasswordController } from './change-password.controller';
+import { ChangePasswordService } from './change-password.service';
+import { ChangePasswordDto } from './dto/change-password.dto';
+
+const changePasswordDto: ChangePasswordDto = {
+  email: 'text@example.com',
+  password: 'password123',
+};
+
+describe('ChangePassword Controller', () => {
+  let changePasswordController: ChangePasswordController;
+  let changePasswordService: ChangePasswordService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [ChangePasswordController],
+      providers: [
+        {
+          provide: ChangePasswordService,
+          useValue: {
+            changePassword: jest.fn(() => {}),
+          },
+        },
+      ],
+    }).compile();
+
+    changePasswordController = module.get<ChangePasswordController>(
+      ChangePasswordController,
+    );
+    changePasswordService = module.get<ChangePasswordService>(
+      ChangePasswordService,
+    );
+  });
+
+  describe('Change Password', () => {
+    it('should be defined', () => {
+      expect(changePasswordController).toBeDefined();
+    });
+
+    it('should call method changePassword in changePasswordService', async () => {
+      const createSpy = jest.spyOn(changePasswordService, 'changePassword');
+
+      await changePasswordController.changePassword(changePasswordDto);
+      expect(createSpy).toHaveBeenCalledWith(changePasswordDto);
+    });
+
+    it('should throw an exception if it not find an user email', async () => {
+      changePasswordService.changePassword = jest
+        .fn()
+        .mockRejectedValueOnce(null);
+      await expect(
+        changePasswordController.changePassword({
+          email: 'not a correct email',
+          password: 'not a correct password',
+        }),
+      ).rejects.toThrow(BadRequestException);
+    });
+  });
+});

+ 35 - 0
src/iam/change-password/change-password.controller.ts

@@ -0,0 +1,35 @@
+import {
+  Controller,
+  Post,
+  Body,
+  HttpStatus,
+  BadRequestException,
+} from '@nestjs/common';
+import { ChangePasswordService } from './change-password.service';
+import { ChangePasswordDto } from './dto/change-password.dto';
+import { ApiTags } from '@nestjs/swagger';
+import { AuthGuard } from '../login/decorators/auth-guard.decorator';
+import { AuthType } from '../login/enums/auth-type.enum';
+
+@ApiTags('auth')
+@AuthGuard(AuthType.Bearer)
+@Controller('auth/change-password')
+export class ChangePasswordController {
+  constructor(private readonly changePasswordService: ChangePasswordService) {}
+
+  @Post()
+  public async changePassword(
+    @Body() changePasswordDto: ChangePasswordDto,
+  ): Promise<any> {
+    try {
+      await this.changePasswordService.changePassword(changePasswordDto);
+
+      return {
+        message: 'Request Change Password Successfully!',
+        status: HttpStatus.OK,
+      };
+    } catch (err) {
+      throw new BadRequestException(err, 'Error: Change password failed!');
+    }
+  }
+}

+ 39 - 0
src/iam/change-password/change-password.module.ts

@@ -0,0 +1,39 @@
+import { Module } from '@nestjs/common';
+import { ChangePasswordController } from './change-password.controller';
+import { ChangePasswordService } from './change-password.service';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { Users } from '../../users/entities/users.entity';
+import { UsersService } from '../../users/users.service';
+import { MailerModule } from '../../shared/mailer/mailer.module';
+import { BcryptService } from '../../shared/hashing/bcrypt.service';
+import { HashingService } from '../../shared/hashing/hashing.service';
+import { APP_GUARD } from '@nestjs/core';
+import { AuthenticationGuard } from '../login/guards/authentication/authentication.guard';
+import { AccessTokenGuard } from '../login/guards/access-token/access-token.guard';
+import { JwtService } from '@nestjs/jwt';
+import { ConfigModule } from '@nestjs/config';
+import jwtConfig from '../login/config/jwt.config';
+
+@Module({
+  imports: [
+    ConfigModule.forFeature(jwtConfig),
+    TypeOrmModule.forFeature([Users]),
+    MailerModule,
+  ],
+  controllers: [ChangePasswordController],
+  providers: [
+    {
+      provide: HashingService,
+      useClass: BcryptService,
+    },
+    {
+      provide: APP_GUARD,
+      useClass: AuthenticationGuard,
+    },
+    AccessTokenGuard,
+    ChangePasswordService,
+    UsersService,
+    JwtService,
+  ],
+})
+export class ChangePasswordModule {}

+ 70 - 0
src/iam/change-password/change-password.service.spec.ts

@@ -0,0 +1,70 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { ChangePasswordService } from './change-password.service';
+import { getRepositoryToken } from '@nestjs/typeorm';
+import { Users } from '../../users/entities/users.entity';
+import { Repository } from 'typeorm';
+import { UsersService } from '../../users/users.service';
+import { MailerService } from '../../shared/mailer/mailer.service';
+import { ConfigService } from '@nestjs/config';
+
+const changePasswordUser = {
+  email: 'test@example.it',
+  password: '1234567',
+};
+
+describe('ChangePasswordService', () => {
+  let service: ChangePasswordService;
+  let repository: Repository<Users>;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        ChangePasswordService,
+        {
+          provide: UsersService,
+          useValue: {
+            updateByPassword: jest.fn().mockResolvedValue(changePasswordUser),
+          },
+        },
+        {
+          provide: MailerService,
+          useValue: {
+            sendMail: jest.fn(),
+          },
+        },
+        {
+          provide: ConfigService,
+          useValue: {
+            get: jest.fn().mockReturnValue('some string'),
+          },
+        },
+        {
+          provide: getRepositoryToken(Users),
+          useValue: {
+            findOneBy: jest.fn(),
+            updateByPassword: jest.fn(),
+            save: jest.fn(),
+          },
+        },
+      ],
+    }).compile();
+
+    service = module.get<ChangePasswordService>(ChangePasswordService);
+    repository = module.get<Repository<Users>>(getRepositoryToken(Users));
+  });
+
+  describe('change password user', () => {
+    it('should be defined', () => {
+      expect(service).toBeDefined();
+    });
+
+    it('should change password a user', () => {
+      expect(
+        service.changePassword({
+          email: 'test@example.it',
+          password: '1234567',
+        }),
+      ).resolves.toEqual(changePasswordUser);
+    });
+  });
+});

+ 45 - 0
src/iam/change-password/change-password.service.ts

@@ -0,0 +1,45 @@
+import { Injectable, Logger } from '@nestjs/common';
+import { UsersService } from '../../users/users.service';
+import { ChangePasswordDto } from './dto/change-password.dto';
+import { MailerService } from '../../shared/mailer/mailer.service';
+
+@Injectable()
+export class ChangePasswordService {
+  constructor(
+    private readonly usersService: UsersService,
+    private readonly mailerService: MailerService,
+  ) {}
+
+  public async changePassword(
+    changePasswordDto: ChangePasswordDto,
+  ): Promise<any> {
+    this.sendMailChangePassword(changePasswordDto);
+
+    return await this.usersService.updateByPassword(
+      changePasswordDto.email,
+      changePasswordDto.password,
+    );
+  }
+
+  private sendMailChangePassword(user): void {
+    try {
+      this.mailerService.sendMail({
+        to: user.email,
+        from: 'from@example.com',
+        subject: 'Change Password successful ✔',
+        text: 'Change Password successful!',
+        template: 'index',
+        context: {
+          title: 'Change Password successful!',
+          description:
+            'Change Password Successfully! ✔, This is your new password: ' +
+            user.password,
+          nameUser: user.name,
+        },
+      });
+      Logger.log('[MailService] Change Password: Send Mail successfully!');
+    } catch (err) {
+      Logger.error('[MailService] Change Password: Send Mail Failed!', err);
+    }
+  }
+}

+ 7 - 0
src/iam/change-password/dto/change-password.dto.ts

@@ -0,0 +1,7 @@
+import { PickType } from '@nestjs/swagger';
+import { UserDto } from '../../../users/dto/user.dto';
+
+export class ChangePasswordDto extends PickType(UserDto, [
+  'email',
+  'password',
+] as const) {}

+ 0 - 13
src/iam/config/jwt.config.ts

@@ -1,13 +0,0 @@
-import { ConfigService, registerAs } from '@nestjs/config'
-import { config } from 'dotenv'
-
-config()
-
-const configService = new ConfigService()
-
-export default registerAs('jwt', () => {
-    return {
-        secret: configService.get<string>('JWT_SECRET_KEY'),
-        accessTokenTtl: parseInt(configService.get<string>('JWT_ACCESS_TOKEN_TTL') ?? '3600', 10)
-    }
-})

+ 0 - 6
src/iam/decorators/auth-guard.decorator.ts

@@ -1,6 +0,0 @@
-import { SetMetadata } from '@nestjs/common'
-import { AuthType } from '../enums/auth-type.enum'
-
-export const AUTH_TYPE_KEY = 'authType'
-
-export const AuthGuard = (...authTypes: AuthType[]) => SetMetadata(AUTH_TYPE_KEY, authTypes)

+ 0 - 4
src/iam/dto/change-password.dto.ts

@@ -1,4 +0,0 @@
-import { PickType } from '@nestjs/swagger'
-import { UserDto } from '../../users/dto/user.dto'
-
-export class ChangePasswordDto extends PickType(UserDto, ['email', 'password'] as const) {}

+ 0 - 4
src/iam/dto/forgot-password.dto.ts

@@ -1,4 +0,0 @@
-import { PickType } from '@nestjs/swagger'
-import { UserDto } from '../../users/dto/user.dto'
-
-export class ForgotPasswordDto extends PickType(UserDto, ['email'] as const) {}

+ 0 - 4
src/iam/dto/login.dto.ts

@@ -1,4 +0,0 @@
-import { PickType } from '@nestjs/swagger'
-import { UserDto } from '../../users/dto/user.dto'
-
-export class LoginDto extends PickType(UserDto, ['email', 'password'] as const) {}

+ 0 - 3
src/iam/dto/register-user.dto.ts

@@ -1,3 +0,0 @@
-import { UserDto } from '../../users/dto/user.dto'
-
-export class RegisterUserDto extends UserDto {}

+ 4 - 0
src/iam/forgot-password/dto/forgot-password.dto.ts

@@ -0,0 +1,4 @@
+import { PickType } from '@nestjs/swagger';
+import { UserDto } from '../../../users/dto/user.dto';
+
+export class ForgotPasswordDto extends PickType(UserDto, ['email'] as const) {}

+ 59 - 0
src/iam/forgot-password/forgot-password.controller.spec.ts

@@ -0,0 +1,59 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { ForgotPasswordController } from './forgot-password.controller';
+import { ForgotPasswordService } from './forgot-password.service';
+import { ForgotPasswordDto } from './dto/forgot-password.dto';
+import { BadRequestException } from '@nestjs/common';
+
+const forgotPasswordDto: ForgotPasswordDto = {
+  email: 'test@example.com',
+};
+
+describe('ForgotPassword Controller', () => {
+  let forgotPasswordController: ForgotPasswordController;
+  let forgotPasswordService: ForgotPasswordService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [ForgotPasswordController],
+      providers: [
+        {
+          provide: ForgotPasswordService,
+          useValue: {
+            forgotPassword: jest.fn(() => {}),
+          },
+        },
+      ],
+    }).compile();
+
+    forgotPasswordController = module.get<ForgotPasswordController>(
+      ForgotPasswordController,
+    );
+    forgotPasswordService = module.get<ForgotPasswordService>(
+      ForgotPasswordService,
+    );
+  });
+
+  describe('Forgot Password', () => {
+    it('should be defined', () => {
+      expect(forgotPasswordController).toBeDefined();
+    });
+
+    it('should call method forgotPassword in forgotPasswordService', async () => {
+      const createSpy = jest.spyOn(forgotPasswordService, 'forgotPassword');
+
+      await forgotPasswordController.forgotPassword(forgotPasswordDto);
+      expect(createSpy).toHaveBeenCalledWith(forgotPasswordDto);
+    });
+
+    it('should throw an exception if it not find an user email', async () => {
+      forgotPasswordService.forgotPassword = jest
+        .fn()
+        .mockRejectedValueOnce(null);
+      await expect(
+        forgotPasswordController.forgotPassword({
+          email: 'not a correct email',
+        }),
+      ).rejects.toThrow(BadRequestException);
+    });
+  });
+});

+ 35 - 0
src/iam/forgot-password/forgot-password.controller.ts

@@ -0,0 +1,35 @@
+import {
+  Controller,
+  Post,
+  Body,
+  HttpStatus,
+  BadRequestException,
+} from '@nestjs/common';
+import { ApiTags } from '@nestjs/swagger';
+import { ForgotPasswordService } from '../forgot-password/forgot-password.service';
+import { AuthGuard } from '../login/decorators/auth-guard.decorator';
+import { AuthType } from '../login/enums/auth-type.enum';
+import { ForgotPasswordDto } from './dto/forgot-password.dto';
+
+@ApiTags('auth')
+@AuthGuard(AuthType.None)
+@Controller('auth/forgot-password')
+export class ForgotPasswordController {
+  constructor(private readonly forgotPasswordService: ForgotPasswordService) {}
+
+  @Post()
+  public async forgotPassword(
+    @Body() forgotPasswordDto: ForgotPasswordDto,
+  ): Promise<any> {
+    try {
+      await this.forgotPasswordService.forgotPassword(forgotPasswordDto);
+
+      return {
+        message: 'Request Reset Password Successfully!',
+        status: HttpStatus.OK,
+      };
+    } catch (err) {
+      throw new BadRequestException(err, 'Error: Forgot password failed!');
+    }
+  }
+}

+ 24 - 0
src/iam/forgot-password/forgot-password.module.ts

@@ -0,0 +1,24 @@
+import { Module } from '@nestjs/common';
+import { ForgotPasswordService } from './forgot-password.service';
+import { ForgotPasswordController } from './forgot-password.controller';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { Users } from '../../users/entities/users.entity';
+import { UsersService } from '../../users/users.service';
+import { MailerModule } from '../../shared/mailer/mailer.module';
+import { UtilsModule } from '../../shared/utils/utils.module';
+import { BcryptService } from '../../shared/hashing/bcrypt.service';
+import { HashingService } from '../../shared/hashing/hashing.service';
+
+@Module({
+  imports: [TypeOrmModule.forFeature([Users]), MailerModule, UtilsModule],
+  providers: [
+    {
+      provide: HashingService,
+      useClass: BcryptService,
+    },
+    ForgotPasswordService,
+    UsersService,
+  ],
+  controllers: [ForgotPasswordController],
+})
+export class ForgotPasswordModule {}

+ 83 - 0
src/iam/forgot-password/forgot-password.service.spec.ts

@@ -0,0 +1,83 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { getRepositoryToken } from '@nestjs/typeorm';
+import { Users } from '../../users/entities/users.entity';
+import { ForgotPasswordService } from './forgot-password.service';
+import { MailerService } from '../../shared/mailer/mailer.service';
+import { UtilsService } from '../../shared/utils/utils.service';
+import { ConfigService } from '@nestjs/config';
+import { HashingService } from '../../shared/hashing/hashing.service';
+import { Repository } from 'typeorm';
+import { UsersService } from '../../users/users.service';
+
+const oneUser = {
+  email: 'test@example.com',
+};
+
+const user = {
+  email: 'test@example.com',
+  password: 'pass123',
+};
+
+describe('ForgotPasswordService', () => {
+  let service: ForgotPasswordService;
+  let repository: Repository<Users>;
+  let mailerService: MailerService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        ForgotPasswordService,
+        {
+          provide: UsersService,
+          useValue: {
+            forgotPassword: jest.fn(),
+          },
+        },
+        {
+          provide: getRepositoryToken(Users),
+          useValue: {
+            findOneBy: jest.fn(() => oneUser),
+            save: jest.fn(() => user),
+          },
+        },
+        {
+          provide: HashingService,
+          useValue: {
+            hash: jest.fn(() => 'pass123'),
+          },
+        },
+        {
+          provide: ConfigService,
+          useValue: {
+            get: jest.fn().mockReturnValue('some string'),
+          },
+        },
+        {
+          provide: MailerService,
+          useValue: {
+            sendMail: jest.fn(),
+          },
+        },
+        UtilsService,
+      ],
+    }).compile();
+
+    service = module.get<ForgotPasswordService>(ForgotPasswordService);
+    mailerService = module.get<MailerService>(MailerService);
+    repository = module.get<Repository<Users>>(getRepositoryToken(Users));
+  });
+
+  describe('forgot password user', () => {
+    it('should be defined', () => {
+      expect(service).toBeDefined();
+    });
+
+    it('should generate a new password for user by email', async () => {
+      expect(
+        await service.forgotPassword({
+          email: 'test@example.com',
+        }),
+      ).toEqual(oneUser);
+    });
+  });
+});

+ 54 - 0
src/iam/forgot-password/forgot-password.service.ts

@@ -0,0 +1,54 @@
+import { Injectable, Logger } from '@nestjs/common';
+import { Repository } from 'typeorm';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Users } from '../../users/entities/users.entity';
+import { ForgotPasswordDto } from './dto/forgot-password.dto';
+import { MailerService } from '../../shared/mailer/mailer.service';
+import { UtilsService } from '../../shared/utils/utils.service';
+import { HashingService } from '../../shared/hashing/hashing.service';
+
+@Injectable()
+export class ForgotPasswordService {
+  constructor(
+    @InjectRepository(Users)
+    private readonly userRepository: Repository<Users>,
+    private readonly mailerService: MailerService,
+    private readonly utilsService: UtilsService,
+    private readonly hashingService: HashingService,
+  ) {}
+
+  public async forgotPassword(
+    forgotPasswordDto: ForgotPasswordDto,
+  ): Promise<any> {
+    const userUpdate = await this.userRepository.findOneBy({
+      email: forgotPasswordDto.email,
+    });
+    const passwordRand = this.utilsService.generatePassword();
+    userUpdate.password = await this.hashingService.hash(passwordRand);
+
+    this.sendMailForgotPassword(userUpdate.email, passwordRand);
+
+    return await this.userRepository.save(userUpdate);
+  }
+
+  private sendMailForgotPassword(email, password): void {
+    try {
+      this.mailerService.sendMail({
+        to: email,
+        from: 'from@example.com',
+        subject: 'Forgot Password successful ✔',
+        text: 'Forgot Password successful!',
+        template: 'index',
+        context: {
+          title: 'Forgot Password successful!',
+          description:
+            'Request Reset Password Successfully!  ✔, This is your new password: ' +
+            password,
+        },
+      });
+      Logger.log('[MailService] Forgot Password: Send Mail successfully!');
+    } catch (err) {
+      Logger.error('[MailService] Forgot Password: Send Mail Failed!', err);
+    }
+  }
+}

+ 0 - 36
src/iam/guards/access-token/access-token.guard.ts

@@ -1,36 +0,0 @@
-import { CanActivate, ExecutionContext, HttpStatus, Inject, Injectable, UnauthorizedException } from '@nestjs/common'
-import { ConfigType } from '@nestjs/config'
-import { JwtService } from '@nestjs/jwt'
-import { Request } from 'express'
-import { REQUEST_USER_KEY, TYPE_TOKEN_BEARER } from '../../iam.constants'
-import jwtConfig from '../../config/jwt.config'
-
-@Injectable()
-export class AccessTokenGuard implements CanActivate {
-    constructor(
-        private readonly jwtService: JwtService,
-        @Inject(jwtConfig.KEY)
-        private readonly jwtConfiguration: ConfigType<typeof jwtConfig>
-    ) {}
-
-    async canActivate(context: ExecutionContext): Promise<boolean> {
-        const request = context.switchToHttp().getRequest()
-        const token = this.extractTokenFromHeader(request)
-        if (!token) {
-            throw new UnauthorizedException()
-        }
-
-        try {
-            const payload = await this.jwtService.verifyAsync(token, this.jwtConfiguration)
-            request[REQUEST_USER_KEY] = payload
-        } catch (err) {
-            throw new UnauthorizedException(HttpStatus.UNAUTHORIZED, err)
-        }
-        return true
-    }
-
-    private extractTokenFromHeader(request: Request): string | undefined {
-        const [type, token] = request.headers.authorization?.split(' ') ?? []
-        return type === TYPE_TOKEN_BEARER ? token : undefined
-    }
-}

+ 0 - 36
src/iam/guards/authentication/authentication.guard.ts

@@ -1,36 +0,0 @@
-import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'
-import { Reflector } from '@nestjs/core'
-import { AuthType } from '../../enums/auth-type.enum'
-import { AccessTokenGuard } from '../access-token/access-token.guard'
-import { AUTH_TYPE_KEY } from '../../decorators/auth-guard.decorator'
-
-@Injectable()
-export class AuthenticationGuard implements CanActivate {
-    private static readonly defaultAuthType = AuthType.Bearer
-    private readonly authTypeGuardMap: Record<AuthType, CanActivate | CanActivate[]> = {
-        [AuthType.Bearer]: this.accessTokenGuard,
-        [AuthType.None]: { canActivate: () => true }
-    }
-
-    constructor(private readonly reflector: Reflector, private readonly accessTokenGuard: AccessTokenGuard) {}
-
-    async canActivate(context: ExecutionContext): Promise<boolean> {
-        const authTypes = this.reflector.getAllAndOverride<AuthType[]>(AUTH_TYPE_KEY, [
-            context.getHandler(),
-            context.getClass()
-        ]) ?? [AuthenticationGuard.defaultAuthType]
-        const guards = authTypes.map((type) => this.authTypeGuardMap[type]).flat()
-        let error = new UnauthorizedException()
-
-        for (const instance of guards) {
-            const canActivate = await Promise.resolve(instance.canActivate(context)).catch((err) => {
-                error = err
-            })
-
-            if (canActivate) {
-                return true
-            }
-        }
-        throw error
-    }
-}

+ 2 - 2
src/iam/iam.constants.ts

@@ -1,2 +1,2 @@
-export const REQUEST_USER_KEY = 'user'
-export const TYPE_TOKEN_BEARER = 'Bearer'
+export const REQUEST_USER_KEY = 'user';
+export const TYPE_TOKEN_BEARER = 'Bearer';

+ 17 - 8
src/iam/iam.module.ts

@@ -1,12 +1,21 @@
-import { Module } from '@nestjs/common'
-import { JwtService } from '@nestjs/jwt'
-import { UtilsModule } from '../shared/utils/utils.module'
-import { UsersModule } from '../users/users.module'
-import { AuthController } from './auth.controller'
+import { Module } from '@nestjs/common';
+import { JwtService } from '@nestjs/jwt';
+import { UtilsModule } from '../shared/utils/utils.module';
+import { UsersModule } from '../users/users.module';
+import { ChangePasswordModule } from './change-password/change-password.module';
+import { ForgotPasswordModule } from './forgot-password/forgot-password.module';
+import { LoginModule } from './login/login.module';
+import { RegisterModule } from './register/register.module';
 
 @Module({
-    imports: [UsersModule, UtilsModule],
-    providers: [JwtService],
-    controllers: [AuthController]
+  imports: [
+    LoginModule,
+    RegisterModule,
+    UsersModule,
+    ForgotPasswordModule,
+    ChangePasswordModule,
+    UtilsModule,
+  ],
+  providers: [JwtService],
 })
 export class IamModule {}

+ 0 - 5
src/iam/interfaces/jwt-payload.interface.ts

@@ -1,5 +0,0 @@
-export interface JWTPayload {
-    id: number
-    email: string
-    name: string
-}

+ 16 - 0
src/iam/login/config/jwt.config.ts

@@ -0,0 +1,16 @@
+import { ConfigService, registerAs } from '@nestjs/config';
+import { config } from 'dotenv';
+
+config();
+
+const configService = new ConfigService();
+
+export default registerAs('jwt', () => {
+  return {
+    secret: configService.get<string>('JWT_SECRET_KEY'),
+    accessTokenTtl: parseInt(
+      configService.get<string>('JWT_ACCESS_TOKEN_TTL') ?? '3600',
+      10,
+    ),
+  };
+});

+ 7 - 0
src/iam/login/decorators/auth-guard.decorator.ts

@@ -0,0 +1,7 @@
+import { SetMetadata } from '@nestjs/common';
+import { AuthType } from '../enums/auth-type.enum';
+
+export const AUTH_TYPE_KEY = 'authType';
+
+export const AuthGuard = (...authTypes: AuthType[]) =>
+  SetMetadata(AUTH_TYPE_KEY, authTypes);

+ 7 - 0
src/iam/login/dto/login.dto.ts

@@ -0,0 +1,7 @@
+import { PickType } from '@nestjs/swagger';
+import { UserDto } from '../../../users/dto/user.dto';
+
+export class LoginDto extends PickType(UserDto, [
+  'email',
+  'password',
+] as const) {}

+ 2 - 2
src/iam/enums/auth-type.enum.ts → src/iam/login/enums/auth-type.enum.ts

@@ -1,4 +1,4 @@
 export enum AuthType {
-    Bearer,
-    None
+  Bearer,
+  None,
 }

+ 46 - 0
src/iam/login/guards/access-token/access-token.guard.ts

@@ -0,0 +1,46 @@
+import {
+  CanActivate,
+  ExecutionContext,
+  HttpStatus,
+  Inject,
+  Injectable,
+  UnauthorizedException,
+} from '@nestjs/common';
+import { ConfigType } from '@nestjs/config';
+import { JwtService } from '@nestjs/jwt';
+import { Request } from 'express';
+import { REQUEST_USER_KEY, TYPE_TOKEN_BEARER } from '../../../iam.constants';
+import jwtConfig from '../../config/jwt.config';
+
+@Injectable()
+export class AccessTokenGuard implements CanActivate {
+  constructor(
+    private readonly jwtService: JwtService,
+    @Inject(jwtConfig.KEY)
+    private readonly jwtConfiguration: ConfigType<typeof jwtConfig>,
+  ) {}
+
+  async canActivate(context: ExecutionContext): Promise<boolean> {
+    const request = context.switchToHttp().getRequest();
+    const token = this.extractTokenFromHeader(request);
+    if (!token) {
+      throw new UnauthorizedException();
+    }
+
+    try {
+      const payload = await this.jwtService.verifyAsync(
+        token,
+        this.jwtConfiguration,
+      );
+      request[REQUEST_USER_KEY] = payload;
+    } catch (err) {
+      throw new UnauthorizedException(HttpStatus.UNAUTHORIZED, err);
+    }
+    return true;
+  }
+
+  private extractTokenFromHeader(request: Request): string | undefined {
+    const [type, token] = request.headers.authorization?.split(' ') ?? [];
+    return type === TYPE_TOKEN_BEARER ? token : undefined;
+  }
+}

+ 49 - 0
src/iam/login/guards/authentication/authentication.guard.ts

@@ -0,0 +1,49 @@
+import {
+  CanActivate,
+  ExecutionContext,
+  Injectable,
+  UnauthorizedException,
+} from '@nestjs/common';
+import { Reflector } from '@nestjs/core';
+import { AuthType } from '../../enums/auth-type.enum';
+import { AccessTokenGuard } from '../access-token/access-token.guard';
+import { AUTH_TYPE_KEY } from '../../decorators/auth-guard.decorator';
+
+@Injectable()
+export class AuthenticationGuard implements CanActivate {
+  private static readonly defaultAuthType = AuthType.Bearer;
+  private readonly authTypeGuardMap: Record<
+    AuthType,
+    CanActivate | CanActivate[]
+  > = {
+    [AuthType.Bearer]: this.accessTokenGuard,
+    [AuthType.None]: { canActivate: () => true },
+  };
+
+  constructor(
+    private readonly reflector: Reflector,
+    private readonly accessTokenGuard: AccessTokenGuard,
+  ) {}
+
+  async canActivate(context: ExecutionContext): Promise<boolean> {
+    const authTypes = this.reflector.getAllAndOverride<AuthType[]>(
+      AUTH_TYPE_KEY,
+      [context.getHandler(), context.getClass()],
+    ) ?? [AuthenticationGuard.defaultAuthType];
+    const guards = authTypes.map((type) => this.authTypeGuardMap[type]).flat();
+    let error = new UnauthorizedException();
+
+    for (const instance of guards) {
+      const canActivate = await Promise.resolve(
+        instance.canActivate(context),
+      ).catch((err) => {
+        error = err;
+      });
+
+      if (canActivate) {
+        return true;
+      }
+    }
+    throw error;
+  }
+}

+ 5 - 0
src/iam/login/interfaces/jwt-payload.interface.ts

@@ -0,0 +1,5 @@
+export interface JWTPayload {
+  id: number;
+  email: string;
+  name: string;
+}

+ 51 - 0
src/iam/login/login.controller.spec.ts

@@ -0,0 +1,51 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { UsersService } from '../../users/users.service';
+import { LoginController } from './login.controller';
+import { LoginService } from './login.service';
+import { LoginDto } from './dto/login.dto';
+
+const loginDto: LoginDto = {
+  email: 'test@example.com',
+  password: 'password123',
+};
+
+describe('Login Controller', () => {
+  let loginController: LoginController;
+  let loginService: LoginService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [LoginController],
+      providers: [
+        {
+          provide: LoginService,
+          useValue: {
+            login: jest.fn(() => {}),
+          },
+        },
+        {
+          provide: UsersService,
+          useValue: {
+            findByEmail: jest.fn(() => {}),
+          },
+        },
+      ],
+    }).compile();
+
+    loginController = module.get<LoginController>(LoginController);
+    loginService = module.get<LoginService>(LoginService);
+  });
+
+  describe('Login user', () => {
+    it('should be defined', () => {
+      expect(loginController).toBeDefined();
+    });
+
+    it('should call method login in loginService', async () => {
+      const createSpy = jest.spyOn(loginService, 'login');
+
+      await loginController.login(loginDto);
+      expect(createSpy).toHaveBeenCalledWith(loginDto);
+    });
+  });
+});

+ 18 - 0
src/iam/login/login.controller.ts

@@ -0,0 +1,18 @@
+import { Controller, Post, Body } from '@nestjs/common';
+import { LoginService } from './login.service';
+import { LoginDto } from '../login/dto/login.dto';
+import { ApiTags } from '@nestjs/swagger';
+import { AuthType } from './enums/auth-type.enum';
+import { AuthGuard } from './decorators/auth-guard.decorator';
+
+@ApiTags('auth')
+@AuthGuard(AuthType.None)
+@Controller('auth/login')
+export class LoginController {
+  constructor(private readonly loginService: LoginService) {}
+
+  @Post()
+  public async login(@Body() loginDto: LoginDto): Promise<any> {
+    return await this.loginService.login(loginDto);
+  }
+}

+ 37 - 0
src/iam/login/login.module.ts

@@ -0,0 +1,37 @@
+import { Module } from '@nestjs/common';
+import { LoginService } from './login.service';
+import { LoginController } from './login.controller';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { Users } from '../../users/entities/users.entity';
+import { JwtModule } from '@nestjs/jwt';
+import { UsersService } from '../../users/users.service';
+import { ConfigModule } from '@nestjs/config';
+import { HashingService } from '../../shared/hashing/hashing.service';
+import { BcryptService } from '../../shared/hashing/bcrypt.service';
+import { APP_GUARD } from '@nestjs/core';
+import { AuthenticationGuard } from './guards/authentication/authentication.guard';
+import { AccessTokenGuard } from './guards/access-token/access-token.guard';
+import jwtConfig from './config/jwt.config';
+
+@Module({
+  imports: [
+    TypeOrmModule.forFeature([Users]),
+    ConfigModule.forFeature(jwtConfig),
+    JwtModule.registerAsync(jwtConfig.asProvider()),
+  ],
+  providers: [
+    {
+      provide: HashingService,
+      useClass: BcryptService,
+    },
+    {
+      provide: APP_GUARD,
+      useClass: AuthenticationGuard,
+    },
+    AccessTokenGuard,
+    LoginService,
+    UsersService,
+  ],
+  controllers: [LoginController],
+})
+export class LoginModule {}

+ 129 - 0
src/iam/login/login.service.spec.ts

@@ -0,0 +1,129 @@
+import { ConfigService } from '@nestjs/config';
+import { JwtService } from '@nestjs/jwt';
+import { Test, TestingModule } from '@nestjs/testing';
+import { HashingService } from '../../shared/hashing/hashing.service';
+import { LoginService } from './login.service';
+import { UsersService } from '../../users/users.service';
+import { Users } from '../../users/entities/users.entity';
+import { getRepositoryToken } from '@nestjs/typeorm';
+import { LoginDto } from './dto/login.dto';
+import { UnauthorizedException, HttpException } from '@nestjs/common';
+
+const oneUser = {
+  id: 1,
+  name: 'name #1',
+  username: 'username #1',
+  email: 'test@example.com',
+  password: 'pass123',
+};
+
+const loginDto: LoginDto = {
+  email: 'test@example.com',
+  password: 'pass123',
+};
+
+const userLogin = {
+  sub: 1,
+  accessToken: undefined,
+  audience: 'some string',
+  expiresIn: 'some string',
+  issuer: 'some string',
+  user: {
+    id: 1,
+    name: 'name #1',
+    email: 'test@example.com',
+  },
+};
+
+const payload = {
+  id: 1,
+  name: 'name #1',
+  email: 'test@example.com',
+};
+
+describe('LoginService', () => {
+  let loginService: LoginService;
+  let usersService: UsersService;
+  let hashingService: HashingService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        LoginService,
+        {
+          provide: JwtService,
+          useValue: {
+            signAsync: jest.fn(),
+            signToken: jest.fn(() => payload),
+          },
+        },
+        {
+          provide: ConfigService,
+          useValue: {
+            get: jest.fn().mockReturnValue('some string'),
+          },
+        },
+        {
+          provide: HashingService,
+          useValue: {
+            hash: jest.fn(() => Promise.resolve('pass123')),
+            compare: jest.fn(() => Promise.resolve(true)),
+          },
+        },
+        {
+          provide: UsersService,
+          useValue: {
+            findByEmail: jest.fn().mockResolvedValue(oneUser),
+          },
+        },
+        {
+          provide: getRepositoryToken(Users),
+          useValue: {
+            findByEmail: jest.fn(),
+            findOneBy: jest.fn().mockReturnValue(oneUser),
+            findOne: jest.fn().mockReturnValue(oneUser),
+          },
+        },
+      ],
+    }).compile();
+
+    loginService = module.get<LoginService>(LoginService);
+    usersService = module.get<UsersService>(UsersService);
+    hashingService = module.get<HashingService>(HashingService);
+  });
+
+  it('should be defined', () => {
+    expect(loginService).toBeDefined();
+  });
+
+  describe('findUserByEmail() method', () => {
+    it('should find a user by email', async () => {
+      expect(await loginService.findUserByEmail(loginDto)).toEqual(oneUser);
+    });
+
+    it('should generate token jwt', async () => {
+      expect(await loginService.login(loginDto)).toEqual(userLogin);
+    });
+
+    it('should return an exception if wrong password', async () => {
+      usersService.findByEmail = jest.fn().mockResolvedValueOnce(oneUser);
+      hashingService.compare = jest.fn().mockResolvedValueOnce(false);
+      await expect(
+        loginService.login({
+          email: 'someemail@test.com',
+          password: 'not a correct password',
+        }),
+      ).rejects.toThrow(HttpException);
+    });
+
+    it('should return an exception if login fails', async () => {
+      usersService.findByEmail = jest.fn().mockResolvedValueOnce(null);
+      await expect(
+        loginService.login({
+          email: 'not a correct email',
+          password: 'not a correct password',
+        }),
+      ).rejects.toThrow(HttpException);
+    });
+  });
+});

+ 68 - 0
src/iam/login/login.service.ts

@@ -0,0 +1,68 @@
+import {
+  HttpException,
+  HttpStatus,
+  Injectable,
+  UnauthorizedException,
+} from '@nestjs/common';
+import { JwtService } from '@nestjs/jwt';
+import { UsersService } from '../../users/users.service';
+import { IUsers } from '../../users/interfaces/users.interface';
+import { LoginDto } from './dto/login.dto';
+import { ConfigService } from '@nestjs/config';
+import { HashingService } from '../../shared/hashing/hashing.service';
+import { JWTPayload } from './interfaces/jwt-payload.interface';
+
+@Injectable()
+export class LoginService {
+  constructor(
+    private readonly usersService: UsersService,
+    private readonly jwtService: JwtService,
+    private readonly configService: ConfigService,
+    private readonly hashingService: HashingService,
+  ) {}
+
+  public async findUserByEmail(loginDto: LoginDto): Promise<IUsers> {
+    return await this.usersService.findByEmail(loginDto.email);
+  }
+
+  public async login(loginDto: LoginDto): Promise<any> {
+    try {
+      const user = await this.findUserByEmail(loginDto);
+      if (!user) {
+        throw new UnauthorizedException('User does not exists');
+      }
+
+      const passwordIsValid = await this.hashingService.compare(
+        loginDto.password,
+        user.password,
+      );
+
+      if (!passwordIsValid) {
+        throw new UnauthorizedException(
+          'Authentication failed. Wrong password',
+        );
+      }
+
+      return await this.signToken({
+        name: user.name,
+        email: user.email,
+        id: user.id,
+      });
+    } catch (err) {
+      throw new HttpException(err, HttpStatus.BAD_REQUEST);
+    }
+  }
+
+  private async signToken(payload: JWTPayload): Promise<any> {
+    const accessToken = await this.jwtService.signAsync(payload);
+
+    return {
+      sub: payload.id,
+      expiresIn: this.configService.get<string>('JWT_ACCESS_TOKEN_TTL'),
+      audience: this.configService.get<string>('JWT_TOKEN_AUDIENCE'),
+      issuer: this.configService.get<string>('JWT_TOKEN_ISSUER'),
+      accessToken: accessToken,
+      user: payload,
+    };
+  }
+}

+ 3 - 0
src/iam/register/dto/register-user.dto.ts

@@ -0,0 +1,3 @@
+import { UserDto } from '../../../users/dto/user.dto';
+
+export class RegisterUserDto extends UserDto {}

+ 81 - 0
src/iam/register/register.controller.spec.ts

@@ -0,0 +1,81 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { RegisterController } from './register.controller';
+import { RegisterService } from './register.service';
+import { UsersService } from '../../users/users.service';
+import { MailerService } from '../../shared/mailer/mailer.service';
+import { ConfigService } from '@nestjs/config';
+import { RegisterUserDto } from './dto/register-user.dto';
+import { BadRequestException } from '@nestjs/common';
+
+const registerUserDto: RegisterUserDto = {
+  name: 'name #1',
+  username: 'username #1',
+  email: 'test@example.com',
+  password: 'password123',
+};
+
+describe('Register Controller', () => {
+  let registerController: RegisterController;
+  let registerService: RegisterService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [RegisterController],
+      providers: [
+        RegisterService,
+        {
+          provide: MailerService,
+          useValue: {
+            sendMail: jest.fn(),
+          },
+        },
+        {
+          provide: ConfigService,
+          useValue: {
+            get: jest.fn().mockReturnValue('some string'),
+          },
+        },
+        {
+          provide: UsersService,
+          useValue: {
+            register: jest.fn(() => {}),
+          },
+        },
+        {
+          provide: RegisterService,
+          useValue: {
+            register: jest.fn(() => {}),
+          },
+        },
+      ],
+    }).compile();
+
+    registerController = module.get<RegisterController>(RegisterController);
+    registerService = module.get<RegisterService>(RegisterService);
+  });
+
+  describe('Registration user', () => {
+    it('should be defined', () => {
+      expect(registerController).toBeDefined();
+    });
+
+    it('should call method register in registerService', async () => {
+      const createSpy = jest.spyOn(registerService, 'register');
+
+      await registerController.register(registerUserDto);
+      expect(createSpy).toHaveBeenCalledWith(registerUserDto);
+    });
+
+    it('should throw an exception if it not register fails', async () => {
+      registerService.register = jest.fn().mockRejectedValueOnce(null);
+      await expect(
+        registerController.register({
+          name: 'not a correct name',
+          email: 'not a correct email',
+          username: 'not a correct username',
+          password: 'not a correct password',
+        }),
+      ).rejects.toThrow(BadRequestException);
+    });
+  });
+});

+ 35 - 0
src/iam/register/register.controller.ts

@@ -0,0 +1,35 @@
+import {
+  Controller,
+  Post,
+  Body,
+  HttpStatus,
+  BadRequestException,
+} from '@nestjs/common';
+import { RegisterService } from './register.service';
+import { RegisterUserDto } from './dto/register-user.dto';
+import { ApiTags } from '@nestjs/swagger';
+import { AuthType } from '../login/enums/auth-type.enum';
+import { AuthGuard } from '../login/decorators/auth-guard.decorator';
+
+@ApiTags('auth')
+@AuthGuard(AuthType.None)
+@Controller('auth/register')
+export class RegisterController {
+  constructor(private readonly registerService: RegisterService) {}
+
+  @Post()
+  public async register(
+    @Body() registerUserDto: RegisterUserDto,
+  ): Promise<any> {
+    try {
+      await this.registerService.register(registerUserDto);
+
+      return {
+        message: 'User registration successfully!',
+        status: HttpStatus.CREATED,
+      };
+    } catch (err) {
+      throw new BadRequestException(err, 'Error: User not registration!');
+    }
+  }
+}

+ 23 - 0
src/iam/register/register.module.ts

@@ -0,0 +1,23 @@
+import { Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { BcryptService } from '../../shared/hashing/bcrypt.service';
+import { HashingService } from '../../shared/hashing/hashing.service';
+import { MailerModule } from '../../shared/mailer/mailer.module';
+import { Users } from '../../users/entities/users.entity';
+import { UsersService } from '../../users/users.service';
+import { RegisterController } from './register.controller';
+import { RegisterService } from './register.service';
+
+@Module({
+  imports: [TypeOrmModule.forFeature([Users]), MailerModule],
+  controllers: [RegisterController],
+  providers: [
+    {
+      provide: HashingService,
+      useClass: BcryptService,
+    },
+    RegisterService,
+    UsersService,
+  ],
+})
+export class RegisterModule {}

+ 80 - 0
src/iam/register/register.service.spec.ts

@@ -0,0 +1,80 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { UsersService } from '../../users/users.service';
+import { RegisterService } from './register.service';
+import { Users } from '../../users/entities/users.entity';
+import { getRepositoryToken } from '@nestjs/typeorm';
+import { RegisterUserDto } from './dto/register-user.dto';
+import { HashingService } from '../../shared/hashing/hashing.service';
+import { MailerService } from '../../shared/mailer/mailer.service';
+import { ConfigService } from '@nestjs/config';
+import { Repository } from 'typeorm';
+
+const registerUserDto: RegisterUserDto = {
+  name: 'name #1',
+  username: 'username #1',
+  email: 'test@example.com',
+  password: 'password123',
+};
+
+describe('RegisterService', () => {
+  let service: RegisterService;
+  let repository: Repository<Users>;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        RegisterService,
+        {
+          provide: UsersService,
+          useValue: {
+            create: jest.fn().mockResolvedValue(registerUserDto),
+          },
+        },
+        {
+          provide: MailerService,
+          useValue: {
+            sendMail: jest.fn(),
+          },
+        },
+        {
+          provide: ConfigService,
+          useValue: {
+            get: jest.fn().mockReturnValue('some string'),
+          },
+        },
+        {
+          provide: HashingService,
+          useValue: {
+            hash: jest.fn(),
+          },
+        },
+        {
+          provide: getRepositoryToken(Users),
+          useValue: {
+            save: jest.fn(),
+          },
+        },
+      ],
+    }).compile();
+
+    service = module.get<RegisterService>(RegisterService);
+    repository = module.get<Repository<Users>>(getRepositoryToken(Users));
+  });
+
+  describe('Create user', () => {
+    it('should be defined', () => {
+      expect(service).toBeDefined();
+    });
+
+    it('should create a user during registration', async () => {
+      expect(
+        await service.register({
+          name: 'name #1',
+          username: 'username #1',
+          email: 'test@example.com',
+          password: 'password123',
+        }),
+      ).toEqual(registerUserDto);
+    });
+  });
+});

+ 46 - 0
src/iam/register/register.service.ts

@@ -0,0 +1,46 @@
+import { Injectable, Logger } from '@nestjs/common';
+import { HashingService } from '../../shared/hashing/hashing.service';
+import { MailerService } from '../../shared/mailer/mailer.service';
+import { IUsers } from '../../users/interfaces/users.interface';
+import { UsersService } from '../../users/users.service';
+import { RegisterUserDto } from './dto/register-user.dto';
+
+@Injectable()
+export class RegisterService {
+  constructor(
+    private readonly usersService: UsersService,
+    private readonly mailerService: MailerService,
+    private readonly hashingService: HashingService,
+  ) {}
+
+  public async register(registerUserDto: RegisterUserDto): Promise<IUsers> {
+    registerUserDto.password = await this.hashingService.hash(
+      registerUserDto.password,
+    );
+
+    this.sendMailRegisterUser(registerUserDto);
+
+    return this.usersService.create(registerUserDto);
+  }
+
+  private sendMailRegisterUser(user): void {
+    try {
+      this.mailerService.sendMail({
+        to: user.email,
+        from: 'from@example.com',
+        subject: 'Registration successful ✔',
+        text: 'Registration successful!',
+        template: 'index',
+        context: {
+          title: 'Registration successfully',
+          description:
+            "You did it! You registered!, You're successfully registered.✔",
+          nameUser: user.name,
+        },
+      });
+      Logger.log('[MailService] User Registration: Send Mail successfully!');
+    } catch (err) {
+      Logger.error('[MailService] User Registration: Send Mail failed!', err);
+    }
+  }
+}

+ 28 - 28
src/main.ts

@@ -1,34 +1,34 @@
-import { NestFactory } from '@nestjs/core'
-import { AppModule } from './app.module'
-import { Logger, ValidationPipe } from '@nestjs/common'
-import { ConfigService } from '@nestjs/config'
-import { configureSwaggerDocs } from './helpers/configure-swagger-docs.helper'
+import { NestFactory } from '@nestjs/core';
+import { AppModule } from './app.module';
+import { Logger, ValidationPipe } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { configureSwaggerDocs } from './helpers/configure-swagger-docs.helper';
 
 async function bootstrap() {
-    const app = await NestFactory.create(AppModule)
-    const configService = app.get<ConfigService>(ConfigService)
+  const app = await NestFactory.create(AppModule);
+  const configService = app.get<ConfigService>(ConfigService);
 
-    app.setGlobalPrefix('api')
-    app.useGlobalPipes(
-        new ValidationPipe({
-            whitelist: true,
-            transform: true,
-            forbidNonWhitelisted: true,
-            transformOptions: {
-                enableImplicitConversion: true
-            }
-        })
-    )
+  app.setGlobalPrefix('api');
+  app.useGlobalPipes(
+    new ValidationPipe({
+      whitelist: true,
+      transform: true,
+      forbidNonWhitelisted: true,
+      transformOptions: {
+        enableImplicitConversion: true,
+      },
+    }),
+  );
 
-    configureSwaggerDocs(app, configService)
+  configureSwaggerDocs(app, configService);
 
-    app.enableCors({
-        origin: configService.get<string>('ENDPOINT_CORS'),
-        methods: 'GET,POST,PUT,PATCH,DELETE',
-        credentials: true
-    })
-    const port = configService.get<number>('NODE_API_PORT') || 3000
-    await app.listen(port)
-    Logger.log(`Url for OpenApi: ${await app.getUrl()}/docs`, 'Swagger')
+  app.enableCors({
+    origin: configService.get<string>('ENDPOINT_CORS'),
+    methods: 'GET,POST,PUT,PATCH,DELETE',
+    credentials: true,
+  });
+  const port = configService.get<number>('NODE_API_PORT') || 3000;
+  await app.listen(port);
+  Logger.log(`Url for OpenApi: ${await app.getUrl()}/docs`, 'Swagger');
 }
-bootstrap()
+bootstrap();

+ 4 - 6
src/migrations/1589834500772-Api.ts

@@ -1,16 +1,14 @@
-import { MigrationInterface, QueryRunner } from 'typeorm'
+import {MigrationInterface, QueryRunner} from "typeorm";
 
 export class Api1589834500772 implements MigrationInterface {
     name = 'Api1589834500772'
 
     public async up(queryRunner: QueryRunner): Promise<void> {
-        await queryRunner.query(
-            'CREATE TABLE `user` (`id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `username` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, `password` varchar(60) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB',
-            undefined
-        )
+        await queryRunner.query("CREATE TABLE `user` (`id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `username` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, `password` varchar(60) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB", undefined);
     }
 
     public async down(queryRunner: QueryRunner): Promise<void> {
-        await queryRunner.query('DROP TABLE `user`', undefined)
+        await queryRunner.query("DROP TABLE `user`", undefined);
     }
+
 }

+ 4 - 4
src/repl.ts

@@ -1,7 +1,7 @@
-import { repl } from '@nestjs/core'
-import { AppModule } from './app.module'
+import { repl } from '@nestjs/core';
+import { AppModule } from './app.module';
 
 async function bootstrap() {
-    await repl(AppModule)
+  await repl(AppModule);
 }
-bootstrap()
+bootstrap();

+ 21 - 21
src/shared/hashing/bcrypt.service.spec.ts

@@ -1,26 +1,26 @@
-import { Test, TestingModule } from '@nestjs/testing'
-import { BcryptService } from './bcrypt.service'
+import { Test, TestingModule } from '@nestjs/testing';
+import { BcryptService } from './bcrypt.service';
 
 describe('BcryptService', () => {
-    let service: BcryptService
+  let service: BcryptService;
 
-    beforeEach(async () => {
-        const module: TestingModule = await Test.createTestingModule({
-            providers: [
-                {
-                    provide: BcryptService,
-                    useValue: {
-                        hash: jest.fn(() => 'pass123'),
-                        compare: jest.fn(() => true)
-                    }
-                }
-            ]
-        }).compile()
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        {
+          provide: BcryptService,
+          useValue: {
+            hash: jest.fn(() => 'pass123'),
+            compare: jest.fn(() => true),
+          },
+        },
+      ],
+    }).compile();
 
-        service = module.get<BcryptService>(BcryptService)
-    })
+    service = module.get<BcryptService>(BcryptService);
+  });
 
-    it('should be defined', () => {
-        expect(service).toBeDefined()
-    })
-})
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+});

+ 10 - 10
src/shared/hashing/bcrypt.service.ts

@@ -1,15 +1,15 @@
-import { Injectable } from '@nestjs/common'
-import { compare, genSalt, hash } from 'bcrypt'
-import { HashingService } from './hashing.service'
+import { Injectable } from '@nestjs/common';
+import { compare, genSalt, hash } from 'bcrypt';
+import { HashingService } from './hashing.service';
 
 @Injectable()
 export class BcryptService implements HashingService {
-    async hash(data: string | Buffer): Promise<string> {
-        const salt = await genSalt()
-        return hash(data, salt)
-    }
+  async hash(data: string | Buffer): Promise<string> {
+    const salt = await genSalt();
+    return hash(data, salt);
+  }
 
-    compare(data: string | Buffer, encrypted: string): Promise<boolean> {
-        return compare(data, encrypted)
-    }
+  compare(data: string | Buffer, encrypted: string): Promise<boolean> {
+    return compare(data, encrypted);
+  }
 }

+ 19 - 19
src/shared/hashing/hashing.service.spec.ts

@@ -1,24 +1,24 @@
-import { Test, TestingModule } from '@nestjs/testing'
-import { BcryptService } from './bcrypt.service'
-import { HashingService } from './hashing.service'
+import { Test, TestingModule } from '@nestjs/testing';
+import { BcryptService } from './bcrypt.service';
+import { HashingService } from './hashing.service';
 
 describe('HashingService', () => {
-    let service: HashingService
+  let service: HashingService;
 
-    beforeEach(async () => {
-        const module: TestingModule = await Test.createTestingModule({
-            providers: [
-                {
-                    provide: HashingService,
-                    useClass: BcryptService
-                }
-            ]
-        }).compile()
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        {
+          provide: HashingService,
+          useClass: BcryptService,
+        },
+      ],
+    }).compile();
 
-        service = module.get<HashingService>(HashingService)
-    })
+    service = module.get<HashingService>(HashingService);
+  });
 
-    it('should be defined', () => {
-        expect(service).toBeDefined()
-    })
-})
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+});

+ 3 - 3
src/shared/hashing/hashing.service.ts

@@ -1,7 +1,7 @@
-import { Injectable } from '@nestjs/common'
+import { Injectable } from '@nestjs/common';
 
 @Injectable()
 export abstract class HashingService {
-    abstract hash(data: string | Buffer): Promise<string>
-    abstract compare(data: string | Buffer, encrypted: string): Promise<boolean>
+  abstract hash(data: string | Buffer): Promise<string>;
+  abstract compare(data: string | Buffer, encrypted: string): Promise<boolean>;
 }

+ 6 - 6
src/shared/mailer/mailer.module.ts

@@ -1,10 +1,10 @@
-import { Module } from '@nestjs/common'
-import { MailerService } from './mailer.service'
-import { ConfigModule } from '@nestjs/config'
+import { Module } from '@nestjs/common';
+import { MailerService } from './mailer.service';
+import { ConfigModule } from '@nestjs/config';
 
 @Module({
-    imports: [ConfigModule.forRoot({ isGlobal: true })],
-    providers: [MailerService],
-    exports: [MailerService]
+  imports: [ConfigModule.forRoot({ isGlobal: true })],
+  providers: [MailerService],
+  exports: [MailerService],
 })
 export class MailerModule {}

+ 14 - 14
src/shared/mailer/mailer.service.spec.ts

@@ -1,19 +1,19 @@
-import { ConfigService } from '@nestjs/config'
-import { Test, TestingModule } from '@nestjs/testing'
-import { MailerService } from './mailer.service'
+import { ConfigService } from '@nestjs/config';
+import { Test, TestingModule } from '@nestjs/testing';
+import { MailerService } from './mailer.service';
 
 describe('MailerService', () => {
-    let service: MailerService
+  let service: MailerService;
 
-    beforeEach(async () => {
-        const module: TestingModule = await Test.createTestingModule({
-            providers: [ConfigService, MailerService]
-        }).compile()
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [ConfigService, MailerService],
+    }).compile();
 
-        service = module.get<MailerService>(MailerService)
-    })
+    service = module.get<MailerService>(MailerService);
+  });
 
-    it('should be defined', () => {
-        expect(service).toBeDefined()
-    })
-})
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+});

+ 39 - 32
src/shared/mailer/mailer.service.ts

@@ -1,39 +1,46 @@
-import { Injectable } from '@nestjs/common'
-import { createTransport } from 'nodemailer'
-import * as Mail from 'nodemailer/lib/mailer'
-import { ConfigService } from '@nestjs/config'
-import * as hbs from 'nodemailer-express-handlebars'
+import { Injectable } from '@nestjs/common';
+import { createTransport } from 'nodemailer';
+import * as Mail from 'nodemailer/lib/mailer';
+import { ConfigService } from '@nestjs/config';
+import * as hbs from 'nodemailer-express-handlebars';
 
 @Injectable()
 export class MailerService {
-    private nodemailerTransport: Mail
+  private nodemailerTransport: Mail;
 
-    constructor(private readonly configService: ConfigService) {
-        this.nodemailerTransport = createTransport({
-            host: this.configService.get<string>('EMAIL_HOST'),
-            port: this.configService.get<number>('EMAIL_PORT'),
-            auth: {
-                user: this.configService.get<string>('EMAIL_AUTH_USER'),
-                pass: this.configService.get<string>('EMAIL_AUTH_PASSWORD')
-            },
-            debug: this.configService.get<boolean>('EMAIL_DEBUG'),
-            logger: false
-        })
+  constructor(private readonly configService: ConfigService) {
+    this.nodemailerTransport = createTransport({
+      host: this.configService.get<string>('EMAIL_HOST'),
+      port: this.configService.get<number>('EMAIL_PORT'),
+      auth: {
+        user: this.configService.get<string>('EMAIL_AUTH_USER'),
+        pass: this.configService.get<string>('EMAIL_AUTH_PASSWORD'),
+      },
+      debug: this.configService.get<boolean>('EMAIL_DEBUG'),
+      logger: false,
+    });
 
-        const options = {
-            viewEngine: {
-                extname: '.hbs', // handlebars extension
-                layoutsDir: process.cwd() + `${this.configService.get<string>('EMAIL_LAYOUT_DIR')}`, // location of handlebars templates
-                defaultLayout: `${this.configService.get<string>('EMAIL_DEFAULT_LAYOUT')}`, // name of main template
-                partialsDir: process.cwd() + `${this.configService.get<string>('EMAIL_PARTIAL_DIR')}` // location of your subtemplates aka. header, footer etc
-            },
-            viewPath: process.cwd() + `${this.configService.get<string>('EMAIL_VIEW_PATH')}`,
-            extName: '.hbs'
-        }
-        this.nodemailerTransport.use('compile', hbs(options))
-    }
+    const options = {
+      viewEngine: {
+        extname: '.hbs', // handlebars extension
+        layoutsDir:
+          process.cwd() +
+          `${this.configService.get<string>('EMAIL_LAYOUT_DIR')}`, // location of handlebars templates
+        defaultLayout: `${this.configService.get<string>(
+          'EMAIL_DEFAULT_LAYOUT',
+        )}`, // name of main template
+        partialsDir:
+          process.cwd() +
+          `${this.configService.get<string>('EMAIL_PARTIAL_DIR')}`, // location of your subtemplates aka. header, footer etc
+      },
+      viewPath:
+        process.cwd() + `${this.configService.get<string>('EMAIL_VIEW_PATH')}`,
+      extName: '.hbs',
+    };
+    this.nodemailerTransport.use('compile', hbs(options));
+  }
 
-    sendMail(options: any) {
-        return this.nodemailerTransport.sendMail(options)
-    }
+  sendMail(options: any) {
+    return this.nodemailerTransport.sendMail(options);
+  }
 }

+ 4 - 4
src/shared/utils/utils.module.ts

@@ -1,8 +1,8 @@
-import { Module } from '@nestjs/common'
-import { UtilsService } from './utils.service'
+import { Module } from '@nestjs/common';
+import { UtilsService } from './utils.service';
 
 @Module({
-    providers: [UtilsService],
-    exports: [UtilsService]
+  providers: [UtilsService],
+  exports: [UtilsService],
 })
 export class UtilsModule {}

+ 13 - 13
src/shared/utils/utils.service.spec.ts

@@ -1,18 +1,18 @@
-import { Test, TestingModule } from '@nestjs/testing'
-import { UtilsService } from './utils.service'
+import { Test, TestingModule } from '@nestjs/testing';
+import { UtilsService } from './utils.service';
 
 describe('UtilsService', () => {
-    let service: UtilsService
+  let service: UtilsService;
 
-    beforeEach(async () => {
-        const module: TestingModule = await Test.createTestingModule({
-            providers: [UtilsService]
-        }).compile()
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [UtilsService],
+    }).compile();
 
-        service = module.get<UtilsService>(UtilsService)
-    })
+    service = module.get<UtilsService>(UtilsService);
+  });
 
-    it('should be defined', () => {
-        expect(service).toBeDefined()
-    })
-})
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+});

+ 5 - 5
src/shared/utils/utils.service.ts

@@ -1,9 +1,9 @@
-import { Injectable } from '@nestjs/common'
-import * as crypto from 'crypto'
+import { Injectable } from '@nestjs/common';
+import * as crypto from 'crypto';
 
 @Injectable()
 export class UtilsService {
-    public generatePassword(): string {
-        return crypto.randomUUID()
-    }
+  public generatePassword(): string {
+    return crypto.randomUUID();
+  }
 }

+ 2 - 2
src/users/dto/user-profile.dto.ts

@@ -1,4 +1,4 @@
-import { OmitType } from '@nestjs/swagger'
-import { UserDto } from './user.dto'
+import { OmitType } from '@nestjs/swagger';
+import { UserDto } from './user.dto';
 
 export class UserProfileDto extends OmitType(UserDto, ['password'] as const) {}

+ 2 - 2
src/users/dto/user-update.dto.ts

@@ -1,4 +1,4 @@
-import { PartialType } from '@nestjs/swagger'
-import { UserDto } from './user.dto'
+import { PartialType } from '@nestjs/swagger';
+import { UserDto } from './user.dto';
 
 export class UserUpdateDto extends PartialType(UserDto) {}

+ 15 - 15
src/users/dto/user.dto.ts

@@ -1,21 +1,21 @@
-import { MaxLength, IsNotEmpty, IsEmail, IsString } from 'class-validator'
+import { MaxLength, IsNotEmpty, IsEmail, IsString } from 'class-validator';
 
 export class UserDto {
-    @IsString()
-    @MaxLength(30)
-    readonly name: string
+  @IsString()
+  @MaxLength(30)
+  readonly name: string;
 
-    @IsString()
-    @MaxLength(40)
-    readonly username: string
+  @IsString()
+  @MaxLength(40)
+  readonly username: string;
 
-    @IsEmail()
-    @IsString()
-    @IsNotEmpty()
-    readonly email: string
+  @IsEmail()
+  @IsString()
+  @IsNotEmpty()
+  readonly email: string;
 
-    @IsNotEmpty()
-    @IsString()
-    @MaxLength(60)
-    password: string
+  @IsNotEmpty()
+  @IsString()
+  @MaxLength(60)
+  password: string;
 }

+ 13 - 13
src/users/entities/users.entity.ts

@@ -1,21 +1,21 @@
-import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
 
 @Entity()
 export class Users {
-    @PrimaryGeneratedColumn()
-    id: number
+  @PrimaryGeneratedColumn()
+  id: number;
 
-    @Column()
-    name: string
+  @Column()
+  name: string;
 
-    @Column()
-    username: string
+  @Column()
+  username: string;
 
-    @Column({
-        unique: true
-    })
-    email: string
+  @Column({
+    unique: true,
+  })
+  email: string;
 
-    @Column({ length: 60 })
-    password: string
+  @Column({ length: 60 })
+  password: string;
 }

+ 5 - 5
src/users/interfaces/users.interface.ts

@@ -1,7 +1,7 @@
 export interface IUsers {
-    readonly id: number
-    readonly name: string
-    readonly username: string
-    readonly email: string
-    readonly password: string
+  readonly id: number;
+  readonly name: string;
+  readonly username: string;
+  readonly email: string;
+  readonly password: string;
 }

+ 134 - 132
src/users/users.controller.spec.ts

@@ -1,140 +1,142 @@
-import { BadRequestException, NotFoundException } from '@nestjs/common'
-import { Test, TestingModule } from '@nestjs/testing'
-import { UserProfileDto } from './dto/user-profile.dto'
-import { UserDto } from './dto/user.dto'
-import { UsersController } from './users.controller'
-import { UsersService } from './users.service'
+import { BadRequestException, NotFoundException } from '@nestjs/common';
+import { Test, TestingModule } from '@nestjs/testing';
+import { UserProfileDto } from './dto/user-profile.dto';
+import { UserDto } from './dto/user.dto';
+import { UsersController } from './users.controller';
+import { UsersService } from './users.service';
 
 const userDto: UserDto = {
-    name: 'name #1',
-    username: 'username #1',
-    email: 'test@example.com',
-    password: 'password123'
-}
+  name: 'name #1',
+  username: 'username #1',
+  email: 'test@example.com',
+  password: 'password123',
+};
 
 const userUpdateDto: UserDto = {
-    name: 'name #1 update',
-    username: 'username #1 update',
-    email: 'test@example.com',
-    password: 'password123'
-}
+  name: 'name #1 update',
+  username: 'username #1 update',
+  email: 'test@example.com',
+  password: 'password123',
+};
 
 const userProfileDto: UserProfileDto = {
-    name: 'name #1',
-    username: 'username #1',
-    email: 'test@example.com'
-}
+  name: 'name #1',
+  username: 'username #1',
+  email: 'test@example.com',
+};
 
 describe('Users Controller', () => {
-    let usersController: UsersController
-    let usersService: UsersService
-
-    beforeEach(async () => {
-        const module: TestingModule = await Test.createTestingModule({
-            controllers: [UsersController],
-            providers: [
-                {
-                    provide: UsersService,
-                    useValue: {
-                        findAll: jest.fn(() => {}),
-                        findById: jest.fn(() => userDto),
-                        updateProfileUser: jest.fn(() => {}),
-                        updateUser: jest.fn(() => {}),
-                        deleteUser: jest.fn(() => userDto)
-                    }
-                }
-            ]
-        }).compile()
-
-        usersController = module.get<UsersController>(UsersController)
-        usersService = module.get<UsersService>(UsersService)
-    })
-
-    describe('Users Controller', () => {
-        it('should be defined', () => {
-            expect(usersController).toBeDefined()
-        })
-
-        describe('findAllUser() method', () => {
-            it('should call method findAllUser in userService', async () => {
-                const createSpy = jest.spyOn(usersService, 'findAll')
-
-                await usersController.findAllUser()
-                expect(createSpy).toHaveBeenCalled()
-            })
-        })
-
-        describe('findOneUser() method', () => {
-            it('should call method findOneUser in userService', async () => {
-                const createSpy = jest.spyOn(usersService, 'findById')
-
-                await usersController.findOneUser('anyid')
-                expect(createSpy).toHaveBeenCalledWith('anyid')
-            })
-        })
-
-        describe('findById() method', () => {
-            it('should call method getUser in userService', async () => {
-                const createSpy = jest.spyOn(usersService, 'findById')
-
-                await usersController.getUser('1')
-                expect(createSpy).toHaveBeenCalledWith('1')
-            })
-
-            it('should return an exception if update user fails', async () => {
-                usersService.findById = jest.fn().mockResolvedValueOnce(null)
-                await expect(usersController.getUser('not correct id')).rejects.toThrow(NotFoundException)
-            })
-        })
-
-        describe('updateProfileUser() method', () => {
-            it('should call method updateProfileUser in userService', async () => {
-                const createSpy = jest.spyOn(usersService, 'updateProfileUser')
-
-                await usersController.updateProfileUser('1', userProfileDto)
-                expect(createSpy).toHaveBeenCalledWith('1', userProfileDto)
-            })
-
-            it('should return an exception if update profile user fails', async () => {
-                usersService.updateProfileUser = jest.fn().mockRejectedValueOnce(null)
-                await expect(
-                    usersController.updateProfileUser('not a correct id', {
-                        name: 'not a correct name',
-                        username: 'not a correct username',
-                        email: 'not a correct email'
-                    })
-                ).rejects.toThrow(BadRequestException)
-            })
-        })
-
-        describe('updateUser() method', () => {
-            it('should call method updateUser in userService', async () => {
-                const createSpy = jest.spyOn(usersService, 'updateUser')
-
-                await usersController.updateUser('1', userUpdateDto)
-                expect(createSpy).toHaveBeenCalledWith('1', userUpdateDto)
-            })
-
-            it('should return an exception if update user fails', async () => {
-                usersService.updateUser = jest.fn().mockRejectedValueOnce(null)
-                await expect(
-                    usersController.updateUser('not a correct id', {
-                        name: 'not a correct name',
-                        username: 'not a correct username',
-                        email: 'not a correct email',
-                        password: 'not a correct password'
-                    })
-                ).rejects.toThrow(BadRequestException)
-            })
-        })
-
-        describe('deleteUser() method', () => {
-            it('should call method deleteUser in userService', async () => {
-                const createSpy = jest.spyOn(usersService, 'deleteUser')
-
-                await usersController.deleteUser('anyid')
-                expect(createSpy).toHaveBeenCalledWith('anyid')
-            })
-        })
-    })
-})
+  let usersController: UsersController;
+  let usersService: UsersService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [UsersController],
+      providers: [
+        {
+          provide: UsersService,
+          useValue: {
+            findAll: jest.fn(() => {}),
+            findById: jest.fn(() => userDto),
+            updateProfileUser: jest.fn(() => {}),
+            updateUser: jest.fn(() => {}),
+            deleteUser: jest.fn(() => userDto),
+          },
+        },
+      ],
+    }).compile();
+
+    usersController = module.get<UsersController>(UsersController);
+    usersService = module.get<UsersService>(UsersService);
+  });
+
+  describe('Users Controller', () => {
+    it('should be defined', () => {
+      expect(usersController).toBeDefined();
+    });
+
+    describe('findAllUser() method', () => {
+      it('should call method findAllUser in userService', async () => {
+        const createSpy = jest.spyOn(usersService, 'findAll');
+
+        await usersController.findAllUser();
+        expect(createSpy).toHaveBeenCalled();
+      });
+    });
+
+    describe('findOneUser() method', () => {
+      it('should call method findOneUser in userService', async () => {
+        const createSpy = jest.spyOn(usersService, 'findById');
+
+        await usersController.findOneUser('anyid');
+        expect(createSpy).toHaveBeenCalledWith('anyid');
+      });
+    });
+
+    describe('findById() method', () => {
+      it('should call method getUser in userService', async () => {
+        const createSpy = jest.spyOn(usersService, 'findById');
+
+        await usersController.getUser('1');
+        expect(createSpy).toHaveBeenCalledWith('1');
+      });
+
+      it('should return an exception if update user fails', async () => {
+        usersService.findById = jest.fn().mockResolvedValueOnce(null);
+        await expect(usersController.getUser('not correct id')).rejects.toThrow(
+          NotFoundException,
+        );
+      });
+    });
+
+    describe('updateProfileUser() method', () => {
+      it('should call method updateProfileUser in userService', async () => {
+        const createSpy = jest.spyOn(usersService, 'updateProfileUser');
+
+        await usersController.updateProfileUser('1', userProfileDto);
+        expect(createSpy).toHaveBeenCalledWith('1', userProfileDto);
+      });
+
+      it('should return an exception if update profile user fails', async () => {
+        usersService.updateProfileUser = jest.fn().mockRejectedValueOnce(null);
+        await expect(
+          usersController.updateProfileUser('not a correct id', {
+            name: 'not a correct name',
+            username: 'not a correct username',
+            email: 'not a correct email',
+          }),
+        ).rejects.toThrow(BadRequestException);
+      });
+    });
+
+    describe('updateUser() method', () => {
+      it('should call method updateUser in userService', async () => {
+        const createSpy = jest.spyOn(usersService, 'updateUser');
+
+        await usersController.updateUser('1', userUpdateDto);
+        expect(createSpy).toHaveBeenCalledWith('1', userUpdateDto);
+      });
+
+      it('should return an exception if update user fails', async () => {
+        usersService.updateUser = jest.fn().mockRejectedValueOnce(null);
+        await expect(
+          usersController.updateUser('not a correct id', {
+            name: 'not a correct name',
+            username: 'not a correct username',
+            email: 'not a correct email',
+            password: 'not a correct password',
+          }),
+        ).rejects.toThrow(BadRequestException);
+      });
+    });
+
+    describe('deleteUser() method', () => {
+      it('should call method deleteUser in userService', async () => {
+        const createSpy = jest.spyOn(usersService, 'deleteUser');
+
+        await usersController.deleteUser('anyid');
+        expect(createSpy).toHaveBeenCalledWith('anyid');
+      });
+    });
+  });
+});

+ 69 - 66
src/users/users.controller.ts

@@ -1,85 +1,88 @@
 import {
-    Controller,
-    Put,
-    Get,
-    Body,
-    Param,
-    HttpStatus,
-    NotFoundException,
-    Delete,
-    BadRequestException
-} from '@nestjs/common'
-import { UsersService } from './users.service'
-import { UserProfileDto } from './dto/user-profile.dto'
-import { UserUpdateDto } from './dto/user-update.dto'
-import { IUsers } from './interfaces/users.interface'
-import { ApiTags } from '@nestjs/swagger'
-import { AuthGuard } from '../iam/login/decorators/auth-guard.decorator'
-import { AuthType } from '../iam/login/enums/auth-type.enum'
+  Controller,
+  Put,
+  Get,
+  Body,
+  Param,
+  HttpStatus,
+  NotFoundException,
+  Delete,
+  BadRequestException,
+} from '@nestjs/common';
+import { UsersService } from './users.service';
+import { UserProfileDto } from './dto/user-profile.dto';
+import { UserUpdateDto } from './dto/user-update.dto';
+import { IUsers } from './interfaces/users.interface';
+import { ApiTags } from '@nestjs/swagger';
+import { AuthGuard } from '../iam/login/decorators/auth-guard.decorator';
+import { AuthType } from '../iam/login/enums/auth-type.enum';
 
 @ApiTags('users')
 @AuthGuard(AuthType.Bearer)
 @Controller('users')
 export class UsersController {
-    constructor(private readonly usersService: UsersService) {}
+  constructor(private readonly usersService: UsersService) {}
 
-    @Get()
-    public async findAllUser(): Promise<IUsers[]> {
-        return this.usersService.findAll()
-    }
-
-    @Get('/:userId')
-    public async findOneUser(@Param('userId') userId: string): Promise<IUsers> {
-        return this.usersService.findById(userId)
-    }
+  @Get()
+  public async findAllUser(): Promise<IUsers[]> {
+    return this.usersService.findAll();
+  }
 
-    @Get('/:userId/profile')
-    public async getUser(@Param('userId') userId: string): Promise<any> {
-        const user = await this.findOneUser(userId)
+  @Get('/:userId')
+  public async findOneUser(@Param('userId') userId: string): Promise<IUsers> {
+    return this.usersService.findById(userId);
+  }
 
-        if (!user) {
-            throw new NotFoundException('User does not exist!')
-        }
+  @Get('/:userId/profile')
+  public async getUser(@Param('userId') userId: string): Promise<any> {
+    const user = await this.findOneUser(userId);
 
-        return {
-            user: user,
-            status: HttpStatus.OK
-        }
+    if (!user) {
+      throw new NotFoundException('User does not exist!');
     }
 
-    @Put('/:userId/profile')
-    public async updateProfileUser(
-        @Param('userId') userId: string,
-        @Body() userProfileDto: UserProfileDto
-    ): Promise<any> {
-        try {
-            await this.usersService.updateProfileUser(userId, userProfileDto)
+    return {
+      user: user,
+      status: HttpStatus.OK,
+    };
+  }
 
-            return {
-                message: 'User Updated successfully!',
-                status: HttpStatus.OK
-            }
-        } catch (err) {
-            throw new BadRequestException(err, 'Error: User not updated!')
-        }
+  @Put('/:userId/profile')
+  public async updateProfileUser(
+    @Param('userId') userId: string,
+    @Body() userProfileDto: UserProfileDto,
+  ): Promise<any> {
+    try {
+      await this.usersService.updateProfileUser(userId, userProfileDto);
+
+      return {
+        message: 'User Updated successfully!',
+        status: HttpStatus.OK,
+      };
+    } catch (err) {
+      throw new BadRequestException(err, 'Error: User not updated!');
     }
+  }
 
-    @Put('/:userId')
-    public async updateUser(@Param('userId') userId: string, @Body() userUpdateDto: UserUpdateDto) {
-        try {
-            await this.usersService.updateUser(userId, userUpdateDto)
+  @Put('/:userId')
+  public async updateUser(
+    @Param('userId') userId: string,
+    @Body() userUpdateDto: UserUpdateDto,
+  ) {
+    try {
+      await this.usersService.updateUser(userId, userUpdateDto);
 
-            return {
-                message: 'User Updated successfully!',
-                status: HttpStatus.OK
-            }
-        } catch (err) {
-            throw new BadRequestException(err, 'Error: User not updated!')
-        }
+      return {
+        message: 'User Updated successfully!',
+        status: HttpStatus.OK,
+      };
+    } catch (err) {
+      throw new BadRequestException(err, 'Error: User not updated!');
     }
+  }
 
-    @Delete('/:userId')
-    public async deleteUser(@Param('userId') userId: string): Promise<void> {
-        await this.usersService.deleteUser(userId)
-    }
+  @Delete('/:userId')
+  public async deleteUser(@Param('userId') userId: string): Promise<void> {
+    await this.usersService.deleteUser(userId);
+  }
 }

+ 17 - 33
src/users/users.module.ts

@@ -1,37 +1,21 @@
-import { Module } from '@nestjs/common'
-import { TypeOrmModule } from '@nestjs/typeorm'
-import { Users } from './entities/users.entity'
-import { UsersService } from './users.service'
-import { UsersController } from './users.controller'
-import { MailerModule } from '../shared/mailer/mailer.module'
-import { BcryptService } from '../shared/hashing/bcrypt.service'
-import { HashingService } from '../shared/hashing/hashing.service'
-import jwtConfig from 'src/iam/config/jwt.config'
-import { JwtModule } from '@nestjs/jwt'
-import { ConfigModule } from '@nestjs/config'
-import { APP_GUARD } from '@nestjs/core'
-import { AuthenticationGuard } from 'src/iam/guards/authentication/authentication.guard'
-import { UtilsModule } from 'src/shared/utils/utils.module'
+import { Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { Users } from './entities/users.entity';
+import { UsersService } from './users.service';
+import { UsersController } from './users.controller';
+import { MailerModule } from '../shared/mailer/mailer.module';
+import { BcryptService } from '../shared/hashing/bcrypt.service';
+import { HashingService } from '../shared/hashing/hashing.service';
 
 @Module({
-    imports: [
-        TypeOrmModule.forFeature([Users]),
-        MailerModule,
-        ConfigModule.forFeature(jwtConfig),
-        JwtModule.registerAsync(jwtConfig.asProvider()),
-        UtilsModule
-    ],
-    controllers: [UsersController],
-    providers: [
-        {
-            provide: HashingService,
-            useClass: BcryptService
-        },
-        {
-            provide: APP_GUARD,
-            useClass: AuthenticationGuard
-        },
-        UsersService
-    ]
+  imports: [TypeOrmModule.forFeature([Users]), MailerModule],
+  controllers: [UsersController],
+  providers: [
+    {
+      provide: HashingService,
+      useClass: BcryptService,
+    },
+    UsersService,
+  ],
 })
 export class UsersModule {}

+ 232 - 218
src/users/users.service.spec.ts

@@ -1,235 +1,249 @@
-import { HttpException, HttpStatus, NotFoundException } from '@nestjs/common'
-import { Test, TestingModule } from '@nestjs/testing'
-import { getRepositoryToken } from '@nestjs/typeorm'
-import { Repository } from 'typeorm'
-import { BcryptService } from '../shared/hashing/bcrypt.service'
-import { HashingService } from '../shared/hashing/hashing.service'
-import { UserDto } from './dto/user.dto'
-import { Users } from './entities/users.entity'
-import { UsersService } from './users.service'
+import { HttpException, HttpStatus, NotFoundException } from '@nestjs/common';
+import { Test, TestingModule } from '@nestjs/testing';
+import { getRepositoryToken } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+import { BcryptService } from '../shared/hashing/bcrypt.service';
+import { HashingService } from '../shared/hashing/hashing.service';
+import { UserDto } from './dto/user.dto';
+import { Users } from './entities/users.entity';
+import { UsersService } from './users.service';
 
 const userArray = [
-    {
-        id: 1,
-        name: 'name #1',
-        username: 'username #1',
-        email: 'test1@example.com',
-        password: 'pass123'
-    },
-    {
-        id: 2,
-        name: 'name #2',
-        username: 'username #2',
-        email: 'test2@example.com',
-        password: 'pass123'
-    }
-]
-
-const oneUser = {
+  {
     id: 1,
     name: 'name #1',
     username: 'username #1',
-    email: 'test@example.com',
-    password: 'pass123'
-}
+    email: 'test1@example.com',
+    password: 'pass123',
+  },
+  {
+    id: 2,
+    name: 'name #2',
+    username: 'username #2',
+    email: 'test2@example.com',
+    password: 'pass123',
+  },
+];
+
+const oneUser = {
+  id: 1,
+  name: 'name #1',
+  username: 'username #1',
+  email: 'test@example.com',
+  password: 'pass123',
+};
 
 const createUser: UserDto = {
-    name: 'name #1',
-    username: 'username #1',
-    email: 'test@example.com',
-    password: 'pass123'
-}
+  name: 'name #1',
+  username: 'username #1',
+  email: 'test@example.com',
+  password: 'pass123',
+};
 
 const updateUserByEmail = {
-    name: 'name #1',
-    username: 'username #1',
-    email: 'test@example.com',
-    password: 'pass123'
-}
+  name: 'name #1',
+  username: 'username #1',
+  email: 'test@example.com',
+  password: 'pass123',
+};
 
 const updateUserByPassword = {
-    name: 'name #1',
-    username: 'username #1',
-    email: 'test@example.com',
-    password: 'pass123'
-}
+  name: 'name #1',
+  username: 'username #1',
+  email: 'test@example.com',
+  password: 'pass123',
+};
 
 const updateProfileUser = {
-    name: 'name #1',
-    username: 'username #1',
-    email: 'test@example.com',
-    password: 'pass123'
-}
+  name: 'name #1',
+  username: 'username #1',
+  email: 'test@example.com',
+  password: 'pass123',
+};
 
 const updateUser = {
-    id: 1,
-    name: 'name #1 update',
-    username: 'username #1 update',
-    email: 'test@example.com',
-    password: 'pass123'
-}
+  id: 1,
+  name: 'name #1 update',
+  username: 'username #1 update',
+  email: 'test@example.com',
+  password: 'pass123',
+};
 
 describe('UsersService', () => {
-    let service: UsersService
-    let repository: Repository<Users>
-
-    beforeEach(async () => {
-        const module: TestingModule = await Test.createTestingModule({
-            providers: [
-                UsersService,
-                {
-                    provide: HashingService,
-                    useClass: BcryptService
-                },
-                {
-                    provide: getRepositoryToken(Users),
-                    useValue: {
-                        find: jest.fn().mockResolvedValue(userArray),
-                        findOne: jest.fn().mockResolvedValue(oneUser),
-                        findOneBy: jest.fn().mockResolvedValueOnce(oneUser),
-                        save: jest.fn().mockReturnValue(createUser),
-                        updateByEmail: jest.fn().mockResolvedValue(updateUserByEmail),
-                        updateByPassword: jest.fn().mockResolvedValue(updateUserByPassword),
-                        updateProfileUser: jest.fn().mockResolvedValue(updateProfileUser),
-                        update: jest.fn().mockReturnValue(updateUser),
-                        remove: jest.fn()
-                    }
-                }
-            ]
-        }).compile()
-
-        service = module.get<UsersService>(UsersService)
-        repository = module.get<Repository<Users>>(getRepositoryToken(Users))
-    })
-
-    it('should be defined', () => {
-        expect(service).toBeDefined()
-    })
-
-    describe('findAll() method', () => {
-        it('should return an array of all users', async () => {
-            const users = await service.findAll()
-            expect(users).toEqual(userArray)
-        })
-    })
-
-    describe('findByEmail() method', () => {
-        it('should find a user by email', async () => {
-            expect(await service.findByEmail('test@example.com')).toEqual(oneUser)
-        })
-
-        it('should throw an exception if it not found a user by email', async () => {
-            repository.findOneBy = jest.fn().mockResolvedValueOnce(null)
-            await expect(service.findByEmail('not a correct email')).rejects.toThrow(NotFoundException)
-        })
-    })
-    describe('findById() method', () => {
-        it('should find a user by id', async () => {
-            expect(await service.findById('anyid')).toEqual(oneUser)
-        })
-
-        it('should throw an exception if it not found a user by id', async () => {
-            repository.findOneBy = jest.fn().mockResolvedValueOnce(null)
-            await expect(service.findById('not a correct id')).rejects.toThrow(NotFoundException)
-        })
-    })
-
-    describe('create() method', () => {
-        it('should create a new user', async () => {
-            expect(
-                await service.create({
-                    name: 'name #1',
-                    username: 'username #1',
-                    email: 'test@example.com',
-                    password: 'pass123'
-                })
-            ).toEqual(createUser)
-        })
-
-        it('should return an exception if login fails', async () => {
-            repository.save = jest.fn().mockRejectedValueOnce(null)
-            await expect(
-                service.create({
-                    name: 'not a correct name',
-                    username: 'not a correct username',
-                    email: 'not a correct email',
-                    password: 'not a correct password'
-                })
-            ).rejects.toThrow(HttpException)
-        })
-    })
-
-    describe('updateByEmail() method', () => {
-        it('should update a user by email', async () => {
-            expect(await service.updateByEmail('test@example.com')).toEqual(updateUserByEmail)
-        })
-
-        it('should return an exception if update by email fails', async () => {
-            repository.save = jest.fn().mockRejectedValueOnce(null)
-            await expect(service.updateByEmail('not a correct email')).rejects.toThrow(HttpException)
-        })
-    })
-
-    describe('updateByPassword() method', () => {
-        it('should update a user by password', async () => {
-            expect(await service.updateByPassword('test@example.com', 'pass123')).toEqual(updateUserByPassword)
-        })
-
-        it('should return an exception if update by password fails', async () => {
-            repository.save = jest.fn().mockRejectedValueOnce(null)
-            await expect(service.updateByPassword('not a correct email', 'not correct password')).rejects.toThrow(
-                HttpException
-            )
-        })
-    })
-
-    describe('updateProfileUser() method', () => {
-        it('should update profile of a user by id', async () => {
-            expect(await service.updateProfileUser('anyid', updateProfileUser)).toEqual(updateProfileUser)
-        })
-
-        it('should return an exception if update profile user fails', async () => {
-            repository.save = jest.fn().mockRejectedValueOnce(null)
-            await expect(
-                service.updateProfileUser('not a correct id', {
-                    name: 'not a correct name',
-                    username: 'not a correct username',
-                    email: 'not a correct email'
-                })
-            ).rejects.toThrow(HttpException)
-        })
-    })
-
-    describe('updateUser() method', () => {
-        it('should update a user by id', async () => {
-            expect(await service.updateUser('anyid', updateUser)).toEqual(updateUser)
-        })
-
-        it('should return an exception if update profile user fails', async () => {
-            repository.update = jest.fn().mockRejectedValueOnce(null)
-            await expect(
-                service.updateUser('not a correct id', {
-                    name: 'not a correct name',
-                    username: 'not a correct username',
-                    email: 'not a correct email',
-                    password: 'not a correct password'
-                })
-            ).rejects.toThrow(HttpException)
-        })
-    })
-
-    describe('deleteUser() method', () => {
-        it('should remove a user by id', async () => {
-            const removeSpy = jest.spyOn(repository, 'remove')
-            const user = await service.deleteUser('any id')
-            expect(removeSpy).toBeCalledWith(oneUser)
-            expect(user).toBeUndefined()
-        })
-
-        it('should throw an error if no user is found with an id', async () => {
-            repository.findOneBy = jest.fn().mockResolvedValueOnce(undefined)
-            await expect(service.deleteUser('bad id')).rejects.toThrow(NotFoundException)
-            expect(repository.findOneBy).toBeCalledTimes(1)
-        })
-    })
-})
+  let service: UsersService;
+  let repository: Repository<Users>;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        UsersService,
+        {
+          provide: HashingService,
+          useClass: BcryptService,
+        },
+        {
+          provide: getRepositoryToken(Users),
+          useValue: {
+            find: jest.fn().mockResolvedValue(userArray),
+            findOne: jest.fn().mockResolvedValue(oneUser),
+            findOneBy: jest.fn().mockResolvedValueOnce(oneUser),
+            save: jest.fn().mockReturnValue(createUser),
+            updateByEmail: jest.fn().mockResolvedValue(updateUserByEmail),
+            updateByPassword: jest.fn().mockResolvedValue(updateUserByPassword),
+            updateProfileUser: jest.fn().mockResolvedValue(updateProfileUser),
+            update: jest.fn().mockReturnValue(updateUser),
+            remove: jest.fn(),
+          },
+        },
+      ],
+    }).compile();
+
+    service = module.get<UsersService>(UsersService);
+    repository = module.get<Repository<Users>>(getRepositoryToken(Users));
+  });
+
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+
+  describe('findAll() method', () => {
+    it('should return an array of all users', async () => {
+      const users = await service.findAll();
+      expect(users).toEqual(userArray);
+    });
+  });
+
+  describe('findByEmail() method', () => {
+    it('should find a user by email', async () => {
+      expect(await service.findByEmail('test@example.com')).toEqual(oneUser);
+    });
+
+    it('should throw an exception if it not found a user by email', async () => {
+      repository.findOneBy = jest.fn().mockResolvedValueOnce(null);
+      await expect(service.findByEmail('not a correct email')).rejects.toThrow(
+        NotFoundException,
+      );
+    });
+  });
+  describe('findById() method', () => {
+    it('should find a user by id', async () => {
+      expect(await service.findById('anyid')).toEqual(oneUser);
+    });
+
+    it('should throw an exception if it not found a user by id', async () => {
+      repository.findOneBy = jest.fn().mockResolvedValueOnce(null);
+      await expect(service.findById('not a correct id')).rejects.toThrow(
+        NotFoundException,
+      );
+    });
+  });
+
+  describe('create() method', () => {
+    it('should create a new user', async () => {
+      expect(
+        await service.create({
+          name: 'name #1',
+          username: 'username #1',
+          email: 'test@example.com',
+          password: 'pass123',
+        }),
+      ).toEqual(createUser);
+    });
+
+    it('should return an exception if login fails', async () => {
+      repository.save = jest.fn().mockRejectedValueOnce(null);
+      await expect(
+        service.create({
+          name: 'not a correct name',
+          username: 'not a correct username',
+          email: 'not a correct email',
+          password: 'not a correct password',
+        }),
+      ).rejects.toThrow(HttpException);
+    });
+  });
+
+  describe('updateByEmail() method', () => {
+    it('should update a user by email', async () => {
+      expect(await service.updateByEmail('test@example.com')).toEqual(
+        updateUserByEmail,
+      );
+    });
+
+    it('should return an exception if update by email fails', async () => {
+      repository.save = jest.fn().mockRejectedValueOnce(null);
+      await expect(
+        service.updateByEmail('not a correct email'),
+      ).rejects.toThrow(HttpException);
+    });
+  });
+
+  describe('updateByPassword() method', () => {
+    it('should update a user by password', async () => {
+      expect(
+        await service.updateByPassword('test@example.com', 'pass123'),
+      ).toEqual(updateUserByPassword);
+    });
+
+    it('should return an exception if update by password fails', async () => {
+      repository.save = jest.fn().mockRejectedValueOnce(null);
+      await expect(
+        service.updateByPassword('not a correct email', 'not correct password'),
+      ).rejects.toThrow(HttpException);
+    });
+  });
+
+  describe('updateProfileUser() method', () => {
+    it('should update profile of a user by id', async () => {
+      expect(
+        await service.updateProfileUser('anyid', updateProfileUser),
+      ).toEqual(updateProfileUser);
+    });
+
+    it('should return an exception if update profile user fails', async () => {
+      repository.save = jest.fn().mockRejectedValueOnce(null);
+      await expect(
+        service.updateProfileUser('not a correct id', {
+          name: 'not a correct name',
+          username: 'not a correct username',
+          email: 'not a correct email',
+        }),
+      ).rejects.toThrow(HttpException);
+    });
+  });
+
+  describe('updateUser() method', () => {
+    it('should update a user by id', async () => {
+      expect(await service.updateUser('anyid', updateUser)).toEqual(updateUser);
+    });
+
+    it('should return an exception if update profile user fails', async () => {
+      repository.update = jest.fn().mockRejectedValueOnce(null);
+      await expect(
+        service.updateUser('not a correct id', {
+          name: 'not a correct name',
+          username: 'not a correct username',
+          email: 'not a correct email',
+          password: 'not a correct password',
+        }),
+      ).rejects.toThrow(HttpException);
+    });
+  });
+
+  describe('deleteUser() method', () => {
+    it('should remove a user by id', async () => {
+      const removeSpy = jest.spyOn(repository, 'remove');
+      const user = await service.deleteUser('any id');
+      expect(removeSpy).toBeCalledWith(oneUser);
+      expect(user).toBeUndefined();
+    });
+
+    it('should throw an error if no user is found with an id', async () => {
+      repository.findOneBy = jest.fn().mockResolvedValueOnce(undefined);
+      await expect(service.deleteUser('bad id')).rejects.toThrow(
+        NotFoundException,
+      );
+      expect(repository.findOneBy).toBeCalledTimes(1);
+    });
+  });
+});

+ 121 - 250
src/users/users.service.ts

@@ -1,255 +1,126 @@
 import {
-    Injectable,
-    NotFoundException,
-    HttpException,
-    HttpStatus,
-    BadRequestException,
-    Logger,
-    UnauthorizedException
-} from '@nestjs/common'
-import { Repository, UpdateResult } from 'typeorm'
-import { InjectRepository } from '@nestjs/typeorm'
-import { Users } from './entities/users.entity'
-import { IUsers } from './interfaces/users.interface'
-import { UserDto } from './dto/user.dto'
-import { UserProfileDto } from './dto/user-profile.dto'
-import { UserUpdateDto } from './dto/user-update.dto'
-import { HashingService } from '../shared/hashing/hashing.service'
-import { MailerService } from 'src/shared/mailer/mailer.service'
-import { RegisterUserDto } from 'src/iam/dto/register-user.dto'
-import { LoginDto } from 'src/iam/dto/login.dto'
-import { JwtService } from '@nestjs/jwt'
-import { ConfigService } from '@nestjs/config'
-import { JWTPayload } from '../iam/interfaces/jwt-payload.interface'
-import { UtilsService } from 'src/shared/utils/utils.service'
-import { ForgotPasswordDto } from 'src/iam/dto/forgot-password.dto'
-import { ChangePasswordDto } from 'src/iam/dto/change-password.dto'
+  Injectable,
+  NotFoundException,
+  HttpException,
+  HttpStatus,
+  BadRequestException,
+} from '@nestjs/common';
+import { Repository, UpdateResult } from 'typeorm';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Users } from './entities/users.entity';
+import { IUsers } from './interfaces/users.interface';
+import { UserDto } from './dto/user.dto';
+import { UserProfileDto } from './dto/user-profile.dto';
+import { UserUpdateDto } from './dto/user-update.dto';
+import { HashingService } from '../shared/hashing/hashing.service';
 
 @Injectable()
 export class UsersService {
-    constructor(
-        @InjectRepository(Users)
-        private readonly userRepository: Repository<Users>,
-        private readonly hashingService: HashingService,
-        private readonly mailerService: MailerService,
-        private readonly jwtService: JwtService,
-        private readonly configService: ConfigService,
-        private readonly utilsService: UtilsService
-    ) {}
-
-    public async register(registerUserDto: RegisterUserDto): Promise<IUsers> {
-        registerUserDto.password = await this.hashingService.hash(registerUserDto.password)
-
-        this.sendMailRegisterUser(registerUserDto)
-
-        return this.create(registerUserDto)
-    }
-
-    private sendMailRegisterUser(user): void {
-        try {
-            this.mailerService.sendMail({
-                to: user.email,
-                from: 'from@example.com',
-                subject: 'Registration successful ✔',
-                text: 'Registration successful!',
-                template: 'index',
-                context: {
-                    title: 'Registration successfully',
-                    description: "You did it! You registered!, You're successfully registered.✔",
-                    nameUser: user.name
-                }
-            })
-            Logger.log('[MailService] User Registration: Send Mail successfully!')
-        } catch (err) {
-            Logger.error('[MailService] User Registration: Send Mail failed!', err)
-        }
-    }
-
-    public async findUserByEmail(loginDto: LoginDto): Promise<IUsers> {
-        return await this.findByEmail(loginDto.email)
-    }
-
-    public async login(loginDto: LoginDto): Promise<any> {
-        try {
-            const user = await this.findUserByEmail(loginDto)
-            if (!user) {
-                throw new UnauthorizedException('User does not exists')
-            }
-
-            const passwordIsValid = await this.hashingService.compare(loginDto.password, user.password)
-
-            if (!passwordIsValid) {
-                throw new UnauthorizedException('Authentication failed. Wrong password')
-            }
-
-            return await this.signToken({
-                name: user.name,
-                email: user.email,
-                id: user.id
-            })
-        } catch (err) {
-            throw new HttpException(err, HttpStatus.BAD_REQUEST)
-        }
-    }
-
-    private async signToken(payload: JWTPayload): Promise<any> {
-        const accessToken = await this.jwtService.signAsync(payload)
-
-        return {
-            sub: payload.id,
-            expiresIn: this.configService.get<string>('JWT_ACCESS_TOKEN_TTL'),
-            audience: this.configService.get<string>('JWT_TOKEN_AUDIENCE'),
-            issuer: this.configService.get<string>('JWT_TOKEN_ISSUER'),
-            accessToken: accessToken,
-            user: payload
-        }
-    }
-
-    public async findAll(): Promise<Users[]> {
-        return await this.userRepository.find()
-    }
-
-    public async findByEmail(email: string): Promise<Users> {
-        const user = await this.userRepository.findOneBy({
-            email: email
-        })
-
-        if (!user) {
-            throw new NotFoundException(`User not found`)
-        }
-
-        return user
-    }
-
-    public async findById(userId: string): Promise<Users> {
-        const user = await this.userRepository.findOneBy({
-            id: +userId
-        })
-
-        if (!user) {
-            throw new NotFoundException(`User #${userId} not found`)
-        }
-
-        return user
-    }
-
-    public async create(userDto: UserDto): Promise<IUsers> {
-        try {
-            return await this.userRepository.save(userDto)
-        } catch (err) {
-            throw new HttpException(err, HttpStatus.BAD_REQUEST)
-        }
-    }
-
-    public async updateByEmail(email: string): Promise<Users> {
-        try {
-            const user = await this.userRepository.findOneBy({ email: email })
-            user.password = await this.hashingService.hash(Math.random().toString(36).slice(-8))
-
-            return await this.userRepository.save(user)
-        } catch (err) {
-            throw new HttpException(err, HttpStatus.BAD_REQUEST)
-        }
-    }
-
-    public async updateByPassword(email: string, password: string): Promise<Users> {
-        try {
-            const user = await this.userRepository.findOneBy({ email: email })
-            user.password = await this.hashingService.hash(password)
-
-            return await this.userRepository.save(user)
-        } catch (err) {
-            throw new HttpException(err, HttpStatus.BAD_REQUEST)
-        }
-    }
-
-    public async updateProfileUser(id: string, userProfileDto: UserProfileDto): Promise<Users> {
-        try {
-            const user = await this.userRepository.findOneBy({ id: +id })
-            user.name = userProfileDto.name
-            user.email = userProfileDto.email
-            user.username = userProfileDto.username
-
-            return await this.userRepository.save(user)
-        } catch (err) {
-            throw new HttpException(err, HttpStatus.BAD_REQUEST)
-        }
-    }
-
-    public async updateUser(id: string, userUpdateDto: UserUpdateDto): Promise<UpdateResult> {
-        try {
-            const user = await this.userRepository.update(
-                {
-                    id: +id
-                },
-                { ...userUpdateDto }
-            )
-
-            return user
-        } catch (err) {
-            throw new BadRequestException('User not updated')
-        }
-    }
-
-    public async deleteUser(id: string): Promise<void> {
-        const user = await this.findById(id)
-        await this.userRepository.remove(user)
-    }
-
-    public async forgotPassword(forgotPasswordDto: ForgotPasswordDto): Promise<any> {
-        const userUpdate = await this.userRepository.findOneBy({
-            email: forgotPasswordDto.email
-        })
-        const passwordRand = this.utilsService.generatePassword()
-        userUpdate.password = await this.hashingService.hash(passwordRand)
-
-        this.sendMailForgotPassword(userUpdate.email, passwordRand)
-
-        return await this.userRepository.save(userUpdate)
-    }
-
-    private sendMailForgotPassword(email, password): void {
-        try {
-            this.mailerService.sendMail({
-                to: email,
-                from: 'from@example.com',
-                subject: 'Forgot Password successful ✔',
-                text: 'Forgot Password successful!',
-                template: 'index',
-                context: {
-                    title: 'Forgot Password successful!',
-                    description: 'Request Reset Password Successfully!  ✔, This is your new password: ' + password
-                }
-            })
-            Logger.log('[MailService] Forgot Password: Send Mail successfully!')
-        } catch (err) {
-            Logger.error('[MailService] Forgot Password: Send Mail Failed!', err)
-        }
-    }
-
-    public async changePassword(changePasswordDto: ChangePasswordDto): Promise<any> {
-        this.sendMailChangePassword(changePasswordDto)
-
-        return await this.updateByPassword(changePasswordDto.email, changePasswordDto.password)
-    }
-
-    private sendMailChangePassword(user): void {
-        try {
-            this.mailerService.sendMail({
-                to: user.email,
-                from: 'from@example.com',
-                subject: 'Change Password successful ✔',
-                text: 'Change Password successful!',
-                template: 'index',
-                context: {
-                    title: 'Change Password successful!',
-                    description: 'Change Password Successfully! ✔, This is your new password: ' + user.password,
-                    nameUser: user.name
-                }
-            })
-            Logger.log('[MailService] Change Password: Send Mail successfully!')
-        } catch (err) {
-            Logger.error('[MailService] Change Password: Send Mail Failed!', err)
-        }
-    }
+  constructor(
+    @InjectRepository(Users)
+    private readonly userRepository: Repository<Users>,
+    private readonly hashingService: HashingService,
+  ) {}
+
+  public async findAll(): Promise<Users[]> {
+    return await this.userRepository.find();
+  }
+
+  public async findByEmail(email: string): Promise<Users> {
+    const user = await this.userRepository.findOneBy({
+      email: email,
+    });
+
+    if (!user) {
+      throw new NotFoundException(`User not found`);
+    }
+
+    return user;
+  }
+
+  public async findById(userId: string): Promise<Users> {
+    const user = await this.userRepository.findOneBy({
+      id: +userId,
+    });
+
+    if (!user) {
+      throw new NotFoundException(`User #${userId} not found`);
+    }
+
+    return user;
+  }
+
+  public async create(userDto: UserDto): Promise<IUsers> {
+    try {
+      return await this.userRepository.save(userDto);
+    } catch (err) {
+      throw new HttpException(err, HttpStatus.BAD_REQUEST);
+    }
+  }
+
+  public async updateByEmail(email: string): Promise<Users> {
+    try {
+      const user = await this.userRepository.findOneBy({ email: email });
+      user.password = await this.hashingService.hash(
+        Math.random().toString(36).slice(-8),
+      );
+
+      return await this.userRepository.save(user);
+    } catch (err) {
+      throw new HttpException(err, HttpStatus.BAD_REQUEST);
+    }
+  }
+
+  public async updateByPassword(
+    email: string,
+    password: string,
+  ): Promise<Users> {
+    try {
+      const user = await this.userRepository.findOneBy({ email: email });
+      user.password = await this.hashingService.hash(password);
+
+      return await this.userRepository.save(user);
+    } catch (err) {
+      throw new HttpException(err, HttpStatus.BAD_REQUEST);
+    }
+  }
+
+  public async updateProfileUser(
+    id: string,
+    userProfileDto: UserProfileDto,
+  ): Promise<Users> {
+    try {
+      const user = await this.userRepository.findOneBy({ id: +id });
+      user.name = userProfileDto.name;
+      user.email = userProfileDto.email;
+      user.username = userProfileDto.username;
+
+      return await this.userRepository.save(user);
+    } catch (err) {
+      throw new HttpException(err, HttpStatus.BAD_REQUEST);
+    }
+  }
+
+  public async updateUser(
+    id: string,
+    userUpdateDto: UserUpdateDto,
+  ): Promise<UpdateResult> {
+    try {
+      const user = await this.userRepository.update(
+        {
+          id: +id,
+        },
+        { ...userUpdateDto },
+      );
+
+      return user;
+    } catch (err) {
+      throw new BadRequestException('User not updated');
+    }
+  }
+
+  public async deleteUser(id: string): Promise<void> {
+    const user = await this.findById(id);
+    await this.userRepository.remove(user);
+  }
 }

+ 1 - 1
test/app.e2e-spec.ts

@@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
 import * as request from 'supertest';
 import { AppModule } from './../src/app.module';
 import { HttpStatus, ValidationPipe } from '@nestjs/common';
-import { AccessTokenGuard } from '../src/iam/guards/access-token/access-token.guard';
+import { AccessTokenGuard } from '../src/iam/login/guards/access-token/access-token.guard';
 
 describe('App (e2e)', () => {
   let app;

+ 126 - 116
test/change-password/change-password.e2e-spec.ts

@@ -1,130 +1,140 @@
-import { Test, TestingModule } from '@nestjs/testing'
-import * as request from 'supertest'
-import { AppModule } from './../../src/app.module'
-import { MailerService } from '../../src/shared/mailer/mailer.service'
-import { BadRequestException, HttpStatus, ValidationPipe } from '@nestjs/common'
-import { AccessTokenGuard } from 'src/iam/guards/access-token/access-token.guard'
+import { Test, TestingModule } from '@nestjs/testing';
+import * as request from 'supertest';
+import { AppModule } from './../../src/app.module';
+import { MailerService } from '../../src/shared/mailer/mailer.service';
+import {
+  BadRequestException,
+  HttpStatus,
+  ValidationPipe,
+} from '@nestjs/common';
+import { AccessTokenGuard } from '../../src/iam/login/guards/access-token/access-token.guard';
 
 const user = {
-    email: 'test@example.com',
-    password: 'pass123'
-}
+  email: 'test@example.com',
+  password: 'pass123',
+};
 
 describe('App (e2e)', () => {
-    let app
-    let accessTokenJwt: string
+  let app;
+  let accessTokenJwt: string;
 
-    beforeAll(async () => {
-        const moduleFixture: TestingModule = await Test.createTestingModule({
-            imports: [AppModule]
-        })
-            .overrideProvider(MailerService)
-            .useValue({
-                sendMail: jest.fn(() => true)
-            })
-            .overrideGuard(AccessTokenGuard)
-            .useValue({ canActivate: () => true })
-            .compile()
-
-        app = moduleFixture.createNestApplication()
-        app.setGlobalPrefix('api')
-        app.useGlobalPipes(
-            new ValidationPipe({
-                whitelist: true,
-                transform: true,
-                forbidNonWhitelisted: true,
-                transformOptions: {
-                    enableImplicitConversion: true
-                }
-            })
-        )
-
-        await app.init()
+  beforeAll(async () => {
+    const moduleFixture: TestingModule = await Test.createTestingModule({
+      imports: [AppModule],
     })
+      .overrideProvider(MailerService)
+      .useValue({
+        sendMail: jest.fn(() => true),
+      })
+      .overrideGuard(AccessTokenGuard)
+      .useValue({ canActivate: () => true })
+      .compile();
 
-    describe('should sign in and get a "live" JWT', () => {
-        it('should authenticates user with valid credentials and provides a jwt token', () => {
-            return request(app.getHttpServer())
-                .post('/api/auth/login')
-                .send({
-                    email: 'test@example.com',
-                    password: 'pass123'
-                })
-                .then(({ body }) => {
-                    accessTokenJwt = body.accessToken
-                    expect(accessTokenJwt).toMatch(/^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$/)
+    app = moduleFixture.createNestApplication();
+    app.setGlobalPrefix('api');
+    app.useGlobalPipes(
+      new ValidationPipe({
+        whitelist: true,
+        transform: true,
+        forbidNonWhitelisted: true,
+        transformOptions: {
+          enableImplicitConversion: true,
+        },
+      }),
+    );
 
-                    expect(body).toEqual({
-                        sub: 1,
-                        expiresIn: '3600',
-                        audience: '127.0.0.1:3001',
-                        issuer: '127.0.0.1:3001',
-                        accessToken: accessTokenJwt,
-                        user: { name: 'name #1', email: 'test@example.com', id: 1 }
-                    })
+    await app.init();
+  });
 
-                    expect(HttpStatus.OK)
-                })
+  describe('should sign in and get a "live" JWT', () => {
+    it('should authenticates user with valid credentials and provides a jwt token', () => {
+      return request(app.getHttpServer())
+        .post('/api/auth/login')
+        .send({
+          email: 'test@example.com',
+          password: 'pass123',
         })
-    })
+        .then(({ body }) => {
+          accessTokenJwt = body.accessToken;
+          expect(accessTokenJwt).toMatch(
+            /^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$/,
+          );
 
-    describe('ChangePasswordController (e2e) - [POST /api/auth/change-password]', () => {
-        it('should change password an user', async () => {
-            return await request(app.getHttpServer())
-                .post('/api/auth/change-password')
-                .set('Authorization', `Bearer ${accessTokenJwt}`)
-                .send(user)
-                .then(({ body }) => {
-                    expect(body).toEqual({
-                        message: 'Request Change Password Successfully!',
-                        status: 200
-                    })
-                    expect(HttpStatus.OK)
-                })
-        })
-    })
+          expect(body).toEqual({
+            sub: 1,
+            expiresIn: '3600',
+            audience: '127.0.0.1:3001',
+            issuer: '127.0.0.1:3001',
+            accessToken: accessTokenJwt,
+            user: { name: 'name #1', email: 'test@example.com', id: 1 },
+          });
 
-    it('should throw an error for a bad email', async () => {
-        return await request(app.getHttpServer())
-            .post('/api/auth/change-password')
-            .set('Authorization', `Bearer ${accessTokenJwt}`)
-            .send({
-                password: 'new123456'
-            })
-            .then(({ body }) => {
-                expect(body).toEqual({
-                    error: 'Bad Request',
-                    message: ['email should not be empty', 'email must be a string', 'email must be an email'],
-                    statusCode: 400
-                })
-                expect(HttpStatus.BAD_REQUEST)
-                expect(new BadRequestException())
-            })
-    })
+          expect(HttpStatus.OK);
+        });
+    });
+  });
 
-    it('should throw an error for a bad password', async () => {
-        return await request(app.getHttpServer())
-            .post('/api/auth/change-password')
-            .set('Authorization', `Bearer ${accessTokenJwt}`)
-            .send({
-                email: 'test@example.it'
-            })
-            .then(({ body }) => {
-                expect(body).toEqual({
-                    error: 'Bad Request',
-                    message: [
-                        'password must be shorter than or equal to 60 characters',
-                        'password must be a string',
-                        'password should not be empty'
-                    ],
-                    statusCode: 400
-                })
-                expect(HttpStatus.BAD_REQUEST)
-                expect(new BadRequestException())
-            })
-    })
+  describe('ChangePasswordController (e2e) - [POST /api/auth/change-password]', () => {
+    it('should change password an user', async () => {
+      return await request(app.getHttpServer())
+        .post('/api/auth/change-password')
+        .set('Authorization', `Bearer ${accessTokenJwt}`)
+        .send(user)
+        .then(({ body }) => {
+          expect(body).toEqual({
+            message: 'Request Change Password Successfully!',
+            status: 200,
+          });
+          expect(HttpStatus.OK);
+        });
+    });
+  });
 
-    afterAll(async () => {
-        await app.close()
-    })
-})
+  it('should throw an error for a bad email', async () => {
+    return await request(app.getHttpServer())
+      .post('/api/auth/change-password')
+      .set('Authorization', `Bearer ${accessTokenJwt}`)
+      .send({
+        password: 'new123456',
+      })
+      .then(({ body }) => {
+        expect(body).toEqual({
+          error: 'Bad Request',
+          message: [
+            'email should not be empty',
+            'email must be a string',
+            'email must be an email',
+          ],
+          statusCode: 400,
+        });
+        expect(HttpStatus.BAD_REQUEST);
+        expect(new BadRequestException());
+      });
+  });
+
+  it('should throw an error for a bad password', async () => {
+    return await request(app.getHttpServer())
+      .post('/api/auth/change-password')
+      .set('Authorization', `Bearer ${accessTokenJwt}`)
+      .send({
+        email: 'test@example.it',
+      })
+      .then(({ body }) => {
+        expect(body).toEqual({
+          error: 'Bad Request',
+          message: [
+            'password must be shorter than or equal to 60 characters',
+            'password must be a string',
+            'password should not be empty',
+          ],
+          statusCode: 400,
+        });
+        expect(HttpStatus.BAD_REQUEST);
+        expect(new BadRequestException());
+      });
+  });
+
+  afterAll(async () => {
+    await app.close();
+  });
+});

+ 1 - 1
test/forgot-password/forgot-password.e2e-spec.ts

@@ -7,7 +7,7 @@ import {
   HttpStatus,
   ValidationPipe,
 } from '@nestjs/common';
-import { ForgotPasswordDto } from 'src/iam/dto/forgot-password.dto';
+import { ForgotPasswordDto } from 'src/iam/forgot-password/dto/forgot-password.dto';
 import { UserDto } from '../../src/users/dto/user.dto';
 
 const user = {

+ 1 - 1
test/users/users.e2e-spec.ts

@@ -3,7 +3,7 @@ import * as request from 'supertest';
 import { AppModule } from './../../src/app.module';
 import { MailerService } from '../../src/shared/mailer/mailer.service';
 import { HttpStatus, ValidationPipe } from '@nestjs/common';
-import { AccessTokenGuard } from '../../src/iam/guards/access-token/access-token.guard';
+import { AccessTokenGuard } from '../../src/iam/login/guards/access-token/access-token.guard';
 
 const users = [
   {