From e0139891caccb9de77d10eb3dc67148e0172d4f5 Mon Sep 17 00:00:00 2001 From: Anas Abbal Date: Fri, 28 Jun 2024 15:54:19 +0100 Subject: [PATCH 1/2] add client proxy and rabbitmq --- apps/auth/src/auth.controller.ts | 10 ++++---- apps/auth/src/auth.module.ts | 28 +++++++++++++++++++++- apps/auth/src/auth.service.ts | 29 +++++++++++++++++++++-- apps/gateway/src/gateway.module.ts | 8 +++++++ apps/gateway/src/rest/auth.controller.ts | 11 ++++++--- apps/gateway/src/services/auth.service.ts | 12 ++++++---- apps/user/src/user.module.ts | 14 +++++++++++ 7 files changed, 98 insertions(+), 14 deletions(-) diff --git a/apps/auth/src/auth.controller.ts b/apps/auth/src/auth.controller.ts index 758fe3b..e9803a5 100644 --- a/apps/auth/src/auth.controller.ts +++ b/apps/auth/src/auth.controller.ts @@ -1,12 +1,14 @@ -import { Controller, Get } from '@nestjs/common'; +import { Body, Controller, Get } from '@nestjs/common'; import { AuthService } from './auth.service'; +import { MessagePattern } from '@nestjs/microservices'; +import { UserCreateCommand } from '@app/shared/commands/auth/user.create.cmd'; @Controller() export class AuthController { constructor(private readonly authService: AuthService) {} - @Get() - getHello(): string { - return this.authService.getHello(); + @MessagePattern('register-auth') + async register(@Body() command: UserCreateCommand): Promise { + return this.authService.register(command); } } diff --git a/apps/auth/src/auth.module.ts b/apps/auth/src/auth.module.ts index 03810e7..af4fefd 100644 --- a/apps/auth/src/auth.module.ts +++ b/apps/auth/src/auth.module.ts @@ -1,9 +1,35 @@ import { Module } from '@nestjs/common'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; +import { PassportModule } from '@nestjs/passport'; +import { JwtModule } from '@nestjs/jwt'; +import * as dotenv from 'dotenv'; +import { ClientsModule, Transport } from '@nestjs/microservices'; + + +dotenv.config(); @Module({ - imports: [], + imports: [ + PassportModule.register({ defaultStrategy: 'jwt' }), + JwtModule.register({ + secret: process.env.JWT_SECRET, + signOptions: { expiresIn: '1h' }, + }), + ClientsModule.register([ + { + name: 'USER_SERVICE', + transport: Transport.RMQ, + options: { + urls: ['amqp://localhost:5672'], + queue: 'user_queue', + queueOptions: { + durable: false, + }, + }, + }, + ]), + ], controllers: [AuthController], providers: [AuthService], }) diff --git a/apps/auth/src/auth.service.ts b/apps/auth/src/auth.service.ts index 59c8c15..b978a0e 100644 --- a/apps/auth/src/auth.service.ts +++ b/apps/auth/src/auth.service.ts @@ -1,9 +1,34 @@ import { UserCreateCommand } from '@app/shared/commands/auth/user.create.cmd'; -import { IResponse } from '@app/shared/interfaces/response.interface'; import { validateCommand } from '@app/shared/utils/validate'; -import { BadRequestException, Injectable } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { ClientProxy } from '@nestjs/microservices'; +import { firstValueFrom } from 'rxjs'; @Injectable() export class AuthService { + private readonly logger = new Logger(AuthService.name); + + constructor( + private readonly jwtService: JwtService, + @Inject('USER_SERVICE') private client: ClientProxy + ){} + + async register(command: UserCreateCommand): Promise { + const user = await firstValueFrom(this.client.send({ cmd: 'createUser' }, command)); + if(user) + return user; + return null; + } + async login(user: any) { + this.logger.log(`Generating JWT token for user: ${user.email}`); + const payload = { email: user.email, sub: user.id }; + const token = this.jwtService.sign(payload); + this.logger.log(`JWT token generated successfully for user: ${user.email}`); + return { + access_token: token, + email: user.email + }; + } } diff --git a/apps/gateway/src/gateway.module.ts b/apps/gateway/src/gateway.module.ts index 269b3e2..40d41c1 100644 --- a/apps/gateway/src/gateway.module.ts +++ b/apps/gateway/src/gateway.module.ts @@ -34,6 +34,14 @@ import { AuthService } from './services/auth.service'; port: 3002, // port of user-service }, }, + { + name: 'AUTH_SERVICE', + transport: Transport.TCP, + options: { + host: 'localhost', + port: 3005, // port of user-service + }, + }, ]), WinstonModule.forRoot({ transports: [ diff --git a/apps/gateway/src/rest/auth.controller.ts b/apps/gateway/src/rest/auth.controller.ts index 2f8c981..a2f260d 100644 --- a/apps/gateway/src/rest/auth.controller.ts +++ b/apps/gateway/src/rest/auth.controller.ts @@ -11,12 +11,17 @@ import { ResponseSuccess } from '@app/shared/dto/response.dto'; -@Controller() +@Controller('auth') export class AuthController { - /*constructor(private readonly authService: AuthService) {} + constructor(private readonly authService: AuthService) {} - @Post('register/:userTypeId') + + @Post('register') + async REGISTER(@Body() command: any) : Promise{ + return this.authService.auth(command); + } + /*@Post('register/:userTypeId') async register(@Body() createUserDto: UserCreateCommand | DriverCreateCmd, @Param('userTypeId') userTypeId: string ) { diff --git a/apps/gateway/src/services/auth.service.ts b/apps/gateway/src/services/auth.service.ts index 583015c..3d53996 100644 --- a/apps/gateway/src/services/auth.service.ts +++ b/apps/gateway/src/services/auth.service.ts @@ -11,13 +11,14 @@ import { DriverService } from "./driver-service"; @Injectable() export class AuthService { - /*constructor( + constructor( @Inject('USER_SERVICE') private readonly userClient: ClientProxy, private readonly userService: UserService, - private readonly driverService: DriverService + private readonly driverService: DriverService, + @Inject('AUTH_SERVICE') private readonly authClient: ClientProxy ){} - async register(userTypeId: string, command: UserCreateCommand | DriverCreateCmd): Promise { + /*async register(userTypeId: string, command: UserCreateCommand | DriverCreateCmd): Promise { try { const userType = await this.userService.findUserTypeById(userTypeId); console.log(`User type with id ${userType.id} found`); @@ -39,8 +40,11 @@ export class AuthService { throw new InternalServerErrorException('Failed to register user'); } } + }*/ + async auth(command: UserCreateCommand) : Promise { + return await this.authClient.send({cmd: 'register-auth'}, command).toPromise(); } - async login(loginUserDto: any): Promise { + /*async login(loginUserDto: any): Promise { try { const result = await this.userClient.send({ cmd: 'login' }, loginUserDto).toPromise(); return result; diff --git a/apps/user/src/user.module.ts b/apps/user/src/user.module.ts index 6df14f7..e471dc9 100644 --- a/apps/user/src/user.module.ts +++ b/apps/user/src/user.module.ts @@ -8,6 +8,7 @@ import { User } from './model/user.entity'; import { UserType } from './model/user.type'; import { UserTypeController } from './services/user.type.controller'; import { UserTypeService } from './services/user.type.service'; +import { ClientsModule, Transport } from '@nestjs/microservices'; dotenv.config(); @@ -20,6 +21,19 @@ dotenv.config(); process.env.DATABASE_NAME, process.env.DATABASE_TYPE as 'mongodb' | 'postgres' ), + ClientsModule.register([ + { + name: 'AUTH_SERVICE', + transport: Transport.RMQ, + options: { + urls: ['amqp://localhost:5672'], + queue: 'auth_queue', + queueOptions: { + durable: false, + }, + }, + }, + ]), ], controllers: [UserController, UserTypeController], providers: [UserService, UserTypeService] From bb07ca5d5673e1ed4bd210ed170afeabf8d24bb2 Mon Sep 17 00:00:00 2001 From: Anas Abbal Date: Thu, 4 Jul 2024 19:59:48 +0100 Subject: [PATCH 2/2] add caching --- apps/gateway/src/gateway.module.ts | 16 +++++- apps/gateway/src/rest/user.controller.ts | 9 +++- apps/gateway/src/services/user-service.ts | 19 +++++++- package-lock.json | 59 +++++++++++++++++++++++ package.json | 2 + 5 files changed, 101 insertions(+), 4 deletions(-) diff --git a/apps/gateway/src/gateway.module.ts b/apps/gateway/src/gateway.module.ts index 40d41c1..02c0818 100644 --- a/apps/gateway/src/gateway.module.ts +++ b/apps/gateway/src/gateway.module.ts @@ -10,6 +10,8 @@ import { DriversController } from './rest/driver.controller'; import { UserService } from './services/user-service'; import { DriverService } from './services/driver-service'; import { AuthService } from './services/auth.service'; +import { CacheInterceptor, CacheModule } from '@nestjs/cache-manager'; +import { APP_INTERCEPTOR } from '@nestjs/core'; @Module({ imports: [ @@ -43,6 +45,10 @@ import { AuthService } from './services/auth.service'; }, }, ]), + CacheModule.register({ + ttl: 5, // seconds + max: 100, // maximum number of items in cache + }), WinstonModule.forRoot({ transports: [ new winston.transports.Console({ @@ -55,6 +61,14 @@ import { AuthService } from './services/auth.service'; }), ], controllers: [AuthController, UserController, DriversController], - providers: [UserService, DriverService, AuthService] + providers: [ + UserService, + DriverService, + AuthService, + { + provide: APP_INTERCEPTOR, + useClass: CacheInterceptor, + }, + ], }) export class GatewayModule {} diff --git a/apps/gateway/src/rest/user.controller.ts b/apps/gateway/src/rest/user.controller.ts index 1e1664e..ccb1dcd 100644 --- a/apps/gateway/src/rest/user.controller.ts +++ b/apps/gateway/src/rest/user.controller.ts @@ -1,25 +1,30 @@ -import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post, UseInterceptors } from '@nestjs/common'; import { UserCreateCommand } from '@app/shared/commands/auth/user.create.cmd'; import { UserService } from '../services/user-service'; +import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager'; @Controller('user') +@UseInterceptors(CacheInterceptor) export class UserController { constructor(private readonly userService: UserService) {} @Get(':id') + @CacheKey('findUserById') + @CacheTTL(10) // cache for 10 seconds async findUserById(@Param('id') id: string): Promise { return this.userService.findUserById(id); } @Get() + @CacheKey('getAllUsers') + @CacheTTL(30) // cache for 30 seconds async getAllUsers(): Promise { return this.userService.getAllUsers(); } - // Example of handling a POST request to create a user @Post() async createUser(@Body() command: UserCreateCommand): Promise { return this.userService.createUser(command); diff --git a/apps/gateway/src/services/user-service.ts b/apps/gateway/src/services/user-service.ts index 3501937..2615a5c 100644 --- a/apps/gateway/src/services/user-service.ts +++ b/apps/gateway/src/services/user-service.ts @@ -1,8 +1,10 @@ import { Injectable, Inject } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; +import { Cache } from 'cache-manager'; import { UserCreateCommand } from '@app/shared/commands/auth/user.create.cmd'; // Adjust import path as needed import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { Logger } from 'winston'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; @Injectable() export class UserService { @@ -10,6 +12,7 @@ export class UserService { constructor( @Inject('USER_SERVICE') private readonly userClient: ClientProxy, @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: Logger, + @Inject(CACHE_MANAGER) private cacheManager: Cache ) {} async createUser(command: UserCreateCommand): Promise { @@ -25,8 +28,15 @@ export class UserService { async findUserById(id: string): Promise { try { + const cachedUser = await this.cacheManager.get(`user-${id}`); + if (cachedUser) { + this.logger.info(`Found user with id ${id} in cache`); + return cachedUser; + } + const result = await this.userClient.send({ cmd: 'findUserById' }, id).toPromise(); this.logger.info(`Found user with id ${id}`); + await this.cacheManager.set(`user-${id}`, result, 300); // Cache for 5 minutes return result; } catch (error) { this.logger.error(`Error finding user with id ${id}: ${error.message}`); @@ -36,12 +46,19 @@ export class UserService { async getAllUsers(): Promise { try { + const cachedUsers = await this.cacheManager.get('all-users'); + if (cachedUsers) { + this.logger.info(`Fetched all users from cache`); + return cachedUsers; + } + const result = await this.userClient.send({ cmd: 'getAllUsers' }, {}).toPromise(); this.logger.info(`Fetched all users, count: ${result.length}`); + await this.cacheManager.set('all-users', result, 300); // Cache for 5 minutes return result; } catch (error) { this.logger.error(`Error fetching all users: ${error.message}`); throw error; } } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index f49cc3d..58ef42a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "UNLICENSED", "dependencies": { "@nestjs-modules/mailer": "^2.0.2", + "@nestjs/cache-manager": "^2.2.2", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.2", "@nestjs/core": "^10.0.0", @@ -25,6 +26,7 @@ "amqp-connection-manager": "^4.1.14", "amqplib": "^0.10.4", "bcrypt": "^5.1.1", + "cache-manager": "^5.7.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", "mongoose": "^8.4.1", @@ -2128,6 +2130,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@nestjs/cache-manager": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-2.2.2.tgz", + "integrity": "sha512-+n7rpU1QABeW2WV17Dl1vZCG3vWjJU1MaamWgZvbGxYE9EeCM0lVLfw3z7acgDTNwOy+K68xuQPoIMxD0bhjlA==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^9.0.0 || ^10.0.0", + "@nestjs/core": "^9.0.0 || ^10.0.0", + "cache-manager": "<=5", + "rxjs": "^7.0.0" + } + }, "node_modules/@nestjs/cli": { "version": "10.3.2", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz", @@ -4392,6 +4406,30 @@ "node": ">= 0.8" } }, + "node_modules/cache-manager": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.7.1.tgz", + "integrity": "sha512-dONlk3HCRelyxH9ctcXBgFihKmtSdudc9H0E39pBQeUaNcBJV3vcmcvifRUGqbeKk+uWCQpAts3uvnj+79Ebrg==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "lodash.clonedeep": "^4.5.0", + "lru-cache": "^10.2.2", + "promise-coalesce": "^1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/cache-manager/node_modules/lru-cache": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.3.0.tgz", + "integrity": "sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==", + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -6058,6 +6096,12 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -8963,6 +9007,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -11077,6 +11127,15 @@ "integrity": "sha512-BthzO9yTPswGf7etOBiHCVuugs2N01/Q/94dIPls48z2zCmrnDptUUZzfIb+41xq0MnYZ/BzmOd6ikDR4ibNZA==", "license": "MIT" }, + "node_modules/promise-coalesce": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/promise-coalesce/-/promise-coalesce-1.1.2.tgz", + "integrity": "sha512-zLaJ9b8hnC564fnJH6NFSOGZYYdzrAJn2JUUIwzoQb32fG2QAakpDNM+CZo1km6keXkRXRM+hml1BFAPVnPkxg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=16" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", diff --git a/package.json b/package.json index 3e6175c..5e38ed7 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@nestjs-modules/mailer": "^2.0.2", + "@nestjs/cache-manager": "^2.2.2", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.2", "@nestjs/core": "^10.0.0", @@ -36,6 +37,7 @@ "amqp-connection-manager": "^4.1.14", "amqplib": "^0.10.4", "bcrypt": "^5.1.1", + "cache-manager": "^5.7.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", "mongoose": "^8.4.1",