From 2549dc9077242016e85eee228fed84b573160471 Mon Sep 17 00:00:00 2001 From: Igor Propisnov Date: Wed, 22 May 2024 10:11:29 +0200 Subject: [PATCH 1/6] chnage type to dto --- .../auth-module/controller/auth.controller.ts | 9 ++++----- .../src/modules/auth-module/models/dto/index.ts | 1 + .../modules/auth-module/models/dto/tokens.dto.ts | 11 +++++++++++ .../src/modules/auth-module/models/types/index.ts | 1 - .../auth-module/models/types/tokens.type.ts | 4 ---- .../modules/auth-module/services/auth.service.ts | 14 ++++++++------ .../services/token-management.service.ts | 7 +++++-- 7 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 backend/src/modules/auth-module/models/dto/tokens.dto.ts delete mode 100644 backend/src/modules/auth-module/models/types/tokens.type.ts diff --git a/backend/src/modules/auth-module/controller/auth.controller.ts b/backend/src/modules/auth-module/controller/auth.controller.ts index 87607a2..a9bda8d 100644 --- a/backend/src/modules/auth-module/controller/auth.controller.ts +++ b/backend/src/modules/auth-module/controller/auth.controller.ts @@ -7,8 +7,7 @@ import { UseGuards, } from '@nestjs/common'; import { AuthService } from '../services/auth.service'; -import { UserCredentialsDto } from '../models/dto'; -import { Tokens } from '../models/types'; +import { TokensDto, UserCredentialsDto } from '../models/dto'; import { RefreshTokenGuard } from '../common/guards'; import { GetCurrentUser, GetCurrentUserId, Public } from '../common/decorators'; @@ -21,7 +20,7 @@ export class AuthController { @HttpCode(HttpStatus.CREATED) public async signup( @Body() userCredentials: UserCredentialsDto - ): Promise { + ): Promise { return this.authService.signup(userCredentials); } @@ -30,7 +29,7 @@ export class AuthController { @HttpCode(HttpStatus.OK) public async signin( @Body() userCredentials: UserCredentialsDto - ): Promise { + ): Promise { return this.authService.signin(userCredentials); } @@ -47,7 +46,7 @@ export class AuthController { public async refresh( @GetCurrentUserId() userId: number, @GetCurrentUser('refresh_token') refresh_token: string - ): Promise { + ): Promise { return this.authService.refresh(userId, refresh_token); } } diff --git a/backend/src/modules/auth-module/models/dto/index.ts b/backend/src/modules/auth-module/models/dto/index.ts index 7d5fd28..bd47ab5 100644 --- a/backend/src/modules/auth-module/models/dto/index.ts +++ b/backend/src/modules/auth-module/models/dto/index.ts @@ -1 +1,2 @@ export * from './user-credentials.dto'; +export * from './tokens.dto'; diff --git a/backend/src/modules/auth-module/models/dto/tokens.dto.ts b/backend/src/modules/auth-module/models/dto/tokens.dto.ts new file mode 100644 index 0000000..c4ad37b --- /dev/null +++ b/backend/src/modules/auth-module/models/dto/tokens.dto.ts @@ -0,0 +1,11 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class TokensDto { + @IsNotEmpty() + @IsString() + public access_token: string; + + @IsNotEmpty() + @IsString() + public refresh_token: string; +} diff --git a/backend/src/modules/auth-module/models/types/index.ts b/backend/src/modules/auth-module/models/types/index.ts index 90e9169..0e70b68 100644 --- a/backend/src/modules/auth-module/models/types/index.ts +++ b/backend/src/modules/auth-module/models/types/index.ts @@ -1,3 +1,2 @@ -export * from './tokens.type'; export * from './jwt-payload.type'; export * from './jwt-payload-with-refresh-token.type'; diff --git a/backend/src/modules/auth-module/models/types/tokens.type.ts b/backend/src/modules/auth-module/models/types/tokens.type.ts deleted file mode 100644 index 1c0a510..0000000 --- a/backend/src/modules/auth-module/models/types/tokens.type.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type Tokens = { - access_token: string; - refresh_token: string; -}; diff --git a/backend/src/modules/auth-module/services/auth.service.ts b/backend/src/modules/auth-module/services/auth.service.ts index 272e841..5b2f56f 100644 --- a/backend/src/modules/auth-module/services/auth.service.ts +++ b/backend/src/modules/auth-module/services/auth.service.ts @@ -1,6 +1,5 @@ import { ForbiddenException, Injectable } from '@nestjs/common'; -import { UserCredentialsDto } from '../models/dto'; -import { Tokens } from '../models/types'; +import { TokensDto, UserCredentialsDto } from '../models/dto'; import { EncryptionService } from './encryption.service'; import { UserRepository } from '../repositories/user.repository'; import { TokenManagementService } from './token-management.service'; @@ -13,7 +12,7 @@ export class AuthService { private readonly encryptionService: EncryptionService ) {} - public async signup(userCredentials: UserCredentialsDto): Promise { + public async signup(userCredentials: UserCredentialsDto): Promise { const passwordHashed = await this.encryptionService.hashData( userCredentials.password ); @@ -24,7 +23,7 @@ export class AuthService { return this.generateAndPersistTokens(user.id, user.email); } - public async signin(userCredentials: UserCredentialsDto): Promise { + public async signin(userCredentials: UserCredentialsDto): Promise { const user = await this.userRepository.findUserByEmail( userCredentials.email ); @@ -43,7 +42,10 @@ export class AuthService { return this.generateAndPersistTokens(user.id, user.email); } - public async refresh(userId: number, refreshToken: string): Promise { + public async refresh( + userId: number, + refreshToken: string + ): Promise { const user = await this.userRepository.findUserById(userId); if (!user || !user.hashedRt) { throw new ForbiddenException('Access Denied'); @@ -71,7 +73,7 @@ export class AuthService { private async generateAndPersistTokens( userId: number, email: string - ): Promise { + ): Promise { const tokens = await this.tokenManagementService.generateTokens( userId, email diff --git a/backend/src/modules/auth-module/services/token-management.service.ts b/backend/src/modules/auth-module/services/token-management.service.ts index 73ad211..2ebc7d8 100644 --- a/backend/src/modules/auth-module/services/token-management.service.ts +++ b/backend/src/modules/auth-module/services/token-management.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { Tokens } from '../models/types'; import { JwtService } from '@nestjs/jwt'; +import { TokensDto } from '../models/dto'; @Injectable() export class TokenManagementService { @@ -24,7 +24,10 @@ export class TokenManagementService { this.JWT_SECRET_RT = this.configService.get('JWT_SECRET_RT'); } - public async generateTokens(userId: number, email: string): Promise { + public async generateTokens( + userId: number, + email: string + ): Promise { const access_token: string = await this.createAccessToken(userId, email); const refresh_token: string = await this.createRefreshToken(userId, email); return { access_token, refresh_token }; -- 2.40.1 From e6f3e8f6be6e7adeb29f8cdec0dad1bc3df803cc Mon Sep 17 00:00:00 2001 From: Igor Propisnov Date: Wed, 22 May 2024 10:11:40 +0200 Subject: [PATCH 2/6] init swagger api --- backend/src/main.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/main.ts b/backend/src/main.ts index e476576..4b8e604 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -4,7 +4,12 @@ import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { ValidationPipe } from '@nestjs/common'; async function setupSwagger(app) { - const config = new DocumentBuilder().build(); + const config = new DocumentBuilder() + .setTitle('Tickets API') + .setDescription('Description of the API') + .setVersion('0.0.0') + .addTag('tickets') + .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); } -- 2.40.1 From 0fca8f38313f2d45f3ee9acb851cf1e80ae66c9d Mon Sep 17 00:00:00 2001 From: Igor Propisnov Date: Wed, 22 May 2024 11:28:20 +0200 Subject: [PATCH 3/6] Added some swagger api docs --- backend/src/main.ts | 1 - .../auth-module/controller/auth.controller.ts | 18 ++++++++++++++++++ .../auth-module/models/dto/tokens.dto.ts | 11 +++++++++++ .../models/dto/user-credentials.dto.ts | 12 ++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/backend/src/main.ts b/backend/src/main.ts index 4b8e604..6963b6c 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -8,7 +8,6 @@ async function setupSwagger(app) { .setTitle('Tickets API') .setDescription('Description of the API') .setVersion('0.0.0') - .addTag('tickets') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); diff --git a/backend/src/modules/auth-module/controller/auth.controller.ts b/backend/src/modules/auth-module/controller/auth.controller.ts index a9bda8d..91de9b2 100644 --- a/backend/src/modules/auth-module/controller/auth.controller.ts +++ b/backend/src/modules/auth-module/controller/auth.controller.ts @@ -10,11 +10,17 @@ import { AuthService } from '../services/auth.service'; import { TokensDto, UserCredentialsDto } from '../models/dto'; import { RefreshTokenGuard } from '../common/guards'; import { GetCurrentUser, GetCurrentUserId, Public } from '../common/decorators'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; +@ApiTags('Authentication') @Controller('auth') export class AuthController { constructor(private readonly authService: AuthService) {} + @ApiCreatedResponse({ + description: 'User signed up successfully', + type: TokensDto, + }) @Public() @Post('signup') @HttpCode(HttpStatus.CREATED) @@ -24,6 +30,10 @@ export class AuthController { return this.authService.signup(userCredentials); } + @ApiCreatedResponse({ + description: 'User signin successfully', + type: TokensDto, + }) @Public() @Post('signin') @HttpCode(HttpStatus.OK) @@ -33,12 +43,20 @@ export class AuthController { return this.authService.signin(userCredentials); } + @ApiCreatedResponse({ + description: 'User signed out successfully', + type: Boolean, + }) @Post('logout') @HttpCode(HttpStatus.OK) public async logout(@GetCurrentUserId() userId: number): Promise { return this.authService.logout(userId); } + @ApiCreatedResponse({ + description: 'User tokens refreshed successfully', + type: TokensDto, + }) @Public() @UseGuards(RefreshTokenGuard) @Post('refresh') diff --git a/backend/src/modules/auth-module/models/dto/tokens.dto.ts b/backend/src/modules/auth-module/models/dto/tokens.dto.ts index c4ad37b..2d6e111 100644 --- a/backend/src/modules/auth-module/models/dto/tokens.dto.ts +++ b/backend/src/modules/auth-module/models/dto/tokens.dto.ts @@ -1,10 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; export class TokensDto { + @ApiProperty({ + title: 'Access token', + description: 'Access token', + example: 'eyJhbGci', + }) @IsNotEmpty() @IsString() public access_token: string; + @ApiProperty({ + title: 'Refresh token', + description: 'Refresh token', + example: 'eyJhbGci', + }) @IsNotEmpty() @IsString() public refresh_token: string; diff --git a/backend/src/modules/auth-module/models/dto/user-credentials.dto.ts b/backend/src/modules/auth-module/models/dto/user-credentials.dto.ts index 3fd7563..b258423 100644 --- a/backend/src/modules/auth-module/models/dto/user-credentials.dto.ts +++ b/backend/src/modules/auth-module/models/dto/user-credentials.dto.ts @@ -1,10 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator'; export class UserCredentialsDto { + @ApiProperty({ + title: 'E-Mail', + description: 'User email', + example: 'foo@bar.com', + }) @IsNotEmpty() @IsEmail() public email: string; + @ApiProperty({ + title: 'Password', + description: 'User password', + example: '$tr0ngP@$$w0rd', + minLength: 8, + }) @IsNotEmpty() @IsString() @MinLength(8) -- 2.40.1 From 0b027c77cd8aec4d758352addbe2bd2a21b947fc Mon Sep 17 00:00:00 2001 From: Igor Propisnov Date: Wed, 22 May 2024 14:15:36 +0200 Subject: [PATCH 4/6] added api files generator --- backend/openapiconfig.json | 15 ++++++++++++ backend/openapitools.json | 48 ++++++++++++++++++++++++++++++++++++++ backend/package.json | 4 +++- backend/pnpm-lock.yaml | 7 +++--- 4 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 backend/openapiconfig.json create mode 100644 backend/openapitools.json diff --git a/backend/openapiconfig.json b/backend/openapiconfig.json new file mode 100644 index 0000000..d42a2a2 --- /dev/null +++ b/backend/openapiconfig.json @@ -0,0 +1,15 @@ +{ + "npmName": "Ticket-API-Services", + "npmVersion": "0.0.0", + "providedIn": "root", + "withInterfaces": true, + "enumNameSuffix": "Enum", + "supportsES6": true, + "ngVersion": "17.0.0", + "modelSuffix": "Model", + "stringEnums": true, + "enumPropertyNaming": "PascalCase", + "modelPropertyNaming": "original", + "fileNaming": "camelCase", + "paramNaming": "camelCase" +} diff --git a/backend/openapitools.json b/backend/openapitools.json new file mode 100644 index 0000000..7b50e33 --- /dev/null +++ b/backend/openapitools.json @@ -0,0 +1,48 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.6.0", + "generators": { + "typescript-angular": { + "generatorName": "typescript-angular", + "output": "web-api", + "inputSpec": "http://localhost:3000/api-json", + "additionalProperties": { + "npmName": "Ticket-API-Services", + "npmVersion": "0.0.0", + "ngVersion": "17.0.0", + "npmRepository": null, + "configurationPrefix": null, + "apiModulePrefix" : "TicketApi", + "providedIn": "any", + "fileNaming": "camelCase", + "paramNaming": "camelCase", + "enumPropertyNamingReplaceSpecialChar": "false", + "enumUnknownDefaultCase": "false", + "enumNameSuffix": "ApiEnum", + "enumPropertyNaming": "PascalCase", + "modelPropertyNaming": "original", + "modelSuffix": "ApiModel", + "modelFileSuffix": ".api.model", + "serviceSuffix": "ApiService", + "serviceFileSuffix": ".api.service", + "withInterfaces": true, + "supportsES6": true, + "stringEnums": true, + "sortParamsByRequiredFlag": true, + "sortModelPropertiesByRequiredFlag": true, + "useSingleRequestParameter": false, + "taggedUnions": false, + "snapshot": false, + "prependFormOrBodyParameters": false, + "nullSafeAdditionalProps": false, + "legacyDiscriminatorBehavior": true, + "ensureUniqueParams": true, + "disallowAdditionalPropertiesIfNotPresent": true, + "allowUnicodeIdentifiers": false + } + } + } + } +} diff --git a/backend/package.json b/backend/package.json index e5bd261..32ac129 100644 --- a/backend/package.json +++ b/backend/package.json @@ -17,7 +17,8 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --config ./test/jest-e2e.json", + "build:api": "pnpm openapi-generator-cli generate" }, "dependencies": { "@nestjs/common": "^10.0.0", @@ -28,6 +29,7 @@ "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.3.1", "@nestjs/typeorm": "^10.0.2", + "argon2": "^0.40.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "install": "^0.13.0", diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index 1fdc979..58ceaed 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -29,6 +29,9 @@ dependencies: '@nestjs/typeorm': specifier: ^10.0.2 version: 10.0.2(@nestjs/common@10.3.7)(@nestjs/core@10.3.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20) + argon2: + specifier: ^0.40.1 + version: 0.40.1 class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -1182,7 +1185,6 @@ packages: /@phc/format@1.0.0: resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} engines: {node: '>=10'} - dev: true /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -1823,7 +1825,6 @@ packages: '@phc/format': 1.0.0 node-addon-api: 7.1.0 node-gyp-build: 4.8.1 - dev: true /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -4230,7 +4231,6 @@ packages: /node-addon-api@7.1.0: resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==} engines: {node: ^16 || ^18 || >= 20} - dev: true /node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} @@ -4252,7 +4252,6 @@ packages: /node-gyp-build@4.8.1: resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} hasBin: true - dev: true /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} -- 2.40.1 From 28cac3ba32f1a4602f198c219dace8afad08314e Mon Sep 17 00:00:00 2001 From: Igor Propisnov Date: Wed, 22 May 2024 15:20:15 +0200 Subject: [PATCH 5/6] small changes --- backend/docker-compose.yml | 2 +- backend/src/app.module.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index b52631f..26d8d3c 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -9,7 +9,7 @@ services: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_NAME} ports: - - "${DB_PORT}:5432" + - "${DB_PORT}:${DB_PORT}" pgadmin: container_name: pgadmin4_container image: dpage/pgadmin4 diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 137e70a..5af1902 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -24,7 +24,7 @@ import { CorsMiddleware } from './middleware/cors-middleware/cors.middlware'; export class AppModule { configure(consumer: MiddlewareConsumer) { consumer - // TODO: Redirect via Reverse Proxy all HTTP requests to HTTPS + // TODO Redirect via Reverse Proxy all HTTP requests to HTTPS .apply( CspMiddleware, SecurityHeadersMiddleware, -- 2.40.1 From 6c301fb97ac8accceb81b9bbe20b7f245ea4f204 Mon Sep 17 00:00:00 2001 From: Igor Propisnov Date: Wed, 22 May 2024 15:46:19 +0200 Subject: [PATCH 6/6] hygiene --- backend/src/entities/index.ts | 1 + backend/src/modules/database-module/database-config.ts | 2 +- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 7 +++++++ 4 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 backend/src/entities/index.ts diff --git a/backend/src/entities/index.ts b/backend/src/entities/index.ts new file mode 100644 index 0000000..2c05a4a --- /dev/null +++ b/backend/src/entities/index.ts @@ -0,0 +1 @@ +export * from './user-credentials.entity'; diff --git a/backend/src/modules/database-module/database-config.ts b/backend/src/modules/database-module/database-config.ts index 4d3d2bc..b674b2f 100644 --- a/backend/src/modules/database-module/database-config.ts +++ b/backend/src/modules/database-module/database-config.ts @@ -1,6 +1,6 @@ import { ConfigService } from '@nestjs/config'; import { TypeOrmModuleOptions } from '@nestjs/typeorm'; -import { UserCredentials } from '../../entities/user-credentials.entity'; +import { UserCredentials } from 'src/entities'; export const databaseConfigFactory = ( configService: ConfigService diff --git a/frontend/package.json b/frontend/package.json index 48c08f8..ca914bb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,6 +31,7 @@ "@types/dompurify": "^3.0.5", "crypto-js": "^4.2.0", "dompurify": "^3.1.3", + "primeicons": "^7.0.0", "primeng": "^17.11.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 0e4e337..b9d43e3 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -38,6 +38,9 @@ dependencies: dompurify: specifier: ^3.1.3 version: 3.1.3 + primeicons: + specifier: ^7.0.0 + version: 7.0.0 primeng: specifier: ^17.11.0 version: 17.11.0(@angular/common@17.3.0)(@angular/core@17.3.0)(@angular/forms@17.3.0)(rxjs@7.8.1)(zone.js@0.14.4) @@ -8564,6 +8567,10 @@ packages: react-is: 18.2.0 dev: true + /primeicons@7.0.0: + resolution: {integrity: sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==} + dev: false + /primeng@17.11.0(@angular/common@17.3.0)(@angular/core@17.3.0)(@angular/forms@17.3.0)(rxjs@7.8.1)(zone.js@0.14.4): resolution: {integrity: sha512-EtkXnE7I6gcrqjNDvUi4qvwTvwL/m3hwPJImoO/s088HDOBerwjN1c2Pq3bwGRG8Eg4Ga9nXdE/IrYj2mbAZLw==} peerDependencies: -- 2.40.1